mirror of
https://github.com/0x192/universal-android-debloater.git
synced 2026-10-23 06:40:42 +00:00
refactor: major code overhaul + support for the new Iced theme API (#369)
Note: UAD is now temporarily based on my fork of Iced until iced-rs/iced#1425 is merged upstream. - Remove custom code for theming: UAD now uses the new Iced styling API - All the code for initializing UAD (download debloat lists, fetch connected devices etc...) is now a lot more streamlined - Other miscellaneous code improvement - Replace row() and column() functions by the new Iced vector-like helper functions (row![], column![]) - Move navigation menu GUI code in a dedicated file - Rework the way UAD uses events for async code
This commit is contained in:
parent
7788f3cf83
commit
9b564d3825
14 changed files with 1450 additions and 1382 deletions
915
Cargo.lock
generated
915
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -19,7 +19,7 @@ self-update = ["flate2", "tar"]
|
|||
no-self-update = []
|
||||
|
||||
[dependencies]
|
||||
iced = { git = "https://github.com/hecrj/iced.git", features = ["pure"] }
|
||||
iced = { git = "https://github.com/0x192/iced.git", branch = "patch/theme" }
|
||||
serde = { version = "^1.0", features = ["derive"] }
|
||||
serde_json = "^1.0"
|
||||
static_init = "^1.0"
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ pub fn adb_shell_command(shell: bool, args: &str) -> Result<String, String> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn list_all_system_packages(user_id: &Option<&User>) -> String {
|
||||
pub fn list_all_system_packages(user_id: Option<&User>) -> String {
|
||||
let action = match user_id {
|
||||
Some(user_id) => format!("pm list packages -s -u --user {}", user_id.id),
|
||||
None => "pm list packages -s -u".to_string(),
|
||||
|
|
@ -101,7 +101,7 @@ pub fn list_all_system_packages(user_id: &Option<&User>) -> String {
|
|||
.replace("package:", "")
|
||||
}
|
||||
|
||||
pub fn hashset_system_packages(state: PackageState, user_id: &Option<&User>) -> HashSet<String> {
|
||||
pub fn hashset_system_packages(state: PackageState, user_id: Option<&User>) -> HashSet<String> {
|
||||
let user = match user_id {
|
||||
Some(user_id) => format!(" --user {}", user_id.id),
|
||||
None => "".to_string(),
|
||||
|
|
@ -137,10 +137,10 @@ pub fn action_handler(
|
|||
};
|
||||
|
||||
match phone.android_sdk {
|
||||
i if i >= 23 => commands, // > Android Lollipop (6.0)
|
||||
sdk if sdk >= 23 => commands, // > Android Lollipop (6.0)
|
||||
21 | 22 => vec!["pm hide", "pm clear"], // Android Lollipop (5.x)
|
||||
19 | 20 => vec!["pm block", "pm clear"], // Android KitKat (4.4/4.4W)
|
||||
_ => vec!["pm uninstall"], // Disable mode is unavailable on older devices because the needed ADB commands need root
|
||||
_ => vec!["pm uninstall"], // Disable mode is unavailable on older devices because the specific ADB commands need root
|
||||
}
|
||||
}
|
||||
PackageState::Uninstalled => {
|
||||
|
|
@ -217,7 +217,7 @@ pub fn get_user_list() -> Vec<User> {
|
|||
}
|
||||
|
||||
// getprop ro.serialno
|
||||
pub async fn get_device_list() -> Vec<Phone> {
|
||||
pub async fn get_devices_list() -> Vec<Phone> {
|
||||
match retry(
|
||||
Fixed::from_millis(500).take(120),
|
||||
|| match adb_shell_command(false, "devices") {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,12 @@ pub struct Theme {
|
|||
pub palette: ColorPalette,
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
Theme::lupin()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BaseColors {
|
||||
pub background: Color,
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ impl std::fmt::Display for Removal {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn load_debloat_lists(remote: bool) -> (Result<HashMap<String, Package>, ()>, bool) {
|
||||
pub fn load_debloat_lists(remote: bool) -> (Result<HashMap<String, Package>, ()>, bool) {
|
||||
let cached_uad_lists: PathBuf = CACHE_DIR.join("uad_lists.json");
|
||||
let mut error = false;
|
||||
let list: Vec<Package> = if remote {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ use crate::core::theme::Theme;
|
|||
use crate::core::uad_lists::{Package, PackageState, Removal, UadList};
|
||||
use crate::gui::views::list::Selection;
|
||||
use crate::gui::widgets::package_row::PackageRow;
|
||||
use crate::gui::ICONS;
|
||||
use chrono::offset::Utc;
|
||||
use chrono::DateTime;
|
||||
use iced::{alignment, Length, Text};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::io::{self, prelude::*, BufReader};
|
||||
|
|
@ -17,7 +15,7 @@ use std::process::Command;
|
|||
|
||||
pub fn fetch_packages(
|
||||
uad_lists: &HashMap<String, Package>,
|
||||
user_id: &Option<&User>,
|
||||
user_id: Option<&User>,
|
||||
) -> Vec<PackageRow> {
|
||||
let all_system_packages = list_all_system_packages(user_id); // installed and uninstalled packages
|
||||
let enabled_system_packages = hashset_system_packages(PackageState::Enabled, user_id);
|
||||
|
|
@ -122,14 +120,6 @@ pub fn import_selection(packages: &mut [PackageRow], selection: &mut Selection)
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn icon(unicode: char) -> Text {
|
||||
Text::new(&unicode.to_string())
|
||||
.font(ICONS)
|
||||
.width(Length::Units(17))
|
||||
.horizontal_alignment(alignment::Horizontal::Center)
|
||||
.size(17)
|
||||
}
|
||||
|
||||
pub fn string_to_theme(theme: String) -> Theme {
|
||||
match theme.as_str() {
|
||||
"Dark" => Theme::dark(),
|
||||
|
|
|
|||
398
src/gui/mod.rs
398
src/gui/mod.rs
|
|
@ -2,60 +2,47 @@ pub mod style;
|
|||
pub mod views;
|
||||
pub mod widgets;
|
||||
|
||||
pub use crate::core::sync::{get_device_list, Phone};
|
||||
pub use crate::core::uad_lists::Package;
|
||||
use crate::core::uad_lists::{load_debloat_lists, UadListState};
|
||||
use crate::core::update::{get_latest_release, SelfUpdateState, SelfUpdateStatus};
|
||||
use crate::core::utils::{icon, perform_commands};
|
||||
use iced::pure::widget::Text;
|
||||
use iced::pure::{button, column, container, pick_list, row, text, Pure, State};
|
||||
use iced::{
|
||||
window::Settings as Window, Alignment, Application, Command, Element, Font, Length, Settings,
|
||||
Space,
|
||||
};
|
||||
use crate::core::sync::{get_devices_list, Phone};
|
||||
use crate::core::theme::Theme;
|
||||
use crate::core::uad_lists::UadListState;
|
||||
use crate::core::update::{get_latest_release, Release, SelfUpdateState, SelfUpdateStatus};
|
||||
use crate::core::utils::perform_commands;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
pub use views::about::{About as AboutView, Message as AboutMessage};
|
||||
pub use views::list::{
|
||||
List as AppsView, LoadingState as ListLoadingState, Message as AppsMessage, State as ListState,
|
||||
};
|
||||
pub use views::settings::{
|
||||
Message as SettingsMessage, Phone as SettingsPhone, Settings as SettingsView,
|
||||
};
|
||||
use views::about::{About as AboutView, Message as AboutMessage};
|
||||
use views::list::{List as AppsView, LoadingState as ListLoadingState, Message as AppsMessage};
|
||||
use views::settings::{Message as SettingsMessage, Settings as SettingsView};
|
||||
use widgets::navigation_menu::nav_menu;
|
||||
|
||||
pub const ICONS: Font = Font::External {
|
||||
name: "Icons",
|
||||
bytes: include_bytes!("../../resources/assets/icons.ttf"),
|
||||
};
|
||||
use iced::widget::column;
|
||||
use iced::{window::Settings as Window, Application, Command, Element, Length, Renderer, Settings};
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
#[cfg(feature = "self-update")]
|
||||
use crate::core::update::{bin_name, download_update_to_temp_file, remove_file};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum View {
|
||||
#[derive(Default, Debug, Clone)]
|
||||
enum View {
|
||||
#[default]
|
||||
List,
|
||||
About,
|
||||
Settings,
|
||||
}
|
||||
|
||||
impl Default for View {
|
||||
fn default() -> Self {
|
||||
Self::List
|
||||
}
|
||||
#[derive(Default, Clone)]
|
||||
pub struct UpdateState {
|
||||
self_update: SelfUpdateState,
|
||||
uad_list: UadListState,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct UadGui {
|
||||
ready: bool,
|
||||
view: View,
|
||||
state: State,
|
||||
apps_view: AppsView,
|
||||
about_view: AboutView,
|
||||
settings_view: SettingsView,
|
||||
device_list: Vec<Phone>,
|
||||
selected_device: Option<Phone>,
|
||||
devices_list: Vec<Phone>,
|
||||
selected_device: Option<Phone>, // index of devices_list
|
||||
update_state: UpdateState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -70,14 +57,14 @@ pub enum Message {
|
|||
SettingsAction(SettingsMessage),
|
||||
RefreshButtonPressed,
|
||||
RebootButtonPressed,
|
||||
UadListsDownloaded((Result<HashMap<String, Package>, ()>, bool)),
|
||||
InitList,
|
||||
InitDevice(Vec<Phone>),
|
||||
LoadDevices(Vec<Phone>),
|
||||
_NewReleaseDownloaded(Result<(PathBuf, PathBuf), ()>),
|
||||
GetLatestRelease(Result<Option<Release>, ()>),
|
||||
Nothing,
|
||||
}
|
||||
|
||||
impl Application for UadGui {
|
||||
type Theme = Theme;
|
||||
type Executor = iced::executor::Default;
|
||||
type Message = Message;
|
||||
type Flags = ();
|
||||
|
|
@ -86,102 +73,37 @@ impl Application for UadGui {
|
|||
(
|
||||
Self::default(),
|
||||
Command::batch([
|
||||
Command::perform(Self::download_uad_list(), |_| Message::InitList),
|
||||
Command::perform(get_device_list(), Message::InitDevice),
|
||||
Command::perform(Self::send_self_update_message(), Message::SettingsAction),
|
||||
Command::perform(get_devices_list(), Message::LoadDevices),
|
||||
Command::perform(
|
||||
async move { get_latest_release() },
|
||||
Message::GetLatestRelease,
|
||||
),
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
self.settings_view.theme.clone()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Universal Android Debloater")
|
||||
}
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::InitList => {
|
||||
warn!("Trying to download remote UAD list");
|
||||
self.apps_view.state = ListState::Loading(ListLoadingState::DownloadingList);
|
||||
Command::perform(load_debloat_lists(true), Message::UadListsDownloaded)
|
||||
}
|
||||
Message::UadListsDownloaded((uad_lists, _)) => match uad_lists {
|
||||
Ok(list) => {
|
||||
if !list.is_empty() {
|
||||
self.apps_view.uad_lists = list;
|
||||
fn update(&mut self, msg: Message) -> Command<Message> {
|
||||
match msg {
|
||||
Message::LoadDevices(devices_list) => {
|
||||
self.selected_device = match &self.selected_device {
|
||||
Some(s_device) => {
|
||||
// Try to reload last selected phone
|
||||
devices_list
|
||||
.iter()
|
||||
.find(|phone| phone.adb_id == s_device.adb_id)
|
||||
.map(|x| x.to_owned())
|
||||
}
|
||||
self.settings_view.list_update_state = UadListState::Done;
|
||||
|
||||
if self.ready {
|
||||
Command::perform(
|
||||
Self::load_phone_packages(
|
||||
self.selected_device.clone().unwrap_or_default(),
|
||||
),
|
||||
Message::AppsAction,
|
||||
)
|
||||
} else {
|
||||
self.ready = true;
|
||||
self.apps_view.state = ListState::Loading(ListLoadingState::FindingPhones);
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
self.settings_view.list_update_state = UadListState::Failed;
|
||||
if let ListState::Ready = self.apps_view.state {
|
||||
} else {
|
||||
self.apps_view.state =
|
||||
ListState::Loading(ListLoadingState::DownloadingList);
|
||||
}
|
||||
Command::none()
|
||||
}
|
||||
},
|
||||
Message::InitDevice(device_list) => {
|
||||
self.device_list = device_list;
|
||||
self.settings_view.phone = SettingsPhone::default();
|
||||
|
||||
// Save the current selected device
|
||||
let i = self
|
||||
.device_list
|
||||
.iter()
|
||||
.position(|phone| *phone == self.selected_device.clone().unwrap_or_default())
|
||||
.unwrap_or(0);
|
||||
|
||||
// Try to reload last selected phone
|
||||
if !self.device_list.is_empty() {
|
||||
self.selected_device = match i < self.device_list.len() {
|
||||
true => Some(self.device_list[i].clone()),
|
||||
false => match self.device_list.last() {
|
||||
Some(last) => Some(last.clone()),
|
||||
None => Some(Phone::default()),
|
||||
},
|
||||
};
|
||||
self.view = View::List;
|
||||
} else {
|
||||
self.selected_device = None;
|
||||
}
|
||||
if self.settings_view.list_update_state != UadListState::Downloading || self.ready {
|
||||
Command::perform(
|
||||
Self::load_phone_packages(self.selected_device.clone().unwrap_or_default()),
|
||||
Message::AppsAction,
|
||||
)
|
||||
} else {
|
||||
self.ready = true;
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
Message::RefreshButtonPressed => {
|
||||
self.apps_view.state = ListState::Loading(ListLoadingState::FindingPhones);
|
||||
self.selected_device = None;
|
||||
self.ready = true;
|
||||
Command::perform(get_device_list(), Message::InitDevice)
|
||||
}
|
||||
Message::RebootButtonPressed => {
|
||||
self.apps_view.state = ListState::Loading(ListLoadingState::FindingPhones);
|
||||
self.selected_device = None;
|
||||
self.device_list = vec![];
|
||||
self.ready = false;
|
||||
Command::perform(
|
||||
perform_commands("reboot".to_string(), 0, "ADB".to_string()),
|
||||
|_| Message::Nothing,
|
||||
)
|
||||
None => devices_list.first().map(|x| x.to_owned()),
|
||||
};
|
||||
self.devices_list = devices_list;
|
||||
self.update(Message::AppsAction(AppsMessage::LoadUadList(true)))
|
||||
}
|
||||
Message::AppsPress => {
|
||||
self.view = View::List;
|
||||
|
|
@ -189,18 +111,35 @@ impl Application for UadGui {
|
|||
}
|
||||
Message::AboutPressed => {
|
||||
self.view = View::About;
|
||||
self.settings_view.self_update_state = SelfUpdateState::default();
|
||||
Command::perform(Self::send_self_update_message(), Message::SettingsAction)
|
||||
self.update_state.self_update = SelfUpdateState::default();
|
||||
Command::perform(
|
||||
async move { get_latest_release() },
|
||||
Message::GetLatestRelease,
|
||||
)
|
||||
}
|
||||
Message::SettingsPressed => {
|
||||
self.view = View::Settings;
|
||||
Command::none()
|
||||
}
|
||||
Message::RefreshButtonPressed => {
|
||||
self.apps_view.loading_state = ListLoadingState::FindingPhones;
|
||||
Command::perform(get_devices_list(), Message::LoadDevices)
|
||||
}
|
||||
Message::RebootButtonPressed => {
|
||||
self.apps_view.loading_state = ListLoadingState::FindingPhones;
|
||||
self.selected_device = None;
|
||||
self.devices_list = vec![];
|
||||
Command::perform(
|
||||
perform_commands("reboot".to_string(), 0, "ADB".to_string()),
|
||||
|_| Message::Nothing,
|
||||
)
|
||||
}
|
||||
Message::AppsAction(msg) => self
|
||||
.apps_view
|
||||
.update(
|
||||
&mut self.settings_view,
|
||||
&mut self.selected_device.clone().unwrap_or_default(),
|
||||
&mut self.update_state.uad_list,
|
||||
msg,
|
||||
)
|
||||
.map(Message::AppsAction),
|
||||
|
|
@ -214,25 +153,19 @@ impl Application for UadGui {
|
|||
|
||||
match msg {
|
||||
AboutMessage::UpdateUadLists => {
|
||||
self.settings_view.list_update_state = UadListState::Downloading;
|
||||
Command::perform(Self::download_uad_list(), |_| Message::InitList)
|
||||
self.update_state.uad_list = UadListState::Downloading;
|
||||
self.apps_view.loading_state = ListLoadingState::DownloadingList;
|
||||
self.update(Message::AppsAction(AppsMessage::LoadUadList(true)))
|
||||
}
|
||||
AboutMessage::DoSelfUpdate => {
|
||||
#[cfg(feature = "self-update")]
|
||||
if self
|
||||
.settings_view
|
||||
.self_update_state
|
||||
.latest_release
|
||||
.is_some()
|
||||
{
|
||||
self.settings_view.self_update_state.status =
|
||||
SelfUpdateStatus::Updating;
|
||||
self.apps_view.state =
|
||||
ListState::Loading(ListLoadingState::_UpdatingUad);
|
||||
if self.update_state.self_update.latest_release.is_some() {
|
||||
self.update_state.self_update.status = SelfUpdateStatus::Updating;
|
||||
self.apps_view.loading_state = ListLoadingState::_UpdatingUad;
|
||||
let bin_name = bin_name().to_owned();
|
||||
let release = self
|
||||
.settings_view
|
||||
.self_update_state
|
||||
.update_state
|
||||
.self_update
|
||||
.latest_release
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
|
|
@ -250,13 +183,20 @@ impl Application for UadGui {
|
|||
_ => Command::none(),
|
||||
}
|
||||
}
|
||||
Message::DeviceSelected(device) => {
|
||||
self.selected_device = Some(device);
|
||||
Message::DeviceSelected(s_device) => {
|
||||
self.selected_device = Some(s_device.clone());
|
||||
self.view = View::List;
|
||||
Command::perform(
|
||||
Self::load_phone_packages(self.selected_device.clone().unwrap_or_default()),
|
||||
Message::AppsAction,
|
||||
)
|
||||
env::set_var("ANDROID_SERIAL", s_device.adb_id);
|
||||
info!("{:-^65}", "-");
|
||||
info!(
|
||||
"ANDROID_SDK: {} | PHONE: {}",
|
||||
s_device.android_sdk, s_device.model
|
||||
);
|
||||
self.apps_view.loading_state = ListLoadingState::FindingPhones;
|
||||
self.update(Message::AppsAction(AppsMessage::LoadPhonePackages((
|
||||
self.apps_view.uad_lists.clone(),
|
||||
UadListState::Done,
|
||||
))))
|
||||
}
|
||||
Message::_NewReleaseDownloaded(_res) => {
|
||||
debug!("UAD update has been download!");
|
||||
|
|
@ -303,137 +243,47 @@ impl Application for UadGui {
|
|||
}
|
||||
Command::none()
|
||||
}
|
||||
Message::Nothing => Command::perform(get_device_list(), Message::InitDevice),
|
||||
Message::GetLatestRelease(release) => {
|
||||
match release {
|
||||
Ok(r) => {
|
||||
self.update_state.self_update.status = SelfUpdateStatus::Done;
|
||||
self.update_state.self_update.latest_release = r
|
||||
}
|
||||
Err(_) => self.update_state.self_update.status = SelfUpdateStatus::Failed,
|
||||
};
|
||||
Command::none()
|
||||
}
|
||||
_ => Command::none(),
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&mut self) -> Element<Self::Message> {
|
||||
let apps_refresh_btn = button(refresh_icon())
|
||||
.on_press(Message::RefreshButtonPressed)
|
||||
.padding(5)
|
||||
.style(style::RefreshButton(self.settings_view.theme.palette));
|
||||
|
||||
let reboot_btn = button("Reboot")
|
||||
.on_press(Message::RebootButtonPressed)
|
||||
.padding(5)
|
||||
.style(style::RefreshButton(self.settings_view.theme.palette));
|
||||
|
||||
let uad_version_text = if let Some(r) = &self.settings_view.self_update_state.latest_release
|
||||
{
|
||||
if self.settings_view.self_update_state.status == SelfUpdateStatus::Updating {
|
||||
Text::new("Updating please wait...")
|
||||
.color(self.settings_view.theme.palette.normal.surface)
|
||||
} else {
|
||||
Text::new(format!(
|
||||
"New UAD version available {} -> {}",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
r.tag_name
|
||||
))
|
||||
.color(self.settings_view.theme.palette.normal.surface)
|
||||
}
|
||||
} else {
|
||||
Text::new(env!("CARGO_PKG_VERSION"))
|
||||
};
|
||||
|
||||
let apps_btn = if self
|
||||
.settings_view
|
||||
.self_update_state
|
||||
.latest_release
|
||||
.is_some()
|
||||
{
|
||||
button("Update")
|
||||
.on_press(Message::AboutAction(AboutMessage::DoSelfUpdate))
|
||||
.padding(5)
|
||||
.style(style::SelfUpdateButton(self.settings_view.theme.palette))
|
||||
} else {
|
||||
button("Apps")
|
||||
.on_press(Message::AppsPress)
|
||||
.padding(5)
|
||||
.style(style::PrimaryButton(self.settings_view.theme.palette))
|
||||
};
|
||||
|
||||
let about_btn = button("About")
|
||||
.on_press(Message::AboutPressed)
|
||||
.padding(5)
|
||||
.style(style::PrimaryButton(self.settings_view.theme.palette));
|
||||
|
||||
let settings_btn = button("Settings")
|
||||
.on_press(Message::SettingsPressed)
|
||||
.padding(5)
|
||||
.style(style::PrimaryButton(self.settings_view.theme.palette));
|
||||
|
||||
let device_picklist = pick_list(
|
||||
&self.device_list,
|
||||
fn view(&self) -> Element<Self::Message, Renderer<Self::Theme>> {
|
||||
let navigation_container = nav_menu(
|
||||
&self.devices_list,
|
||||
self.selected_device.clone(),
|
||||
Message::DeviceSelected,
|
||||
)
|
||||
.style(style::PickList(self.settings_view.theme.palette));
|
||||
|
||||
let device_list_text = match self.apps_view.state {
|
||||
ListState::Loading(ListLoadingState::FindingPhones) => {
|
||||
text("finding connected phone...")
|
||||
}
|
||||
_ => text("no devices/emulators found"),
|
||||
};
|
||||
|
||||
let row = match self.selected_device {
|
||||
Some(_) => row()
|
||||
.width(Length::Fill)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(apps_refresh_btn)
|
||||
.push(reboot_btn)
|
||||
.push(device_picklist)
|
||||
.push(Space::new(Length::Fill, Length::Shrink))
|
||||
.push(uad_version_text)
|
||||
.push(apps_btn)
|
||||
.push(about_btn)
|
||||
.push(settings_btn),
|
||||
None => row()
|
||||
.width(Length::Fill)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(reboot_btn)
|
||||
.push(apps_refresh_btn)
|
||||
.push(device_list_text)
|
||||
.push(Space::new(Length::Fill, Length::Shrink))
|
||||
.push(uad_version_text)
|
||||
.push(apps_btn)
|
||||
.push(about_btn)
|
||||
.push(settings_btn),
|
||||
};
|
||||
|
||||
let navigation_container = container(row)
|
||||
.width(Length::Fill)
|
||||
.padding(10)
|
||||
.style(style::NavigationContainer(self.settings_view.theme.palette));
|
||||
&self.apps_view,
|
||||
&self.update_state.self_update,
|
||||
);
|
||||
|
||||
let selected_device = self.selected_device.clone().unwrap_or_default();
|
||||
let main_container = match self.view {
|
||||
View::List => self
|
||||
.apps_view
|
||||
.view(
|
||||
&self.settings_view,
|
||||
&self.selected_device.clone().unwrap_or_default(),
|
||||
)
|
||||
.view(&self.settings_view, &selected_device)
|
||||
.map(Message::AppsAction),
|
||||
View::About => self
|
||||
.about_view
|
||||
.view(&self.settings_view)
|
||||
.view(&self.update_state)
|
||||
.map(Message::AboutAction),
|
||||
View::Settings => self
|
||||
.settings_view
|
||||
.view(&self.selected_device.clone().unwrap_or_default())
|
||||
.view(&selected_device)
|
||||
.map(Message::SettingsAction),
|
||||
};
|
||||
|
||||
Pure::new(
|
||||
&mut self.state,
|
||||
column()
|
||||
.width(Length::Fill)
|
||||
.push(navigation_container)
|
||||
.push(main_container),
|
||||
)
|
||||
.into()
|
||||
column![navigation_container, main_container]
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -451,26 +301,4 @@ impl UadGui {
|
|||
};
|
||||
Self::run(settings).unwrap_err();
|
||||
}
|
||||
|
||||
pub async fn load_phone_packages(phone: Phone) -> AppsMessage {
|
||||
env::set_var("ANDROID_SERIAL", phone.adb_id);
|
||||
info!("{:-^65}", "-");
|
||||
info!(
|
||||
"ANDROID_SDK: {} | PHONE: {}",
|
||||
phone.android_sdk, phone.model
|
||||
);
|
||||
AppsMessage::LoadPackages
|
||||
}
|
||||
|
||||
pub async fn download_uad_list() -> Message {
|
||||
Message::InitList
|
||||
}
|
||||
|
||||
pub async fn send_self_update_message() -> SettingsMessage {
|
||||
SettingsMessage::GetLatestRelease(get_latest_release())
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_icon() -> Text {
|
||||
icon('\u{E900}')
|
||||
}
|
||||
|
|
|
|||
744
src/gui/style.rs
744
src/gui/style.rs
|
|
@ -1,338 +1,228 @@
|
|||
use crate::core::theme::ColorPalette;
|
||||
use iced::{button, checkbox, container, pick_list, scrollable, text_input, Background, Color};
|
||||
use crate::core::theme::Theme;
|
||||
use iced::overlay::menu;
|
||||
use iced::widget::{button, checkbox, container, pick_list, scrollable, text, text_input};
|
||||
use iced::{application, Background, Color};
|
||||
|
||||
pub struct Content(pub ColorPalette);
|
||||
impl container::StyleSheet for Content {
|
||||
fn style(&self) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(self.0.base.background)),
|
||||
text_color: Some(self.0.bright.surface),
|
||||
..container::Style::default()
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Application {
|
||||
Default,
|
||||
}
|
||||
|
||||
impl Default for Application {
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
impl application::StyleSheet for Theme {
|
||||
type Style = Application;
|
||||
|
||||
fn appearance(&self, _style: Self::Style) -> application::Appearance {
|
||||
application::Appearance {
|
||||
background_color: self.palette.base.background,
|
||||
text_color: self.palette.base.foreground,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NavigationContainer(pub ColorPalette);
|
||||
impl container::StyleSheet for NavigationContainer {
|
||||
fn style(&self) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(self.0.base.foreground)),
|
||||
text_color: Some(self.0.bright.surface),
|
||||
..container::Style::default()
|
||||
}
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Container {
|
||||
Content,
|
||||
Navigation,
|
||||
Description,
|
||||
}
|
||||
|
||||
impl Default for Container {
|
||||
fn default() -> Self {
|
||||
Self::Content
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrimaryButton(pub ColorPalette);
|
||||
impl button::StyleSheet for PrimaryButton {
|
||||
fn active(&self) -> button::Style {
|
||||
button::Style {
|
||||
border_color: Color {
|
||||
a: 0.5,
|
||||
..self.0.bright.primary
|
||||
},
|
||||
border_width: 1.0,
|
||||
border_radius: 2.0,
|
||||
text_color: self.0.bright.primary,
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
impl container::StyleSheet for Theme {
|
||||
type Style = Container;
|
||||
|
||||
fn hovered(&self) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
a: 0.25,
|
||||
..self.0.normal.surface
|
||||
})),
|
||||
text_color: self.0.bright.primary,
|
||||
..self.active()
|
||||
}
|
||||
}
|
||||
}
|
||||
fn appearance(&self, style: Self::Style) -> container::Appearance {
|
||||
let from_appearance = |c: Color| container::Appearance {
|
||||
background: Some(Background::Color(c)),
|
||||
text_color: Some(self.palette.bright.surface),
|
||||
..container::Appearance::default()
|
||||
};
|
||||
|
||||
pub struct UnavailableButton(pub ColorPalette);
|
||||
impl button::StyleSheet for UnavailableButton {
|
||||
fn active(&self) -> button::Style {
|
||||
button::Style {
|
||||
border_color: Color {
|
||||
a: 0.5,
|
||||
..self.0.bright.error
|
||||
},
|
||||
border_width: 1.0,
|
||||
border_radius: 2.0,
|
||||
text_color: self.0.bright.error,
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
a: 0.25,
|
||||
..self.0.normal.primary
|
||||
})),
|
||||
text_color: self.0.normal.error,
|
||||
..self.active()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SelfUpdateButton(pub ColorPalette);
|
||||
impl button::StyleSheet for SelfUpdateButton {
|
||||
fn active(&self) -> button::Style {
|
||||
button::Style {
|
||||
border_color: Color {
|
||||
a: 0.5,
|
||||
..self.0.bright.secondary
|
||||
},
|
||||
border_width: 1.0,
|
||||
border_radius: 2.0,
|
||||
text_color: self.0.bright.secondary,
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
a: 0.25,
|
||||
..self.0.normal.surface
|
||||
})),
|
||||
text_color: self.0.bright.secondary,
|
||||
..self.active()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RefreshButton(pub ColorPalette);
|
||||
impl button::StyleSheet for RefreshButton {
|
||||
fn active(&self) -> button::Style {
|
||||
button::Style {
|
||||
border_color: Color {
|
||||
a: 0.5,
|
||||
..self.0.bright.primary
|
||||
},
|
||||
border_width: 1.0,
|
||||
border_radius: 2.0,
|
||||
text_color: self.0.bright.primary,
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
a: 0.25,
|
||||
..self.0.normal.surface
|
||||
})),
|
||||
text_color: self.0.bright.primary,
|
||||
..self.active()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PackageButton {
|
||||
Uninstall(ColorPalette),
|
||||
Restore(ColorPalette),
|
||||
}
|
||||
|
||||
impl button::StyleSheet for PackageButton {
|
||||
fn active(&self) -> button::Style {
|
||||
match self {
|
||||
Self::Uninstall(palette) => button::Style {
|
||||
border_color: Color {
|
||||
a: 0.5,
|
||||
..palette.bright.error
|
||||
},
|
||||
border_width: 1.0,
|
||||
border_radius: 2.0,
|
||||
text_color: palette.bright.error,
|
||||
..button::Style::default()
|
||||
},
|
||||
Self::Restore(palette) => button::Style {
|
||||
border_color: Color {
|
||||
a: 0.5,
|
||||
..palette.bright.secondary
|
||||
},
|
||||
border_width: 1.0,
|
||||
border_radius: 2.0,
|
||||
text_color: palette.bright.secondary,
|
||||
..button::Style::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self) -> button::Style {
|
||||
match self {
|
||||
Self::Restore(palette) => button::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
a: 0.25,
|
||||
..palette.normal.primary
|
||||
})),
|
||||
text_color: palette.bright.primary,
|
||||
..self.active()
|
||||
},
|
||||
Self::Uninstall(palette) => button::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
a: 0.25,
|
||||
..palette.normal.primary
|
||||
})),
|
||||
text_color: palette.bright.primary,
|
||||
..self.active()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn disabled(&self) -> button::Style {
|
||||
match self {
|
||||
Self::Restore(palette) => button::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
a: 0.05,
|
||||
..palette.normal.primary
|
||||
})),
|
||||
text_color: Color {
|
||||
a: 0.50,
|
||||
..palette.bright.primary
|
||||
},
|
||||
..self.active()
|
||||
},
|
||||
Self::Uninstall(palette) => button::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
a: 0.01,
|
||||
..palette.bright.error
|
||||
})),
|
||||
text_color: Color {
|
||||
a: 0.50,
|
||||
..palette.bright.error
|
||||
},
|
||||
..self.active()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PackageRow {
|
||||
Normal(ColorPalette),
|
||||
Current(ColorPalette),
|
||||
}
|
||||
impl button::StyleSheet for PackageRow {
|
||||
fn active(&self) -> button::Style {
|
||||
match self {
|
||||
Self::Normal(palette) => button::Style {
|
||||
background: Some(Background::Color(palette.base.foreground)),
|
||||
text_color: palette.bright.surface,
|
||||
match style {
|
||||
Container::Content => from_appearance(self.palette.base.background),
|
||||
Container::Navigation => from_appearance(self.palette.base.background),
|
||||
Container::Description => container::Appearance {
|
||||
background: Some(Background::Color(self.palette.base.foreground)),
|
||||
text_color: Some(self.palette.bright.surface),
|
||||
border_radius: 5.0,
|
||||
border_width: 0.0,
|
||||
border_color: palette.base.background,
|
||||
..button::Style::default()
|
||||
border_color: self.palette.base.background,
|
||||
},
|
||||
Self::Current(palette) => button::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
a: 0.25,
|
||||
..palette.normal.primary
|
||||
})),
|
||||
text_color: palette.bright.primary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Button {
|
||||
Primary,
|
||||
Unavailable,
|
||||
SelfUpdate,
|
||||
Refresh,
|
||||
UninstallPackage,
|
||||
RestorePackage,
|
||||
NormalPackage,
|
||||
SelectedPackage,
|
||||
}
|
||||
|
||||
impl Default for Button {
|
||||
fn default() -> Self {
|
||||
Self::Primary
|
||||
}
|
||||
}
|
||||
|
||||
impl button::StyleSheet for Theme {
|
||||
type Style = Button;
|
||||
|
||||
fn active(&self, style: Self::Style) -> button::Appearance {
|
||||
let p = self.palette;
|
||||
|
||||
let appearance = button::Appearance {
|
||||
border_width: 1.0,
|
||||
border_radius: 2.0,
|
||||
..button::Appearance::default()
|
||||
};
|
||||
|
||||
let active_appearance = |bg: Option<Color>, mc| button::Appearance {
|
||||
background: Some(Background::Color(bg.unwrap_or(p.base.foreground))),
|
||||
border_color: Color { a: 0.5, ..mc },
|
||||
text_color: mc,
|
||||
..appearance
|
||||
};
|
||||
|
||||
match style {
|
||||
Button::Primary => active_appearance(None, p.bright.primary),
|
||||
Button::Unavailable => active_appearance(None, p.bright.error),
|
||||
Button::Refresh => active_appearance(None, p.bright.primary),
|
||||
Button::SelfUpdate => active_appearance(None, p.bright.primary),
|
||||
Button::UninstallPackage => active_appearance(None, p.bright.error),
|
||||
Button::RestorePackage => active_appearance(None, p.bright.secondary),
|
||||
Button::NormalPackage => button::Appearance {
|
||||
background: Some(Background::Color(p.base.foreground)),
|
||||
text_color: p.bright.surface,
|
||||
border_radius: 5.0,
|
||||
border_width: 0.0,
|
||||
border_color: palette.normal.primary,
|
||||
..button::Style::default()
|
||||
border_color: p.base.background,
|
||||
..appearance
|
||||
},
|
||||
}
|
||||
}
|
||||
fn hovered(&self) -> button::Style {
|
||||
match self {
|
||||
Self::Normal(palette) => button::Style {
|
||||
Button::SelectedPackage => button::Appearance {
|
||||
background: Some(Background::Color(Color {
|
||||
a: 0.30,
|
||||
..palette.normal.primary
|
||||
a: 0.25,
|
||||
..p.normal.primary
|
||||
})),
|
||||
text_color: palette.bright.primary,
|
||||
..self.active()
|
||||
text_color: p.bright.primary,
|
||||
border_radius: 5.0,
|
||||
border_width: 0.0,
|
||||
border_color: p.normal.primary,
|
||||
..appearance
|
||||
},
|
||||
Self::Current(_) => button::Style { ..self.active() },
|
||||
}
|
||||
}
|
||||
fn pressed(&self) -> button::Style {
|
||||
button::Style { ..self.active() }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Description(pub ColorPalette);
|
||||
impl container::StyleSheet for Description {
|
||||
fn style(&self) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(self.0.base.foreground)),
|
||||
text_color: Some(self.0.bright.surface),
|
||||
border_radius: 5.0,
|
||||
border_width: 0.0,
|
||||
border_color: self.0.base.background,
|
||||
fn hovered(&self, style: Self::Style) -> button::Appearance {
|
||||
let active = self.active(style);
|
||||
let p = self.palette;
|
||||
|
||||
let hover_appearance = |bg, tc: Option<Color>| button::Appearance {
|
||||
background: Some(Background::Color(Color { a: 0.25, ..bg })),
|
||||
text_color: tc.unwrap_or(bg),
|
||||
..active
|
||||
};
|
||||
|
||||
match style {
|
||||
Button::Primary => hover_appearance(p.bright.primary, None),
|
||||
Button::Unavailable => hover_appearance(p.bright.error, None),
|
||||
Button::Refresh => hover_appearance(p.bright.primary, None),
|
||||
Button::SelfUpdate => hover_appearance(p.bright.primary, None),
|
||||
Button::UninstallPackage => hover_appearance(p.bright.error, None),
|
||||
Button::RestorePackage => hover_appearance(p.bright.secondary, None),
|
||||
Button::NormalPackage => hover_appearance(p.normal.primary, Some(p.bright.surface)),
|
||||
Button::SelectedPackage => hover_appearance(p.normal.primary, None),
|
||||
}
|
||||
}
|
||||
|
||||
fn disabled(&self, style: Self::Style) -> button::Appearance {
|
||||
let active = self.active(style);
|
||||
let p = self.palette;
|
||||
|
||||
let disabled_appearance = |bg, tc: Option<Color>| button::Appearance {
|
||||
background: Some(Background::Color(Color { a: 0.05, ..bg })),
|
||||
text_color: Color {
|
||||
a: 0.50,
|
||||
..tc.unwrap_or(bg)
|
||||
},
|
||||
..active
|
||||
};
|
||||
|
||||
match style {
|
||||
Button::RestorePackage => disabled_appearance(p.normal.primary, Some(p.bright.primary)),
|
||||
Button::UninstallPackage => disabled_appearance(p.bright.error, None),
|
||||
_ => button::Appearance { ..active },
|
||||
}
|
||||
}
|
||||
|
||||
fn pressed(&self, style: Self::Style) -> button::Appearance {
|
||||
button::Appearance {
|
||||
..self.active(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DescriptionScrollable(pub ColorPalette);
|
||||
impl scrollable::StyleSheet for DescriptionScrollable {
|
||||
fn active(&self) -> scrollable::Scrollbar {
|
||||
scrollable::Scrollbar {
|
||||
background: Some(Background::Color(self.0.base.foreground)),
|
||||
impl Default for Scrollable {
|
||||
fn default() -> Self {
|
||||
Self::Description
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Scrollable {
|
||||
Description,
|
||||
Packages,
|
||||
}
|
||||
|
||||
impl scrollable::StyleSheet for Theme {
|
||||
type Style = Scrollable;
|
||||
|
||||
fn active(&self, style: Self::Style) -> scrollable::Scrollbar {
|
||||
let from_appearance = |c: Color| scrollable::Scrollbar {
|
||||
background: Some(Background::Color(c)),
|
||||
border_radius: 5.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
scroller: scrollable::Scroller {
|
||||
color: self.0.base.foreground,
|
||||
color: self.palette.base.foreground,
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
};
|
||||
|
||||
match style {
|
||||
Scrollable::Description => from_appearance(self.palette.base.foreground),
|
||||
Scrollable::Packages => from_appearance(self.palette.base.background),
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self) -> scrollable::Scrollbar {
|
||||
let active = self.active();
|
||||
|
||||
fn hovered(&self, style: Self::Style) -> scrollable::Scrollbar {
|
||||
scrollable::Scrollbar {
|
||||
scroller: scrollable::Scroller { ..active.scroller },
|
||||
..active
|
||||
}
|
||||
}
|
||||
|
||||
fn dragging(&self) -> scrollable::Scrollbar {
|
||||
let hovered = self.hovered();
|
||||
scrollable::Scrollbar {
|
||||
scroller: scrollable::Scroller { ..hovered.scroller },
|
||||
..hovered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Scrollable(pub ColorPalette);
|
||||
impl scrollable::StyleSheet for Scrollable {
|
||||
fn active(&self) -> scrollable::Scrollbar {
|
||||
scrollable::Scrollbar {
|
||||
background: Some(Background::Color(self.0.base.background)),
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
scroller: scrollable::Scroller {
|
||||
color: self.0.base.foreground,
|
||||
border_radius: 2.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
..self.active(style).scroller
|
||||
},
|
||||
..self.active(style)
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self) -> scrollable::Scrollbar {
|
||||
let active = self.active();
|
||||
|
||||
scrollable::Scrollbar {
|
||||
scroller: scrollable::Scroller { ..active.scroller },
|
||||
..active
|
||||
}
|
||||
}
|
||||
|
||||
fn dragging(&self) -> scrollable::Scrollbar {
|
||||
let hovered = self.hovered();
|
||||
fn dragging(&self, style: Self::Style) -> scrollable::Scrollbar {
|
||||
let hovered = self.hovered(style);
|
||||
scrollable::Scrollbar {
|
||||
scroller: scrollable::Scroller { ..hovered.scroller },
|
||||
..hovered
|
||||
|
|
@ -340,180 +230,216 @@ impl scrollable::StyleSheet for Scrollable {
|
|||
}
|
||||
}
|
||||
|
||||
pub enum SelectionCheckBox {
|
||||
Enabled(ColorPalette),
|
||||
Disabled(ColorPalette),
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum CheckBox {
|
||||
PackageEnabled,
|
||||
PackageDisabled,
|
||||
SettingsEnabled,
|
||||
SettingsDisabled,
|
||||
}
|
||||
|
||||
impl checkbox::StyleSheet for SelectionCheckBox {
|
||||
fn active(&self, _is_checked: bool) -> checkbox::Style {
|
||||
match self {
|
||||
Self::Enabled(palette) => checkbox::Style {
|
||||
background: Background::Color(palette.base.background),
|
||||
checkmark_color: palette.bright.primary,
|
||||
impl Default for CheckBox {
|
||||
fn default() -> Self {
|
||||
Self::PackageEnabled
|
||||
}
|
||||
}
|
||||
|
||||
impl checkbox::StyleSheet for Theme {
|
||||
type Style = CheckBox;
|
||||
|
||||
fn active(&self, style: Self::Style, _is_checked: bool) -> checkbox::Appearance {
|
||||
match style {
|
||||
CheckBox::PackageEnabled => checkbox::Appearance {
|
||||
background: Background::Color(self.palette.base.background),
|
||||
checkmark_color: self.palette.bright.primary,
|
||||
border_radius: 5.0,
|
||||
border_width: 1.0,
|
||||
border_color: palette.base.background,
|
||||
text_color: Some(palette.bright.surface),
|
||||
border_color: self.palette.base.background,
|
||||
text_color: Some(self.palette.bright.surface),
|
||||
},
|
||||
Self::Disabled(palette) => checkbox::Style {
|
||||
CheckBox::PackageDisabled => checkbox::Appearance {
|
||||
background: Background::Color(Color {
|
||||
a: 0.55,
|
||||
..palette.base.background
|
||||
..self.palette.base.background
|
||||
}),
|
||||
checkmark_color: palette.bright.primary,
|
||||
border_radius: 5.0,
|
||||
border_width: 0.0,
|
||||
border_color: palette.normal.primary,
|
||||
text_color: Some(palette.normal.primary),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self, is_checked: bool) -> checkbox::Style {
|
||||
match self {
|
||||
Self::Enabled(palette) => checkbox::Style {
|
||||
background: Background::Color(Color {
|
||||
a: 0.5,
|
||||
..palette.bright.primary
|
||||
}),
|
||||
checkmark_color: palette.bright.primary,
|
||||
border_radius: 5.0,
|
||||
border_width: 2.0,
|
||||
border_color: Color {
|
||||
a: 0.5,
|
||||
..palette.bright.primary
|
||||
},
|
||||
text_color: Some(palette.bright.surface),
|
||||
},
|
||||
|
||||
Self::Disabled(_) => checkbox::Style {
|
||||
..self.active(is_checked)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SettingsCheckBox {
|
||||
Enabled(ColorPalette),
|
||||
Disabled(ColorPalette),
|
||||
}
|
||||
|
||||
impl checkbox::StyleSheet for SettingsCheckBox {
|
||||
fn active(&self, _is_checked: bool) -> checkbox::Style {
|
||||
match self {
|
||||
Self::Enabled(palette) => checkbox::Style {
|
||||
background: Background::Color(palette.base.background),
|
||||
checkmark_color: palette.bright.primary,
|
||||
checkmark_color: self.palette.bright.primary,
|
||||
border_radius: 5.0,
|
||||
border_width: 1.0,
|
||||
border_color: palette.bright.primary,
|
||||
text_color: Some(palette.bright.surface),
|
||||
border_color: self.palette.normal.primary,
|
||||
text_color: Some(self.palette.normal.primary),
|
||||
},
|
||||
Self::Disabled(palette) => checkbox::Style {
|
||||
background: Background::Color(palette.base.foreground),
|
||||
checkmark_color: palette.bright.primary,
|
||||
CheckBox::SettingsEnabled => checkbox::Appearance {
|
||||
background: Background::Color(self.palette.base.background),
|
||||
checkmark_color: self.palette.bright.primary,
|
||||
border_radius: 5.0,
|
||||
border_width: 1.0,
|
||||
border_color: palette.normal.primary,
|
||||
text_color: Some(palette.normal.primary),
|
||||
border_color: self.palette.bright.primary,
|
||||
text_color: Some(self.palette.bright.surface),
|
||||
},
|
||||
CheckBox::SettingsDisabled => checkbox::Appearance {
|
||||
background: Background::Color(self.palette.base.foreground),
|
||||
checkmark_color: self.palette.bright.primary,
|
||||
border_radius: 5.0,
|
||||
border_width: 1.0,
|
||||
border_color: self.palette.normal.primary,
|
||||
text_color: Some(self.palette.normal.primary),
|
||||
},
|
||||
}
|
||||
}
|
||||
fn hovered(&self, is_checked: bool) -> checkbox::Style {
|
||||
match self {
|
||||
Self::Enabled(palette) => checkbox::Style {
|
||||
background: Background::Color(palette.base.foreground),
|
||||
checkmark_color: palette.bright.primary,
|
||||
border_radius: 5.0,
|
||||
border_width: 2.0,
|
||||
border_color: palette.bright.primary,
|
||||
text_color: Some(palette.bright.surface),
|
||||
},
|
||||
Self::Disabled(_) => checkbox::Style {
|
||||
..self.active(is_checked)
|
||||
},
|
||||
|
||||
fn hovered(&self, style: Self::Style, is_checked: bool) -> checkbox::Appearance {
|
||||
let from_appearance = || checkbox::Appearance {
|
||||
background: Background::Color(self.palette.base.foreground),
|
||||
checkmark_color: self.palette.bright.primary,
|
||||
border_radius: 5.0,
|
||||
border_width: 2.0,
|
||||
border_color: self.palette.bright.primary,
|
||||
text_color: Some(self.palette.bright.surface),
|
||||
};
|
||||
|
||||
match style {
|
||||
CheckBox::PackageEnabled => from_appearance(),
|
||||
CheckBox::SettingsEnabled => from_appearance(),
|
||||
CheckBox::PackageDisabled => self.active(style, is_checked),
|
||||
CheckBox::SettingsDisabled => self.active(style, is_checked),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SearchInput(pub ColorPalette);
|
||||
impl text_input::StyleSheet for SearchInput {
|
||||
fn active(&self) -> text_input::Style {
|
||||
text_input::Style {
|
||||
background: Background::Color(self.0.base.foreground),
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum TextInput {
|
||||
Default,
|
||||
}
|
||||
|
||||
impl Default for TextInput {
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
impl text_input::StyleSheet for Theme {
|
||||
type Style = TextInput;
|
||||
|
||||
fn active(&self, _style: Self::Style) -> text_input::Appearance {
|
||||
text_input::Appearance {
|
||||
background: Background::Color(self.palette.base.foreground),
|
||||
border_radius: 5.0,
|
||||
border_width: 0.0,
|
||||
border_color: self.0.base.foreground,
|
||||
border_color: self.palette.base.foreground,
|
||||
}
|
||||
}
|
||||
|
||||
fn focused(&self) -> text_input::Style {
|
||||
text_input::Style {
|
||||
background: Background::Color(self.0.base.foreground),
|
||||
fn focused(&self, _style: Self::Style) -> text_input::Appearance {
|
||||
text_input::Appearance {
|
||||
background: Background::Color(self.palette.base.foreground),
|
||||
border_radius: 2.0,
|
||||
border_width: 1.0,
|
||||
border_color: Color {
|
||||
a: 0.5,
|
||||
..self.0.normal.primary
|
||||
..self.palette.normal.primary
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn placeholder_color(&self) -> Color {
|
||||
self.0.normal.surface
|
||||
fn placeholder_color(&self, _style: Self::Style) -> Color {
|
||||
self.palette.normal.surface
|
||||
}
|
||||
|
||||
fn value_color(&self) -> Color {
|
||||
self.0.bright.primary
|
||||
fn value_color(&self, _style: Self::Style) -> Color {
|
||||
self.palette.bright.primary
|
||||
}
|
||||
|
||||
fn selection_color(&self) -> Color {
|
||||
self.0.bright.secondary
|
||||
fn selection_color(&self, _style: Self::Style) -> Color {
|
||||
self.palette.normal.primary
|
||||
}
|
||||
|
||||
/// Produces the style of an hovered text input.
|
||||
fn hovered(&self) -> text_input::Style {
|
||||
self.focused()
|
||||
fn hovered(&self, style: Self::Style) -> text_input::Appearance {
|
||||
self.focused(style)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PickList(pub ColorPalette);
|
||||
impl pick_list::StyleSheet for PickList {
|
||||
fn menu(&self) -> pick_list::Menu {
|
||||
pick_list::Menu {
|
||||
text_color: self.0.bright.surface,
|
||||
background: Background::Color(self.0.base.foreground),
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum PickList {
|
||||
Default,
|
||||
}
|
||||
|
||||
impl Default for PickList {
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
impl menu::StyleSheet for Theme {
|
||||
type Style = ();
|
||||
|
||||
fn appearance(&self, _style: Self::Style) -> menu::Appearance {
|
||||
let p = self.palette;
|
||||
|
||||
menu::Appearance {
|
||||
text_color: p.bright.surface,
|
||||
background: p.base.background.into(),
|
||||
border_width: 1.0,
|
||||
border_color: self.0.base.background,
|
||||
selected_background: Background::Color(Color {
|
||||
a: 0.15,
|
||||
..self.0.normal.primary
|
||||
}),
|
||||
selected_text_color: self.0.bright.primary,
|
||||
border_radius: 2.0,
|
||||
border_color: p.base.background,
|
||||
selected_text_color: p.bright.surface,
|
||||
selected_background: p.normal.primary.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn active(&self) -> pick_list::Style {
|
||||
pick_list::Style {
|
||||
text_color: self.0.bright.surface,
|
||||
background: self.0.base.background.into(),
|
||||
impl pick_list::StyleSheet for Theme {
|
||||
type Style = ();
|
||||
|
||||
fn active(&self, _style: ()) -> pick_list::Appearance {
|
||||
pick_list::Appearance {
|
||||
text_color: self.palette.bright.surface,
|
||||
background: self.palette.base.background.into(),
|
||||
border_width: 1.0,
|
||||
border_color: Color {
|
||||
a: 0.5,
|
||||
..self.0.normal.primary
|
||||
..self.palette.normal.primary
|
||||
},
|
||||
border_radius: 2.0,
|
||||
icon_size: 0.5,
|
||||
placeholder_color: self.0.bright.surface,
|
||||
placeholder_color: self.palette.bright.surface,
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self) -> pick_list::Style {
|
||||
let active = self.active();
|
||||
pick_list::Style {
|
||||
text_color: self.0.bright.primary,
|
||||
fn hovered(&self, style: ()) -> pick_list::Appearance {
|
||||
let active = self.active(style);
|
||||
pick_list::Appearance {
|
||||
text_color: self.palette.bright.primary,
|
||||
..active
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Text {
|
||||
Default,
|
||||
Color(Color),
|
||||
}
|
||||
|
||||
impl Default for Text {
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for Text {
|
||||
fn from(color: Color) -> Self {
|
||||
Text::Color(color)
|
||||
}
|
||||
}
|
||||
|
||||
impl text::StyleSheet for Theme {
|
||||
type Style = Text;
|
||||
|
||||
fn appearance(&self, style: Self::Style) -> text::Appearance {
|
||||
match style {
|
||||
Text::Default => Default::default(),
|
||||
Text::Color(c) => text::Appearance { color: Some(c) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use crate::core::theme::Theme;
|
||||
use crate::core::utils::{last_modified_date, open_url};
|
||||
use crate::gui::style;
|
||||
use crate::gui::views::settings::Settings;
|
||||
use crate::gui::{style, UpdateState};
|
||||
use crate::CACHE_DIR;
|
||||
use iced::pure::{button, column, container, row, text, Element};
|
||||
use iced::{Alignment, Length, Space};
|
||||
use iced::widget::{button, column, container, row, text, Space};
|
||||
use iced::{Alignment, Element, Length, Renderer};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(feature = "self-update")]
|
||||
|
|
@ -21,19 +21,12 @@ pub enum Message {
|
|||
|
||||
impl About {
|
||||
pub fn update(&mut self, msg: Message) {
|
||||
match msg {
|
||||
Message::UrlPressed(url) => {
|
||||
open_url(url);
|
||||
}
|
||||
Message::UpdateUadLists => {
|
||||
// Action is taken by UadGui update()
|
||||
}
|
||||
Message::DoSelfUpdate => {
|
||||
// Action is taken by UadGui update()
|
||||
}
|
||||
if let Message::UrlPressed(url) = msg {
|
||||
open_url(url);
|
||||
}
|
||||
// other events are handled by UadGui update()
|
||||
}
|
||||
pub fn view(&mut self, settings: &Settings) -> Element<Message> {
|
||||
pub fn view(&self, update_state: &UpdateState) -> Element<Message, Renderer<Theme>> {
|
||||
let about_text = text(
|
||||
"Universal Android Debloater (UAD) is a Free and Open-Source community project aiming at simplifying \
|
||||
the removal of pre-installed apps on any Android device.",
|
||||
|
|
@ -42,133 +35,118 @@ impl About {
|
|||
let descr_container = container(about_text)
|
||||
.width(Length::Fill)
|
||||
.padding(25)
|
||||
.style(style::NavigationContainer(settings.theme.palette));
|
||||
.style(style::Container::Navigation);
|
||||
|
||||
let date = last_modified_date(CACHE_DIR.join("uad_lists.json"));
|
||||
let uad_list_text =
|
||||
text(format!("Documentation: v{}", date.format("%Y%m%d"))).width(Length::Units(250));
|
||||
let last_update_text = text(settings.list_update_state.to_string())
|
||||
.color(settings.theme.palette.normal.surface);
|
||||
let last_update_text = text(update_state.uad_list.to_string());
|
||||
let uad_lists_btn = button("Update")
|
||||
.on_press(Message::UpdateUadLists)
|
||||
.padding(5)
|
||||
.style(style::PrimaryButton(settings.theme.palette));
|
||||
.style(style::Button::Primary);
|
||||
|
||||
#[cfg(feature = "self-update")]
|
||||
let self_update_btn = button("Update")
|
||||
.on_press(Message::DoSelfUpdate)
|
||||
.padding(5)
|
||||
.style(style::PrimaryButton(settings.theme.palette));
|
||||
.style(style::Button::Primary);
|
||||
|
||||
#[cfg(feature = "self-update")]
|
||||
let uad_version_text =
|
||||
text(format!("UAD version: v{}", env!("CARGO_PKG_VERSION"))).width(Length::Units(250));
|
||||
|
||||
#[cfg(feature = "self-update")]
|
||||
let self_update_text = match &settings.self_update_state.latest_release {
|
||||
let self_update_text = match &update_state.self_update.latest_release {
|
||||
Some(r) => {
|
||||
if settings.self_update_state.status == SelfUpdateStatus::Updating {
|
||||
settings.self_update_state.status.to_string()
|
||||
if update_state.self_update.status == SelfUpdateStatus::Updating {
|
||||
update_state.self_update.status.to_string()
|
||||
} else {
|
||||
format!("(v{} available)", r.tag_name)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if settings.self_update_state.status == SelfUpdateStatus::Done {
|
||||
if update_state.self_update.status == SelfUpdateStatus::Done {
|
||||
"(No update available)".to_string()
|
||||
} else {
|
||||
settings.self_update_state.status.to_string()
|
||||
update_state.self_update.status.to_string()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "self-update")]
|
||||
let last_self_update_text =
|
||||
text(self_update_text).color(settings.theme.palette.normal.surface);
|
||||
let last_self_update_text = text(self_update_text).style(style::Text::Default);
|
||||
|
||||
#[cfg(feature = "self-update")]
|
||||
let self_update_row = row()
|
||||
let self_update_row = row![uad_version_text, self_update_btn, last_self_update_text,]
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.width(iced::Length::Units(550))
|
||||
.push(uad_version_text)
|
||||
.push(self_update_btn)
|
||||
.push(last_self_update_text);
|
||||
.width(iced::Length::Units(550));
|
||||
|
||||
let uad_list_row = row()
|
||||
let uad_list_row = row![uad_list_text, uad_lists_btn, last_update_text,]
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.width(iced::Length::Units(550))
|
||||
.push(uad_list_text)
|
||||
.push(uad_lists_btn)
|
||||
.push(last_update_text);
|
||||
.width(iced::Length::Units(550));
|
||||
|
||||
#[cfg(feature = "self-update")]
|
||||
let update_column = column()
|
||||
let update_column = column![uad_list_row, self_update_row]
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(uad_list_row)
|
||||
.push(self_update_row);
|
||||
.spacing(10);
|
||||
|
||||
#[cfg(not(feature = "self-update"))]
|
||||
let update_column = column()
|
||||
let update_column = column![uad_list_row]
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.push(uad_list_row);
|
||||
.spacing(10);
|
||||
|
||||
let update_container = container(update_column)
|
||||
.width(Length::Fill)
|
||||
.center_x()
|
||||
.padding(10)
|
||||
.style(style::NavigationContainer(settings.theme.palette));
|
||||
.style(style::Container::Navigation);
|
||||
|
||||
let website_btn = button("Github page")
|
||||
.on_press(Message::UrlPressed(PathBuf::from(
|
||||
"https://github.com/0x192/universal-android-debloater",
|
||||
)))
|
||||
.padding(5)
|
||||
.style(style::PrimaryButton(settings.theme.palette));
|
||||
.style(style::Button::Primary);
|
||||
|
||||
let issue_btn = button("Have an issue?")
|
||||
.on_press(Message::UrlPressed(PathBuf::from(
|
||||
"https://github.com/0x192/universal-android-debloater/issues",
|
||||
)))
|
||||
.padding(5)
|
||||
.style(style::PrimaryButton(settings.theme.palette));
|
||||
.style(style::Button::Primary);
|
||||
|
||||
let log_btn = button("Locate the logfiles")
|
||||
.on_press(Message::UrlPressed(CACHE_DIR.to_path_buf()))
|
||||
.padding(5)
|
||||
.style(style::PrimaryButton(settings.theme.palette));
|
||||
.style(style::Button::Primary);
|
||||
|
||||
let wiki_btn = button("Wiki")
|
||||
.on_press(Message::UrlPressed(PathBuf::from(
|
||||
"https://github.com/0x192/universal-android-debloater/wiki",
|
||||
)))
|
||||
.padding(5)
|
||||
.style(style::PrimaryButton(settings.theme.palette));
|
||||
.style(style::Button::Primary);
|
||||
|
||||
let row = row()
|
||||
.spacing(20)
|
||||
.push(website_btn)
|
||||
.push(wiki_btn)
|
||||
.push(issue_btn)
|
||||
.push(log_btn);
|
||||
let row = row![website_btn, wiki_btn, issue_btn, log_btn,].spacing(20);
|
||||
|
||||
let content = column()
|
||||
.width(Length::Fill)
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center)
|
||||
.push(Space::new(Length::Fill, Length::Shrink))
|
||||
.push(descr_container)
|
||||
.push(update_container)
|
||||
.push(row);
|
||||
let content = column![
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
descr_container,
|
||||
update_container,
|
||||
row,
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
container(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding(10)
|
||||
.style(style::Content(settings.theme.palette))
|
||||
.style(style::Container::Content)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::core::sync::{action_handler, Phone, User};
|
||||
use crate::core::theme::Theme;
|
||||
use crate::core::uad_lists::{
|
||||
load_debloat_lists, Opposite, Package, PackageState, Removal, UadList, UadListState,
|
||||
};
|
||||
|
|
@ -11,10 +12,10 @@ use std::env;
|
|||
|
||||
use crate::gui::views::settings::Settings;
|
||||
use crate::gui::widgets::package_row::{Message as RowMessage, PackageRow};
|
||||
use iced::pure::{
|
||||
button, column, container, pick_list, row, scrollable, text, text_input, Element,
|
||||
use iced::widget::{
|
||||
button, column, container, pick_list, row, scrollable, text, text_input, Space,
|
||||
};
|
||||
use iced::{Alignment, Command, Length, Space};
|
||||
use iced::{Alignment, Command, Element, Length, Renderer};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Selection {
|
||||
|
|
@ -30,29 +31,19 @@ pub enum Action {
|
|||
Restore,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub enum LoadingState {
|
||||
FindingPhones,
|
||||
DownloadingList,
|
||||
#[default]
|
||||
FindingPhones,
|
||||
LoadingPackages,
|
||||
_UpdatingUad,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum State {
|
||||
Ready,
|
||||
Loading(LoadingState),
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
State::Loading(LoadingState::LoadingPackages)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct List {
|
||||
pub state: State,
|
||||
pub loading_state: LoadingState,
|
||||
pub uad_lists: HashMap<String, Package>,
|
||||
phone_packages: Vec<Vec<PackageRow>>, // packages of all users of the phone
|
||||
filtered_packages: Vec<usize>, // phone_packages indexes of the selected user (= what you see on screen)
|
||||
|
|
@ -68,11 +59,11 @@ pub struct List {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
LoadUadList(bool),
|
||||
LoadPhonePackages((HashMap<String, Package>, UadListState)),
|
||||
ApplyFilters(Vec<Vec<PackageRow>>),
|
||||
SearchInputChanged(String),
|
||||
ToggleAllSelected(bool),
|
||||
InitUadList(bool),
|
||||
ListsIsInitialized((Result<HashMap<String, Package>, ()>, bool)),
|
||||
LoadPackages,
|
||||
ListSelected(UadList),
|
||||
UserSelected(User),
|
||||
PackageStateSelected(PackageState),
|
||||
|
|
@ -82,7 +73,6 @@ pub enum Message {
|
|||
List(usize, RowMessage),
|
||||
ExportedSelection(Result<bool, String>),
|
||||
ChangePackageState(Result<usize, ()>),
|
||||
PackagesLoaded(Vec<Vec<PackageRow>>),
|
||||
Nothing,
|
||||
}
|
||||
|
||||
|
|
@ -90,69 +80,54 @@ impl List {
|
|||
pub fn update(
|
||||
&mut self,
|
||||
settings: &mut Settings,
|
||||
phone: &mut Phone,
|
||||
selected_device: &mut Phone,
|
||||
list_update_state: &mut UadListState,
|
||||
message: Message,
|
||||
) -> Command<Message> {
|
||||
let i_user = &self.selected_user.unwrap_or(User { id: 0, index: 0 }).index;
|
||||
let i_user = self.selected_user.unwrap_or(User { id: 0, index: 0 }).index;
|
||||
match message {
|
||||
Message::Nothing => Command::none(),
|
||||
Message::InitUadList(remote) => {
|
||||
self.state = State::Loading(LoadingState::FindingPhones);
|
||||
if remote {
|
||||
Command::perform(load_debloat_lists(true), Message::ListsIsInitialized)
|
||||
} else {
|
||||
Command::perform(load_debloat_lists(false), Message::ListsIsInitialized)
|
||||
}
|
||||
}
|
||||
Message::ListsIsInitialized((uad_lists, remote)) => match uad_lists {
|
||||
Ok(list) => {
|
||||
if !remote {
|
||||
settings.list_update_state = UadListState::Failed;
|
||||
} else {
|
||||
settings.list_update_state = UadListState::Done;
|
||||
}
|
||||
self.uad_lists = list;
|
||||
if !phone.adb_id.is_empty() {
|
||||
env::set_var("ANDROID_SERIAL", phone.adb_id.clone());
|
||||
Command::perform(Self::do_load_packages(), |_| Message::LoadPackages)
|
||||
} else {
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
Err(_) => Command::none(),
|
||||
},
|
||||
Message::LoadPackages => {
|
||||
self.state = State::Loading(LoadingState::LoadingPackages);
|
||||
self.selected_package_state = Some(PackageState::Enabled);
|
||||
self.selected_list = Some(UadList::All);
|
||||
self.selected_removal = Some(Removal::Recommended);
|
||||
self.selected_user = Some(User { id: 0, index: 0 });
|
||||
Message::LoadUadList(remote) => {
|
||||
self.loading_state = LoadingState::DownloadingList;
|
||||
Command::perform(
|
||||
Self::load_packages(self.uad_lists.clone(), phone.user_list.clone()),
|
||||
Message::PackagesLoaded,
|
||||
Self::init_apps_view(remote, selected_device.clone()),
|
||||
Message::LoadPhonePackages,
|
||||
)
|
||||
}
|
||||
Message::PackagesLoaded(packages) => {
|
||||
Message::LoadPhonePackages(list_box) => {
|
||||
let (uad_list, list_state) = list_box;
|
||||
self.loading_state = LoadingState::LoadingPackages;
|
||||
self.uad_lists = uad_list.clone();
|
||||
*list_update_state = list_state;
|
||||
Command::perform(
|
||||
Self::load_packages(uad_list, selected_device.user_list.clone()),
|
||||
Message::ApplyFilters,
|
||||
)
|
||||
}
|
||||
Message::ApplyFilters(packages) => {
|
||||
self.phone_packages = packages;
|
||||
self.filtered_packages = (0..self.phone_packages[*i_user].len()).collect();
|
||||
self.filtered_packages = (0..self.phone_packages[i_user].len()).collect();
|
||||
self.selected_package_state = Some(PackageState::Enabled);
|
||||
self.selected_removal = Some(Removal::Recommended);
|
||||
self.selected_list = Some(UadList::All);
|
||||
self.selected_user = Some(User { id: 0, index: 0 });
|
||||
Self::filter_package_lists(self);
|
||||
|
||||
match import_selection(&mut self.phone_packages[*i_user], &mut self.selection) {
|
||||
match import_selection(&mut self.phone_packages[i_user], &mut self.selection) {
|
||||
Ok(_) => info!("Custom selection has been successfully imported"),
|
||||
Err(err) => warn!("No custom selection imported: {}", err),
|
||||
};
|
||||
self.state = State::Ready;
|
||||
self.loading_state = LoadingState::Ready;
|
||||
Command::none()
|
||||
}
|
||||
Message::ToggleAllSelected(selected) => {
|
||||
for i in self.filtered_packages.clone() {
|
||||
self.phone_packages[*i_user][i].selected = selected;
|
||||
self.phone_packages[i_user][i].selected = selected;
|
||||
|
||||
if !selected {
|
||||
if self.selection.selected_packages.contains(&i) {
|
||||
update_selection_count(
|
||||
&mut self.selection,
|
||||
self.phone_packages[*i_user][i].state,
|
||||
self.phone_packages[i_user][i].state,
|
||||
false,
|
||||
);
|
||||
self.selection
|
||||
|
|
@ -163,7 +138,7 @@ impl List {
|
|||
self.selection.selected_packages.push(i);
|
||||
update_selection_count(
|
||||
&mut self.selection,
|
||||
self.phone_packages[*i_user][i].state,
|
||||
self.phone_packages[i_user][i].state,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
|
@ -191,11 +166,11 @@ impl List {
|
|||
Command::none()
|
||||
}
|
||||
Message::List(i_package, row_message) => {
|
||||
self.phone_packages[*i_user][i_package]
|
||||
self.phone_packages[i_user][i_package]
|
||||
.update(row_message.clone())
|
||||
.map(move |row_message| Message::List(i_package, row_message));
|
||||
|
||||
let package = &mut self.phone_packages[*i_user][i_package];
|
||||
let package = &mut self.phone_packages[i_user][i_package];
|
||||
|
||||
match row_message {
|
||||
RowMessage::ToggleSelection(toggle) => {
|
||||
|
|
@ -224,7 +199,7 @@ impl List {
|
|||
let actions = action_handler(
|
||||
&self.selected_user.unwrap(),
|
||||
package,
|
||||
phone,
|
||||
selected_device,
|
||||
&settings.phone,
|
||||
);
|
||||
|
||||
|
|
@ -256,8 +231,7 @@ impl List {
|
|||
self.description = package.clone().description;
|
||||
package.current = true;
|
||||
if self.current_package_index != i_package {
|
||||
self.phone_packages[*i_user][self.current_package_index].current =
|
||||
false;
|
||||
self.phone_packages[i_user][self.current_package_index].current = false;
|
||||
}
|
||||
self.current_package_index = i_package;
|
||||
Command::none()
|
||||
|
|
@ -270,12 +244,12 @@ impl List {
|
|||
match action {
|
||||
Action::Remove => {
|
||||
selected_packages.drain_filter(|i| {
|
||||
self.phone_packages[*i_user][*i].state != PackageState::Enabled
|
||||
self.phone_packages[i_user][*i].state != PackageState::Enabled
|
||||
});
|
||||
}
|
||||
Action::Restore => {
|
||||
selected_packages.drain_filter(|i| {
|
||||
self.phone_packages[*i_user][*i].state == PackageState::Enabled
|
||||
self.phone_packages[i_user][*i].state == PackageState::Enabled
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -283,8 +257,8 @@ impl List {
|
|||
for i in selected_packages {
|
||||
let actions = action_handler(
|
||||
&self.selected_user.unwrap(),
|
||||
&self.phone_packages[*i_user][i],
|
||||
phone,
|
||||
&self.phone_packages[i_user][i],
|
||||
selected_device,
|
||||
&settings.phone,
|
||||
);
|
||||
for (j, action) in actions.into_iter().enumerate() {
|
||||
|
|
@ -294,7 +268,7 @@ impl List {
|
|||
perform_commands(
|
||||
action,
|
||||
i,
|
||||
self.phone_packages[*i_user][i].removal.to_string(),
|
||||
self.phone_packages[i_user][i].removal.to_string(),
|
||||
),
|
||||
|_| Message::Nothing,
|
||||
));
|
||||
|
|
@ -303,7 +277,7 @@ impl List {
|
|||
perform_commands(
|
||||
action,
|
||||
i,
|
||||
self.phone_packages[*i_user][i].removal.to_string(),
|
||||
self.phone_packages[i_user][i].removal.to_string(),
|
||||
),
|
||||
Message::ChangePackageState,
|
||||
));
|
||||
|
|
@ -313,7 +287,7 @@ impl List {
|
|||
Command::batch(commands)
|
||||
}
|
||||
Message::ExportSelectionPressed => Command::perform(
|
||||
export_selection(self.phone_packages[*i_user].clone()),
|
||||
export_selection(self.phone_packages[i_user].clone()),
|
||||
Message::ExportedSelection,
|
||||
),
|
||||
Message::ExportedSelection(export) => {
|
||||
|
|
@ -324,7 +298,7 @@ impl List {
|
|||
Command::none()
|
||||
}
|
||||
Message::UserSelected(user) => {
|
||||
for p in &mut self.phone_packages[*i_user] {
|
||||
for p in &mut self.phone_packages[i_user] {
|
||||
p.selected = false;
|
||||
}
|
||||
self.selected_user = Some(user);
|
||||
|
|
@ -337,14 +311,14 @@ impl List {
|
|||
}
|
||||
Message::ChangePackageState(res) => {
|
||||
if let Ok(i) = res {
|
||||
let package = &mut self.phone_packages[*i_user][i];
|
||||
let package = &mut self.phone_packages[i_user][i];
|
||||
update_selection_count(&mut self.selection, package.state, false);
|
||||
|
||||
if !settings.phone.multi_user_mode {
|
||||
package.state = package.state.opposite(settings.phone.disable_mode);
|
||||
package.selected = false;
|
||||
} else {
|
||||
for u in &phone.user_list {
|
||||
for u in &selected_device.user_list {
|
||||
self.phone_packages[u.index][i].state = self.phone_packages[u.index][i]
|
||||
.state
|
||||
.opposite(settings.phone.disable_mode);
|
||||
|
|
@ -358,123 +332,112 @@ impl List {
|
|||
}
|
||||
Command::none()
|
||||
}
|
||||
Message::Nothing => Command::none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(&mut self, settings: &Settings, phone: &Phone) -> Element<Message> {
|
||||
match self.state {
|
||||
State::Loading(LoadingState::DownloadingList) => {
|
||||
pub fn view(
|
||||
&self,
|
||||
settings: &Settings,
|
||||
selected_device: &Phone,
|
||||
) -> Element<Message, Renderer<Theme>> {
|
||||
match self.loading_state {
|
||||
LoadingState::DownloadingList => {
|
||||
let text = "Downloading latest UAD lists from Github. Please wait...";
|
||||
waiting_view(settings, text, true)
|
||||
}
|
||||
State::Loading(LoadingState::FindingPhones) => {
|
||||
LoadingState::FindingPhones => {
|
||||
let text = "Finding connected devices...";
|
||||
waiting_view(settings, text, false)
|
||||
}
|
||||
State::Loading(LoadingState::LoadingPackages) => {
|
||||
let text = "Pulling packages from the phone. Please wait...";
|
||||
LoadingState::LoadingPackages => {
|
||||
let text = "Pulling packages from the device. Please wait...";
|
||||
waiting_view(settings, text, false)
|
||||
}
|
||||
State::Loading(LoadingState::_UpdatingUad) => {
|
||||
LoadingState::_UpdatingUad => {
|
||||
let text = "Updating UAD. Please wait...";
|
||||
waiting_view(settings, text, false)
|
||||
}
|
||||
State::Ready => {
|
||||
LoadingState::Ready => {
|
||||
let search_packages = text_input(
|
||||
"Search packages...",
|
||||
&self.input_value,
|
||||
Message::SearchInputChanged,
|
||||
)
|
||||
.padding(5)
|
||||
.style(style::SearchInput(settings.theme.palette));
|
||||
.padding(5);
|
||||
|
||||
// let package_amount = text(format!("{} packages found", packages.len()));
|
||||
|
||||
let user_picklist = pick_list(
|
||||
phone.user_list.clone(),
|
||||
selected_device.user_list.clone(),
|
||||
self.selected_user,
|
||||
Message::UserSelected,
|
||||
)
|
||||
.width(Length::Units(85))
|
||||
.style(style::PickList(settings.theme.palette));
|
||||
.width(Length::Units(85));
|
||||
|
||||
let divider = Space::new(Length::Fill, Length::Shrink);
|
||||
|
||||
let list_picklist =
|
||||
pick_list(&UadList::ALL[..], self.selected_list, Message::ListSelected)
|
||||
.style(style::PickList(settings.theme.palette));
|
||||
|
||||
pick_list(&UadList::ALL[..], self.selected_list, Message::ListSelected);
|
||||
let package_state_picklist = pick_list(
|
||||
&PackageState::ALL[..],
|
||||
self.selected_package_state,
|
||||
Message::PackageStateSelected,
|
||||
)
|
||||
.style(style::PickList(settings.theme.palette));
|
||||
);
|
||||
|
||||
let removal_picklist = pick_list(
|
||||
&Removal::ALL[..],
|
||||
self.selected_removal,
|
||||
Message::RemovalSelected,
|
||||
)
|
||||
.style(style::PickList(settings.theme.palette));
|
||||
);
|
||||
|
||||
let control_panel = row()
|
||||
.width(Length::Fill)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.padding([0, 16, 0, 0])
|
||||
.push(search_packages)
|
||||
.push(user_picklist)
|
||||
.push(divider)
|
||||
.push(removal_picklist)
|
||||
.push(package_state_picklist)
|
||||
.push(list_picklist);
|
||||
let control_panel = row![
|
||||
search_packages,
|
||||
user_picklist,
|
||||
divider,
|
||||
removal_picklist,
|
||||
package_state_picklist,
|
||||
list_picklist,
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10)
|
||||
.padding([0, 16, 0, 0]);
|
||||
|
||||
let packages = self.phone_packages[self.selected_user.unwrap().index]
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.filter(|(i, _)| self.filtered_packages.contains(i))
|
||||
.fold(column().spacing(6), |col, (i, p)| {
|
||||
col.push(
|
||||
p.view(settings, phone)
|
||||
.map(move |msg| Message::List(i, msg)),
|
||||
)
|
||||
});
|
||||
|
||||
/*
|
||||
// Seems better to me but I can't fix the lifetime issues
|
||||
let packages = self.filtered_packages
|
||||
.into_iter()
|
||||
.fold(column().spacing(6), |col, i| {
|
||||
col.push(self.phone_packages[self.selected_user.unwrap().index][i].view().map(move |msg| Message::List(i, msg)))
|
||||
});
|
||||
*/
|
||||
let packages =
|
||||
self.filtered_packages
|
||||
.iter()
|
||||
.fold(column![].spacing(6), |col, i| {
|
||||
col.push(
|
||||
self.phone_packages[self.selected_user.unwrap().index][*i]
|
||||
.view(settings, selected_device)
|
||||
.map(move |msg| Message::List(*i, msg)),
|
||||
)
|
||||
});
|
||||
|
||||
let packages_scrollable = scrollable(packages)
|
||||
.scrollbar_margin(2)
|
||||
.height(Length::FillPortion(6))
|
||||
.style(style::Scrollable(settings.theme.palette));
|
||||
.style(style::Scrollable::Packages);
|
||||
|
||||
// let mut packages_v: Vec<&str> = self.packages.lines().collect();
|
||||
|
||||
let description_scroll = scrollable(text(&self.description))
|
||||
.scrollbar_margin(7)
|
||||
.style(style::DescriptionScrollable(settings.theme.palette));
|
||||
.style(style::Scrollable::Description);
|
||||
|
||||
let description_panel = container(description_scroll)
|
||||
.height(Length::FillPortion(2))
|
||||
.width(Length::Fill)
|
||||
.style(style::Description(settings.theme.palette));
|
||||
.style(style::Container::Description);
|
||||
|
||||
let restore_action = if settings.phone.disable_mode {
|
||||
"Enable/Restore"
|
||||
} else {
|
||||
"Restore"
|
||||
let restore_action = match settings.phone.disable_mode {
|
||||
true => "Enable/Restore",
|
||||
false => "Restore",
|
||||
};
|
||||
let remove_action = if settings.phone.disable_mode {
|
||||
"Disable"
|
||||
} else {
|
||||
"Uninstall"
|
||||
let remove_action = match settings.phone.disable_mode {
|
||||
true => "Disable",
|
||||
false => "Uninstall",
|
||||
};
|
||||
|
||||
let apply_restore_selection = button(text(format!(
|
||||
|
|
@ -484,7 +447,7 @@ impl List {
|
|||
)))
|
||||
.on_press(Message::ApplyActionOnSelection(Action::Restore))
|
||||
.padding(5)
|
||||
.style(style::PrimaryButton(settings.theme.palette));
|
||||
.style(style::Button::Primary);
|
||||
|
||||
let apply_remove_selection = button(text(format!(
|
||||
"{} selection ({})",
|
||||
|
|
@ -492,17 +455,17 @@ impl List {
|
|||
)))
|
||||
.on_press(Message::ApplyActionOnSelection(Action::Remove))
|
||||
.padding(5)
|
||||
.style(style::PrimaryButton(settings.theme.palette));
|
||||
.style(style::Button::Primary);
|
||||
|
||||
let select_all_btn = button("Select all")
|
||||
.padding(5)
|
||||
.on_press(Message::ToggleAllSelected(true))
|
||||
.style(style::PrimaryButton(settings.theme.palette));
|
||||
.style(style::Button::Primary);
|
||||
|
||||
let unselect_all_btn = button("Unselect all")
|
||||
.padding(5)
|
||||
.on_press(Message::ToggleAllSelected(false))
|
||||
.style(style::PrimaryButton(settings.theme.palette));
|
||||
.style(style::Button::Primary);
|
||||
|
||||
let export_selection_btn = button(text(format!(
|
||||
"Export current selection ({})",
|
||||
|
|
@ -510,32 +473,34 @@ impl List {
|
|||
)))
|
||||
.padding(5)
|
||||
.on_press(Message::ExportSelectionPressed)
|
||||
.style(style::PrimaryButton(settings.theme.palette));
|
||||
.style(style::Button::Primary);
|
||||
|
||||
let action_row = row()
|
||||
.width(Length::Fill)
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
.push(select_all_btn)
|
||||
.push(unselect_all_btn)
|
||||
.push(Space::new(Length::Fill, Length::Shrink))
|
||||
.push(export_selection_btn)
|
||||
.push(apply_restore_selection)
|
||||
.push(apply_remove_selection);
|
||||
let action_row = row![
|
||||
select_all_btn,
|
||||
unselect_all_btn,
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
export_selection_btn,
|
||||
apply_restore_selection,
|
||||
apply_remove_selection,
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
let content = column()
|
||||
.width(Length::Fill)
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
.push(control_panel)
|
||||
.push(packages_scrollable)
|
||||
.push(description_panel)
|
||||
.push(action_row);
|
||||
let content = column![
|
||||
control_panel,
|
||||
packages_scrollable,
|
||||
description_panel,
|
||||
action_row,
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
container(content)
|
||||
.height(Length::Fill)
|
||||
.padding(10)
|
||||
.style(style::Content(settings.theme.palette))
|
||||
.style(style::Container::Content)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
@ -559,41 +524,68 @@ impl List {
|
|||
.collect();
|
||||
}
|
||||
|
||||
async fn do_load_packages() -> Message {
|
||||
Message::LoadPackages
|
||||
}
|
||||
|
||||
async fn load_packages(
|
||||
uad_lists: HashMap<String, Package>,
|
||||
uad_list: HashMap<String, Package>,
|
||||
user_list: Vec<User>,
|
||||
) -> Vec<Vec<PackageRow>> {
|
||||
let mut temp = vec![];
|
||||
match user_list.len() {
|
||||
0 | 1 => temp.push(fetch_packages(&uad_lists, &None)),
|
||||
_ => {
|
||||
for user in &user_list {
|
||||
temp.push(fetch_packages(&uad_lists, &Some(user)));
|
||||
let mut phone_packages = vec![];
|
||||
|
||||
if user_list.len() <= 1 {
|
||||
phone_packages.push(fetch_packages(&uad_list, None))
|
||||
} else {
|
||||
phone_packages.extend(
|
||||
user_list
|
||||
.iter()
|
||||
.map(|user| fetch_packages(&uad_list, Some(user))),
|
||||
)
|
||||
};
|
||||
phone_packages
|
||||
}
|
||||
|
||||
async fn init_apps_view(
|
||||
remote: bool,
|
||||
phone: Phone,
|
||||
) -> (HashMap<String, Package>, UadListState) {
|
||||
let (uad_lists, remote) = load_debloat_lists(remote);
|
||||
match uad_lists {
|
||||
Ok(list) => {
|
||||
env::set_var("ANDROID_SERIAL", phone.adb_id.clone());
|
||||
if phone.adb_id.is_empty() {
|
||||
error!("AppsView ready but no phone found");
|
||||
}
|
||||
|
||||
if !remote {
|
||||
(list, UadListState::Failed)
|
||||
} else {
|
||||
(list, UadListState::Done)
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
error!("Error loading debloat list for the phone");
|
||||
(HashMap::new(), UadListState::Failed)
|
||||
}
|
||||
}
|
||||
temp
|
||||
}
|
||||
}
|
||||
|
||||
fn waiting_view<'a>(settings: &Settings, displayed_text: &str, btn: bool) -> Element<'a, Message> {
|
||||
fn waiting_view<'a>(
|
||||
_settings: &Settings,
|
||||
displayed_text: &str,
|
||||
btn: bool,
|
||||
) -> Element<'a, Message, Renderer<Theme>> {
|
||||
let col = if btn {
|
||||
let no_internet_btn = button("No internet?")
|
||||
.padding(5)
|
||||
.on_press(Message::InitUadList(false))
|
||||
.style(style::PrimaryButton(settings.theme.palette));
|
||||
.on_press(Message::LoadUadList(false))
|
||||
.style(style::Button::Primary);
|
||||
|
||||
column()
|
||||
column![]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
.push(text(displayed_text).size(20))
|
||||
.push(no_internet_btn)
|
||||
} else {
|
||||
column()
|
||||
column![]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center)
|
||||
.push(text(displayed_text).size(20))
|
||||
|
|
@ -604,6 +596,6 @@ fn waiting_view<'a>(settings: &Settings, displayed_text: &str, btn: bool) -> Ele
|
|||
.height(Length::Fill)
|
||||
.center_y()
|
||||
.center_x()
|
||||
.style(style::Content(settings.theme.palette))
|
||||
.style(style::Container::Content)
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,18 @@
|
|||
use crate::core::config::Config;
|
||||
use crate::core::sync::{get_android_sdk, Phone as CorePhone};
|
||||
use crate::core::theme::Theme;
|
||||
use crate::core::uad_lists::UadListState;
|
||||
use crate::core::update::{Release, SelfUpdateState, SelfUpdateStatus};
|
||||
use crate::core::utils::{open_url, string_to_theme};
|
||||
use crate::gui::style;
|
||||
use crate::IN_FILE_CONFIGURATION;
|
||||
|
||||
use iced::pure::widget::Text;
|
||||
use iced::pure::{button, checkbox, column, container, pick_list, row, text, Element};
|
||||
use iced::{Length, Space};
|
||||
use iced::widget::{button, checkbox, column, container, pick_list, row, text, Space};
|
||||
use iced::{Element, Length, Renderer};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Settings {
|
||||
pub phone: Phone,
|
||||
pub theme: Theme,
|
||||
pub self_update_state: SelfUpdateState,
|
||||
pub list_update_state: UadListState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -42,8 +37,6 @@ impl Default for Settings {
|
|||
Self {
|
||||
phone: Phone::default(),
|
||||
theme: string_to_theme(IN_FILE_CONFIGURATION.theme.clone()),
|
||||
self_update_state: SelfUpdateState::default(),
|
||||
list_update_state: UadListState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,21 +48,11 @@ pub enum Message {
|
|||
MultiUserMode(bool),
|
||||
ApplyTheme(Theme),
|
||||
UrlPressed(PathBuf),
|
||||
GetLatestRelease(Result<Option<Release>, ()>),
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn update(&mut self, phone: &CorePhone, msg: Message) {
|
||||
match msg {
|
||||
Message::GetLatestRelease(release) => {
|
||||
match release {
|
||||
Ok(r) => {
|
||||
self.self_update_state.status = SelfUpdateStatus::Done;
|
||||
self.self_update_state.latest_release = r
|
||||
}
|
||||
Err(_) => self.self_update_state.status = SelfUpdateStatus::Failed,
|
||||
};
|
||||
}
|
||||
Message::ExpertMode(toggled) => {
|
||||
info!(
|
||||
"Expert mode {}",
|
||||
|
|
@ -103,58 +86,52 @@ impl Settings {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn view(&mut self, phone: &CorePhone) -> Element<Message> {
|
||||
pub fn view(&self, phone: &CorePhone) -> Element<Message, Renderer<Theme>> {
|
||||
let general_category_text = text("General").size(25);
|
||||
|
||||
let theme_picklist = pick_list(Theme::all(), Some(self.theme.clone()), Message::ApplyTheme)
|
||||
.style(style::PickList(self.theme.palette));
|
||||
let theme_picklist = pick_list(Theme::all(), Some(self.theme.clone()), Message::ApplyTheme);
|
||||
|
||||
let uad_category_text = text("Non-persistent settings").size(25);
|
||||
|
||||
let expert_mode_descr =
|
||||
text("Most of unsafe packages are known to bootloop the device if removed.")
|
||||
.size(15)
|
||||
.color(self.theme.palette.normal.surface);
|
||||
text("Most of unsafe packages are known to bootloop the device if removed.").size(15);
|
||||
|
||||
let expert_mode_checkbox = checkbox(
|
||||
"Allow to uninstall packages marked as \"unsafe\" (I KNOW WHAT I AM DOING)",
|
||||
self.phone.expert_mode,
|
||||
Message::ExpertMode,
|
||||
)
|
||||
.style(style::SettingsCheckBox::Enabled(self.theme.palette));
|
||||
.style(style::CheckBox::SettingsEnabled);
|
||||
|
||||
let multi_user_mode_descr =
|
||||
text("Disabling this setting will typically prevent affecting your work profile")
|
||||
.size(15)
|
||||
.color(self.theme.palette.normal.surface);
|
||||
.size(15);
|
||||
|
||||
let multi_user_mode_checkbox = checkbox(
|
||||
"Affect all the users of the phone (not only the selected user)",
|
||||
self.phone.multi_user_mode,
|
||||
Message::MultiUserMode,
|
||||
)
|
||||
.style(style::SettingsCheckBox::Enabled(self.theme.palette));
|
||||
.style(style::CheckBox::SettingsEnabled);
|
||||
|
||||
let disable_color = if phone.android_sdk >= 23 {
|
||||
let _disable_color = if phone.android_sdk >= 23 {
|
||||
self.theme.palette.normal.surface
|
||||
} else {
|
||||
self.theme.palette.normal.primary
|
||||
};
|
||||
|
||||
let disable_checkbox_style = if phone.android_sdk >= 23 {
|
||||
style::SettingsCheckBox::Enabled(self.theme.palette)
|
||||
style::CheckBox::SettingsEnabled
|
||||
} else {
|
||||
style::SettingsCheckBox::Disabled(self.theme.palette)
|
||||
style::CheckBox::SettingsDisabled
|
||||
};
|
||||
|
||||
let disable_mode_descr =
|
||||
text("In some cases, it can be better to disable a package instead of uninstalling it")
|
||||
.size(15)
|
||||
.color(disable_color);
|
||||
.size(15);
|
||||
|
||||
let _unavailable_text = Text::new("[Unavailable before Android 8.0]")
|
||||
.size(16)
|
||||
.color(self.theme.palette.bright.error);
|
||||
/* let _unavailable_text = text("[Unavailable before Android 8.0]")
|
||||
.size(16);*/
|
||||
|
||||
let unavailable_btn = button(text("Unavailable").size(13))
|
||||
.on_press(Message::UrlPressed(PathBuf::from(
|
||||
|
|
@ -162,7 +139,7 @@ impl Settings {
|
|||
why-is-the-disable-mode-setting-not-available-for-my-device",
|
||||
)))
|
||||
.height(Length::Units(22))
|
||||
.style(style::UnavailableButton(self.theme.palette));
|
||||
.style(style::Button::Unavailable);
|
||||
|
||||
// Disabling package without root isn't really possible before Android Oreo (8.0)
|
||||
// see https://github.com/0x192/universal-android-debloater/wiki/ADB-reference
|
||||
|
|
@ -174,40 +151,43 @@ impl Settings {
|
|||
.style(disable_checkbox_style);
|
||||
|
||||
let disable_setting_row = if phone.android_sdk >= 23 {
|
||||
row()
|
||||
.width(Length::Fill)
|
||||
.push(disable_mode_checkbox)
|
||||
.push(Space::new(Length::Fill, Length::Shrink))
|
||||
row![
|
||||
disable_mode_checkbox,
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
]
|
||||
.width(Length::Fill)
|
||||
} else {
|
||||
row()
|
||||
.width(Length::Fill)
|
||||
.push(disable_mode_checkbox)
|
||||
.push(Space::new(Length::Fill, Length::Shrink))
|
||||
.push(unavailable_btn)
|
||||
row![
|
||||
disable_mode_checkbox,
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
unavailable_btn,
|
||||
]
|
||||
.width(Length::Fill)
|
||||
};
|
||||
|
||||
let content = column()
|
||||
.width(Length::Fill)
|
||||
.spacing(10)
|
||||
.push(general_category_text)
|
||||
.push("Theme")
|
||||
.push(theme_picklist)
|
||||
.push(Space::new(Length::Fill, Length::Shrink))
|
||||
.push(uad_category_text)
|
||||
.push(expert_mode_checkbox)
|
||||
.push(expert_mode_descr)
|
||||
.push(Space::new(Length::Fill, Length::Shrink))
|
||||
.push(multi_user_mode_checkbox)
|
||||
.push(multi_user_mode_descr)
|
||||
.push(Space::new(Length::Fill, Length::Shrink))
|
||||
.push(disable_setting_row)
|
||||
.push(disable_mode_descr);
|
||||
let content = column![
|
||||
general_category_text,
|
||||
"Theme",
|
||||
theme_picklist,
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
uad_category_text,
|
||||
expert_mode_checkbox,
|
||||
expert_mode_descr,
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
multi_user_mode_checkbox,
|
||||
multi_user_mode_descr,
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
disable_setting_row,
|
||||
disable_mode_descr,
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.spacing(10);
|
||||
|
||||
container(content)
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(style::Content(self.theme.palette))
|
||||
.style(style::Container::Content)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
pub mod navigation_menu;
|
||||
pub mod package_row;
|
||||
|
|
|
|||
112
src/gui/widgets/navigation_menu.rs
Normal file
112
src/gui/widgets/navigation_menu.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
pub use crate::core::sync::Phone;
|
||||
use crate::core::theme::Theme;
|
||||
use crate::core::update::{SelfUpdateState, SelfUpdateStatus};
|
||||
pub use crate::gui::views::about::Message as AboutMessage;
|
||||
pub use crate::gui::views::list::{List as AppsView, LoadingState as ListLoadingState};
|
||||
use crate::gui::{style, Message};
|
||||
use iced::widget::{button, container, pick_list, row, text, Space, Text};
|
||||
use iced::{alignment, Alignment, Element, Font, Length, Renderer};
|
||||
|
||||
pub const ICONS: Font = Font::External {
|
||||
name: "Icons",
|
||||
bytes: include_bytes!("../../../resources/assets/icons.ttf"),
|
||||
};
|
||||
|
||||
pub fn nav_menu<'a>(
|
||||
device_list: &'a Vec<Phone>,
|
||||
selected_device: Option<Phone>,
|
||||
apps_view: &AppsView,
|
||||
self_update_state: &SelfUpdateState,
|
||||
) -> Element<'a, Message, Renderer<Theme>> {
|
||||
let apps_refresh_btn = button(
|
||||
Text::new('\u{E900}')
|
||||
.font(ICONS)
|
||||
.width(Length::Units(17))
|
||||
.horizontal_alignment(alignment::Horizontal::Center)
|
||||
.size(17),
|
||||
)
|
||||
.on_press(Message::RefreshButtonPressed)
|
||||
.padding(5)
|
||||
.style(style::Button::Refresh);
|
||||
|
||||
let reboot_btn = button("Reboot")
|
||||
.on_press(Message::RebootButtonPressed)
|
||||
.padding(5)
|
||||
.style(style::Button::Refresh);
|
||||
|
||||
let uad_version_text = if let Some(r) = &self_update_state.latest_release {
|
||||
if self_update_state.status == SelfUpdateStatus::Updating {
|
||||
Text::new("Updating please wait...")
|
||||
} else {
|
||||
Text::new(format!(
|
||||
"New UAD version available {} -> {}",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
r.tag_name
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Text::new(env!("CARGO_PKG_VERSION"))
|
||||
};
|
||||
|
||||
let mut apps_btn = button("Apps")
|
||||
.on_press(Message::AppsPress)
|
||||
.padding(5)
|
||||
.style(style::Button::Primary);
|
||||
|
||||
if self_update_state.latest_release.is_some() {
|
||||
apps_btn = button("Update")
|
||||
.on_press(Message::AboutAction(AboutMessage::DoSelfUpdate))
|
||||
.padding(5)
|
||||
.style(style::Button::SelfUpdate);
|
||||
}
|
||||
|
||||
let about_btn = button("About")
|
||||
.on_press(Message::AboutPressed)
|
||||
.padding(5)
|
||||
.style(style::Button::Primary);
|
||||
|
||||
let settings_btn = button("Settings")
|
||||
.on_press(Message::SettingsPressed)
|
||||
.padding(5)
|
||||
.style(style::Button::Primary);
|
||||
|
||||
let device_list_text = match apps_view.loading_state {
|
||||
ListLoadingState::FindingPhones => text("finding connected phone..."),
|
||||
_ => text("no devices/emulators found"),
|
||||
};
|
||||
|
||||
let row = match selected_device {
|
||||
Some(phone) => row![
|
||||
apps_refresh_btn,
|
||||
reboot_btn,
|
||||
pick_list(device_list, Some(phone), Message::DeviceSelected,),
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
uad_version_text,
|
||||
apps_btn,
|
||||
about_btn,
|
||||
settings_btn,
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10),
|
||||
None => row![
|
||||
reboot_btn,
|
||||
apps_refresh_btn,
|
||||
device_list_text,
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
uad_version_text,
|
||||
apps_btn,
|
||||
about_btn,
|
||||
settings_btn,
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(10),
|
||||
};
|
||||
|
||||
container(row)
|
||||
.width(Length::Fill)
|
||||
.padding(10)
|
||||
.style(style::Container::Navigation)
|
||||
.into()
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
use crate::core::sync::Phone;
|
||||
use crate::core::theme::Theme;
|
||||
use crate::core::uad_lists::{PackageState, Removal, UadList};
|
||||
|
||||
use crate::gui::style;
|
||||
use crate::gui::views::settings::Settings;
|
||||
|
||||
use iced::pure::{button, checkbox, row, text, Element};
|
||||
use iced::{alignment, Alignment, Command, Length, Space};
|
||||
use iced::widget::{button, checkbox, row, text, Space};
|
||||
use iced::{alignment, Alignment, Command, Element, Length, Renderer};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PackageRow {
|
||||
|
|
@ -50,7 +50,7 @@ impl PackageRow {
|
|||
Command::none()
|
||||
}
|
||||
|
||||
pub fn view(&mut self, settings: &Settings, _phone: &Phone) -> Element<Message> {
|
||||
pub fn view(&self, settings: &Settings, _phone: &Phone) -> Element<Message, Renderer<Theme>> {
|
||||
//let trash_svg = format!("{}/resources/assets/trash.svg", env!("CARGO_MANIFEST_DIR"));
|
||||
//let restore_svg = format!("{}/resources/assets/rotate.svg", env!("CARGO_MANIFEST_DIR"));
|
||||
let button_style;
|
||||
|
|
@ -65,19 +65,19 @@ impl PackageRow {
|
|||
} else {
|
||||
"Uninstall"
|
||||
};
|
||||
button_style = style::PackageButton::Uninstall(settings.theme.palette);
|
||||
button_style = style::Button::UninstallPackage;
|
||||
}
|
||||
PackageState::Disabled => {
|
||||
action_text = "Enable";
|
||||
button_style = style::PackageButton::Restore(settings.theme.palette);
|
||||
button_style = style::Button::RestorePackage;
|
||||
}
|
||||
PackageState::Uninstalled => {
|
||||
action_text = "Restore";
|
||||
button_style = style::PackageButton::Restore(settings.theme.palette);
|
||||
button_style = style::Button::RestorePackage;
|
||||
}
|
||||
PackageState::All => {
|
||||
action_text = "Error";
|
||||
button_style = style::PackageButton::Restore(settings.theme.palette);
|
||||
button_style = style::Button::RestorePackage;
|
||||
warn!("Incredible! Something impossible happened!");
|
||||
}
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ impl PackageRow {
|
|||
|| settings.phone.expert_mode
|
||||
{
|
||||
selection_checkbox = checkbox("", self.selected, Message::ToggleSelection)
|
||||
.style(style::SelectionCheckBox::Enabled(settings.theme.palette));
|
||||
.style(style::CheckBox::PackageEnabled);
|
||||
|
||||
action_btn = button(
|
||||
text(action_text)
|
||||
|
|
@ -97,7 +97,7 @@ impl PackageRow {
|
|||
.on_press(Message::ActionPressed);
|
||||
} else {
|
||||
selection_checkbox = checkbox("", self.selected, Message::ToggleSelection)
|
||||
.style(style::SelectionCheckBox::Disabled(settings.theme.palette));
|
||||
.style(style::CheckBox::PackageDisabled);
|
||||
|
||||
action_btn = button(
|
||||
text(action_text)
|
||||
|
|
@ -106,26 +106,26 @@ impl PackageRow {
|
|||
);
|
||||
}
|
||||
|
||||
row()
|
||||
.push(
|
||||
button(
|
||||
row()
|
||||
.align_items(Alignment::Center)
|
||||
.push(selection_checkbox)
|
||||
.push(text(&self.name).width(Length::FillPortion(8)))
|
||||
.push(action_btn.style(button_style)),
|
||||
)
|
||||
.padding(8)
|
||||
.style(if self.current {
|
||||
style::PackageRow::Current(settings.theme.palette)
|
||||
} else {
|
||||
style::PackageRow::Normal(settings.theme.palette)
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.on_press(Message::PackagePressed),
|
||||
row![
|
||||
button(
|
||||
row![
|
||||
selection_checkbox,
|
||||
text(&self.name).width(Length::FillPortion(8)),
|
||||
action_btn.style(button_style)
|
||||
]
|
||||
.align_items(Alignment::Center)
|
||||
)
|
||||
.push(Space::with_width(Length::Units(15)))
|
||||
.align_items(Alignment::Center)
|
||||
.into()
|
||||
.padding(8)
|
||||
.style(if self.current {
|
||||
style::Button::SelectedPackage
|
||||
} else {
|
||||
style::Button::NormalPackage
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.on_press(Message::PackagePressed),
|
||||
Space::with_width(Length::Units(15))
|
||||
]
|
||||
.align_items(Alignment::Center)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue