This commit is contained in:
0x192 2021-10-10 03:56:13 +02:00
commit 1764665f37
No known key found for this signature in database
GPG key ID: 34D27465928A0A1D
15 changed files with 456 additions and 367 deletions

View file

@ -13,4 +13,4 @@ fn main() {
println!("cargo:rustc-link-lib=static=stdc++");
println!("cargo:rustc-link-lib=static=winpthread");
}
}
}

View file

@ -1,4 +1,4 @@
pub mod sync;
pub mod theme;
pub mod uad_lists;
pub mod utils;
pub mod theme;

View file

@ -1,15 +1,14 @@
use std::process::Command;
use std::collections::{HashSet};
use regex::Regex;
use crate::core::uad_lists::{PackageState};
use crate::core::uad_lists::PackageState;
use crate::gui::views::settings::Settings;
use crate::gui::widgets::package_row::PackageRow;
use static_init::{dynamic};
use regex::Regex;
use static_init::dynamic;
use std::collections::HashSet;
use std::process::Command;
#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;
#[derive(Debug)]
pub struct Phone {
pub model: String,
@ -35,36 +34,27 @@ pub struct User {
impl std::fmt::Display for User {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
format!("user {}", self.id),
)
write!(f, "{}", format!("user {}", self.id),)
}
}
pub fn adb_shell_command(args: &str) -> Result<String,String> {
}
pub fn adb_shell_command(args: &str) -> Result<String, String> {
#[cfg(target_os = "windows")]
let output = Command::new("adb")
.args(&["shell", args])
.creation_flags(0x08000000) // do not open a cmd window
.output();
let output = Command::new("adb")
.args(&["shell", args])
.creation_flags(0x08000000) // do not open a cmd window
.output();
#[cfg(target_os = "macos")]
let output = Command::new("adb")
.args(&["shell", args])
.output();
let output = Command::new("adb").args(&["shell", args]).output();
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
let output = Command::new("adb")
.args(&["shell", args])
.output();
let output = Command::new("adb").args(&["shell", args]).output();
match output {
Err(e) => {
error!("ADB: {}", e);
Err("ADB was not found".to_string())
},
}
Ok(o) => {
if !o.status.success() {
let stdout = String::from_utf8(o.stdout).unwrap().trim_end().to_string();
@ -77,11 +67,9 @@ pub fn adb_shell_command(args: &str) -> Result<String,String> {
Ok(String::from_utf8(o.stdout).unwrap().trim_end().to_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),
@ -96,7 +84,7 @@ pub fn list_all_system_packages(user_id: &Option<&User>) -> 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()
None => "".to_string(),
};
let action = match state {
@ -113,80 +101,105 @@ pub fn hashset_system_packages(state: PackageState, user_id: &Option<&User>) ->
.collect()
}
pub fn action_handler(selected_user: &User, package: &PackageRow, phone: &Phone, settings: &Settings) -> Result<bool, bool> {
pub fn action_handler(
selected_user: &User,
package: &PackageRow,
phone: &Phone,
settings: &Settings,
) -> Result<bool, bool> {
let actions: Vec<String> = match package.state {
PackageState::Enabled => {
match settings.disable_mode {
true => {
if phone.android_sdk < 21 {
vec![
format!("am force-stop {}", package.name),
format!("pm disable-user {}", package.name),
format!("pm clear {}", package.name)
]
} else if settings.multi_user_mode{
phone.user_list
.iter()
.flat_map(
|u| [
format!("am force-stop --user {} {}", u.id, package.name),
format!("pm disable-user --user {} {}", u.id, package.name),
format!("pm clear --user {} {}", u.id, package.name)
])
.collect()
} else {
vec![
format!("am force-stop --user {} {}", selected_user.id, package.name),
format!("pm disable-user --user {} {}", selected_user.id, package.name),
format!("pm clear --user {} {}", selected_user.id, package.name)
]
}
}
false => {
if phone.android_sdk < 21 {
vec![format!("pm uninstall {}", package.name)]
} else if settings.multi_user_mode {
phone.user_list
.iter()
.map(|u| format!("pm uninstall --user {} {}", u.id, package.name))
.collect()
} else {
vec![format!("pm uninstall --user {} {}", selected_user.id, package.name)]
}
PackageState::Enabled => match settings.disable_mode {
true => {
if phone.android_sdk < 21 {
vec![
format!("am force-stop {}", package.name),
format!("pm disable-user {}", package.name),
format!("pm clear {}", package.name),
]
} else if settings.multi_user_mode {
phone
.user_list
.iter()
.flat_map(|u| {
[
format!("am force-stop --user {} {}", u.id, package.name),
format!("pm disable-user --user {} {}", u.id, package.name),
format!("pm clear --user {} {}", u.id, package.name),
]
})
.collect()
} else {
vec![
format!("am force-stop --user {} {}", selected_user.id, package.name),
format!(
"pm disable-user --user {} {}",
selected_user.id, package.name
),
format!("pm clear --user {} {}", selected_user.id, package.name),
]
}
}
}
false => {
if phone.android_sdk < 21 {
vec![format!("pm uninstall {}", package.name)]
} else if settings.multi_user_mode {
phone
.user_list
.iter()
.map(|u| format!("pm uninstall --user {} {}", u.id, package.name))
.collect()
} else {
vec![format!(
"pm uninstall --user {} {}",
selected_user.id, package.name
)]
}
}
},
PackageState::Uninstalled => {
if phone.android_sdk < 21 {
Vec::new() // action already prevented by the GUI
} else if settings.multi_user_mode {
phone.user_list
phone
.user_list
.iter()
.map(|u| format!("cmd package install-existing --user {} {}", u.id, package.name))
.map(|u| {
format!(
"cmd package install-existing --user {} {}",
u.id, package.name
)
})
.collect()
} else {
vec![format!("cmd package install-existing --user {} {}", selected_user.id, package.name)]
vec![format!(
"cmd package install-existing --user {} {}",
selected_user.id, package.name
)]
}
},
}
PackageState::Disabled => {
if phone.android_sdk < 21 {
vec![format!("pm enable {}", package.name)]
} else if settings.multi_user_mode {
phone.user_list
phone
.user_list
.iter()
.map(|u| format!("pm enable --user {} {}", u.id, package.name))
.collect()
} else {
vec![format!("pm enable --user {} {}", selected_user.id, package.name)]
vec![format!(
"pm enable --user {} {}",
selected_user.id, package.name
)]
}
},
}
PackageState::All => vec![], // This can't happen (like... never)
};
for action in actions {
match adb_shell_command(&action) {
Ok(_) => {
info!("[{}] {}", package.removal , action);
Ok(_) => {
info!("[{}] {}", package.removal, action);
}
Err(err) => {
if err.contains("[not installed for") {
@ -211,21 +224,22 @@ pub fn get_phone_model() -> String {
err
}
}
}
}
pub fn get_android_sdk() -> u8 {
match adb_shell_command("getprop ro.build.version.sdk") {
Ok(sdk) => sdk.parse().unwrap(),
Err(_) => 0,
Err(_) => 0,
}
}
pub fn get_phone_brand() -> String {
format!("{} {}",
adb_shell_command("getprop ro.product.brand").unwrap_or_else(|_| "".to_string()).trim(),
format!(
"{} {}",
adb_shell_command("getprop ro.product.brand")
.unwrap_or_else(|_| "".to_string())
.trim(),
get_phone_model()
)
}
@ -235,10 +249,14 @@ pub fn get_user_list() -> Vec<User> {
static RE: Regex = Regex::new(r"\{([0-9]+)").unwrap();
match adb_shell_command("pm list users") {
Ok(users) => RE.find_iter(&users)
.enumerate()
.map(|(i,u)| User { id: u.as_str()[1..].parse().unwrap(), index: i})
.collect(),
Ok(users) => RE
.find_iter(&users)
.enumerate()
.map(|(i, u)| User {
id: u.as_str()[1..].parse().unwrap(),
index: i,
})
.collect(),
Err(_) => vec![],
}
}
}

View file

@ -1,5 +1,5 @@
use std::cmp::Ordering;
use iced::Color;
use std::cmp::Ordering;
#[derive(Debug, Clone)]
pub struct Theme {
@ -38,11 +38,7 @@ pub struct ColorPalette {
impl Theme {
pub fn all() -> Vec<Theme> {
vec![
Theme::dark(),
Theme::light(),
Theme::lupin(),
]
vec![Theme::dark(), Theme::light(), Theme::lupin()]
}
pub fn dark() -> Theme {
@ -116,7 +112,6 @@ impl Theme {
},
}
}
}
impl PartialEq for Theme {
@ -173,4 +168,4 @@ fn hex_to_color(hex: &str) -> Option<Color> {
}
None
}
}

View file

@ -1,8 +1,7 @@
use serde::{Deserialize};
use serde::Deserialize;
use serde_json;
//use std::fs;
use std::{collections::HashMap};
use std::collections::HashMap;
#[derive(Deserialize, Debug, Clone, PartialEq, Hash, Eq)]
#[serde(rename_all = "camelCase")]
@ -16,7 +15,6 @@ pub struct Package {
pub removal: Removal,
}
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UadList {
All,
@ -90,7 +88,6 @@ impl PackageState {
];
}
impl std::fmt::Display for PackageState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
@ -114,8 +111,12 @@ impl Opposite for PackageState {
fn opposite(&self, disable: bool) -> PackageState {
match self {
PackageState::Enabled => {
if disable {PackageState::Disabled} else {PackageState::Uninstalled}
},
if disable {
PackageState::Disabled
} else {
PackageState::Uninstalled
}
}
PackageState::Uninstalled => PackageState::Enabled,
PackageState::Disabled => PackageState::Enabled,
PackageState::All => PackageState::All,
@ -151,7 +152,6 @@ impl Removal {
];
}
impl std::fmt::Display for Removal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
@ -169,7 +169,6 @@ impl std::fmt::Display for Removal {
}
}
pub fn load_debloat_lists() -> HashMap<String, Package> {
const DATA: &str = include_str!("../../ressources/assets/uad_lists.json");
let mut package_lists = HashMap::new();
@ -185,6 +184,3 @@ pub fn load_debloat_lists() -> HashMap<String, Package> {
package_lists
}

View file

@ -1,15 +1,18 @@
use crate::core::uad_lists::{UadList, PackageState, Package, Removal};
use crate::gui::widgets::package_row::PackageRow;
use crate::core::sync::{hashset_system_packages, list_all_system_packages, User};
use crate::core::uad_lists::{Package, PackageState, Removal, UadList};
use crate::gui::views::list::Selection;
use crate::core::sync::{list_all_system_packages, hashset_system_packages, User};
use crate::gui::widgets::package_row::PackageRow;
use crate::gui::ICONS;
use iced::{Text, alignment, Length};
use iced::{alignment, Length, Text};
use std::collections::HashMap;
use std::fs;
use std::fs;
use std::io::{self, prelude::*, BufReader};
pub fn fetch_packages(uad_lists: &'static HashMap<String, Package>, user_id: &Option<&User>) -> Vec<PackageRow> {
pub fn fetch_packages(
uad_lists: &'static HashMap<String, Package>,
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);
let disabled_system_packages = hashset_system_packages(PackageState::Disabled, user_id);
@ -27,46 +30,49 @@ pub fn fetch_packages(uad_lists: &'static HashMap<String, Package>, user_id: &Op
if uad_lists.contains_key(p_name) {
description = match &uad_lists.get(p_name).unwrap().description {
Some(descr) => descr,
None => "[No description]",
};
Some(descr) => descr,
None => "[No description]",
};
uad_list = uad_lists.get(p_name).unwrap().list;
removal = uad_lists.get(p_name).unwrap().removal;
}
if enabled_system_packages.contains(p_name) {
state = PackageState::Enabled;
state = PackageState::Enabled;
} else if disabled_system_packages.contains(p_name) {
state = PackageState::Disabled;
}
let package_row = PackageRow::new(
p_name,
state,
description,
uad_list,
removal,
false,
);
let package_row = PackageRow::new(p_name, state, description, uad_list, removal, false);
user_package.push(package_row);
}
user_package.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
user_package
}
pub fn update_selection_count(selection: &mut Selection, p_state: PackageState, add: bool) {
match p_state {
PackageState::Enabled => {
if add { selection.enabled += 1 } else { selection.enabled -= 1 };
},
if add {
selection.enabled += 1
} else {
selection.enabled -= 1
};
}
PackageState::Disabled => {
if add { selection.disabled += 1 } else { selection.disabled -= 1 };
},
if add {
selection.disabled += 1
} else {
selection.disabled -= 1
};
}
PackageState::Uninstalled => {
if add { selection.uninstalled += 1 } else { selection.uninstalled -= 1 };
},
if add {
selection.uninstalled += 1
} else {
selection.uninstalled -= 1
};
}
PackageState::All => {}
};
}
@ -81,12 +87,15 @@ pub async fn export_selection(packages: Vec<PackageRow>) -> Result<bool, String>
match fs::write("uad_exported_selection.txt", selected) {
Ok(_) => Ok(true),
Err(err) => Err(err.to_string())
Err(err) => Err(err.to_string()),
}
}
#[allow(clippy::needless_collect)] // false positive: https://github.com/rust-lang/rust-clippy/issues/6164
pub fn import_selection(packages: &mut Vec<PackageRow>, selection: &mut Selection) -> io::Result<()> {
pub fn import_selection(
packages: &mut Vec<PackageRow>,
selection: &mut Selection,
) -> io::Result<()> {
let file = fs::File::open("uad_exported_selection.txt")?;
let reader = BufReader::new(file);
let imported_selection: Vec<String> = reader
@ -96,7 +105,7 @@ pub fn import_selection(packages: &mut Vec<PackageRow>, selection: &mut Selectio
*selection = Selection::default(); // should already be empty normally
for (i,p) in packages.iter_mut().enumerate() {
for (i, p) in packages.iter_mut().enumerate() {
if imported_selection.contains(&p.name) {
p.selected = true;
selection.selected_packages.push(i);

View file

@ -2,16 +2,16 @@ pub mod style;
pub mod views;
pub mod widgets;
pub use crate::core::sync::Phone;
pub use crate::core::uad_lists::Package;
use crate::core::utils::icon;
pub use views::about::About as AboutView;
pub use views::list::{List as AppsView, Message as AppsMessage};
pub use views::settings::{Settings as SettingsView, Message as SettingsMessage};
pub use crate::core::uad_lists::Package;
pub use crate::core::sync::{Phone};
use crate::core::utils::icon;
pub use views::settings::{Message as SettingsMessage, Settings as SettingsView};
use iced::{
button, Alignment, Application, Button, Column, Command, Space, Font,
Container, Element, Length, Row, Settings, Text, window::Settings as Window,
button, window::Settings as Window, Alignment, Application, Button, Column, Command, Container,
Element, Font, Length, Row, Settings, Space, Text,
};
pub const ICONS: Font = Font::External {
@ -19,12 +19,11 @@ pub const ICONS: Font = Font::External {
bytes: include_bytes!("../../ressources/assets/icons.ttf"),
};
#[derive(Debug, Clone)]
pub enum View {
List,
About,
Settings,
Settings,
}
impl Default for View {
@ -78,14 +77,20 @@ impl Application for UadGui {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::Init(_) => {
info!("ANDROID_SDK: {} | PHONE: {}", self.phone.android_sdk, self.phone.model);
info!(
"ANDROID_SDK: {} | PHONE: {}",
self.phone.android_sdk, self.phone.model
);
Command::perform(Self::load_phone_packages(), Message::AppsAction)
}
Message::AppsRefreshPress => {
self.phone = Phone::default();
self.settings_view = SettingsView::default();
info!("{:-^65}", "-");
info!("ANDROID_SDK: {} | PHONE: {}", self.phone.android_sdk, self.phone.model);
info!(
"ANDROID_SDK: {} | PHONE: {}",
self.phone.android_sdk, self.phone.model
);
self.apps_view = AppsView::default();
self.view = View::List;
Command::perform(Self::load_phone_packages(), Message::AppsAction)
@ -102,9 +107,10 @@ impl Application for UadGui {
self.view = View::Settings;
Command::none()
}
Message::AppsAction(msg) => {
self.apps_view.update(&self.settings_view, &mut self.phone, msg).map(Message::AppsAction)
}
Message::AppsAction(msg) => self
.apps_view
.update(&self.settings_view, &mut self.phone, msg)
.map(Message::AppsAction),
Message::SettingsAction(msg) => {
self.settings_view.update(msg);
Command::none()
@ -153,7 +159,10 @@ impl Application for UadGui {
.style(style::NavigationContainer(self.settings_view.theme.palette));
let main_container = match self.view {
View::List => self.apps_view.view(&self.settings_view, &self.phone).map(Message::AppsAction),
View::List => self
.apps_view
.view(&self.settings_view, &self.phone)
.map(Message::AppsAction),
View::About => self.about_view.view(&self.settings_view),
View::Settings => self.settings_view.view().map(Message::SettingsAction),
};
@ -166,7 +175,6 @@ impl Application for UadGui {
}
}
impl UadGui {
pub fn start() {
let settings: Settings<()> = Settings {
@ -187,7 +195,6 @@ impl UadGui {
}
}
fn refresh_icon() -> Text {
icon('\u{E900}')
}
}

View file

@ -1,8 +1,5 @@
use iced::{
button, container, scrollable, Background, Color, checkbox,
text_input, pick_list,
};
use crate::core::theme::ColorPalette;
use iced::{button, checkbox, container, pick_list, scrollable, text_input, Background, Color};
pub struct Content(pub ColorPalette);
impl container::StyleSheet for Content {
@ -80,7 +77,6 @@ impl button::StyleSheet for RefreshButton {
}
}
pub enum PackageButton {
Uninstall(ColorPalette),
Restore(ColorPalette),
@ -137,8 +133,8 @@ impl button::StyleSheet for PackageButton {
match self {
Self::Restore(palette) => button::Style {
background: Some(Background::Color(Color {
a: 0.05,
..palette.normal.primary
a: 0.05,
..palette.normal.primary
})),
text_color: Color {
a: 0.50,
@ -148,8 +144,8 @@ impl button::StyleSheet for PackageButton {
},
Self::Uninstall(palette) => button::Style {
background: Some(Background::Color(Color {
a: 0.01,
..palette.bright.error
a: 0.01,
..palette.bright.error
})),
text_color: Color {
a: 0.50,
@ -184,12 +180,12 @@ impl button::StyleSheet for PackageRow {
}
}
fn pressed(&self) -> button::Style {
button::Style {
background: Some(Background::Color(Color::from_rgb(0.35, 0.43, 0.46))),
text_color: self.0.bright.primary,
..self.active()
}
button::Style {
background: Some(Background::Color(Color::from_rgb(0.35, 0.43, 0.46))),
text_color: self.0.bright.primary,
..self.active()
}
}
}
pub struct Description(pub ColorPalette);
@ -276,10 +272,9 @@ impl scrollable::StyleSheet for Scrollable {
}
}
pub enum SelectionCheckBox {
Enabled(ColorPalette),
Disabled(ColorPalette)
Disabled(ColorPalette),
}
impl checkbox::StyleSheet for SelectionCheckBox {

View file

@ -1,6 +1,6 @@
use crate::gui::style;
use crate::gui::Message;
use crate::gui::views::settings::Settings;
use crate::gui::Message;
use iced::{Alignment, Column, Container, Element, Length, Text};
#[derive(Default, Debug, Clone)]
@ -25,4 +25,4 @@ impl About {
.style(style::Content(settings.theme.palette))
.into()
}
}
}

View file

@ -1,25 +1,39 @@
use crate::gui::style;
use crate::core::sync::{Phone, action_handler, User};
use crate::core::sync::{action_handler, Phone, User};
use crate::core::uad_lists::{
UadList, PackageState, Package, Removal, Opposite, load_debloat_lists
load_debloat_lists, Opposite, Package, PackageState, Removal, UadList,
};
use crate::core::utils::{
import_selection, export_selection, update_selection_count, fetch_packages,
export_selection, fetch_packages, import_selection, update_selection_count,
};
use std::{collections::HashMap};
use static_init::{dynamic};
use crate::gui::style;
use static_init::dynamic;
use std::collections::HashMap;
use crate::gui::views::settings::Settings;
use crate::gui::widgets::package_row::{ PackageRow, Message as RowMessage };
use crate::gui::widgets::package_row::{Message as RowMessage, PackageRow};
use iced::{
scrollable, Alignment, alignment, Column, Container, Element, Space,
Length, Row, Scrollable, Text, text_input, TextInput, Command,//Svg,
PickList, pick_list, Button, button,
alignment,
button,
pick_list,
scrollable,
text_input,
Alignment,
Button,
Column,
Command, //Svg,
Container,
Element,
Length,
PickList,
Row,
Scrollable,
Space,
Text,
TextInput,
};
#[dynamic]
static UAD_LISTS: HashMap<String, Package> = load_debloat_lists();
static UAD_LISTS: HashMap<String, Package> = load_debloat_lists();
#[derive(Debug, Default, Clone)]
pub struct Selection {
@ -60,7 +74,6 @@ pub struct List {
description: String,
}
#[derive(Debug, Clone)]
pub enum Message {
SearchInputChanged(String),
@ -76,22 +89,27 @@ pub enum Message {
ExportedSelection(Result<bool, String>),
}
impl List {
pub fn update(&mut self, settings: &Settings, phone: &mut Phone, message: Message) -> Command<Message> {
let i_user = &self.selected_user.unwrap_or(User {id: 0, index: 0}).index; // for readability
pub fn update(
&mut self,
settings: &Settings,
phone: &mut Phone,
message: Message,
) -> Command<Message> {
let i_user = &self.selected_user.unwrap_or(User { id: 0, index: 0 }).index; // for readability
match message {
Message::LoadPackages => {
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});
self.selected_user = Some(User { id: 0, index: 0 });
match phone.user_list.len() {
0|1 => self.phone_packages.push(fetch_packages(&UAD_LISTS, &None)),
0 | 1 => self.phone_packages.push(fetch_packages(&UAD_LISTS, &None)),
_ => {
for user in &phone.user_list {
self.phone_packages.push(fetch_packages(&UAD_LISTS, &Some(user)));
self.phone_packages
.push(fetch_packages(&UAD_LISTS, &Some(user)));
}
}
}
@ -111,7 +129,6 @@ impl List {
Command::none()
}
Message::ListSelected(list) => {
self.selected_list = Some(list);
Self::filter_package_lists(self);
Command::none()
@ -127,77 +144,97 @@ impl List {
Command::none()
}
Message::List(i, row_message) => {
self.phone_packages[*i_user][i].update(row_message.clone()).map(move |row_message| Message::List(i, row_message));
self.phone_packages[*i_user][i]
.update(row_message.clone())
.map(move |row_message| Message::List(i, row_message));
match row_message {
RowMessage::ToggleSelection(toggle) => {
if self.phone_packages[*i_user][i].removal == Removal::Unsafe && !settings.expert_mode {
if self.phone_packages[*i_user][i].removal == Removal::Unsafe
&& !settings.expert_mode
{
self.phone_packages[*i_user][i].selected = false;
} else {
self.phone_packages[*i_user][i].selected = toggle;
if self.phone_packages[*i_user][i].selected {
self.selection.selected_packages.push(i);
update_selection_count(&mut self.selection, self.phone_packages[*i_user][i].state, true);
update_selection_count(
&mut self.selection,
self.phone_packages[*i_user][i].state,
true,
);
} else {
self.selection.selected_packages.drain_filter(|s_i| *s_i == i);
update_selection_count(&mut self.selection, self.phone_packages[*i_user][i].state, false);
self.selection
.selected_packages
.drain_filter(|s_i| *s_i == i);
update_selection_count(
&mut self.selection,
self.phone_packages[*i_user][i].state,
false,
);
}
}
},
}
RowMessage::ActionPressed => {
let success = action_handler(
&self.selected_user.unwrap(),
&self.phone_packages[*i_user][i],
phone,
settings,
).unwrap_or_else(|err| err);
&self.selected_user.unwrap(),
&self.phone_packages[*i_user][i],
phone,
settings,
)
.unwrap_or_else(|err| err);
if success {
let i = self.phone_packages[*i_user]
.iter()
.position(|p| p.name == self.phone_packages[*i_user][i].name)
.unwrap();
if self.phone_packages[*i_user][i].selected {
update_selection_count(&mut self.selection, self.phone_packages[*i_user][i].state, false);
update_selection_count(
&mut self.selection,
self.phone_packages[*i_user][i].state,
false,
);
}
self.phone_packages[*i_user][i].state = self.phone_packages[*i_user][i].state
self.phone_packages[*i_user][i].state = self.phone_packages[*i_user][i]
.state
.opposite(settings.disable_mode);
if settings.multi_user_mode {
for u in &phone.user_list {
self.phone_packages[u.index][i].state = self.phone_packages[*i_user][i].state;
self.phone_packages[u.index][i].state =
self.phone_packages[*i_user][i].state;
self.phone_packages[u.index][i].selected = false;
}
}
self.selection.selected_packages.drain_filter(|s_i| *s_i == i);
self.selection
.selected_packages
.drain_filter(|s_i| *s_i == i);
self.phone_packages[*i_user][i].selected = false;
Self::filter_package_lists(self);
}
},
}
RowMessage::PackagePressed => {
self.description = self.phone_packages[*i_user][i].clone().description;
},
}
}
Command::none()
},
}
Message::ApplyActionOnSelection(action) => {
let mut selected_packages = self.selection.selected_packages.clone();
match action {
Action::Remove => {
selected_packages.drain_filter(
|i|
selected_packages.drain_filter(|i| {
self.phone_packages[*i_user][*i].state != PackageState::Enabled
);
});
}
Action::Restore => {
selected_packages.drain_filter(
|i|
selected_packages.drain_filter(|i| {
self.phone_packages[*i_user][*i].state == PackageState::Enabled
);
});
}
}
@ -206,52 +243,62 @@ impl List {
&self.selected_user.unwrap(),
&self.phone_packages[*i_user][i],
phone,
settings
).unwrap_or_else(|err| err);
settings,
)
.unwrap_or_else(|err| err);
if success {
update_selection_count(
&mut self.selection,
self.phone_packages[*i_user][i].state,
false
&mut self.selection,
self.phone_packages[*i_user][i].state,
false,
);
if !settings.multi_user_mode {
self.phone_packages[*i_user][i].state = self.phone_packages[*i_user][i].state
self.phone_packages[*i_user][i].state = self.phone_packages[*i_user][i]
.state
.opposite(settings.disable_mode);
self.phone_packages[*i_user][i].selected = false;
}
for u in &phone.user_list {
self.phone_packages[u.index][i].state = self.phone_packages[u.index][i].state
self.phone_packages[u.index][i].state = self.phone_packages[u.index][i]
.state
.opposite(settings.disable_mode);
self.phone_packages[u.index][i].selected = false;
}
self.selection.selected_packages.drain_filter(|s_i| *s_i == i);
self.selection
.selected_packages
.drain_filter(|s_i| *s_i == i);
}
}
Self::filter_package_lists(self);
Command::none()
},
}
Message::SelectAllPressed => {
for i in self.filtered_packages.clone() {
self.phone_packages[*i_user][i].selected = true;
if !self.selection.selected_packages.contains(&i) {
self.selection.selected_packages.push(i);
update_selection_count(&mut self.selection, self.phone_packages[*i_user][i].state, true);
update_selection_count(
&mut self.selection,
self.phone_packages[*i_user][i].state,
true,
);
}
}
Command::none()
},
Message::ExportSelectionPressed => {
Command::perform(export_selection(self.phone_packages[*i_user].clone()), Message::ExportedSelection)
},
}
Message::ExportSelectionPressed => Command::perform(
export_selection(self.phone_packages[*i_user].clone()),
Message::ExportedSelection,
),
Message::ExportedSelection(export) => {
match export {
Ok(_) => info!("Selection exported"),
Err(err) => error!("Selection export: {}", err),
};
Command::none()
},
Command::none()
}
Message::UserSelected(user) => {
for p in &mut self.phone_packages[*i_user] {
p.selected = false;
@ -262,7 +309,7 @@ impl List {
}
self.filtered_packages = (0..self.phone_packages[user.index].len()).collect();
Self::filter_package_lists(self);
Command::none()
Command::none()
}
}
}
@ -279,42 +326,41 @@ impl List {
.style(style::SearchInput(settings.theme.palette));
// let package_amount = Text::new(format!("{} packages found", packages.len()));
let user_picklist = PickList::new(
&mut self.user_picklist,
phone.user_list.clone(),
self.selected_user,
Message::UserSelected,
)
.width(Length::Units(85))
.style(style::PickList(settings.theme.palette));
&mut self.user_picklist,
phone.user_list.clone(),
self.selected_user,
Message::UserSelected,
)
.width(Length::Units(85))
.style(style::PickList(settings.theme.palette));
let divider = Space::new(Length::Fill, Length::Shrink);
let list_picklist = PickList::new(
&mut self.list_picklist,
&UadList::ALL[..],
self.selected_list,
Message::ListSelected,
)
.style(style::PickList(settings.theme.palette));
&mut self.list_picklist,
&UadList::ALL[..],
self.selected_list,
Message::ListSelected,
)
.style(style::PickList(settings.theme.palette));
let package_state_picklist = PickList::new(
&mut self.package_state_picklist,
&PackageState::ALL[..],
self.selected_package_state,
Message::PackageStateSelected,
)
.style(style::PickList(settings.theme.palette));
&mut self.package_state_picklist,
&PackageState::ALL[..],
self.selected_package_state,
Message::PackageStateSelected,
)
.style(style::PickList(settings.theme.palette));
let removal_picklist = PickList::new(
&mut self.removal_picklist,
&Removal::ALL[..],
self.selected_removal,
Message::RemovalSelected,
)
.style(style::PickList(settings.theme.palette));
&mut self.removal_picklist,
&Removal::ALL[..],
self.selected_removal,
Message::RemovalSelected,
)
.style(style::PickList(settings.theme.palette));
let control_panel = Row::new()
.width(Length::Fill)
@ -330,9 +376,12 @@ impl List {
let packages = self.phone_packages[self.selected_user.unwrap().index]
.iter_mut()
.enumerate()
.filter(|(i,_)| self.filtered_packages.contains(i))
.fold(Column::new().spacing(6), |col, (i,p)| {
col.push(p.view(settings, phone).map(move |msg| Message::List(i, msg)))
.filter(|(i, _)| self.filtered_packages.contains(i))
.fold(Column::new().spacing(6), |col, (i, p)| {
col.push(
p.view(settings, phone)
.map(move |msg| Message::List(i, msg)),
)
});
/*
@ -363,38 +412,56 @@ impl List {
.width(Length::Fill)
.style(style::Description(settings.theme.palette));
let restore_action = if settings.disable_mode {"Enable/Restore"} else {"Restore"};
let remove_action = if settings.disable_mode {"Disable"} else {"Uninstall"};
let restore_action = if settings.disable_mode {
"Enable/Restore"
} else {
"Restore"
};
let remove_action = if settings.disable_mode {
"Disable"
} else {
"Uninstall"
};
let apply_restore_selection = Button::new(
&mut self.apply_restore_selection,
Text::new(format!("{} selection ({})",
restore_action,
self.selection.uninstalled + self.selection.disabled)
)
)
.on_press(Message::ApplyActionOnSelection(Action::Restore))
.padding(5)
.style(style::PrimaryButton(settings.theme.palette));
&mut self.apply_restore_selection,
Text::new(format!(
"{} selection ({})",
restore_action,
self.selection.uninstalled + self.selection.disabled
)),
)
.on_press(Message::ApplyActionOnSelection(Action::Restore))
.padding(5)
.style(style::PrimaryButton(settings.theme.palette));
let apply_remove_selection = Button::new(
&mut self.apply_remove_selection,
Text::new(format!("{} selection ({})", remove_action, self.selection.enabled)))
.on_press(Message::ApplyActionOnSelection(Action::Remove))
.padding(5)
.style(style::PrimaryButton(settings.theme.palette));
&mut self.apply_remove_selection,
Text::new(format!(
"{} selection ({})",
remove_action, self.selection.enabled
)),
)
.on_press(Message::ApplyActionOnSelection(Action::Remove))
.padding(5)
.style(style::PrimaryButton(settings.theme.palette));
let select_all_btn = Button::new(&mut self.select_all_btn_state, Text::new("Select all"))
.padding(5)
.on_press(Message::SelectAllPressed)
.style(style::PrimaryButton(settings.theme.palette));
let select_all_btn =
Button::new(&mut self.select_all_btn_state, Text::new("Select all"))
.padding(5)
.on_press(Message::SelectAllPressed)
.style(style::PrimaryButton(settings.theme.palette));
let export_selection_btn = Button::new(
&mut self.export_selection_btn_state,
Text::new(format!("Export current selection ({})", self.selection.selected_packages.len())))
.padding(5)
.on_press(Message::ExportSelectionPressed)
.style(style::PrimaryButton(settings.theme.palette));
&mut self.export_selection_btn_state,
Text::new(format!(
"Export current selection ({})",
self.selection.selected_packages.len()
)),
)
.padding(5)
.on_press(Message::ExportSelectionPressed)
.style(style::PrimaryButton(settings.theme.palette));
let action_row = Row::new()
.width(Length::Fill)
@ -427,7 +494,6 @@ impl List {
}
fn filter_package_lists(&mut self) {
let list_filter: UadList = self.selected_list.unwrap();
let package_filter: PackageState = self.selected_package_state.unwrap();
let removal_filter: Removal = self.selected_removal.unwrap();
@ -435,19 +501,17 @@ impl List {
self.filtered_packages = self.phone_packages[self.selected_user.unwrap().index]
.iter()
.enumerate()
.filter(
|(_,p)|
(list_filter == UadList::All || p.uad_list == list_filter) &&
(package_filter == PackageState::All || p.state == package_filter) &&
(removal_filter == Removal::All || p.removal == removal_filter) &&
(self.input_value.is_empty() || p.name.contains(&self.input_value))
)
.map(|(i,_)| i)
.filter(|(_, p)| {
(list_filter == UadList::All || p.uad_list == list_filter)
&& (package_filter == PackageState::All || p.state == package_filter)
&& (removal_filter == Removal::All || p.removal == removal_filter)
&& (self.input_value.is_empty() || p.name.contains(&self.input_value))
})
.map(|(i, _)| i)
.collect();
}
}
fn loading_data<'a>(settings: &Settings) -> Element<'a, Message> {
Container::new(
Text::new("Pulling packages from the phone. Please wait...")
@ -461,4 +525,4 @@ fn loading_data<'a>(settings: &Settings) -> Element<'a, Message> {
.center_x()
.style(style::Content(settings.theme.palette))
.into()
}
}

View file

@ -1,3 +1,3 @@
pub mod about;
pub mod list;
pub mod settings;
pub mod about;

View file

@ -1,9 +1,7 @@
use crate::gui::style;
use crate::core::theme::{Theme};
use crate::core::sync::get_android_sdk;
use iced::{Checkbox, Column, Container, Element, Length, Text, Space,
pick_list, PickList,
};
use crate::core::theme::Theme;
use crate::gui::style;
use iced::{pick_list, Checkbox, Column, Container, Element, Length, PickList, Space, Text};
#[derive(Debug, Clone)]
pub struct Settings {
@ -21,7 +19,7 @@ impl Default for Settings {
disable_mode: get_android_sdk() < 26,
multi_user_mode: get_android_sdk() > 21,
theme: Theme::lupin(),
theme_picklist: pick_list::State::default()
theme_picklist: pick_list::State::default(),
}
}
}
@ -38,17 +36,26 @@ impl Settings {
pub fn update(&mut self, msg: Message) {
match msg {
Message::ExpertMode(toggled) => {
info!("Expert mode {}", if toggled {"enabled"} else {"disabled"});
info!(
"Expert mode {}",
if toggled { "enabled" } else { "disabled" }
);
self.expert_mode = toggled;
},
}
Message::DisableMode(toggled) => {
info!("Disable mode {}", if toggled {"enabled"} else {"disabled"});
info!(
"Disable mode {}",
if toggled { "enabled" } else { "disabled" }
);
self.disable_mode = toggled;
},
}
Message::MultiUserMode(toggled) => {
info!("Multi-user mode {}", if toggled {"enabled"} else {"disabled"});
info!(
"Multi-user mode {}",
if toggled { "enabled" } else { "disabled" }
);
self.multi_user_mode = toggled;
},
}
Message::ApplyTheme(theme) => {
self.theme = theme;
}
@ -63,19 +70,20 @@ impl Settings {
Theme::all(),
Some(self.theme.clone()),
Message::ApplyTheme,
)
.style(style::PickList(self.theme.palette));
)
.style(style::PickList(self.theme.palette));
let uad_category_text = Text::new("UAD").size(25);
let expert_mode_descr = Text::new("Most of unsafe packages are known to bootloop the device if removed.")
.size(15)
.color(self.theme.palette.normal.surface);
let expert_mode_descr =
Text::new("Most of unsafe packages are known to bootloop the device if removed.")
.size(15)
.color(self.theme.palette.normal.surface);
let expert_mode_checkbox = Checkbox::new(
self.expert_mode,
"Allow to uninstall packages marked as \"unsafe\" (I KNOW WHAT I AM DOING)",
Message::ExpertMode
self.expert_mode,
"Allow to uninstall packages marked as \"unsafe\" (I KNOW WHAT I AM DOING)",
Message::ExpertMode,
);
let disable_mode_descr = Text::new("Default mode on older phone (< Android 8.0) where uninstalled packages can't be restored.")
@ -83,19 +91,20 @@ impl Settings {
.color(self.theme.palette.normal.surface);
let disable_mode_checkbox = Checkbox::new(
self.disable_mode,
self.disable_mode,
"Clear and disable packages instead of uninstalling them",
Message::DisableMode
Message::DisableMode,
);
let multi_user_mode_descr = Text::new("Disabling this setting will typically prevent affecting your work profile")
.size(15)
.color(self.theme.palette.normal.surface);
let multi_user_mode_descr =
Text::new("Disabling this setting will typically prevent affecting your work profile")
.size(15)
.color(self.theme.palette.normal.surface);
let multi_user_mode_checkbox = Checkbox::new(
self.multi_user_mode,
self.multi_user_mode,
"Affect all the users of the phone (not only the selected user)",
Message::MultiUserMode
Message::MultiUserMode,
);
let content = Column::new()

View file

@ -1 +1 @@
pub mod package_row;
pub mod package_row;

View file

@ -1,12 +1,11 @@
use crate::core::uad_lists::{UadList, PackageState, Removal};
use crate::core::sync::Phone;
use crate::core::uad_lists::{PackageState, Removal, UadList};
use crate::gui::style;
use crate::gui::views::settings::Settings;
use iced::{
Alignment, alignment, Command, Element, Space, Length, Row, Text, Button,
button, Checkbox,
alignment, button, Alignment, Button, Checkbox, Command, Element, Length, Row, Space, Text,
};
#[derive(Clone, Debug)]
@ -36,7 +35,6 @@ impl PackageRow {
uad_list: UadList,
removal: Removal,
selected: bool,
) -> Self {
Self {
name: name.to_string(),
@ -62,10 +60,13 @@ impl PackageRow {
let action_btn;
let selection_checkbox;
match self.state {
PackageState::Enabled => {
action_text = if settings.disable_mode { "Disable" } else { "Uninstall" };
action_text = if settings.disable_mode {
"Disable"
} else {
"Uninstall"
};
button_style = style::PackageButton::Uninstall(settings.theme.palette);
}
PackageState::Disabled => {
@ -81,13 +82,11 @@ impl PackageRow {
button_style = style::PackageButton::Restore(settings.theme.palette);
warn!("Incredible! Something impossible happenned!");
}
}
if (self.state != PackageState::Uninstalled && settings.expert_mode) ||
(self.removal != Removal::Unsafe && phone.android_sdk >= 26)
if (self.state != PackageState::Uninstalled && settings.expert_mode)
|| (self.removal != Removal::Unsafe && phone.android_sdk >= 26)
{
selection_checkbox = Checkbox::new(self.selected, "", Message::ToggleSelection)
.style(style::SelectionCheckBox::Enabled(settings.theme.palette));
@ -96,7 +95,6 @@ impl PackageRow {
Text::new(action_text).horizontal_alignment(alignment::Horizontal::Center),
)
.on_press(Message::ActionPressed);
} else {
selection_checkbox = Checkbox::new(self.selected, "", Message::ToggleSelection)
.style(style::SelectionCheckBox::Disabled(settings.theme.palette));
@ -108,24 +106,22 @@ impl PackageRow {
}
Row::new()
.push(Button::new(
&mut self.package_btn_state,
Row::new()
.align_items(Alignment::Center)
.push(selection_checkbox)
.push(Text::new(&self.name).width(Length::FillPortion(8)))
.push(action_btn.width(Length::FillPortion(1))
.style(button_style)
)
.push(
Button::new(
&mut self.package_btn_state,
Row::new()
.align_items(Alignment::Center)
.push(selection_checkbox)
.push(Text::new(&self.name).width(Length::FillPortion(8)))
.push(action_btn.width(Length::FillPortion(1)).style(button_style)),
)
.padding(8)
.style(style::PackageRow(settings.theme.palette))
.width(Length::Fill)
.on_press(Message::PackagePressed)
.on_press(Message::PackagePressed),
)
.push(Space::with_width(Length::Units(15)))
.align_items(Alignment::Center)
.into()
}
}
}

View file

@ -1,10 +1,14 @@
#![windows_subsystem = "windows"]
#![feature(drain_filter)]
#[macro_use] extern crate log;
#[macro_use]
extern crate log;
use std::{fs::OpenOptions, fmt::Arguments};
use fern::{FormatCallback, colors::{Color, ColoredLevelConfig}};
use fern::{
colors::{Color, ColoredLevelConfig},
FormatCallback,
};
use log::Record;
use std::{fmt::Arguments, fs::OpenOptions};
mod core;
mod gui;
@ -15,7 +19,6 @@ fn main() {
}
pub fn setup_logger() -> Result<(), fern::InitError> {
let colors = ColoredLevelConfig::new().info(Color::Green);
let make_formatter = |use_colors: bool| {
@ -29,10 +32,7 @@ pub fn setup_logger() -> Result<(), fern::InitError> {
format!("{:5}", record.level().to_string())
},
record.file().unwrap_or("?"),
record
.line()
.map(|l| l.to_string())
.unwrap_or_default(),
record.line().map(|l| l.to_string()).unwrap_or_default(),
message
))
}
@ -64,4 +64,4 @@ pub fn setup_logger() -> Result<(), fern::InitError> {
.apply()?;
Ok(())
}
}