From 08af2c0b2bffb900e18f6e288eea880acbe3e42c Mon Sep 17 00:00:00 2001 From: 2ro <17595647+2ro@users.noreply.github.com> Date: Mon, 6 Jul 2026 00:34:47 -0400 Subject: [PATCH] goblin: double-back at Home returns to the wallet switcher (Build 148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At the wallet Home (the top level, where back was silently swallowed), the first Android back now shows a brief non-blocking pill — "Press back again to leave this wallet" — and a second back within two seconds DESELECTS the wallet to the switcher. The wallet stays UNLOCKED (select(None)-style deselection, no close, no logout), so returning is one tap and its nostr service keeps running untouched. Quitting the app lives only at the switcher, via GRIM's existing native exit-confirmation on back (unchanged). In-wallet sub-screens keep normal back navigation, no hint. The second back never quits directly. The hint is a bottom-anchored fading pill above the tab bar (goblin view), armed by the host (WalletsContent::back_exit_at) in the swallowed-no-op branch; nothing else about back handling changes. New goblin.home.back_again string in all six locales; drift green. --- locales/de.yml | 1 + locales/en.yml | 1 + locales/fr.yml | 1 + locales/ru.yml | 1 + locales/tr.yml | 1 + locales/zh-CN.yml | 1 + src/gui/views/goblin/mod.rs | 58 +++++++++++++++++++++++++ src/gui/views/wallets/content.rs | 25 +++++++++++ src/gui/views/wallets/wallet/content.rs | 6 +++ 9 files changed, 95 insertions(+) diff --git a/locales/de.yml b/locales/de.yml index 1cf8b25e..a2b94924 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -376,6 +376,7 @@ goblin: nav_activity: "Aktivität" nav_receive: "Empfangen" nav_settings: "Einstellungen" + back_again: "Erneut Zurück drücken, um diese Wallet zu verlassen" activity: "Aktivität" news: "Neuigkeiten" empty_title: "Noch keine Aktivität" diff --git a/locales/en.yml b/locales/en.yml index dd0aee21..bd8589f9 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -376,6 +376,7 @@ goblin: nav_activity: "Activity" nav_receive: "Receive" nav_settings: "Settings" + back_again: "Press back again to leave this wallet" activity: "Activity" news: "News" empty_title: "No activity yet" diff --git a/locales/fr.yml b/locales/fr.yml index 505c1f2c..abd8269d 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -376,6 +376,7 @@ goblin: nav_activity: "Activité" nav_receive: "Recevoir" nav_settings: "Réglages" + back_again: "Appuyez à nouveau sur retour pour quitter ce portefeuille" activity: "Activité" news: "Actualités" empty_title: "Aucune activité" diff --git a/locales/ru.yml b/locales/ru.yml index 34cdc6eb..21e6b086 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -376,6 +376,7 @@ goblin: nav_activity: "Действия" nav_receive: "Получить" nav_settings: "Настройки" + back_again: "Нажмите «назад» ещё раз, чтобы выйти из этого кошелька" activity: "Действия" news: "Новости" empty_title: "Пока нет действий" diff --git a/locales/tr.yml b/locales/tr.yml index c971f4ec..f01ac132 100644 --- a/locales/tr.yml +++ b/locales/tr.yml @@ -376,6 +376,7 @@ goblin: nav_activity: "Etkinlik" nav_receive: "Al" nav_settings: "Ayarlar" + back_again: "Bu cüzdandan ayrılmak için tekrar geri bas" activity: "Etkinlik" news: "Haberler" empty_title: "Henüz etkinlik yok" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index ee01cc04..916d0eda 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -376,6 +376,7 @@ goblin: nav_activity: "动态" nav_receive: "收款" nav_settings: "设置" + back_again: "再按一次返回即可离开此钱包" activity: "动态" news: "新闻" empty_title: "暂无动态" diff --git a/src/gui/views/goblin/mod.rs b/src/gui/views/goblin/mod.rs index 9de6057b..028d1362 100644 --- a/src/gui/views/goblin/mod.rs +++ b/src/gui/views/goblin/mod.rs @@ -132,6 +132,9 @@ pub struct GoblinWalletView { wipe_confirm: bool, /// Minimum-confirmations value being edited in its modal (GRIM parity). min_conf_edit: String, + /// When the first back of the double-back was pressed at Home, for the + /// brief "press back again for the wallet switcher" hint. Display only. + back_hint: Option, } /// Whether the per-identity cue is drawn on activity rows (owner-approved). The @@ -262,6 +265,7 @@ impl Default for GoblinWalletView { copy_flash: None, wipe_confirm: false, min_conf_edit: String::new(), + back_hint: None, } } } @@ -471,6 +475,57 @@ impl GoblinWalletView { std::mem::take(&mut self.switch_requested) } + /// Show the brief "press back again for the wallet switcher" hint. Called + /// by the host when the first back of the double-back lands at the wallet + /// Home (the second deselects to the switcher, wallet left unlocked). + pub fn show_back_hint(&mut self) { + self.back_hint = Some(std::time::Instant::now()); + } + + /// Draw the transient back hint as a small bottom-anchored pill above the + /// tab bar, non-blocking, fading out with the double-back window. + fn back_hint_ui(&mut self, ctx: &egui::Context) { + const SHOW_SECS: f32 = 2.0; + let Some(at) = self.back_hint else { + return; + }; + let elapsed = at.elapsed().as_secs_f32(); + if elapsed >= SHOW_SECS { + self.back_hint = None; + return; + } + let t = theme::tokens(); + // Fade over the final 0.5s. + let alpha = ((SHOW_SECS - elapsed) / 0.5).clamp(0.0, 1.0); + let text = t!("goblin.home.back_again"); + let font = FontId::new(14.0, fonts::medium()); + egui::Area::new(egui::Id::new("goblin_back_hint")) + .order(egui::Order::Foreground) + .anchor( + egui::Align2::CENTER_BOTTOM, + Vec2::new(0.0, -(View::get_bottom_inset() + 92.0)), + ) + .interactable(false) + .show(ctx, |ui| { + let galley = + ui.painter() + .layout_no_wrap(text.to_string(), font.clone(), t.surface_text); + let pad = Vec2::new(16.0, 10.0); + let size = galley.size() + pad * 2.0; + let (rect, _) = ui.allocate_exact_size(size, Sense::hover()); + ui.painter().rect( + rect, + CornerRadius::same((size.y / 2.0) as u8), + t.surface2.gamma_multiply(alpha), + Stroke::new(1.0, t.line.gamma_multiply(alpha)), + egui::StrokeKind::Inside, + ); + ui.painter() + .galley(rect.min + pad, galley, t.surface_text.gamma_multiply(alpha)); + }); + ctx.request_repaint_after(std::time::Duration::from_millis(50)); + } + /// Whether back navigation has anything left to consume: an overlay, a /// settings sub-page, or a non-Home tab (back routes to Home). Mirrors /// [`Self::on_back`], so the host never falls back to the wallet chooser. @@ -667,6 +722,9 @@ impl GoblinWalletView { Tab::Me => self.me_ui(ui, wallet, cb), }); }); + // Transient "press back again" hint (first back at Home; the second + // goes to the wallet switcher, wallet left unlocked). + self.back_hint_ui(ui.ctx()); } /// 3-item bar: Wallet · Pay (center ツ) · Activity. A floating pill on most diff --git a/src/gui/views/wallets/content.rs b/src/gui/views/wallets/content.rs index 5833de48..bbfe733e 100644 --- a/src/gui/views/wallets/content.rs +++ b/src/gui/views/wallets/content.rs @@ -43,6 +43,7 @@ use crate::gui::views::wallets::wallet::RecoverySettings; use crate::gui::views::wallets::wallet::types::{WalletContentContainer, wallet_status_text}; use crate::gui::views::{Content, Modal, TitlePanel, View}; use crate::http::{ReleaseInfo, retrieve_release}; +use crate::node::Node; use crate::settings::AppUpdate; use crate::wallet::types::{ConnectionMethod, WalletTask}; use crate::wallet::{Wallet, WalletList}; @@ -52,6 +53,10 @@ pub struct WalletsContent { /// List of wallets. wallets: WalletList, + /// When Android back was last swallowed at the wallet Home, arming the + /// standard double-back-to-exit window (the first press shows a hint). + back_exit_at: Option, + /// Initial wallet creation [`Modal`] content. add_wallet_modal_content: AddWalletModal, /// Wallet opening [`Modal`] content. @@ -92,6 +97,7 @@ impl Default for WalletsContent { fn default() -> Self { Self { wallets: WalletList::default(), + back_exit_at: None, wallet_selection_content: WalletListModal::new(None, None, true), open_wallet_content: OpenWalletModal::new(), add_wallet_modal_content: AddWalletModal::default(), @@ -411,7 +417,26 @@ impl WalletsContent { // never a back fallback to the chooser. if self.wallet_content.can_back() { self.wallet_content.back(cb); + return false; } + // Wallet Home with nothing to pop: double-back to the WALLET + // SWITCHER. The first press arms a short window and shows a "press + // back again" hint; a second press inside it DESELECTS the wallet — + // it stays UNLOCKED (no close, no logout; its nostr listener keeps + // running) and the chooser shows. Quitting the app lives only at + // the switcher, via GRIM's native exit-confirmation on back. + // Sub-screens above kept normal back navigation, no hint. + if self + .back_exit_at + .map(|at| at.elapsed() <= Duration::from_secs(2)) + .unwrap_or(false) + { + self.back_exit_at = None; + self.wallets.select(None); + return false; + } + self.back_exit_at = Some(std::time::Instant::now()); + self.wallet_content.show_back_hint(); return false; } true diff --git a/src/gui/views/wallets/wallet/content.rs b/src/gui/views/wallets/wallet/content.rs index 9da2728d..a8c5fef0 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -325,6 +325,12 @@ impl WalletContent { self.goblin.take_switch_request() } + /// Show the goblin surface's brief "press back again" hint (first back of + /// the double-back-to-switcher at the wallet Home). + pub fn show_back_hint(&mut self) { + self.goblin.show_back_hint(); + } + /// Navigate back on navigation stack. Returns true if not consumed. pub fn back(&mut self, cb: &dyn PlatformCallbacks) -> bool { if self.goblin.overlay_active() {