Feature/Export selection (#8) + selection QoL feature (#31)

This commit is contained in:
w1nst0n 2021-09-25 11:22:18 +02:00
commit 2476a59423
No known key found for this signature in database
GPG key ID: 34D27465928A0A1D
5 changed files with 170 additions and 35 deletions

View file

@ -14,14 +14,16 @@ and `Removed`.
- UAD now comes with a logger. Debug information will be written to a `uad.log` file (Warning level log in *stdout*) (#2)
- Support for older phone (< Android 8.0) (#15):
- Disable mode in settings: clear and disable packages instead of uninstalling them (default for old phones because you can't restore uninstalled packages)
- Export your selection in the `uad_exported_selection.txt` file. Packages from this file (if found in the current directory) will be automatically selected upon the start of UAD (or after a refresh) (#8)
### Changed
- UAD will no longer crash at start if it doesn't find ADB but will display a useful message (#25)
- UAD will no longer crash at start if it doesn't find ADB but will display a useful error message (#25)
- Better handling of ADB errors (#3)
- Updated dependencies (compatibility with [Iced](https://github.com/iced-rs/iced) main branch latest commit)
- Cleanup and refactoring of the code
- Performance improvement
- Refresh settings panel
- Various UI/UX improvement
- The `Debloat/Restore selection` button has been split in 2 buttons: `removing` and `restoring`
### Fixed
- Spelling mistake

View file

@ -1,2 +1,3 @@
pub mod sync;
pub mod uad_lists;
pub mod uad_lists;
pub mod utils;

58
src/core/utils.rs Normal file
View file

@ -0,0 +1,58 @@
use crate::core::uad_lists::PackageState;
use crate::gui::widgets::package_row::PackageRow;
use crate::gui::views::list::Selection;
use std::fs;
use std::io::{self, prelude::*, BufReader};
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 };
},
PackageState::Disabled => {
if add { selection.disabled += 1 } else { selection.disabled -= 1 };
},
PackageState::Uninstalled => {
if add { selection.uninstalled += 1 } else { selection.uninstalled -= 1 };
},
PackageState::All => {}
};
}
pub async fn export_selection(packages: Vec<PackageRow>) -> Result<bool, String> {
let selected = packages
.iter()
.filter(|p| p.selected)
.map(|p| p.name.clone())
.collect::<Vec<String>>()
.join("\n");
match fs::write("uad_selection.txt", selected) {
Ok(_) => Ok(true),
Err(err) => Err(err.to_string())
}
}
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
.lines()
.map(|l| l.expect("Could not parse line"))
.collect();
*selection = Selection::default(); // should already be empty normally
for (i,p) in packages.iter_mut().enumerate() {
if imported_selection.contains(&p.name) {
p.selected = true;
selection.selected_packages.push(i);
update_selection_count(selection, p.state, true);
} else {
p.selected = false;
}
}
Ok(())
}

View file

@ -133,7 +133,9 @@ impl Application for UadGui {
fn view(&mut self) -> Element<Message> {
let add_svg_path = format!("{}/ressources/assets/refresh.svg", env!("CARGO_MANIFEST_DIR"));
let refresh_list_icon = Svg::from_path(add_svg_path);
let refresh_list_icon = Svg::from_path(add_svg_path)
.width(Length::Units(17))
.height(Length::Units(17));
let apps_btn = Button::new(&mut self.apps_btn, Text::new("Apps"))
.on_press(Message::AppsPress)

View file

@ -1,10 +1,10 @@
use crate::gui::style;
use crate::core::uad_lists::{ UadList, PackageState, Package, Removal, Opposite};
use crate::core::utils::{import_selection, export_selection, update_selection_count};
use std::{collections::HashMap};
use crate::gui::views::settings::Settings;
use crate::gui::widgets::package_row::{ PackageRow, Message as RowMessage };
use iced::{
scrollable, Alignment, alignment, Column, Container, Element, Space,
Length, Row, Scrollable, Text, text_input, TextInput, Command,//Svg,
@ -16,16 +16,42 @@ use crate::core::sync::{
Phone, action_handler,
};
#[derive(Debug, Clone)]
pub struct Selection {
pub uninstalled: u16,
pub enabled: u16,
pub disabled: u16,
pub selected_packages: Vec<usize>, // phone_packages indexes (= what you've selected)
}
#[derive(Debug, Clone)]
pub enum Action {
Remove,
Restore,
}
impl Default for Selection {
fn default() -> Self {
Self {
uninstalled: 0,
enabled: 0,
disabled: 0,
selected_packages: Vec::new(),
}
}
}
#[derive(Default, Debug, Clone)]
pub struct List {
ready: bool,
phone_packages: Vec<PackageRow>, // packages of the phone
filtered_packages: Vec<usize>, // phone_packages indexes (= what you see on screen)
selected_packages: Vec<usize>, // phone_packages indexes (= what you've selected)
pub selection: Selection,
search_input: text_input::State,
select_all_btn_state: button::State,
apply_selection_btn_state: button::State,
export_selection_btn_state: button::State,
apply_remove_selection: button::State,
apply_restore_selection: button::State,
package_scrollable_state: scrollable::State,
package_state_picklist: pick_list::State<PackageState>,
list_picklist: pick_list::State<UadList>,
@ -45,9 +71,11 @@ pub enum Message {
ListSelected(UadList),
PackageStateSelected(PackageState),
RemovalSelected(Removal),
ApplyActionOnSelection,
ApplyActionOnSelection(Action),
SelectAllPressed,
ExportSelectionPressed,
List(usize, RowMessage),
ExportedSelection(Result<bool, String>),
}
@ -98,6 +126,11 @@ impl List {
self.phone_packages.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
self.filtered_packages = (0..self.phone_packages.len()).collect();
Self::filter_package_lists(self);
match import_selection(&mut self.phone_packages, &mut self.selection) {
Ok(_) => info!("Custom selection has been successfully imported"),
Err(err) => warn!("No custom selection imported: {}", err),
};
self.ready = true;
Command::none()
}
@ -129,9 +162,11 @@ impl List {
RowMessage::ToggleSelection(toggle) => {
self.phone_packages[i].selected = toggle;
if self.phone_packages[i].selected {
self.selected_packages.push(i);
self.selection.selected_packages.push(i);
update_selection_count(&mut self.selection, self.phone_packages[i].state, true);
} else {
self.selected_packages.drain_filter(|s_i| *s_i == i);
self.selection.selected_packages.drain_filter(|s_i| *s_i == i);
update_selection_count(&mut self.selection, self.phone_packages[i].state, false);
}
},
RowMessage::ActionPressed => {
@ -146,9 +181,11 @@ impl List {
.iter()
.position(|p| p.name == self.phone_packages[i].name)
.unwrap();
if self.phone_packages[i].selected {
update_selection_count(&mut self.selection, self.phone_packages[i].state, false);
}
self.phone_packages[i].state = self.phone_packages[i].state.opposite(settings.disable_mode);
self.selected_packages.drain_filter(|s_i| *s_i == i);
self.selection.selected_packages.drain_filter(|s_i| *s_i == i);
self.phone_packages[i].selected = false;
Self::filter_package_lists(self);
}
@ -159,8 +196,19 @@ impl List {
}
Command::none()
},
Message::ApplyActionOnSelection => {
for i in self.selected_packages.clone() {
Message::ApplyActionOnSelection(action) => {
let mut selected_packages = self.selection.selected_packages.clone();
match action {
Action::Remove => {
selected_packages.drain_filter(|i| self.phone_packages[*i].state != PackageState::Enabled);
}
Action::Restore => {
selected_packages.drain_filter(|i| self.phone_packages[*i].state == PackageState::Enabled);
}
}
for i in selected_packages {
let success = action_handler(
&self.phone_packages[i],
phone,
@ -168,9 +216,10 @@ impl List {
).unwrap_or_else(|err| err);
if success {
update_selection_count(&mut self.selection, self.phone_packages[i].state, false);
self.phone_packages[i].state = self.phone_packages[i].state.opposite(settings.disable_mode);
self.phone_packages[i].selected = false;
self.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);
@ -179,12 +228,23 @@ impl List {
Message::SelectAllPressed => {
for i in self.filtered_packages.clone() {
self.phone_packages[i].selected = true;
if !self.selected_packages.contains(&i) {
self.selected_packages.push(i);
if !self.selection.selected_packages.contains(&i) {
self.selection.selected_packages.push(i);
update_selection_count(&mut self.selection, self.phone_packages[i].state, true);
}
}
Command::none()
},
Message::ExportSelectionPressed => {
Command::perform(export_selection(self.phone_packages.clone()), Message::ExportedSelection)
}
Message::ExportedSelection(export) => {
match export {
Ok(_) => info!("Selection exported"),
Err(err) => error!("Selection export: {}", err),
};
Command::none()
}
}
}
@ -269,43 +329,55 @@ impl List {
.width(Length::Fill);
let action = if settings.disable_mode {"Enable/Disable"} else {"Uninstall/Restore"};
let apply_selection_btn = Button::new(
&mut self.apply_selection_btn_state,
Row::new()
.align_items(Alignment::Center)
.push(Text::new(format!("{} selection ({})", action, self.selected_packages.len())))
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)
.on_press(Message::ApplyActionOnSelection(Action::Restore))
.padding(5)
.height(Length::Units(40))
.style(style::PrimaryButton::Enabled);
let select_all_btn = Button::new(
&mut self.select_all_btn_state,
Row::new()
.align_items(Alignment::Center)
.push(Text::new("Select all"))
)
.on_press(Message::SelectAllPressed)
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)
.height(Length::Units(40))
.style(style::PrimaryButton::Enabled);
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::Enabled);
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::Enabled);
let action_row = Row::new()
.width(Length::Fill)
.spacing(10)
.align_items(Alignment::Center)
.height(Length::FillPortion(1))
.push(select_all_btn)
.push(Space::new(Length::Fill, Length::Shrink))
.push(apply_selection_btn);
.push(export_selection_btn)
.push(apply_restore_selection)
.push(apply_remove_selection);
let content = Column::new()
.width(Length::Fill)
.spacing(10)
.align_items(Alignment::Center)
.push(control_panel)
.push(Space::new(Length::Fill, Length::Units(2)))
.push(Space::new(Length::Fill, Length::Shrink))
.push(packages_scrollable)
.push(description_panel)
.push(action_row);