refactor: move theme to uad-gui; drop core gui deps; mark PmCommand must-use

This commit is contained in:
Angel J 2026-04-25 10:21:16 -07:00
commit 02ba6e2cca
No known key found for this signature in database
GPG key ID: 4CE6C8BA718B9657
14 changed files with 205 additions and 309 deletions

2
Cargo.lock generated
View file

@ -4558,10 +4558,8 @@ version = "1.2.0"
dependencies = [
"chrono",
"csv",
"dark-light",
"dirs",
"flate2",
"iced",
"log",
"retry",
"serde",

View file

@ -44,7 +44,6 @@ format_push_string = "warn"
large_include_file = "warn"
shadow_unrelated = "warn"
struct_field_names = "allow" # annoying
module_name_repetitions = "allow" # annoying
disallowed_types = "deny"
disallowed_methods = "deny"

View file

@ -191,7 +191,7 @@ uad completions powershell > uad.ps1
## Examples
### Remove all Facebook packages
### Remove Facebook packages
```bash
uad list --search facebook

View file

@ -12,7 +12,6 @@ edition.workspace = true
[features]
default = []
gui = ["dep:iced", "dep:dark-light"]
self-update = ["dep:flate2", "dep:tar"]
[dependencies]
@ -25,8 +24,6 @@ dirs.workspace = true
ureq.workspace = true
retry.workspace = true
csv.workspace = true
iced = { workspace = true, optional = true }
dark-light = { workspace = true, optional = true }
flate2 = { workspace = true, optional = true }
tar = { workspace = true, optional = true }
@ -47,7 +44,6 @@ format_push_string = "warn"
large_include_file = "warn"
shadow_unrelated = "warn"
struct_field_names = "allow" # annoying
module_name_repetitions = "allow" # annoying
disallowed_types = "deny"
disallowed_methods = "deny"

View file

@ -229,7 +229,6 @@ impl Default for ACommand {
pub struct ShellCommand(ACommand);
impl ShellCommand {
/// `pm` command builder
#[must_use]
pub fn pm(mut self) -> PmCommand {
self.0.0.arg("pm");
PmCommand(self)
@ -335,6 +334,7 @@ pub const PM_CLEAR_PACK: &str = "pm clear";
/// Builder object for an Android Package Manager command.
/// <https://developer.android.com/tools/adb#pm>
#[derive(Debug)]
#[must_use]
pub struct PmCommand(ShellCommand);
impl PmCommand {
/// `list packages -s` sub-command, [`PACK_PREFIX`] stripped.

View file

@ -1,13 +1,15 @@
use crate::CACHE_DIR;
use crate::CONFIG_DIR;
use crate::sync::User;
use crate::utils::DisplayablePath;
use crate::{sync::User, theme::Theme};
use log::error;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use std::sync::LazyLock;
const DEFAULT_THEME: &str = "Auto (follow system theme)";
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct Config {
pub general: GeneralSettings,
@ -44,7 +46,7 @@ pub struct DeviceSettings {
impl Default for GeneralSettings {
fn default() -> Self {
Self {
theme: Theme::default().to_string(),
theme: DEFAULT_THEME.to_string(),
expert_mode: false,
backup_folder: CACHE_DIR.join("backups"),
}
@ -114,7 +116,7 @@ mod tests {
let config = Config::load_configuration_file();
// non-deterministic
//assert_eq!(config.devices.len(), 0);
assert_eq!(config.general.theme, Theme::default().to_string());
assert_eq!(config.general.theme, DEFAULT_THEME);
assert!(!config.general.expert_mode);
assert_eq!(config.general.backup_folder, CACHE_DIR.join("backups"));
}
@ -136,7 +138,7 @@ mod tests {
fn test_default_config() {
let config = Config::default();
assert_eq!(config.devices.len(), 0);
assert_eq!(config.general.theme, Theme::default().to_string());
assert_eq!(config.general.theme, DEFAULT_THEME);
assert!(!config.general.expert_mode);
assert_eq!(config.general.backup_folder, CACHE_DIR.join("backups"));
}

View file

@ -11,7 +11,6 @@ pub mod adb;
pub mod config;
pub mod save;
pub mod sync;
pub mod theme;
pub mod uad_lists;
pub mod update;
pub mod utils;

View file

@ -1,231 +0,0 @@
#[cfg(feature = "gui")]
use dark_light;
#[cfg(feature = "gui")]
use iced::theme::{self, Mode, Palette, Style};
#[cfg(feature = "gui")]
use iced::{Color, color};
#[cfg(feature = "gui")]
use std::sync::LazyLock;
/*
In-memory caching.
This fixes the perf bug
caused by Iced repeatedly calling `palette`.
Coincidentally, this also ensures consistent colors across the GUI,
at the cost of requiring a restart to update the palette.
(this is just a patch, not a fix)
*/
#[cfg(feature = "gui")]
pub static OS_COLOR_SCHEME: LazyLock<dark_light::Mode> =
LazyLock::new(|| dark_light::detect().unwrap_or(dark_light::Mode::Unspecified));
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
/// Color scheme
pub enum Theme {
#[default]
/// `Dark` or `Light`, according to `dark_light`
Auto,
/// `Dark`-ish and purple
Lupin,
/// white on black
Dark,
/// black on white
Light,
}
#[cfg(feature = "gui")]
#[derive(Debug, Clone, Copy)]
pub struct BaseColors {
pub background: Color,
pub foreground: Color,
}
#[cfg(feature = "gui")]
#[derive(Debug, Clone, Copy)]
pub struct NormalColors {
pub primary: Color,
#[allow(dead_code, reason = "Reserved for future palette updates")]
pub secondary: Color,
pub surface: Color,
pub error: Color,
}
#[cfg(feature = "gui")]
#[derive(Debug, Clone, Copy)]
pub struct BrightColors {
pub primary: Color,
pub secondary: Color,
pub surface: Color,
pub error: Color,
}
#[cfg(feature = "gui")]
#[derive(Debug, Clone, Copy)]
pub struct ColorPalette {
pub base: BaseColors,
pub normal: NormalColors,
pub bright: BrightColors,
}
impl Theme {
pub const ALL: [Self; 4] = [Self::Auto, Self::Lupin, Self::Dark, Self::Light];
#[allow(
clippy::unreadable_literal,
reason = "https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/pull/578#discussion_r1759653408"
)]
/// This `fn` _could_ be `const`,
/// but `deref`ing a lazy-`static` is non-`const`.
#[must_use]
#[cfg(feature = "gui")]
pub fn palette(self) -> ColorPalette {
const DARK: ColorPalette = ColorPalette {
base: BaseColors {
background: color!(0x111111),
foreground: color!(0x1C1C1C),
},
normal: NormalColors {
primary: color!(0x5E4266),
secondary: color!(0x386E50),
surface: color!(0x828282),
error: color!(0x992B2B),
},
bright: BrightColors {
primary: color!(0xBA84FC),
secondary: color!(0x49EB7A),
surface: color!(0xE0E0E0),
error: color!(0xC13047),
},
};
const LIGHT: ColorPalette = ColorPalette {
base: BaseColors {
background: color!(0xEEEEEE),
foreground: color!(0xE0E0E0),
},
normal: NormalColors {
primary: color!(0x818181),
secondary: color!(0xF9D659),
surface: color!(0x818181),
error: color!(0x992B2B),
},
bright: BrightColors {
primary: color!(0x673AB7),
secondary: color!(0x3797A4),
surface: color!(0x000000),
error: color!(0xC13047),
},
};
const LUPIN: ColorPalette = ColorPalette {
base: BaseColors {
background: color!(0x282A36),
foreground: color!(0x353746),
},
normal: NormalColors {
primary: color!(0x58406F),
secondary: color!(0x386E50),
surface: color!(0xA2A4A3),
error: color!(0xA13034),
},
bright: BrightColors {
primary: color!(0xBD94F9),
secondary: color!(0x49EB7A),
surface: color!(0xF4F8F3),
error: color!(0xE63E6D),
},
};
match self {
Self::Dark => DARK,
Self::Light => LIGHT,
Self::Lupin => LUPIN,
Self::Auto => match *OS_COLOR_SCHEME {
dark_light::Mode::Light => LIGHT,
dark_light::Mode::Dark | dark_light::Mode::Unspecified => DARK,
},
}
}
}
impl std::fmt::Display for Theme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Dark => "Dark",
Self::Light => "Light",
Self::Lupin => "Lupin",
Self::Auto => "Auto (follow system theme)",
}
)
}
}
/// Converts a string to the GUI's Theme type
#[must_use]
pub fn string_to_theme(theme: &str) -> Theme {
match theme {
"Lupin" => Theme::Lupin,
"Dark" => Theme::Dark,
"Light" => Theme::Light,
_ => Theme::Auto,
}
}
#[cfg(feature = "gui")]
impl theme::Base for Theme {
fn default(preference: Mode) -> Self {
match preference {
Mode::Light => Self::Light,
Mode::Dark => Self::Dark,
Mode::None => match *OS_COLOR_SCHEME {
dark_light::Mode::Light => Self::Light,
dark_light::Mode::Dark | dark_light::Mode::Unspecified => Self::Dark,
},
}
}
fn mode(&self) -> Mode {
let resolved = match self {
Self::Auto => match *OS_COLOR_SCHEME {
dark_light::Mode::Light => Self::Light,
dark_light::Mode::Dark | dark_light::Mode::Unspecified => Self::Dark,
},
other => *other,
};
match resolved {
Self::Light => Mode::Light,
_ => Mode::Dark,
}
}
fn base(&self) -> Style {
let p = Self::palette(*self);
Style {
background_color: p.base.background,
text_color: p.bright.surface,
}
}
fn palette(&self) -> Option<Palette> {
let p = Self::palette(*self);
Some(Palette {
background: p.base.background,
text: p.bright.surface,
primary: p.normal.primary,
success: p.bright.secondary,
warning: p.normal.surface,
danger: p.normal.error,
})
}
fn name(&self) -> &str {
match self {
Self::Dark => "Dark",
Self::Light => "Light",
Self::Lupin => "Lupin",
Self::Auto => "Auto",
}
}
}

View file

@ -3,7 +3,6 @@
use crate::{
adb::{ACommand as AdbCommand, PmListPacksFlag},
sync::{CorePackage, User},
theme::Theme,
uad_lists::{PackageHashMap, PackageState, Removal, UadList},
};
use chrono::{DateTime, offset::Utc};
@ -128,22 +127,10 @@ pub fn fetch_packages(
};
user_package.push(package);
}
user_package.sort_by_key(|a| a.name.to_lowercase());
user_package.sort_by_key(|package| package.name.to_lowercase());
user_package
}
#[must_use]
pub fn string_to_theme(theme: &str) -> Theme {
match theme {
"Dark" => Theme::Dark,
"Light" => Theme::Light,
"Lupin" => Theme::Lupin,
// Auto uses `Display`, so it doesn't have a canonical repr
t if t.starts_with("Auto") => Theme::Auto,
_ => Theme::default(),
}
}
#[must_use]
pub fn setup_uad_dir(dir: &Path) -> PathBuf {
let dir = dir.join("uad");

View file

@ -22,7 +22,7 @@ no-self-update = []
img = ["image", "iced/image"]
[dependencies]
uad-core = { path = "../uad-core", features = ["gui", "self-update"] }
uad-core = { path = "../uad-core", features = ["self-update"] }
iced.workspace = true
image = { workspace = true, optional = true }
rfd.workspace = true
@ -54,7 +54,6 @@ format_push_string = "warn"
large_include_file = "warn"
shadow_unrelated = "warn"
struct_field_names = "allow" # annoying
module_name_repetitions = "allow" # annoying
disallowed_types = "deny"
disallowed_methods = "deny"

View file

@ -1,7 +1,7 @@
use crate::theme::string_to_theme;
#[cfg(feature = "img")]
use crate::theme::OS_COLOR_SCHEME;
use crate::theme::Theme;
use crate::theme::string_to_theme;
use crate::views::about::{About as AboutView, Message as AboutMessage};
use crate::views::list::{
List as AppsView, LoadingState as ListLoadingState, Message as AppsMessage,
@ -9,13 +9,13 @@ use crate::views::list::{
use crate::views::settings::{Message as SettingsMessage, Settings as SettingsView};
use crate::widgets::navigation_menu::nav_menu;
use iced::font;
use iced::widget::column;
#[cfg(feature = "img")]
use iced::window::icon;
#[cfg(feature = "img")]
use image::ImageFormat;
use iced::widget::column;
use iced::{Alignment, Element, Length, Settings, Task, window::Settings as Window};
use iced::{Subscription, event, keyboard};
#[cfg(feature = "img")]
use image::ImageFormat;
use log::{debug, error, info};
#[cfg(feature = "self-update")]
use std::path::PathBuf;
@ -434,7 +434,7 @@ impl UadGui {
pub fn start() -> iced::Result {
#[cfg(feature = "img")]
let logo: &[u8] = match *OS_COLOR_SCHEME {
// remember to keep `Unspecified` in sync with `src/core/theme`
// remember to keep `Unspecified` in sync with `src/theme`
dark_light::Mode::Dark | dark_light::Mode::Unspecified => {
include_bytes!("../../../resources/assets/logo-dark.png")
}

View file

@ -1,72 +1,219 @@
use iced::theme::{self, Mode, Palette, Style};
use iced::{Color, color};
use std::sync::LazyLock;
pub use uad_core::theme::{BaseColors, BrightColors, ColorPalette, NormalColors, OS_COLOR_SCHEME};
/*
In-memory caching.
This fixes the perf bug
caused by Iced repeatedly calling `palette`.
/// GUI-local wrapper around the core Theme to satisfy orphan rules for
/// iced's Catalog traits.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Theme(pub uad_core::theme::Theme);
Coincidentally, this also ensures consistent colors across the GUI,
at the cost of requiring a restart to update the palette.
(this is just a patch, not a fix)
*/
pub static OS_COLOR_SCHEME: LazyLock<dark_light::Mode> =
LazyLock::new(|| dark_light::detect().unwrap_or(dark_light::Mode::Unspecified));
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
/// Color scheme
pub enum Theme {
#[default]
/// `Dark` or `Light`, according to `dark_light`
Auto,
/// `Dark`-ish and purple
Lupin,
/// white on black
Dark,
/// black on white
Light,
}
#[derive(Debug, Clone, Copy)]
pub struct BaseColors {
pub background: Color,
pub foreground: Color,
}
#[derive(Debug, Clone, Copy)]
pub struct NormalColors {
pub primary: Color,
#[allow(dead_code, reason = "Reserved for future palette updates")]
pub secondary: Color,
pub surface: Color,
pub error: Color,
}
#[derive(Debug, Clone, Copy)]
pub struct BrightColors {
pub primary: Color,
pub secondary: Color,
pub surface: Color,
pub error: Color,
}
#[derive(Debug, Clone, Copy)]
pub struct ColorPalette {
pub base: BaseColors,
pub normal: NormalColors,
pub bright: BrightColors,
}
#[allow(
non_upper_case_globals,
reason = "Keep variant-like names matching core Theme"
)]
impl Theme {
pub const Auto: Self = Self(uad_core::theme::Theme::Auto);
pub const Lupin: Self = Self(uad_core::theme::Theme::Lupin);
pub const Dark: Self = Self(uad_core::theme::Theme::Dark);
pub const Light: Self = Self(uad_core::theme::Theme::Light);
pub const ALL: [Self; 4] = [Self::Auto, Self::Lupin, Self::Dark, Self::Light];
#[allow(
clippy::unreadable_literal,
reason = "https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/pull/578#discussion_r1759653408"
)]
/// This `fn` _could_ be `const`,
/// but `deref`ing a lazy-`static` is non-`const`.
#[must_use]
pub fn palette(self) -> ColorPalette {
self.0.palette()
const DARK: ColorPalette = ColorPalette {
base: BaseColors {
background: color!(0x111111),
foreground: color!(0x1C1C1C),
},
normal: NormalColors {
primary: color!(0x5E4266),
secondary: color!(0x386E50),
surface: color!(0x828282),
error: color!(0x992B2B),
},
bright: BrightColors {
primary: color!(0xBA84FC),
secondary: color!(0x49EB7A),
surface: color!(0xE0E0E0),
error: color!(0xC13047),
},
};
const LIGHT: ColorPalette = ColorPalette {
base: BaseColors {
background: color!(0xEEEEEE),
foreground: color!(0xE0E0E0),
},
normal: NormalColors {
primary: color!(0x818181),
secondary: color!(0xF9D659),
surface: color!(0x818181),
error: color!(0x992B2B),
},
bright: BrightColors {
primary: color!(0x673AB7),
secondary: color!(0x3797A4),
surface: color!(0x000000),
error: color!(0xC13047),
},
};
const LUPIN: ColorPalette = ColorPalette {
base: BaseColors {
background: color!(0x282A36),
foreground: color!(0x353746),
},
normal: NormalColors {
primary: color!(0x58406F),
secondary: color!(0x386E50),
surface: color!(0xA2A4A3),
error: color!(0xA13034),
},
bright: BrightColors {
primary: color!(0xBD94F9),
secondary: color!(0x49EB7A),
surface: color!(0xF4F8F3),
error: color!(0xE63E6D),
},
};
match self {
Self::Dark => DARK,
Self::Light => LIGHT,
Self::Lupin => LUPIN,
Self::Auto => match *OS_COLOR_SCHEME {
dark_light::Mode::Light => LIGHT,
dark_light::Mode::Dark | dark_light::Mode::Unspecified => DARK,
},
}
}
}
impl From<uad_core::theme::Theme> for Theme {
fn from(value: uad_core::theme::Theme) -> Self {
Self(value)
impl std::fmt::Display for Theme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Dark => "Dark",
Self::Light => "Light",
Self::Lupin => "Lupin",
Self::Auto => "Auto (follow system theme)",
}
}
impl From<Theme> for uad_core::theme::Theme {
fn from(value: Theme) -> Self {
value.0
)
}
}
/// Converts a string to the GUI's Theme type
#[must_use]
pub fn string_to_theme(theme: &str) -> Theme {
Theme(uad_core::theme::string_to_theme(theme))
match theme {
"Lupin" => Theme::Lupin,
"Dark" => Theme::Dark,
"Light" => Theme::Light,
_ => Theme::Auto,
}
}
impl theme::Base for Theme {
fn default(preference: Mode) -> Self {
Self(<uad_core::theme::Theme as theme::Base>::default(preference))
match preference {
Mode::Light => Self::Light,
Mode::Dark => Self::Dark,
Mode::None => match *OS_COLOR_SCHEME {
dark_light::Mode::Light => Self::Light,
dark_light::Mode::Dark | dark_light::Mode::Unspecified => Self::Dark,
},
}
}
fn mode(&self) -> Mode {
<uad_core::theme::Theme as theme::Base>::mode(&self.0)
let resolved = match self {
Self::Auto => match *OS_COLOR_SCHEME {
dark_light::Mode::Light => Self::Light,
dark_light::Mode::Dark | dark_light::Mode::Unspecified => Self::Dark,
},
other => *other,
};
match resolved {
Self::Light => Mode::Light,
_ => Mode::Dark,
}
}
fn base(&self) -> Style {
<uad_core::theme::Theme as theme::Base>::base(&self.0)
let p = Self::palette(*self);
Style {
background_color: p.base.background,
text_color: p.bright.surface,
}
}
fn palette(&self) -> Option<Palette> {
<uad_core::theme::Theme as theme::Base>::palette(&self.0)
let p = Self::palette(*self);
Some(Palette {
background: p.base.background,
text: p.bright.surface,
primary: p.normal.primary,
success: p.bright.secondary,
warning: p.normal.surface,
danger: p.normal.error,
})
}
fn name(&self) -> &str {
<uad_core::theme::Theme as theme::Base>::name(&self.0)
}
}
impl std::fmt::Display for Theme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
match self {
Self::Dark => "Dark",
Self::Light => "Light",
Self::Lupin => "Lupin",
Self::Auto => "Auto",
}
}
}

6
flake.lock generated
View file

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1765270179,
"narHash": "sha256-g2a4MhRKu4ymR4xwo+I+auTknXt/+j37Lnf0Mvfl1rE=",
"lastModified": 1776949667,
"narHash": "sha256-GMSVw35Q+294GlrTUKlx087E31z7KurReQ1YHSKp5iw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "677fbe97984e7af3175b6c121f3c39ee5c8d62c9",
"rev": "01fbdeef22b76df85ea168fbfe1bfd9e63681b30",
"type": "github"
},
"original": {

View file

@ -27,10 +27,10 @@
with pkgs;
[
libglvnd
xorg.libX11
xorg.libXcursor
xorg.libXi
xorg.libXrandr
libX11
libXcursor
libXi
libXrandr
libxkbcommon
wayland
]