mirror of
https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation.git
synced 2026-08-27 15:51:34 +02:00
chore(lint): solve clippy warnings
This commit is contained in:
parent
4ab886490e
commit
7580105910
11 changed files with 801 additions and 596 deletions
|
|
@ -53,6 +53,7 @@ pub fn to_trimmed_utf8(v: Vec<u8>) -> String {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
#[cfg(debug_assertions)]
|
||||
fn is_version_triple(s: &str) -> bool {
|
||||
let mut components = s.split('.');
|
||||
for _ in 0..3 {
|
||||
|
|
@ -439,7 +440,7 @@ impl UserInfo {
|
|||
/// Check if the user was logged-in
|
||||
/// at the time `pm list users` was invoked
|
||||
#[must_use]
|
||||
#[allow(dead_code)]
|
||||
#[allow(dead_code, reason = "Currently unused by UI; kept for future features")]
|
||||
pub const fn was_running(&self) -> bool {
|
||||
self.running
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::core::theme::Theme;
|
||||
use crate::gui::style;
|
||||
use iced::Element;
|
||||
use iced::widget::button;
|
||||
|
|
@ -5,8 +6,8 @@ use iced::widget::button::Button;
|
|||
|
||||
/// Wrapper function for `iced::widget::button` with padding and style applied
|
||||
pub fn button_primary<'a, Message>(
|
||||
content: impl Into<Element<'a, Message, crate::core::theme::Theme>>,
|
||||
) -> Button<'a, Message, crate::core::theme::Theme> {
|
||||
content: impl Into<Element<'a, Message, Theme>>,
|
||||
) -> Button<'a, Message, Theme> {
|
||||
button(content)
|
||||
.padding([5, 10])
|
||||
.style(style::Button::Primary)
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ pub enum AdbError {
|
|||
|
||||
/// Run an arbitrary shell action via the typed ADB wrapper.
|
||||
/// This replaces the deprecated `adb_shell_command`.
|
||||
///
|
||||
/// If `serial` is empty, it lets ADB choose the default device.
|
||||
pub async fn run_adb_action<S: AsRef<str>>(
|
||||
device_serial: S,
|
||||
action: String,
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ pub struct BaseColors {
|
|||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct NormalColors {
|
||||
pub primary: Color,
|
||||
#[allow(dead_code)]
|
||||
#[allow(dead_code, reason = "Reserved for future palette updates")]
|
||||
pub secondary: Color,
|
||||
pub surface: Color,
|
||||
pub error: Color,
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ pub fn string_to_theme(theme: &str) -> Theme {
|
|||
pub fn setup_uad_dir(dir: &Path) -> PathBuf {
|
||||
let dir = dir.join("uad");
|
||||
if let Err(e) = fs::create_dir_all(&dir) {
|
||||
error!("Can't create directory: {dir:?}");
|
||||
error!("Can't create directory: {}", dir.display());
|
||||
panic!("{e}");
|
||||
}
|
||||
dir
|
||||
|
|
@ -155,8 +155,10 @@ pub fn open_url(dir: PathBuf) {
|
|||
Ok(o) => {
|
||||
if !o.status.success() {
|
||||
// does Windows print UTF-16?
|
||||
let stderr = String::from_utf8(o.stderr).unwrap().trim_end().to_string();
|
||||
error!("Can't open the following URL: {stderr}");
|
||||
match String::from_utf8(o.stderr) {
|
||||
Ok(s) => error!("Can't open the following URL: {}", s.trim_end()),
|
||||
Err(_e) => error!("Can't open the following URL: <non-UTF8 output>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => error!("Failed to run command to open the file explorer: {e}"),
|
||||
|
|
@ -213,10 +215,11 @@ impl fmt::Display for DisplayablePath {
|
|||
error!("[PATH STEM]: No file stem found");
|
||||
"[File steam not found]".to_string()
|
||||
},
|
||||
|p| match p.to_os_string().into_string() {
|
||||
Ok(stem) => stem,
|
||||
Err(e) => {
|
||||
error!("[PATH ENCODING]: {e:?}");
|
||||
|p| {
|
||||
if let Ok(stem) = p.to_os_string().into_string() {
|
||||
stem
|
||||
} else {
|
||||
error!("[PATH ENCODING]: {}", self.path.display());
|
||||
"[PATH ENCODING ERROR]".to_string()
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -92,7 +92,10 @@ impl UadGui {
|
|||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[allow(
|
||||
clippy::too_many_lines,
|
||||
reason = "Root GUI update orchestrates many flows"
|
||||
)]
|
||||
fn update(&mut self, msg: Message) -> Task<Message> {
|
||||
match msg {
|
||||
Message::LoadDevices(devices_list) => {
|
||||
|
|
@ -332,7 +335,7 @@ impl UadGui {
|
|||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message, Theme> {
|
||||
fn view(&self) -> Element<'_, Message, Theme> {
|
||||
let navigation_container = nav_menu(
|
||||
&self.devices_list,
|
||||
self.selected_device.clone(),
|
||||
|
|
|
|||
103
src/gui/style.rs
103
src/gui/style.rs
|
|
@ -1,3 +1,9 @@
|
|||
#![allow(
|
||||
non_snake_case,
|
||||
clippy::trivially_copy_pass_by_ref,
|
||||
clippy::wildcard_imports,
|
||||
reason = "Iced style modules use PascalCase and &Theme; wildcard for local convenience"
|
||||
)]
|
||||
use crate::core::theme::Theme;
|
||||
use iced::widget::{
|
||||
button, checkbox, container, overlay, pick_list, radio, scrollable, text, text_editor,
|
||||
|
|
@ -187,7 +193,7 @@ impl pick_list::Catalog for Theme {
|
|||
text_color: p.bright.surface,
|
||||
placeholder_color: p.bright.surface,
|
||||
handle_color: p.bright.surface,
|
||||
background: Background::Color(p.base.background.into()),
|
||||
background: Background::Color(p.base.background),
|
||||
border: Border {
|
||||
color: border_color,
|
||||
width: 1.0,
|
||||
|
|
@ -268,9 +274,9 @@ impl text_editor::Catalog for Theme {
|
|||
type Class<'a> = text_editor::StyleFn<'a, Theme>;
|
||||
|
||||
fn default<'a>() -> <Self as text_editor::Catalog>::Class<'a> {
|
||||
Box::new(|t: &Theme, s: text_editor::Status| {
|
||||
Box::new(|t: &Theme, _s: text_editor::Status| {
|
||||
let p = t.palette();
|
||||
let active = text_editor::Style {
|
||||
text_editor::Style {
|
||||
background: Background::Color(p.base.foreground),
|
||||
border: Border {
|
||||
color: Color::TRANSPARENT,
|
||||
|
|
@ -284,12 +290,6 @@ impl text_editor::Catalog for Theme {
|
|||
a: 0.3,
|
||||
..p.normal.primary
|
||||
},
|
||||
};
|
||||
match s {
|
||||
text_editor::Status::Active
|
||||
| text_editor::Status::Focused
|
||||
| text_editor::Status::Hovered => active,
|
||||
text_editor::Status::Disabled => active,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -327,11 +327,10 @@ impl iced::widget::rule::Catalog for Theme {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub mod Container {
|
||||
use super::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(dead_code, reason = "Used by other themes or future styles")]
|
||||
pub fn Invisible(_: &Theme) -> container::Style {
|
||||
container::Style::default()
|
||||
}
|
||||
|
|
@ -364,7 +363,7 @@ pub mod Container {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(dead_code, reason = "Currently unused in some views")]
|
||||
pub fn Tooltip(theme: &Theme) -> container::Style {
|
||||
let p = theme.palette();
|
||||
container::Style {
|
||||
|
|
@ -394,11 +393,13 @@ pub mod Container {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub mod Button {
|
||||
use super::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(
|
||||
dead_code,
|
||||
reason = "Helper used by multiple styles; may be inlined by compiler"
|
||||
)]
|
||||
fn base(border_color: Color) -> button::Style {
|
||||
button::Style {
|
||||
background: None,
|
||||
|
|
@ -421,7 +422,10 @@ pub mod Button {
|
|||
style
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(
|
||||
dead_code,
|
||||
reason = "Alias kept for semantic clarity in some call-sites"
|
||||
)]
|
||||
pub fn SelfUpdate(theme: &Theme, status: button::Status) -> button::Style {
|
||||
Primary(theme, status)
|
||||
}
|
||||
|
|
@ -451,7 +455,10 @@ pub mod Button {
|
|||
style
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(
|
||||
dead_code,
|
||||
reason = "Style exposed for disabled state buttons in some contexts"
|
||||
)]
|
||||
pub fn Unavailable(theme: &Theme, status: button::Status) -> button::Style {
|
||||
UninstallPackage(theme, status)
|
||||
}
|
||||
|
|
@ -459,16 +466,6 @@ pub mod Button {
|
|||
pub fn NormalPackage(theme: &Theme, status: button::Status) -> button::Style {
|
||||
let p = theme.palette();
|
||||
match status {
|
||||
button::Status::Active | button::Status::Pressed => button::Style {
|
||||
background: Some(Background::Color(p.base.foreground)),
|
||||
text_color: p.bright.surface,
|
||||
border: Border {
|
||||
color: p.base.background,
|
||||
width: 0.0,
|
||||
radius: 5.0.into(),
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
},
|
||||
button::Status::Hovered => button::Style {
|
||||
background: Some(Background::Color(Color {
|
||||
a: 0.25,
|
||||
|
|
@ -482,7 +479,7 @@ pub mod Button {
|
|||
},
|
||||
shadow: Shadow::default(),
|
||||
},
|
||||
button::Status::Disabled => button::Style {
|
||||
_ => button::Style {
|
||||
background: Some(Background::Color(p.base.foreground)),
|
||||
text_color: p.bright.surface,
|
||||
border: Border {
|
||||
|
|
@ -512,7 +509,7 @@ pub mod Button {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(dead_code, reason = "Used in views where buttons must be invisible")]
|
||||
pub fn Hidden(_: &Theme, _: button::Status) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
|
|
@ -566,11 +563,10 @@ pub mod Button {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub mod Scrollable {
|
||||
use super::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(dead_code, reason = "Kept for future custom rails variations")]
|
||||
fn rails(scroller_color: Color) -> (scrollable::Rail, scrollable::Rail) {
|
||||
let rail = scrollable::Rail {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
|
|
@ -614,71 +610,69 @@ pub mod Scrollable {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub mod CheckBox {
|
||||
use super::*;
|
||||
|
||||
pub fn PackageEnabled(theme: &Theme, _status: checkbox::Status) -> checkbox::Style {
|
||||
let pal = theme.palette();
|
||||
let p = theme.palette();
|
||||
checkbox::Style {
|
||||
background: Background::Color(pal.base.background),
|
||||
icon_color: pal.bright.primary,
|
||||
background: Background::Color(p.base.background),
|
||||
icon_color: p.bright.primary,
|
||||
border: Border {
|
||||
color: pal.base.background,
|
||||
color: p.base.background,
|
||||
width: 1.0,
|
||||
radius: 5.0.into(),
|
||||
},
|
||||
text_color: Some(pal.bright.surface),
|
||||
text_color: Some(p.bright.surface),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn PackageDisabled(theme: &Theme, _status: checkbox::Status) -> checkbox::Style {
|
||||
let pal = theme.palette();
|
||||
let p = theme.palette();
|
||||
checkbox::Style {
|
||||
background: Background::Color(Color {
|
||||
a: 0.55,
|
||||
..pal.base.background
|
||||
..p.base.background
|
||||
}),
|
||||
icon_color: pal.bright.primary,
|
||||
icon_color: p.bright.primary,
|
||||
border: Border {
|
||||
color: pal.normal.primary,
|
||||
color: p.normal.primary,
|
||||
width: 1.0,
|
||||
radius: 5.0.into(),
|
||||
},
|
||||
text_color: Some(pal.normal.primary),
|
||||
text_color: Some(p.normal.primary),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn SettingsEnabled(theme: &Theme, _status: checkbox::Status) -> checkbox::Style {
|
||||
let pal = theme.palette();
|
||||
let p = theme.palette();
|
||||
checkbox::Style {
|
||||
background: Background::Color(pal.base.background),
|
||||
icon_color: pal.bright.primary,
|
||||
background: Background::Color(p.base.background),
|
||||
icon_color: p.bright.primary,
|
||||
border: Border {
|
||||
color: pal.bright.primary,
|
||||
color: p.bright.primary,
|
||||
width: 1.0,
|
||||
radius: 5.0.into(),
|
||||
},
|
||||
text_color: Some(pal.bright.surface),
|
||||
text_color: Some(p.bright.surface),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn SettingsDisabled(theme: &Theme, _status: checkbox::Status) -> checkbox::Style {
|
||||
let pal = theme.palette();
|
||||
let p = theme.palette();
|
||||
checkbox::Style {
|
||||
background: Background::Color(pal.base.foreground),
|
||||
icon_color: pal.bright.primary,
|
||||
background: Background::Color(p.base.foreground),
|
||||
icon_color: p.bright.primary,
|
||||
border: Border {
|
||||
color: pal.normal.primary,
|
||||
color: p.normal.primary,
|
||||
width: 1.0,
|
||||
radius: 5.0.into(),
|
||||
},
|
||||
text_color: Some(pal.bright.surface),
|
||||
text_color: Some(p.bright.surface),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub mod Text {
|
||||
use super::*;
|
||||
|
||||
|
|
@ -708,7 +702,10 @@ pub mod Text {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(
|
||||
dead_code,
|
||||
reason = "Convenience factory used by dynamic text coloring"
|
||||
)]
|
||||
pub fn Color(c: Color) -> impl Fn(&Theme) -> text::Style {
|
||||
move |_t: &Theme| text::Style { color: Some(c) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,12 +23,20 @@ pub enum Message {
|
|||
}
|
||||
|
||||
impl About {
|
||||
#[allow(
|
||||
clippy::unused_self,
|
||||
reason = "Trait-like shape required by GUI architecture"
|
||||
)]
|
||||
pub fn update(&mut self, msg: Message) {
|
||||
if let Message::UrlPressed(url) = msg {
|
||||
open_url(url);
|
||||
}
|
||||
// other events are handled by UadGui update()
|
||||
}
|
||||
#[allow(
|
||||
clippy::unused_self,
|
||||
reason = "Trait-like shape required by GUI architecture"
|
||||
)]
|
||||
pub fn view(&self, update_state: &UpdateState) -> Element<'_, Message, Theme, Renderer> {
|
||||
let about_text = text(format!(
|
||||
"Universal Android Debloater Next Generation ({NAME}) is a free and open-source community project \naiming at simplifying the removal of pre-installed apps on any Android device."
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use crate::gui::views::settings::Settings;
|
|||
use crate::gui::widgets::modal::Modal;
|
||||
use crate::gui::widgets::package_row::{Message as RowMessage, PackageRow};
|
||||
use crate::gui::widgets::text;
|
||||
use iced::widget::scrollable::{Direction, Scrollbar};
|
||||
use iced::widget::{
|
||||
Column, Space, button, checkbox, column, container, horizontal_space, pick_list, radio, row,
|
||||
scrollable, text_editor, text_input, tooltip, vertical_rule,
|
||||
|
|
@ -113,7 +114,6 @@ impl From<Removal> for SummaryEntry {
|
|||
}
|
||||
|
||||
impl List {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
settings: &mut Settings,
|
||||
|
|
@ -121,272 +121,39 @@ impl List {
|
|||
list_update_state: &mut UadListState,
|
||||
message: Message,
|
||||
) -> Task<Message> {
|
||||
let i_user = self.selected_user.unwrap_or_default().index;
|
||||
match message {
|
||||
Message::ModalHide => {
|
||||
self.selection_modal = false;
|
||||
self.error_modal = None;
|
||||
self.export_modal = false;
|
||||
Task::none()
|
||||
}
|
||||
Message::ModalValidate => {
|
||||
let mut commands = vec![];
|
||||
self.selected_packages.sort_unstable();
|
||||
self.selected_packages.dedup();
|
||||
for selection in &self.selected_packages {
|
||||
commands.append(&mut build_action_pkg_commands(
|
||||
&self.phone_packages,
|
||||
selected_device,
|
||||
&settings.device,
|
||||
*selection,
|
||||
));
|
||||
}
|
||||
self.selection_modal = false;
|
||||
Task::batch(commands)
|
||||
}
|
||||
Message::RestoringDevice(output) => {
|
||||
if let Ok(p) = output {
|
||||
self.loading_state = LoadingState::RestoringDevice(
|
||||
self.phone_packages[i_user][p.index].name.clone(),
|
||||
);
|
||||
} else {
|
||||
self.loading_state = LoadingState::RestoringDevice("Error [TODO]".to_string());
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::LoadUadList(remote) => {
|
||||
info!("{:-^65}", "-");
|
||||
info!(
|
||||
"ANDROID_SDK: {} | DEVICE: {}",
|
||||
selected_device.android_sdk, selected_device.model
|
||||
);
|
||||
info!("{:-^65}", "-");
|
||||
self.loading_state = LoadingState::DownloadingList;
|
||||
Task::perform(
|
||||
Self::init_apps_view(remote, selected_device.clone()),
|
||||
Message::LoadPhonePackages,
|
||||
)
|
||||
}
|
||||
Message::LoadPhonePackages((uad_list, list_state)) => {
|
||||
self.loading_state = LoadingState::LoadingPackages;
|
||||
self.uad_lists.clone_from(&uad_list);
|
||||
*list_update_state = list_state;
|
||||
Task::perform(
|
||||
Self::load_packages(
|
||||
uad_list,
|
||||
selected_device.adb_id.clone(),
|
||||
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.selected_package_state = Some(PackageState::Enabled);
|
||||
self.selected_removal = Some(Removal::Recommended);
|
||||
self.selected_list = Some(UadList::All);
|
||||
self.selected_user = Some(User::default());
|
||||
Self::filter_package_lists(self);
|
||||
self.loading_state = LoadingState::Ready;
|
||||
Task::none()
|
||||
Message::ModalHide => self.on_modal_hide(),
|
||||
Message::ModalValidate => self.on_modal_validate(settings, selected_device),
|
||||
Message::RestoringDevice(output) => self.on_restoring_device(output),
|
||||
Message::LoadUadList(remote) => self.on_load_uad_list(remote, selected_device),
|
||||
Message::LoadPhonePackages(payload) => {
|
||||
self.on_load_phone_packages(payload, selected_device, list_update_state)
|
||||
}
|
||||
Message::ApplyFilters(packages) => self.on_apply_filters(packages),
|
||||
Message::ToggleAllSelected(selected) => {
|
||||
for i in self.filtered_packages.clone() {
|
||||
if self.phone_packages[i_user][i].selected != selected {
|
||||
#[expect(unused_must_use, reason = "side-effect")]
|
||||
self.update(
|
||||
settings,
|
||||
selected_device,
|
||||
list_update_state,
|
||||
Message::List(i, RowMessage::ToggleSelection(selected)),
|
||||
);
|
||||
}
|
||||
}
|
||||
self.all_selected = selected;
|
||||
Task::none()
|
||||
}
|
||||
Message::SearchInputChanged(letter) => {
|
||||
self.input_value = letter;
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
}
|
||||
Message::ListSelected(list) => {
|
||||
self.selected_list = Some(list);
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
}
|
||||
Message::PackageStateSelected(package_state) => {
|
||||
self.selected_package_state = Some(package_state);
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
}
|
||||
Message::RemovalSelected(removal) => {
|
||||
self.selected_removal = Some(removal);
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
}
|
||||
Message::List(i_package, row_message) => {
|
||||
#[expect(unused_must_use, reason = "side-effect")]
|
||||
{
|
||||
self.phone_packages[i_user][i_package]
|
||||
.update(&row_message)
|
||||
.map(move |row_message| Message::List(i_package, row_message));
|
||||
}
|
||||
|
||||
let package = &mut self.phone_packages[i_user][i_package];
|
||||
|
||||
match row_message {
|
||||
RowMessage::ToggleSelection(toggle) => {
|
||||
if package.removal == Removal::Unsafe && !settings.general.expert_mode {
|
||||
package.selected = false;
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
if settings.device.multi_user_mode {
|
||||
for u in selected_device.user_list.iter().filter(|&u| !u.protected) {
|
||||
if let Some(pkg) = self
|
||||
.phone_packages
|
||||
.get_mut(u.index)
|
||||
.and_then(|pkgs| pkgs.get_mut(i_package))
|
||||
{
|
||||
pkg.selected = toggle;
|
||||
if toggle
|
||||
&& !self.selected_packages.contains(&(u.index, i_package))
|
||||
{
|
||||
self.selected_packages.push((u.index, i_package));
|
||||
}
|
||||
}
|
||||
}
|
||||
if !toggle {
|
||||
self.selected_packages.retain(|&x| x.1 != i_package);
|
||||
}
|
||||
} else {
|
||||
package.selected = toggle;
|
||||
if toggle {
|
||||
if !self.selected_packages.contains(&(i_user, i_package)) {
|
||||
self.selected_packages.push((i_user, i_package));
|
||||
}
|
||||
} else {
|
||||
self.selected_packages
|
||||
.retain(|&x| x.1 != i_package || x.0 != i_user);
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
RowMessage::ActionPressed => {
|
||||
self.phone_packages[i_user][i_package].selected = true;
|
||||
Task::batch(build_action_pkg_commands(
|
||||
&self.phone_packages,
|
||||
selected_device,
|
||||
&settings.device,
|
||||
(i_user, i_package),
|
||||
))
|
||||
}
|
||||
RowMessage::PackagePressed => {
|
||||
self.description = package.clone().description;
|
||||
self.description_content =
|
||||
text_editor::Content::with_text(&package.description);
|
||||
package.current = true;
|
||||
if self.current_package_index != i_package {
|
||||
self.phone_packages[i_user][self.current_package_index].current = false;
|
||||
}
|
||||
self.current_package_index = i_package;
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::ApplyActionOnSelection => {
|
||||
self.selection_modal = true;
|
||||
Task::none()
|
||||
}
|
||||
Message::UserSelected(user) => {
|
||||
self.selected_user = Some(user);
|
||||
self.filtered_packages = (0..self.phone_packages[user.index].len()).collect();
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
}
|
||||
Message::ChangePackageState(res) => {
|
||||
match res {
|
||||
Ok(p) => {
|
||||
let package = &mut self.phone_packages[p.i_user][p.index];
|
||||
package.state = package.state.opposite(settings.device.disable_mode);
|
||||
package.selected = false;
|
||||
self.selected_packages
|
||||
.retain(|&x| x.1 != p.index && x.0 != p.i_user);
|
||||
Self::filter_package_lists(self);
|
||||
}
|
||||
Err(AdbError::Generic(err)) => {
|
||||
self.error_modal = Some(err);
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
self.on_toggle_all_selected(selected, settings, selected_device, list_update_state)
|
||||
}
|
||||
Message::SearchInputChanged(letter) => self.on_search_input_changed(letter),
|
||||
Message::ListSelected(list) => self.on_list_selected(list),
|
||||
Message::PackageStateSelected(state) => self.on_package_state_selected(state),
|
||||
Message::RemovalSelected(removal) => self.on_removal_selected(removal),
|
||||
Message::List(i, row_msg) => self.on_list_row(i, &row_msg, settings, selected_device),
|
||||
Message::ApplyActionOnSelection => self.on_apply_action_on_selection(),
|
||||
Message::UserSelected(user) => self.on_user_selected(user),
|
||||
Message::ChangePackageState(res) => self.on_change_package_state(res, settings),
|
||||
Message::ModalUserSelected(user) => {
|
||||
self.selected_user = Some(user);
|
||||
self.update(
|
||||
settings,
|
||||
selected_device,
|
||||
list_update_state,
|
||||
Message::UserSelected(user),
|
||||
)
|
||||
}
|
||||
Message::ClearSelectedPackages => {
|
||||
self.selected_packages = Vec::new();
|
||||
Task::none()
|
||||
}
|
||||
Message::ADBSatisfied(result) => {
|
||||
self.is_adb_satisfied = result;
|
||||
Task::none()
|
||||
}
|
||||
Message::UpdateFailed => {
|
||||
self.loading_state = LoadingState::FailedToUpdate;
|
||||
Task::none()
|
||||
}
|
||||
Message::GoToUrl(url) => {
|
||||
open_url(url);
|
||||
Task::none()
|
||||
}
|
||||
Message::ExportSelection => Task::perform(
|
||||
export_selection(self.phone_packages[i_user].clone()),
|
||||
Message::SelectionExported,
|
||||
),
|
||||
Message::SelectionExported(export) => {
|
||||
match export {
|
||||
Ok(_) => self.export_modal = true,
|
||||
Err(err) => error!("Failed to export current selection: {err:?}"),
|
||||
}
|
||||
Task::none()
|
||||
self.on_modal_user_selected(user, settings, selected_device, list_update_state)
|
||||
}
|
||||
Message::ClearSelectedPackages => self.on_clear_selected_packages(),
|
||||
Message::ADBSatisfied(result) => self.on_adb_satisfied(result),
|
||||
Message::UpdateFailed => self.on_update_failed(),
|
||||
Message::GoToUrl(url) => Self::on_go_to_url(url),
|
||||
Message::ExportSelection => self.on_export_selection(),
|
||||
Message::SelectionExported(res) => self.on_selection_exported(res),
|
||||
Message::Nothing => Task::none(),
|
||||
Message::DescriptionEdit(action) => {
|
||||
match action {
|
||||
text_editor::Action::Edit(_) => {
|
||||
// Do nothing - ignore all editing operations
|
||||
}
|
||||
text_editor::Action::Scroll { lines: _ } => {}
|
||||
// Allow all other actions (movement, selection, clicking, scrolling, etc.)
|
||||
_ => {
|
||||
self.description_content.perform(action);
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::CopyError(err) => {
|
||||
self.copy_confirmation = true;
|
||||
Task::batch(vec![
|
||||
iced::clipboard::write::<Message>(err),
|
||||
Task::perform(
|
||||
// intentional delay
|
||||
async { std::thread::sleep(std::time::Duration::from_secs(1)) },
|
||||
|()| Message::HideCopyConfirmation,
|
||||
),
|
||||
])
|
||||
}
|
||||
Message::HideCopyConfirmation => {
|
||||
self.copy_confirmation = false;
|
||||
Task::none()
|
||||
}
|
||||
Message::DescriptionEdit(action) => self.on_description_edit(action),
|
||||
Message::CopyError(err) => self.on_copy_error(err),
|
||||
Message::HideCopyConfirmation => self.on_hide_copy_confirmation(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -510,7 +277,10 @@ impl List {
|
|||
.into()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[allow(
|
||||
clippy::too_many_lines,
|
||||
reason = "Complex layout; further refactor later"
|
||||
)]
|
||||
fn ready_view(
|
||||
&self,
|
||||
settings: &Settings,
|
||||
|
|
@ -672,7 +442,10 @@ impl List {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[allow(
|
||||
clippy::too_many_lines,
|
||||
reason = "Modal construction is verbose by nature"
|
||||
)]
|
||||
fn apply_selection_modal(
|
||||
&self,
|
||||
device: &Phone,
|
||||
|
|
@ -712,7 +485,7 @@ impl List {
|
|||
.style(style::Container::Frame)
|
||||
.padding([10, 0])
|
||||
.center_y(Length::Shrink)
|
||||
.center_x(Length::Shrink);
|
||||
.center_x(Length::Fill);
|
||||
|
||||
let users_ctn = container(radio_btn_users)
|
||||
.padding(10)
|
||||
|
|
@ -777,7 +550,7 @@ impl List {
|
|||
s.0 == self.selected_user.expect(PACK_NO_USER_MSG).index
|
||||
})
|
||||
.fold(
|
||||
column![].spacing(6).width(Length::Fill),
|
||||
column![].spacing(6).width(Length::Shrink),
|
||||
|col, selection| {
|
||||
col.push(
|
||||
row![
|
||||
|
|
@ -792,14 +565,13 @@ impl List {
|
|||
.uad_list
|
||||
.to_string()
|
||||
)]
|
||||
.width(50),
|
||||
.width(55),
|
||||
row![text(
|
||||
self.phone_packages[selection.0][selection.1]
|
||||
.name
|
||||
.clone()
|
||||
),]
|
||||
.width(540),
|
||||
horizontal_space(),
|
||||
row![match self.phone_packages[selection.0]
|
||||
[selection.1]
|
||||
.state
|
||||
|
|
@ -821,7 +593,7 @@ impl List {
|
|||
},]
|
||||
.width(70),
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.width(Length::Shrink)
|
||||
.spacing(20),
|
||||
)
|
||||
},
|
||||
|
|
@ -833,8 +605,13 @@ impl List {
|
|||
},
|
||||
)
|
||||
.padding(10)
|
||||
.width(Length::Fill),
|
||||
.width(Length::Shrink)
|
||||
.style(style::Container::Invisible),
|
||||
)
|
||||
.direction(Direction::Both {
|
||||
vertical: Scrollbar::default(),
|
||||
horizontal: Scrollbar::default(),
|
||||
})
|
||||
.style(style::Scrollable::Description),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
|
|
@ -885,6 +662,7 @@ impl List {
|
|||
.style(style::Container::Background)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn filter_package_lists(&mut self) {
|
||||
let list_filter: UadList = self.selected_list.expect("UAD-list type must be selected");
|
||||
let package_filter: PackageState = self
|
||||
|
|
@ -911,6 +689,7 @@ impl List {
|
|||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
}
|
||||
|
||||
#[expect(clippy::unused_async, reason = "1 call-site")]
|
||||
async fn load_packages<S: AsRef<str>>(
|
||||
uad_list: PackageHashMap,
|
||||
|
|
@ -946,6 +725,326 @@ impl List {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Split handlers to keep update short ===
|
||||
fn on_modal_hide(&mut self) -> Task<Message> {
|
||||
self.selection_modal = false;
|
||||
self.error_modal = None;
|
||||
self.export_modal = false;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_modal_validate(
|
||||
&mut self,
|
||||
settings: &Settings,
|
||||
selected_device: &mut Phone,
|
||||
) -> Task<Message> {
|
||||
let mut commands = vec![];
|
||||
self.selected_packages.sort_unstable();
|
||||
self.selected_packages.dedup();
|
||||
for selection in &self.selected_packages {
|
||||
commands.append(&mut build_action_pkg_commands(
|
||||
&self.phone_packages,
|
||||
selected_device,
|
||||
&settings.device,
|
||||
*selection,
|
||||
));
|
||||
}
|
||||
self.selection_modal = false;
|
||||
Task::batch(commands)
|
||||
}
|
||||
|
||||
fn on_restoring_device(&mut self, output: Result<PackageInfo, AdbError>) -> Task<Message> {
|
||||
let i_user = self.selected_user.unwrap_or_default().index;
|
||||
if let Ok(p) = output {
|
||||
self.loading_state =
|
||||
LoadingState::RestoringDevice(self.phone_packages[i_user][p.index].name.clone());
|
||||
} else {
|
||||
self.loading_state = LoadingState::RestoringDevice("Error [TODO]".to_string());
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_load_uad_list(&mut self, remote: bool, selected_device: &Phone) -> Task<Message> {
|
||||
info!("{:-^65}", "-");
|
||||
info!(
|
||||
"ANDROID_SDK: {} | DEVICE: {}",
|
||||
selected_device.android_sdk, selected_device.model
|
||||
);
|
||||
info!("{:-^65}", "-");
|
||||
self.loading_state = LoadingState::DownloadingList;
|
||||
Task::perform(
|
||||
Self::init_apps_view(remote, selected_device.clone()),
|
||||
Message::LoadPhonePackages,
|
||||
)
|
||||
}
|
||||
|
||||
fn on_load_phone_packages(
|
||||
&mut self,
|
||||
payload: (PackageHashMap, UadListState),
|
||||
selected_device: &Phone,
|
||||
list_update_state: &mut UadListState,
|
||||
) -> Task<Message> {
|
||||
let (uad_list, list_state) = payload;
|
||||
self.loading_state = LoadingState::LoadingPackages;
|
||||
self.uad_lists.clone_from(&uad_list);
|
||||
*list_update_state = list_state;
|
||||
Task::perform(
|
||||
Self::load_packages(
|
||||
uad_list,
|
||||
selected_device.adb_id.clone(),
|
||||
selected_device.user_list.clone(),
|
||||
),
|
||||
Message::ApplyFilters,
|
||||
)
|
||||
}
|
||||
|
||||
fn on_apply_filters(&mut self, packages: Vec<Vec<PackageRow>>) -> Task<Message> {
|
||||
let i_user = self.selected_user.unwrap_or_default().index;
|
||||
self.phone_packages = packages;
|
||||
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::default());
|
||||
Self::filter_package_lists(self);
|
||||
self.loading_state = LoadingState::Ready;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_toggle_all_selected(
|
||||
&mut self,
|
||||
selected: bool,
|
||||
settings: &mut Settings,
|
||||
selected_device: &mut Phone,
|
||||
list_update_state: &mut UadListState,
|
||||
) -> Task<Message> {
|
||||
let i_user = self.selected_user.unwrap_or_default().index;
|
||||
for i in self.filtered_packages.clone() {
|
||||
if self.phone_packages[i_user][i].selected != selected {
|
||||
#[expect(unused_must_use, reason = "side-effect")]
|
||||
self.update(
|
||||
settings,
|
||||
selected_device,
|
||||
list_update_state,
|
||||
Message::List(i, RowMessage::ToggleSelection(selected)),
|
||||
);
|
||||
}
|
||||
}
|
||||
self.all_selected = selected;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_search_input_changed(&mut self, letter: String) -> Task<Message> {
|
||||
self.input_value = letter;
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_list_selected(&mut self, list: UadList) -> Task<Message> {
|
||||
self.selected_list = Some(list);
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_package_state_selected(&mut self, package_state: PackageState) -> Task<Message> {
|
||||
self.selected_package_state = Some(package_state);
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_removal_selected(&mut self, removal: Removal) -> Task<Message> {
|
||||
self.selected_removal = Some(removal);
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_list_row(
|
||||
&mut self,
|
||||
i_package: usize,
|
||||
row_message: &RowMessage,
|
||||
settings: &Settings,
|
||||
selected_device: &mut Phone,
|
||||
) -> Task<Message> {
|
||||
let i_user = self.selected_user.unwrap_or_default().index;
|
||||
#[expect(unused_must_use, reason = "side-effect")]
|
||||
{
|
||||
self.phone_packages[i_user][i_package]
|
||||
.update(row_message)
|
||||
.map(move |row_message| Message::List(i_package, row_message));
|
||||
}
|
||||
|
||||
let package = &mut self.phone_packages[i_user][i_package];
|
||||
|
||||
match *row_message {
|
||||
RowMessage::ToggleSelection(toggle) => {
|
||||
if package.removal == Removal::Unsafe && !settings.general.expert_mode {
|
||||
package.selected = false;
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
if settings.device.multi_user_mode {
|
||||
for u in selected_device.user_list.iter().filter(|&u| !u.protected) {
|
||||
if let Some(pkg) = self
|
||||
.phone_packages
|
||||
.get_mut(u.index)
|
||||
.and_then(|pkgs| pkgs.get_mut(i_package))
|
||||
{
|
||||
pkg.selected = toggle;
|
||||
if toggle && !self.selected_packages.contains(&(u.index, i_package)) {
|
||||
self.selected_packages.push((u.index, i_package));
|
||||
}
|
||||
}
|
||||
}
|
||||
if !toggle {
|
||||
self.selected_packages.retain(|&x| x.1 != i_package);
|
||||
}
|
||||
} else {
|
||||
package.selected = toggle;
|
||||
if toggle {
|
||||
if !self.selected_packages.contains(&(i_user, i_package)) {
|
||||
self.selected_packages.push((i_user, i_package));
|
||||
}
|
||||
} else {
|
||||
self.selected_packages
|
||||
.retain(|&x| x.1 != i_package || x.0 != i_user);
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
RowMessage::ActionPressed => {
|
||||
self.phone_packages[i_user][i_package].selected = true;
|
||||
Task::batch(build_action_pkg_commands(
|
||||
&self.phone_packages,
|
||||
selected_device,
|
||||
&settings.device,
|
||||
(i_user, i_package),
|
||||
))
|
||||
}
|
||||
RowMessage::PackagePressed => {
|
||||
self.description = package.clone().description;
|
||||
self.description_content = text_editor::Content::with_text(&package.description);
|
||||
package.current = true;
|
||||
if self.current_package_index != i_package {
|
||||
self.phone_packages[i_user][self.current_package_index].current = false;
|
||||
}
|
||||
self.current_package_index = i_package;
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_apply_action_on_selection(&mut self) -> Task<Message> {
|
||||
self.selection_modal = true;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_user_selected(&mut self, user: User) -> Task<Message> {
|
||||
self.selected_user = Some(user);
|
||||
self.filtered_packages = (0..self.phone_packages[user.index].len()).collect();
|
||||
Self::filter_package_lists(self);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_change_package_state(
|
||||
&mut self,
|
||||
res: Result<PackageInfo, AdbError>,
|
||||
settings: &Settings,
|
||||
) -> Task<Message> {
|
||||
match res {
|
||||
Ok(p) => {
|
||||
let package = &mut self.phone_packages[p.i_user][p.index];
|
||||
package.state = package.state.opposite(settings.device.disable_mode);
|
||||
package.selected = false;
|
||||
self.selected_packages
|
||||
.retain(|&x| x.1 != p.index && x.0 != p.i_user);
|
||||
Self::filter_package_lists(self);
|
||||
}
|
||||
Err(AdbError::Generic(err)) => {
|
||||
self.error_modal = Some(err);
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_modal_user_selected(
|
||||
&mut self,
|
||||
user: User,
|
||||
settings: &mut Settings,
|
||||
selected_device: &mut Phone,
|
||||
list_update_state: &mut UadListState,
|
||||
) -> Task<Message> {
|
||||
self.selected_user = Some(user);
|
||||
self.update(
|
||||
settings,
|
||||
selected_device,
|
||||
list_update_state,
|
||||
Message::UserSelected(user),
|
||||
)
|
||||
}
|
||||
|
||||
fn on_clear_selected_packages(&mut self) -> Task<Message> {
|
||||
self.selected_packages = Vec::new();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_adb_satisfied(&mut self, result: bool) -> Task<Message> {
|
||||
self.is_adb_satisfied = result;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_update_failed(&mut self) -> Task<Message> {
|
||||
self.loading_state = LoadingState::FailedToUpdate;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_go_to_url(url: PathBuf) -> Task<Message> {
|
||||
open_url(url);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_export_selection(&mut self) -> Task<Message> {
|
||||
let i_user = self.selected_user.unwrap_or_default().index;
|
||||
Task::perform(
|
||||
export_selection(self.phone_packages[i_user].clone()),
|
||||
Message::SelectionExported,
|
||||
)
|
||||
}
|
||||
|
||||
fn on_selection_exported(&mut self, export: Result<bool, String>) -> Task<Message> {
|
||||
match export {
|
||||
Ok(_) => self.export_modal = true,
|
||||
Err(err) => error!("Failed to export current selection: {err:?}"),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_description_edit(&mut self, action: text_editor::Action) -> Task<Message> {
|
||||
match action {
|
||||
text_editor::Action::Scroll { lines: _ } | text_editor::Action::Edit(_) => {}
|
||||
_ => {
|
||||
self.description_content.perform(action);
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_copy_error(&mut self, err: String) -> Task<Message> {
|
||||
self.copy_confirmation = true;
|
||||
Task::batch(vec![
|
||||
iced::clipboard::write::<Message>(err),
|
||||
Task::perform(
|
||||
// intentional delay
|
||||
async { std::thread::sleep(std::time::Duration::from_secs(1)) },
|
||||
|()| Message::HideCopyConfirmation,
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
fn on_hide_copy_confirmation(&mut self) -> Task<Message> {
|
||||
self.copy_confirmation = false;
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
fn error_view<'a>(
|
||||
|
|
@ -978,11 +1077,7 @@ fn error_view<'a>(
|
|||
} else {
|
||||
Some(Message::CopyError(error.to_string()))
|
||||
})
|
||||
.style(if copy_confirmation {
|
||||
style::Button::Primary
|
||||
} else {
|
||||
style::Button::Primary
|
||||
}),
|
||||
.style(style::Button::Primary),
|
||||
button(
|
||||
text("Close")
|
||||
.width(Length::Fill)
|
||||
|
|
@ -1048,7 +1143,7 @@ fn build_action_pkg_commands(
|
|||
&& packages
|
||||
.get(u.index)
|
||||
.and_then(|user_pkgs| user_pkgs.get(selection.1))
|
||||
.is_some_and(|p| p.selected || settings.multi_user_mode)
|
||||
.is_some_and(|row_pkg| row_pkg.selected || settings.multi_user_mode)
|
||||
}) {
|
||||
let u_pkg = &packages[u.index][selection.1];
|
||||
let wanted_state = if settings.multi_user_mode {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ pub enum Message {
|
|||
}
|
||||
|
||||
impl Settings {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
phone: &Phone,
|
||||
|
|
@ -76,187 +75,290 @@ impl Settings {
|
|||
selected_user: Option<User>,
|
||||
) -> Task<Message> {
|
||||
match msg {
|
||||
Message::ModalHide => {
|
||||
self.modal = None;
|
||||
Task::none()
|
||||
Message::ModalHide => self.handle_modal_hide(),
|
||||
Message::ExpertMode(toggled) => self.handle_expert_mode(phone, toggled),
|
||||
Message::DisableMode(toggled) => self.handle_disable_mode(phone, toggled),
|
||||
Message::MultiUserMode(toggled) => self.handle_multi_user_mode(phone, toggled),
|
||||
Message::ApplyTheme(theme) => self.handle_apply_theme(phone, theme),
|
||||
Message::UrlPressed(url) => Self::handle_url_pressed(url),
|
||||
Message::LoadDeviceSettings => self.handle_load_device_settings(phone),
|
||||
Message::BackupSelected(d_path) => self.handle_backup_selected(d_path),
|
||||
Message::BackupDevice => self.handle_backup_device(phone, packages),
|
||||
Message::DeviceBackedUp(result) => self.handle_device_backed_up(phone, result),
|
||||
Message::RestoreDevice => {
|
||||
self.handle_restore_device(phone, packages, nb_running_async_adb_commands)
|
||||
}
|
||||
Message::ExpertMode(toggled) => {
|
||||
self.general.expert_mode = toggled;
|
||||
debug!("Config change: {self:?}");
|
||||
Config::save_changes(self, &phone.adb_id);
|
||||
Task::none()
|
||||
}
|
||||
Message::DisableMode(toggled) => {
|
||||
if phone.android_sdk >= 23 {
|
||||
self.device.disable_mode = toggled;
|
||||
debug!("Config change: {self:?}");
|
||||
Config::save_changes(self, &phone.adb_id);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::MultiUserMode(toggled) => {
|
||||
self.device.multi_user_mode = toggled;
|
||||
debug!("Config change: {self:?}");
|
||||
Config::save_changes(self, &phone.adb_id);
|
||||
Task::none()
|
||||
}
|
||||
Message::ApplyTheme(theme) => {
|
||||
self.general.theme = theme.to_string();
|
||||
debug!("Config change: {self:?}");
|
||||
Config::save_changes(self, &phone.adb_id);
|
||||
Task::none()
|
||||
}
|
||||
Message::UrlPressed(url) => {
|
||||
open_url(url);
|
||||
Task::none()
|
||||
}
|
||||
Message::LoadDeviceSettings => {
|
||||
let backups =
|
||||
list_available_backups(&self.general.backup_folder.join(&phone.adb_id));
|
||||
let backup = BackupSettings {
|
||||
backups: backups.clone(),
|
||||
selected: backups.first().cloned(),
|
||||
users: phone.user_list.clone(),
|
||||
selected_user: phone.user_list.first().copied(),
|
||||
backup_state: String::default(),
|
||||
};
|
||||
match Config::load_configuration_file()
|
||||
.devices
|
||||
.iter()
|
||||
.find(|d| d.device_id == phone.adb_id)
|
||||
{
|
||||
Some(device) => {
|
||||
self.device.clone_from(device);
|
||||
self.device.backup = backup;
|
||||
}
|
||||
None => {
|
||||
self.device = DeviceSettings {
|
||||
device_id: phone.adb_id.clone(),
|
||||
multi_user_mode: supports_multi_user(phone),
|
||||
disable_mode: false,
|
||||
backup,
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::BackupSelected(d_path) => {
|
||||
self.device.backup.selected = Some(d_path.clone());
|
||||
self.device.backup.users = list_available_backup_user(d_path);
|
||||
Task::none()
|
||||
}
|
||||
Message::BackupDevice => Task::perform(
|
||||
backup_phone(
|
||||
phone.user_list.clone(),
|
||||
self.device.device_id.clone(),
|
||||
packages.to_vec(),
|
||||
),
|
||||
Message::DeviceBackedUp,
|
||||
),
|
||||
Message::DeviceBackedUp(is_backed_up) => {
|
||||
match is_backed_up {
|
||||
Ok(_) => {
|
||||
info!("[BACKUP] Backup successfully created");
|
||||
self.device.backup.backups = list_available_backups(
|
||||
&self.general.backup_folder.join(phone.adb_id.clone()),
|
||||
);
|
||||
self.device.backup.selected = self.device.backup.backups.first().cloned();
|
||||
}
|
||||
Err(err) => {
|
||||
error!("[BACKUP FAILED] Backup creation failed: {err:?}");
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::RestoreDevice => match restore_backup(phone, packages, &self.device) {
|
||||
Ok(r_packages) => {
|
||||
let mut commands = vec![];
|
||||
*nb_running_async_adb_commands = 0;
|
||||
for p in &r_packages {
|
||||
let p_info = PackageInfo {
|
||||
i_user: 0,
|
||||
index: p.index,
|
||||
removal: "RESTORE".to_string(),
|
||||
};
|
||||
for command in p.commands.clone() {
|
||||
*nb_running_async_adb_commands += 1;
|
||||
commands.push(Task::perform(
|
||||
// This is "safe" thanks to serde:
|
||||
// https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/issues/760
|
||||
run_adb_action(phone.adb_id.clone(), command, p_info.clone()),
|
||||
Message::RestoringDevice,
|
||||
));
|
||||
}
|
||||
}
|
||||
if r_packages.is_empty() {
|
||||
if get_android_sdk(&phone.adb_id) == 0 {
|
||||
self.device.backup.backup_state = "Device is not connected".to_string();
|
||||
} else {
|
||||
self.device.backup.backup_state =
|
||||
"Device state is already restored".to_string();
|
||||
}
|
||||
}
|
||||
info!(
|
||||
"[RESTORE] Restoring backup {}",
|
||||
self.device.backup.selected.as_ref().unwrap()
|
||||
);
|
||||
Task::batch(commands)
|
||||
}
|
||||
Err(e) => {
|
||||
self.device.backup.backup_state.clone_from(&e);
|
||||
error!("{} - {}", self.device.backup.selected.as_ref().unwrap(), e);
|
||||
Task::none()
|
||||
}
|
||||
},
|
||||
// Trigger an action in mod.rs (Message::SettingsAction(msg))
|
||||
Message::RestoringDevice(_) => Task::none(),
|
||||
Message::FolderChosen(result) => {
|
||||
self.is_loading = false;
|
||||
Message::FolderChosen(result) => self.handle_folder_chosen(phone, result),
|
||||
Message::ChooseBackUpFolder => self.handle_choose_backup_folder(),
|
||||
Message::ExportPackages => Self::handle_export_packages(selected_user, packages),
|
||||
Message::PackagesExported(result) => self.handle_packages_exported(result),
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(path) = result {
|
||||
self.general.backup_folder = path;
|
||||
Config::save_changes(self, &phone.adb_id);
|
||||
#[expect(unused_must_use, reason = "side-effect")]
|
||||
{
|
||||
self.update(
|
||||
phone,
|
||||
packages,
|
||||
nb_running_async_adb_commands,
|
||||
Message::LoadDeviceSettings,
|
||||
selected_user,
|
||||
);
|
||||
fn handle_modal_hide(&mut self) -> Task<Message> {
|
||||
self.modal = None;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn handle_expert_mode(&mut self, phone: &Phone, toggled: bool) -> Task<Message> {
|
||||
self.general.expert_mode = toggled;
|
||||
debug!("Config change: {self:?}");
|
||||
Config::save_changes(self, &phone.adb_id);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn handle_disable_mode(&mut self, phone: &Phone, toggled: bool) -> Task<Message> {
|
||||
if phone.android_sdk >= 23 {
|
||||
self.device.disable_mode = toggled;
|
||||
debug!("Config change: {self:?}");
|
||||
Config::save_changes(self, &phone.adb_id);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn handle_multi_user_mode(&mut self, phone: &Phone, toggled: bool) -> Task<Message> {
|
||||
self.device.multi_user_mode = toggled;
|
||||
debug!("Config change: {self:?}");
|
||||
Config::save_changes(self, &phone.adb_id);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn handle_apply_theme(&mut self, phone: &Phone, theme: Theme) -> Task<Message> {
|
||||
self.general.theme = theme.to_string();
|
||||
debug!("Config change: {self:?}");
|
||||
Config::save_changes(self, &phone.adb_id);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn handle_url_pressed(url: PathBuf) -> Task<Message> {
|
||||
open_url(url);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn handle_load_device_settings(&mut self, phone: &Phone) -> Task<Message> {
|
||||
self.load_device_settings(phone);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn load_device_settings(&mut self, phone: &Phone) {
|
||||
let backups = list_available_backups(&self.general.backup_folder.join(&phone.adb_id));
|
||||
let backup = BackupSettings {
|
||||
backups: backups.clone(),
|
||||
selected: backups.first().cloned(),
|
||||
users: phone.user_list.clone(),
|
||||
selected_user: phone.user_list.first().copied(),
|
||||
backup_state: String::default(),
|
||||
};
|
||||
|
||||
match Config::load_configuration_file()
|
||||
.devices
|
||||
.iter()
|
||||
.find(|d| d.device_id == phone.adb_id)
|
||||
{
|
||||
Some(device) => {
|
||||
self.device.clone_from(device);
|
||||
self.device.backup = backup;
|
||||
}
|
||||
None => {
|
||||
self.device = DeviceSettings {
|
||||
device_id: phone.adb_id.clone(),
|
||||
multi_user_mode: supports_multi_user(phone),
|
||||
disable_mode: false,
|
||||
backup,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_backup_selected(&mut self, d_path: DisplayablePath) -> Task<Message> {
|
||||
self.device.backup.selected = Some(d_path.clone());
|
||||
self.device.backup.users = list_available_backup_user(d_path);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn handle_backup_device(
|
||||
&mut self,
|
||||
phone: &Phone,
|
||||
packages: &[Vec<PackageRow>],
|
||||
) -> Task<Message> {
|
||||
Task::perform(
|
||||
backup_phone(
|
||||
phone.user_list.clone(),
|
||||
self.device.device_id.clone(),
|
||||
packages.to_vec(),
|
||||
),
|
||||
Message::DeviceBackedUp,
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_device_backed_up(
|
||||
&mut self,
|
||||
phone: &Phone,
|
||||
result: Result<bool, String>,
|
||||
) -> Task<Message> {
|
||||
match result {
|
||||
Ok(_) => {
|
||||
info!("[BACKUP] Backup successfully created");
|
||||
self.device.backup.backups =
|
||||
list_available_backups(&self.general.backup_folder.join(phone.adb_id.clone()));
|
||||
self.device.backup.selected = self.device.backup.backups.first().cloned();
|
||||
}
|
||||
Err(err) => {
|
||||
error!("[BACKUP FAILED] Backup creation failed: {err:?}");
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn handle_restore_device(
|
||||
&mut self,
|
||||
phone: &Phone,
|
||||
packages: &[Vec<PackageRow>],
|
||||
nb_running_async_adb_commands: &mut u32,
|
||||
) -> Task<Message> {
|
||||
match restore_backup(phone, packages, &self.device) {
|
||||
Ok(r_packages) => {
|
||||
let mut commands = vec![];
|
||||
*nb_running_async_adb_commands = 0;
|
||||
for p in &r_packages {
|
||||
let p_info = PackageInfo {
|
||||
i_user: 0,
|
||||
index: p.index,
|
||||
removal: "RESTORE".to_string(),
|
||||
};
|
||||
for command in p.commands.clone() {
|
||||
*nb_running_async_adb_commands += 1;
|
||||
commands.push(Task::perform(
|
||||
run_adb_action(phone.adb_id.clone(), command, p_info.clone()),
|
||||
Message::RestoringDevice,
|
||||
));
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::ChooseBackUpFolder => {
|
||||
if self.is_loading {
|
||||
Task::none()
|
||||
} else {
|
||||
self.is_loading = true;
|
||||
Task::perform(open_folder(), Message::FolderChosen)
|
||||
if r_packages.is_empty() {
|
||||
if get_android_sdk(&phone.adb_id) == 0 {
|
||||
self.device.backup.backup_state = "Device is not connected".to_string();
|
||||
} else {
|
||||
self.device.backup.backup_state =
|
||||
"Device state is already restored".to_string();
|
||||
}
|
||||
}
|
||||
info!(
|
||||
"[RESTORE] Restoring backup {}",
|
||||
self.device.backup.selected.as_ref().unwrap()
|
||||
);
|
||||
Task::batch(commands)
|
||||
}
|
||||
Message::ExportPackages => Task::perform(
|
||||
export_packages(selected_user.unwrap_or_default(), packages.to_vec()),
|
||||
Message::PackagesExported,
|
||||
),
|
||||
Message::PackagesExported(exported) => {
|
||||
match exported {
|
||||
Ok(_) => self.modal = Some(PopUpModal::ExportUninstalled),
|
||||
Err(err) => error!("Failed to export list of uninstalled packages: {err:?}"),
|
||||
}
|
||||
Err(e) => {
|
||||
self.device.backup.backup_state.clone_from(&e);
|
||||
error!("{} - {}", self.device.backup.selected.as_ref().unwrap(), e);
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn handle_folder_chosen(
|
||||
&mut self,
|
||||
phone: &Phone,
|
||||
result: Result<PathBuf, Error>,
|
||||
) -> Task<Message> {
|
||||
self.is_loading = false;
|
||||
|
||||
if let Ok(path) = result {
|
||||
self.general.backup_folder = path;
|
||||
Config::save_changes(self, &phone.adb_id);
|
||||
self.load_device_settings(phone);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn handle_choose_backup_folder(&mut self) -> Task<Message> {
|
||||
if self.is_loading {
|
||||
Task::none()
|
||||
} else {
|
||||
self.is_loading = true;
|
||||
Task::perform(open_folder(), Message::FolderChosen)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_export_packages(
|
||||
selected_user: Option<User>,
|
||||
packages: &[Vec<PackageRow>],
|
||||
) -> Task<Message> {
|
||||
Task::perform(
|
||||
export_packages(selected_user.unwrap_or_default(), packages.to_vec()),
|
||||
Message::PackagesExported,
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_packages_exported(&mut self, result: Result<bool, String>) -> Task<Message> {
|
||||
match result {
|
||||
Ok(_) => self.modal = Some(PopUpModal::ExportUninstalled),
|
||||
Err(err) => error!("Failed to export list of uninstalled packages: {err:?}"),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub fn view(
|
||||
&self,
|
||||
phone: &Phone,
|
||||
apps_view: &AppsView,
|
||||
) -> Element<'_, Message, Theme, Renderer> {
|
||||
let content = if phone.adb_id.is_empty() {
|
||||
self.build_no_device_content()
|
||||
} else {
|
||||
self.build_device_content(phone, apps_view)
|
||||
};
|
||||
|
||||
if let Some(PopUpModal::ExportUninstalled) = self.modal {
|
||||
return Self::render_export_modal(content);
|
||||
}
|
||||
|
||||
container(scrollable(content))
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn build_no_device_content(&self) -> Element<'_, Message, Theme, Renderer> {
|
||||
column![
|
||||
text("Theme").size(26),
|
||||
self.theme_container(),
|
||||
text("General").size(26),
|
||||
self.general_container(),
|
||||
text("Current device").size(26),
|
||||
Self::no_device_container(),
|
||||
text("Backup / Restore").size(26),
|
||||
Self::no_device_container(),
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.spacing(20)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn build_device_content(
|
||||
&self,
|
||||
phone: &Phone,
|
||||
apps_view: &AppsView,
|
||||
) -> Element<'_, Message, Theme, Renderer> {
|
||||
column![
|
||||
text("Theme").size(26),
|
||||
self.theme_container(),
|
||||
text("General").size(26),
|
||||
self.general_container(),
|
||||
text("Current device").size(26),
|
||||
Self::warning_container(phone),
|
||||
self.device_specific_container(phone),
|
||||
text("Backup / Restore").size(26),
|
||||
self.backup_restore_container(phone, apps_view),
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.spacing(20)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn theme_container(&self) -> Element<'_, Message, Theme, Renderer> {
|
||||
let radio_btn_theme = Theme::ALL
|
||||
.iter()
|
||||
.fold(row![].spacing(10), |column, option| {
|
||||
|
|
@ -270,12 +372,16 @@ impl Settings {
|
|||
.size(24),
|
||||
)
|
||||
});
|
||||
let theme_ctn = container(radio_btn_theme)
|
||||
|
||||
container(radio_btn_theme)
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Shrink)
|
||||
.style(style::Container::Frame);
|
||||
.style(style::Container::Frame)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn general_container(&self) -> Element<'_, Message, Theme, Renderer> {
|
||||
let expert_mode_checkbox = checkbox(
|
||||
"Allow to uninstall packages marked as \"unsafe\" (I KNOW WHAT I AM DOING)",
|
||||
self.general.expert_mode,
|
||||
|
|
@ -306,7 +412,7 @@ impl Settings {
|
|||
.spacing(10)
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
let general_ctn = container(
|
||||
container(
|
||||
column![
|
||||
expert_mode_checkbox,
|
||||
expert_mode_descr,
|
||||
|
|
@ -318,9 +424,12 @@ impl Settings {
|
|||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Shrink)
|
||||
.style(style::Container::Frame);
|
||||
.style(style::Container::Frame)
|
||||
.into()
|
||||
}
|
||||
|
||||
let warning_ctn = container(
|
||||
fn warning_container(phone: &Phone) -> Element<'static, Message, Theme, Renderer> {
|
||||
container(
|
||||
row![
|
||||
text("The following settings only affect the currently selected device:")
|
||||
.style(style::Text::Danger),
|
||||
|
|
@ -332,8 +441,11 @@ impl Settings {
|
|||
)
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.style(style::Container::BorderedFrame);
|
||||
.style(style::Container::BorderedFrame)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn device_specific_container(&self, phone: &Phone) -> Element<'_, Message, Theme, Renderer> {
|
||||
let multi_user_mode_descr = row![
|
||||
text("This will not affect the following protected work profile users: ")
|
||||
.style(style::Text::Commentary),
|
||||
|
|
@ -375,8 +487,6 @@ impl Settings {
|
|||
.height(22)
|
||||
.style(style::Button::Unavailable);
|
||||
|
||||
// Disabling package without root isn't really possible before Android Oreo (8.0)
|
||||
// see https://github.com/Universal-Debloater-Alliance/universal-android-debloater/wiki/ADB-reference
|
||||
let disable_mode_checkbox = checkbox(
|
||||
"Clear and disable packages instead of uninstalling them",
|
||||
self.device.disable_mode,
|
||||
|
|
@ -400,7 +510,7 @@ impl Settings {
|
|||
.width(Length::Fill)
|
||||
};
|
||||
|
||||
let device_specific_ctn = container(
|
||||
container(
|
||||
column![
|
||||
multi_user_mode_checkbox,
|
||||
multi_user_mode_descr,
|
||||
|
|
@ -412,8 +522,15 @@ impl Settings {
|
|||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Shrink)
|
||||
.style(style::Container::Frame);
|
||||
.style(style::Container::Frame)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn backup_restore_container(
|
||||
&self,
|
||||
phone: &Phone,
|
||||
apps_view: &AppsView,
|
||||
) -> Element<'_, Message, Theme, Renderer> {
|
||||
let backup_pick_list = pick_list(
|
||||
self.device.backup.backups.clone(),
|
||||
self.device.backup.selected.clone(),
|
||||
|
|
@ -475,101 +592,75 @@ impl Settings {
|
|||
.align_y(Alignment::Center)
|
||||
};
|
||||
|
||||
let no_device_ctn = || {
|
||||
container(text("No device detected").style(style::Text::Danger))
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.style(style::Container::BorderedFrame)
|
||||
};
|
||||
let export_row = row![
|
||||
export_btn,
|
||||
"Export uninstalled packages with their description",
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
text(format!(
|
||||
"Selected: user {}",
|
||||
apps_view.selected_user.unwrap_or_default().id
|
||||
)),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
let content = if phone.adb_id.is_empty() {
|
||||
column![
|
||||
text("Theme").size(26),
|
||||
theme_ctn,
|
||||
text("General").size(26),
|
||||
general_ctn,
|
||||
text("Current device").size(26),
|
||||
no_device_ctn(),
|
||||
text("Backup / Restore").size(26),
|
||||
no_device_ctn(),
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.spacing(20)
|
||||
} else {
|
||||
let export_row = row![
|
||||
export_btn,
|
||||
"Export uninstalled packages with their description",
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
text(format!(
|
||||
"Selected: user {}",
|
||||
apps_view.selected_user.unwrap_or_default().id
|
||||
)),
|
||||
]
|
||||
.spacing(10)
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
let backup_restore_ctn =
|
||||
container(column![backup_row, restore_row, export_row].spacing(10))
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Shrink)
|
||||
.style(style::Container::Frame);
|
||||
|
||||
column![
|
||||
text("Theme").size(26),
|
||||
theme_ctn,
|
||||
text("General").size(26),
|
||||
general_ctn,
|
||||
text("Current device").size(26),
|
||||
warning_ctn,
|
||||
device_specific_ctn,
|
||||
text("Backup / Restore").size(26),
|
||||
backup_restore_ctn,
|
||||
]
|
||||
.width(Length::Fill)
|
||||
.spacing(20)
|
||||
};
|
||||
|
||||
if let Some(PopUpModal::ExportUninstalled) = self.modal {
|
||||
let title = container(row![text("Success").size(24)].align_y(Alignment::Center))
|
||||
.width(Length::Fill)
|
||||
.style(style::Container::Frame)
|
||||
.padding([10, 0])
|
||||
.center_y(Length::Shrink)
|
||||
.center_x(Length::Shrink);
|
||||
|
||||
let text_box = row![
|
||||
text(format!("Exported uninstalled packages into file.\nFile is exported in same directory where {NAME} is located.")).width(Length::Fill),
|
||||
].padding(20);
|
||||
|
||||
let file_row = row![
|
||||
text(generate_backup_name(chrono::Local::now())).style(style::Text::Commentary)
|
||||
]
|
||||
.padding(20);
|
||||
|
||||
let modal_btn_row = row![
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
button(text("Close").width(Length::Shrink))
|
||||
.width(Length::Shrink)
|
||||
.on_press(Message::ModalHide),
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
];
|
||||
|
||||
let ctn = container(column![title, text_box, file_row, modal_btn_row])
|
||||
.height(Length::Shrink)
|
||||
.width(500)
|
||||
.padding(10)
|
||||
.style(style::Container::Frame);
|
||||
|
||||
return Modal::new(content.padding(10), ctn)
|
||||
.on_blur(Message::ModalHide)
|
||||
.into();
|
||||
}
|
||||
|
||||
container(scrollable(content))
|
||||
container(column![backup_row, restore_row, export_row].spacing(10))
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.height(Length::Shrink)
|
||||
.style(style::Container::Frame)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn no_device_container() -> Element<'static, Message, Theme, Renderer> {
|
||||
container(text("No device detected").style(style::Text::Danger))
|
||||
.padding(10)
|
||||
.width(Length::Fill)
|
||||
.style(style::Container::BorderedFrame)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn render_export_modal<'a>(
|
||||
content: Element<'a, Message, Theme, Renderer>,
|
||||
) -> Element<'a, Message, Theme, Renderer> {
|
||||
let title = container(row![text("Success").size(24)].align_y(Alignment::Center))
|
||||
.width(Length::Fill)
|
||||
.style(style::Container::Frame)
|
||||
.padding([10, 0])
|
||||
.center_y(Length::Shrink)
|
||||
.center_x(Length::Shrink);
|
||||
|
||||
let text_box = row![
|
||||
text(format!(
|
||||
"Exported uninstalled packages into file.\nFile is exported in same directory where {NAME} is located."
|
||||
))
|
||||
.width(Length::Fill),
|
||||
]
|
||||
.padding(20);
|
||||
|
||||
let file_row =
|
||||
row![text(generate_backup_name(chrono::Local::now())).style(style::Text::Commentary)]
|
||||
.padding(20);
|
||||
|
||||
let modal_btn_row = row![
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
button(text("Close").width(Length::Shrink))
|
||||
.width(Length::Shrink)
|
||||
.on_press(Message::ModalHide),
|
||||
Space::new(Length::Fill, Length::Shrink),
|
||||
];
|
||||
|
||||
let ctn = container(column![title, text_box, file_row, modal_btn_row])
|
||||
.height(Length::Shrink)
|
||||
.width(500)
|
||||
.padding(10)
|
||||
.style(style::Container::Frame);
|
||||
|
||||
let padded_content: Element<'a, Message, Theme, Renderer> =
|
||||
container(content).padding(10).into();
|
||||
|
||||
Modal::new(padded_content, ctn)
|
||||
.on_blur(Message::ModalHide)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ impl PackageRow {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(
|
||||
clippy::unused_self,
|
||||
reason = "Consistent component API; may change later"
|
||||
)]
|
||||
pub fn update(&mut self, _message: &Message) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue