diff --git a/src/core/sync.rs b/src/core/sync.rs index 558812a..13dee05 100644 --- a/src/core/sync.rs +++ b/src/core/sync.rs @@ -4,28 +4,37 @@ use crate::gui::widgets::package_row::PackageRow; use regex::Regex; use static_init::dynamic; use std::collections::HashSet; +use std::env; use std::process::Command; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Phone { pub model: String, pub android_sdk: u8, pub user_list: Vec, + pub adb_id: String, } impl Default for Phone { fn default() -> Self { Self { - model: get_phone_brand(), - android_sdk: get_android_sdk(), - user_list: get_user_list(), + model: "fetching devices...".to_string(), + android_sdk: 0, + user_list: vec![], + adb_id: "".to_string(), } } } +impl std::fmt::Display for Phone { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.model.to_string(),) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub struct User { pub id: u16, @@ -37,18 +46,24 @@ impl std::fmt::Display for User { write!(f, "{}", format!("user {}", self.id),) } } -pub fn adb_shell_command(args: &str) -> Result { + +pub fn adb_shell_command(shell: bool, args: &str) -> Result { + let adb_command = match shell { + true => vec!["shell", args], + false => vec![args], + }; + #[cfg(target_os = "windows")] let output = Command::new("adb") - .args(&["shell", args]) + .args(adb_command) .creation_flags(0x08000000) // do not open a cmd window .output(); #[cfg(target_os = "macos")] - let output = Command::new("adb").args(&["shell", args]).output(); + let output = Command::new("adb").args(adb_command).output(); #[cfg(not(any(target_os = "macos", target_os = "windows")))] - let output = Command::new("adb").args(&["shell", args]).output(); + let output = Command::new("adb").args(adb_command).output(); match output { Err(e) => { @@ -76,7 +91,7 @@ pub fn list_all_system_packages(user_id: &Option<&User>) -> String { None => "pm list packages -s -u".to_string(), }; - adb_shell_command(&action) + adb_shell_command(true, &action) .unwrap_or_else(|_| "".to_string()) .replace("package:", "") } @@ -93,7 +108,7 @@ pub fn hashset_system_packages(state: PackageState, user_id: &Option<&User>) -> _ => "".to_string(), // You probably don't need to use this function for anything else }; - adb_shell_command(&action) + adb_shell_command(true, &action) .unwrap_or_default() .replace("package:", "") .lines() @@ -197,7 +212,7 @@ pub fn action_handler( }; for action in actions { - match adb_shell_command(&action) { + match adb_shell_command(true, &action) { Ok(_) => { info!("[{}] {}", package.removal, action); } @@ -214,7 +229,7 @@ pub fn action_handler( } pub fn get_phone_model() -> String { - match adb_shell_command("getprop ro.product.model") { + match adb_shell_command(true, "getprop ro.product.model") { Ok(model) => model, Err(err) => { println!("ERROR: {}", err); @@ -228,7 +243,7 @@ pub fn get_phone_model() -> String { } pub fn get_android_sdk() -> u8 { - match adb_shell_command("getprop ro.build.version.sdk") { + match adb_shell_command(true, "getprop ro.build.version.sdk") { Ok(sdk) => sdk.parse().unwrap(), Err(_) => 0, } @@ -237,7 +252,7 @@ pub fn get_android_sdk() -> u8 { pub fn get_phone_brand() -> String { format!( "{} {}", - adb_shell_command("getprop ro.product.brand") + adb_shell_command(true, "getprop ro.product.brand") .unwrap_or_else(|_| "".to_string()) .trim(), get_phone_model() @@ -247,8 +262,7 @@ pub fn get_phone_brand() -> String { pub fn get_user_list() -> Vec { #[dynamic] static RE: Regex = Regex::new(r"\{([0-9]+)").unwrap(); - - match adb_shell_command("pm list users") { + match adb_shell_command(true, "pm list users") { Ok(users) => RE .find_iter(&users) .enumerate() @@ -260,3 +274,30 @@ pub fn get_user_list() -> Vec { Err(_) => vec![], } } + +pub fn get_device_list() -> Vec { + #[dynamic] + static RE: Regex = Regex::new(r"\n([[:alnum:]]+)\s+device").unwrap(); + + match adb_shell_command(false, "devices") { + Ok(devices) => { + let mut device_list: Vec = vec![]; + for device in RE.captures_iter(&devices) { + env::set_var("ANDROID_SERIAL", device[1].to_string()); + + device_list.push(Phone { + model: get_phone_brand(), + android_sdk: get_android_sdk(), + user_list: get_user_list(), + adb_id: device[1].to_string(), + }); + } + device_list + } + + Err(err) => { + warn!("get_device_list() -> {}", err); + vec![] + } + } +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 947f7dc..364b337 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -2,16 +2,17 @@ pub mod style; pub mod views; pub mod widgets; -pub use crate::core::sync::Phone; +pub use crate::core::sync::{get_device_list, Phone}; pub use crate::core::uad_lists::Package; use crate::core::utils::icon; +use std::env; pub use views::about::About as AboutView; pub use views::list::{List as AppsView, Message as AppsMessage}; pub use views::settings::{Message as SettingsMessage, Settings as SettingsView}; use iced::{ - button, window::Settings as Window, Alignment, Application, Button, Column, Command, Container, - Element, Font, Length, Row, Settings, Space, Text, + button, pick_list, window::Settings as Window, Alignment, Application, Button, Column, Command, + Container, Element, Font, Length, PickList, Row, Settings, Space, Text }; pub const ICONS: Font = Font::External { @@ -34,7 +35,6 @@ impl Default for View { #[derive(Debug, Default)] pub struct UadGui { - phone: Phone, view: View, apps_view: AppsView, about_view: AboutView, @@ -43,6 +43,9 @@ pub struct UadGui { settings_btn: button::State, apps_btn: button::State, apps_refresh_btn: button::State, + device_picklist: pick_list::State, + device_list: Vec, + selected_device: Phone, } #[derive(Debug, Clone)] @@ -50,12 +53,14 @@ pub enum Message { // Navigation Panel AboutPressed, SettingsPressed, - AppsRefreshPress, + LoadDevices(usize), AppsPress, + DeviceSelected(Phone), AppsAction(AppsMessage), SettingsAction(SettingsMessage), - Init(AppsMessage), + RefreshButtonPressed, + Init, } impl Application for UadGui { @@ -66,7 +71,7 @@ impl Application for UadGui { fn new(_flags: ()) -> (Self, Command) { ( Self::default(), - Command::perform(Self::load_phone_packages(), Message::Init), + Command::perform(Self::init(), |_| { Message::Init }), ) } @@ -76,20 +81,28 @@ impl Application for UadGui { fn update(&mut self, message: Message) -> Command { match message { - Message::Init(_) => { - info!( - "ANDROID_SDK: {} | PHONE: {}", - self.phone.android_sdk, self.phone.model - ); - Command::perform(Self::load_phone_packages(), Message::AppsAction) + Message::Init => { + Command::perform(Self::refresh(10), Message::LoadDevices) } - Message::AppsRefreshPress => { - self.phone = Phone::default(); + Message::RefreshButtonPressed => { + // Save the current selected device + let i = self.device_list.iter().position(|phone| *phone == self.selected_device).unwrap(); + self.device_list = vec![Phone::default()]; + self.selected_device = self.device_list[0].clone(); + Command::perform(Self::refresh(i), Message::LoadDevices) + } + Message::LoadDevices(old_selected_device) => { self.settings_view = SettingsView::default(); + self.device_list = get_device_list(); + self.selected_device = match old_selected_device < self.device_list.len() { + true => self.device_list[old_selected_device].clone(), + false => self.device_list.last().unwrap().clone(), + }; + env::set_var("ANDROID_SERIAL", self.selected_device.adb_id.clone()); info!("{:-^65}", "-"); info!( "ANDROID_SDK: {} | PHONE: {}", - self.phone.android_sdk, self.phone.model + self.selected_device.android_sdk, self.selected_device.model ); self.apps_view = AppsView::default(); self.view = View::List; @@ -109,12 +122,24 @@ impl Application for UadGui { } Message::AppsAction(msg) => self .apps_view - .update(&self.settings_view, &mut self.phone, msg) + .update(&self.settings_view, &mut self.selected_device, msg) .map(Message::AppsAction), Message::SettingsAction(msg) => { self.settings_view.update(msg); Command::none() } + Message::DeviceSelected(device) => { + self.selected_device = device; + env::set_var("ANDROID_SERIAL", self.selected_device.adb_id.clone()); + info!("{:-^65}", "-"); + info!( + "ANDROID_SDK: {} | PHONE: {}", + self.selected_device.android_sdk, self.selected_device.model + ); + self.apps_view = AppsView::default(); + self.view = View::List; + Command::perform(Self::load_phone_packages(), Message::AppsAction) + } } } @@ -125,10 +150,18 @@ impl Application for UadGui { .style(style::PrimaryButton(self.settings_view.theme.palette)); let apps_refresh_btn = Button::new(&mut self.apps_refresh_btn, refresh_icon()) - .on_press(Message::AppsRefreshPress) + .on_press(Message::RefreshButtonPressed) .padding(5) .style(style::RefreshButton(self.settings_view.theme.palette)); + let device_picklist = PickList::new( + &mut self.device_picklist, + &self.device_list, + Some(self.selected_device.clone()), + Message::DeviceSelected, + ) + .style(style::PickList(self.settings_view.theme.palette)); + let uad_version = Text::new(env!("CARGO_PKG_VERSION")); let about_btn = Button::new(&mut self.about_btn, Text::new("About")) @@ -146,7 +179,7 @@ impl Application for UadGui { .align_items(Alignment::Center) .spacing(10) .push(apps_refresh_btn) - .push(Text::new("Device: ".to_string() + &self.phone.model)) + .push(device_picklist) .push(Space::new(Length::Fill, Length::Shrink)) .push(uad_version) .push(apps_btn) @@ -161,7 +194,7 @@ impl Application for UadGui { let main_container = match self.view { View::List => self .apps_view - .view(&self.settings_view, &self.phone) + .view(&self.settings_view, &self.selected_device) .map(Message::AppsAction), View::About => self.about_view.view(&self.settings_view), View::Settings => self.settings_view.view().map(Message::SettingsAction), @@ -193,6 +226,14 @@ impl UadGui { pub async fn load_phone_packages() -> AppsMessage { AppsMessage::LoadPackages } + + pub async fn init() -> Message { + Message::Init + } + + pub async fn refresh(i: usize) -> usize { + i + } } fn refresh_icon() -> Text { diff --git a/src/gui/views/mod.rs b/src/gui/views/mod.rs index a288610..bb8e232 100644 --- a/src/gui/views/mod.rs +++ b/src/gui/views/mod.rs @@ -1,3 +1,3 @@ pub mod about; pub mod list; -pub mod settings; +pub mod settings; \ No newline at end of file