From 323a1df6e9a22c6f9ad08a5986a4afca1977c54c Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 1 Sep 2026 10:01:07 +0100 Subject: [PATCH] feat: remind users with a balance to back up their recovery phrase --- .../robots/pre_seed_page_robot.dart | 2 +- lib/new-ui/pages/home_page.dart | 42 ++++++++-- .../seed/dismiss_seed_verification_page.dart | 57 +++++++++++++ .../seed/open_wallet_after_seed_flow.dart | 1 - .../pages/seed/seed_backup_reminder_page.dart | 71 ++++++++++++++++ .../coins_page/seed_backup_reminder_card.dart | 80 +++++++++++++++++++ lib/router.dart | 4 +- .../seed_verification_success_view.dart | 4 +- 8 files changed, 249 insertions(+), 12 deletions(-) create mode 100644 lib/new-ui/pages/seed/dismiss_seed_verification_page.dart create mode 100644 lib/new-ui/pages/seed/seed_backup_reminder_page.dart create mode 100644 lib/new-ui/widgets/coins_page/seed_backup_reminder_card.dart diff --git a/integration_test/robots/pre_seed_page_robot.dart b/integration_test/robots/pre_seed_page_robot.dart index c3eeb5fe5..0e0eab4a1 100644 --- a/integration_test/robots/pre_seed_page_robot.dart +++ b/integration_test/robots/pre_seed_page_robot.dart @@ -1,4 +1,4 @@ -import "package:cake_wallet/new-ui/pages/seed/pre_seed_page.dar"'; +import "package:cake_wallet/new-ui/pages/seed/pre_seed_page.dart"; import 'package:flutter_test/flutter_test.dart'; import '../components/common_test_cases.dart'; diff --git a/lib/new-ui/pages/home_page.dart b/lib/new-ui/pages/home_page.dart index f62440952..d34151d19 100644 --- a/lib/new-ui/pages/home_page.dart +++ b/lib/new-ui/pages/home_page.dart @@ -1,15 +1,18 @@ import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import "package:cake_wallet/main.dart"; import 'package:cake_wallet/new-ui/modal_navigator.dart'; import 'package:cake_wallet/new-ui/pages/account_customizer.dart'; import 'package:cake_wallet/new-ui/pages/card_customizer.dart'; +import "package:cake_wallet/new-ui/pages/seed/seed_backup_reminder_page.dart"; import 'package:cake_wallet/new-ui/pages/settings_page.dart'; import 'package:cake_wallet/new-ui/viewmodels/card_customizer/card_customizer_bloc.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/action_row/coin_action_row.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/assets_history_section.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/cards/cards_view.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/mweb_ad.dart'; +import "package:cake_wallet/new-ui/widgets/coins_page/seed_backup_reminder_card.dart"; import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/top_bar.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/unconfirmed_balance_widget.dart'; import 'package:cake_wallet/new-ui/widgets/coins_page/wallet_info.dart'; @@ -18,9 +21,6 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart'; -import "package:cw_core/amount/money.dart"; -import "package:cw_core/crypto_currency.dart"; -import "package:cw_core/transaction_direction.dart"; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -38,7 +38,7 @@ class NewHomePage extends StatefulWidget { State createState() => _NewHomePageState(); } -class _NewHomePageState extends State { +class _NewHomePageState extends State with RouteAware { MoneroAccountListViewModel? accountListViewModel; bool _lightningMode = false; @@ -53,9 +53,9 @@ class _NewHomePageState extends State { }); }); - reaction((_) => widget.dashboardViewModel.isMigratingToIronwood, (val) { + reaction((_) => widget.dashboardViewModel.isMigratingToIronwood, (val) { if (val && !widget.dashboardViewModel.settingsStore.zcashMigrationModalViewed) { - if(!context.mounted) { + if (!context.mounted) { return; } widget.dashboardViewModel.settingsStore.zcashMigrationModalViewed = true; @@ -72,6 +72,21 @@ class _NewHomePageState extends State { }); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute); + } + + @override + void dispose() { + routeObserver.unsubscribe(this); + super.dispose(); + } + + @override + void didPopNext() => widget.dashboardViewModel.loadSeedBackupReminder(); + void _setAccountViewModel() { accountListViewModel = widget.dashboardViewModel.balanceViewModel.hasAccounts ? getIt.get() @@ -191,6 +206,10 @@ class _NewHomePageState extends State { MwebAd( dashboardViewModel: widget.dashboardViewModel, ), + SeedBackupReminderCard( + dashboardViewModel: widget.dashboardViewModel, + onTap: openSeedBackupReminder, + ), ], ); }, @@ -231,6 +250,17 @@ class _NewHomePageState extends State { ); } + void openSeedBackupReminder() { + Navigator.of(context).push( + CupertinoPageRoute( + builder: (_) => SeedBackupReminderPage( + dashboardViewModel: widget.dashboardViewModel, + authService: getIt.get(), + ), + ), + ); + } + void openAccountCustomizer() async { await CupertinoScaffold.showCupertinoModalBottomSheet( barrierColor: Colors.black.withAlpha(60), diff --git a/lib/new-ui/pages/seed/dismiss_seed_verification_page.dart b/lib/new-ui/pages/seed/dismiss_seed_verification_page.dart new file mode 100644 index 000000000..765168cd7 --- /dev/null +++ b/lib/new-ui/pages/seed/dismiss_seed_verification_page.dart @@ -0,0 +1,57 @@ +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/new-ui/widgets/seed/seed_page_header.dart"; +import "package:cake_wallet/new-ui/widgets/seed/seed_page_scaffold.dart"; +import "package:cake_wallet/new-ui/widgets/stacked_buttons.dart"; +import "package:cake_wallet/src/widgets/cake_image_widget.dart"; +import "package:flutter/material.dart"; + +class DismissSeedVerificationPage extends StatelessWidget { + const DismissSeedVerificationPage({required this.onConfirm, super.key}); + + final VoidCallback onConfirm; + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + return SeedPageScaffold( + content: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 295), + child: SeedPageHeader( + image: const CakeImageWidget( + imageUrl: "assets/new-ui/warning_triangle.svg", + width: 125, + height: 111.72, + ), + imageSpacing: 48, + titleSpacing: 24, + title: S.of(context).dismiss_verification_title, + description: Text.rich( + TextSpan( + children: [ + TextSpan(text: "${S.of(context).dismiss_verification_description}\n"), + TextSpan( + text: S.of(context).seed_display_path, + style: TextStyle(color: colors.onSurface), + ), + ], + ), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colors.onSurfaceVariant, + letterSpacing: -0.07, + ), + ), + ), + ), + footer: StackedButtons( + primaryKey: const ValueKey("dismiss_seed_verification_page_confirm_button_key"), + primaryText: S.of(context).dismiss_verification_confirm, + onPrimary: onConfirm, + secondaryKey: const ValueKey("dismiss_seed_verification_page_back_button_key"), + secondaryText: S.of(context).seed_alert_back, + onSecondary: () => Navigator.of(context).pop(), + ), + ); + } +} diff --git a/lib/new-ui/pages/seed/open_wallet_after_seed_flow.dart b/lib/new-ui/pages/seed/open_wallet_after_seed_flow.dart index 22d55117d..edc102abe 100644 --- a/lib/new-ui/pages/seed/open_wallet_after_seed_flow.dart +++ b/lib/new-ui/pages/seed/open_wallet_after_seed_flow.dart @@ -2,7 +2,6 @@ import "package:cake_wallet/routes.dart"; import "package:cw_core/wallet_type.dart"; import "package:flutter/widgets.dart"; -// Bitcoin wallets set up lightning username right after the seed step. void openWalletAfterSeedFlow(BuildContext context, WalletType walletType) { if (walletType == WalletType.bitcoin) { Navigator.of(context).pushNamed(Routes.lightningUsernamePage, arguments: true); diff --git a/lib/new-ui/pages/seed/seed_backup_reminder_page.dart b/lib/new-ui/pages/seed/seed_backup_reminder_page.dart new file mode 100644 index 000000000..d2558ba3f --- /dev/null +++ b/lib/new-ui/pages/seed/seed_backup_reminder_page.dart @@ -0,0 +1,71 @@ +import "package:cake_wallet/core/auth_service.dart"; +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/new-ui/pages/seed/dismiss_seed_verification_page.dart"; +import "package:cake_wallet/new-ui/widgets/seed/seed_hero_image.dart"; +import "package:cake_wallet/new-ui/widgets/seed/seed_page_header.dart"; +import "package:cake_wallet/new-ui/widgets/seed/seed_page_scaffold.dart"; +import "package:cake_wallet/new-ui/widgets/stacked_buttons.dart"; +import "package:cake_wallet/routes.dart"; +import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; + +class SeedBackupReminderPage extends StatelessWidget { + const SeedBackupReminderPage({ + required this.dashboardViewModel, + required this.authService, + super.key, + }); + + final DashboardViewModel dashboardViewModel; + final AuthService authService; + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + return SeedPageScaffold( + content: SeedPageHeader( + image: const SeedHeroImage(), + title: S.of(context).recovery_phrase_intro_title, + description: Text( + S.of(context).recovery_phrase_intro_description, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colors.onSurfaceVariant, + letterSpacing: -0.07, + ), + ), + ), + footer: StackedButtons( + primaryKey: const ValueKey("seed_backup_reminder_page_button_key"), + primaryText: S.of(context).show_recovery_phrase, + onPrimary: () => authService.authenticateAction( + context, + route: Routes.seed, + arguments: false, + conditionToDetermineIfToUse2FA: + dashboardViewModel.settingsStore.shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + ), + secondaryKey: const ValueKey("seed_backup_reminder_page_dismiss_button_key"), + secondaryText: S.of(context).dismiss_this_step, + onSecondary: () => Navigator.of(context).push( + CupertinoPageRoute( + builder: (_) => DismissSeedVerificationPage(onConfirm: () => _dismiss(context)), + ), + ), + secondaryAsLink: true, + ), + ); + } + + Future _dismiss(BuildContext context) async { + await dashboardViewModel.dismissSeedBackupReminder(); + + if (!context.mounted) { + return; + } + + Navigator.of(context).popUntil((route) => route.isFirst); + } +} diff --git a/lib/new-ui/widgets/coins_page/seed_backup_reminder_card.dart b/lib/new-ui/widgets/coins_page/seed_backup_reminder_card.dart new file mode 100644 index 000000000..169440e0c --- /dev/null +++ b/lib/new-ui/widgets/coins_page/seed_backup_reminder_card.dart @@ -0,0 +1,80 @@ +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/src/widgets/cake_image_widget.dart"; +import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart"; +import "package:flutter/material.dart"; +import "package:flutter_mobx/flutter_mobx.dart"; + +class SeedBackupReminderCard extends StatelessWidget { + const SeedBackupReminderCard({required this.dashboardViewModel, required this.onTap, super.key}); + + final DashboardViewModel dashboardViewModel; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) => Observer( + builder: (_) { + if (!dashboardViewModel.shouldShowSeedBackupReminder) { + return const SizedBox.shrink(); + } + + final colors = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + return Padding( + padding: const EdgeInsets.only(left: 18, right: 18, top: 24), + child: MergeSemantics( + child: Semantics( + button: true, + child: GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colors.surfaceContainer, + borderRadius: BorderRadius.circular(20), + ), + child: Row( + children: [ + ExcludeSemantics( + child: CakeImageWidget( + imageUrl: "assets/new-ui/key.svg", + width: 24, + height: 24, + colorFilter: ColorFilter.mode(colors.primary, BlendMode.srcIn), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4, + children: [ + Text( + S.of(context).back_up_recovery_phrase, + style: textTheme.labelLarge?.copyWith( + color: colors.onSurface, + letterSpacing: -0.07, + ), + ), + Text( + S.of(context).back_up_recovery_phrase_description, + style: textTheme.bodySmall?.copyWith( + color: colors.onSurfaceVariant, + letterSpacing: -0.06, + ), + ), + ], + ), + ), + const SizedBox(width: 8), + Icon(Icons.chevron_right, size: 16, color: colors.onSurfaceVariant), + ], + ), + ), + ), + ), + ), + ); + }, + ); +} diff --git a/lib/router.dart b/lib/router.dart index 256f92bb1..2131d6aa5 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -10,8 +10,8 @@ import 'package:cake_wallet/new-ui/pages/bridge/bridge_receiving_wallet_page.dar import 'package:cake_wallet/new-ui/pages/coin_control_page.dart'; import 'package:cake_wallet/new-ui/pages/addresses_page.dart'; import 'package:cake_wallet/new-ui/pages/lightning_username_page.dart'; -import 'package:cake_wallet/new-ui/pages/seed/pre_seed_page.dart'; -import 'package:cake_wallet/new-ui/pages/seed/show_keys_disclaimer_page.dart'; +import "package:cake_wallet/new-ui/pages/seed/pre_seed_page.dart"; +import "package:cake_wallet/new-ui/pages/seed/show_keys_disclaimer_page.dart"; import "package:cake_wallet/new-ui/pages/receive_page.dart"; import 'package:cake_wallet/new-ui/pages/send_page.dart'; import 'package:cake_wallet/new-ui/widgets/hardware_wallet/sync_key_images_sheet.dart'; diff --git a/lib/src/screens/seed/seed_verification/seed_verification_success_view.dart b/lib/src/screens/seed/seed_verification/seed_verification_success_view.dart index ad64a2a81..cb2fec137 100644 --- a/lib/src/screens/seed/seed_verification/seed_verification_success_view.dart +++ b/lib/src/screens/seed/seed_verification/seed_verification_success_view.dart @@ -1,9 +1,9 @@ import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/new-ui/pages/seed/open_wallet_after_seed_flow.dart'; +import "package:cake_wallet/new-ui/pages/seed/open_wallet_after_seed_flow.dart"; import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/themes/core/material_base_theme.dart'; -import 'package:cake_wallet/view_model/wallet_seed_view_model.dart'; +import "package:cake_wallet/view_model/wallet_seed_view_model.dart"; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart';