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() {