From 8ed9599d45e141c8c53951710d097f93bf32e99c Mon Sep 17 00:00:00 2001 From: xodapi <4956501+xodapi@users.noreply.github.com> Date: Fri, 3 Jul 2026 09:05:06 +0700 Subject: [PATCH] feat(android): show Vimichi mascot (closes #103) --- src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ ui/app.slint | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 738eb02..b543d54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -219,6 +219,7 @@ fn android_apply_dashboard( "30d", windows.iter().find(|window| window.key == "30d"), ); + android_apply_vimichi(app, &windows); } Err(error) => { let msg = if error.contains("HTTP 401") { @@ -232,6 +233,42 @@ fn android_apply_dashboard( } } +#[cfg(all(target_os = "android", feature = "android-gui"))] +fn android_apply_vimichi(app: &AppWindow, windows: &[WindowState]) { + let five = windows.iter().find(|window| window.key == "5h"); + let percent = five.map(|window| window.percent).unwrap_or(0.0); + let peak = five + .and_then(|window| peak_percent(window.credits.as_ref(), window.requests.as_ref())) + .unwrap_or(percent); + let level = window_level_from_peak(peak); + let state = android_vimichi_state(percent, level); + let phase = ((percent.clamp(0.0, 100.0) as f32) * 3.6) % 360.0; + + app.set_overlay_delta_level(level.into()); + app.set_overlay_creature_percent(percent as f32); + app.set_overlay_creature_state(state.into()); + app.set_overlay_creature_points(android_vimichi_points(percent)); + app.set_overlay_pulse_phase(phase); +} + +#[cfg(all(target_os = "android", feature = "android-gui"))] +fn android_vimichi_state(percent: f64, level: &str) -> &'static str { + if percent < 15.0 { + "sleeping" + } else if level == "danger" { + "critical" + } else if level == "warning" { + "alert" + } else { + "awake" + } +} + +#[cfg(all(target_os = "android", feature = "android-gui"))] +fn android_vimichi_points(percent: f64) -> i32 { + (8.0 + percent.clamp(0.0, 100.0) * 0.56).round() as i32 +} + #[cfg(all(target_os = "android", feature = "android-gui"))] fn android_apply_window(app: &AppWindow, key: &str, window: Option<&WindowState>) { let Some(window) = window else { return }; diff --git a/ui/app.slint b/ui/app.slint index 71a048f..c76b145 100644 --- a/ui/app.slint +++ b/ui/app.slint @@ -1069,6 +1069,53 @@ export component AppWindow inherits Window { } } + if (root.is-android) : Rectangle { + min-height: 142px; + background: Theme.bg-card; + border-radius: 10px; + border-color: Theme.border; + border-width: 1px; + + HorizontalLayout { + padding: 14px; + spacing: 14px; + alignment: center; + + PulseOrganism { + level: root.overlay-delta-level; + creature-state: root.overlay-creature-state; + phase: root.overlay-pulse-phase; + commands: root.overlay-creature-path; + node-count: root.overlay-creature-points; + } + + VerticalLayout { + spacing: 6px; + alignment: center; + + Text { + text: "Vimichi"; + color: Theme.text-primary; + font-size: 20px; + font-weight: 900; + } + + Text { + text: root.overlay-creature-state == "sleeping" ? "спит, расход тихий" : root.overlay-creature-state == "alert" ? "насторожен, лимит близко" : root.overlay-creature-state == "critical" ? "тревога, лимит горит" : "бодрствует"; + color: root.overlay-delta-level == "danger" ? #f85149 : root.overlay-delta-level == "warning" ? #d29922 : Theme.text-muted; + font-size: 13px; + wrap: word-wrap; + } + + Text { + text: root.stealth-mode ? "***" : "5h: \{round(root.overlay-creature-percent)}%"; + color: Theme.text-muted; + font-size: 12px; + } + } + } + } + GridLayout { spacing: 14px;