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:
w1nst0n 2022-09-01 21:43:45 +00:00 • committed by GitHub
parent 7788f3cf83
commit 9b564d3825
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1450 additions and 1382 deletions

915
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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"

View file

@ -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") {

View file

@ -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,

View file

@ -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 {

View file

@ -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(),

View file

@ -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}')
}

View file

@ -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) },
}
}
}

View file

@ -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()
}
}

View file

@ -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()
}

View file

@ -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()
}
}

View file

@ -1 +1,2 @@
pub mod navigation_menu;
pub mod package_row;

View 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()
}

View file

@ -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()
}
}