diff --git a/mobile-app/lib/l10n/app_en.arb b/mobile-app/lib/l10n/app_en.arb index af37d696..bc16da1c 100644 --- a/mobile-app/lib/l10n/app_en.arb +++ b/mobile-app/lib/l10n/app_en.arb @@ -1935,6 +1935,10 @@ "@settingsRecoveryPhraseDone": { "description": "Done button on recovery phrase screen" }, + "settingsRecoveryAlreadyBackedUp": "I already backed up my wallet", + "@settingsRecoveryAlreadyBackedUp": { + "description": "Secondary action on the recovery phrase caution screen that dismisses the backup reminder without revealing the phrase" + }, "settingsResetTitle": "Reset Wallet", "@settingsResetTitle": { diff --git a/mobile-app/lib/l10n/app_id.arb b/mobile-app/lib/l10n/app_id.arb index cc136cf8..ea1ecd2c 100644 --- a/mobile-app/lib/l10n/app_id.arb +++ b/mobile-app/lib/l10n/app_id.arb @@ -440,6 +440,7 @@ "settingsRecoveryPhraseTitle": "Frasa Pemulihan", "settingsRecoveryPhraseDone": "Selesai", + "settingsRecoveryAlreadyBackedUp": "Saya sudah mencadangkan dompet saya", "settingsResetTitle": "Reset Dompet", "settingsResetAuthReason": "Autentikasi untuk mereset dompet", diff --git a/mobile-app/lib/l10n/app_localizations.dart b/mobile-app/lib/l10n/app_localizations.dart index 6561199c..78daba83 100644 --- a/mobile-app/lib/l10n/app_localizations.dart +++ b/mobile-app/lib/l10n/app_localizations.dart @@ -2534,6 +2534,12 @@ abstract class AppLocalizations { /// **'Done'** String get settingsRecoveryPhraseDone; + /// Secondary action on the recovery phrase caution screen that dismisses the backup reminder without revealing the phrase + /// + /// In en, this message translates to: + /// **'I already backed up my wallet'** + String get settingsRecoveryAlreadyBackedUp; + /// App bar on reset wallet caution screen /// /// In en, this message translates to: diff --git a/mobile-app/lib/l10n/app_localizations_en.dart b/mobile-app/lib/l10n/app_localizations_en.dart index f4274950..c07e27f4 100644 --- a/mobile-app/lib/l10n/app_localizations_en.dart +++ b/mobile-app/lib/l10n/app_localizations_en.dart @@ -1338,6 +1338,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settingsRecoveryPhraseDone => 'Done'; + @override + String get settingsRecoveryAlreadyBackedUp => 'I already backed up my wallet'; + @override String get settingsResetTitle => 'Reset Wallet'; diff --git a/mobile-app/lib/l10n/app_localizations_id.dart b/mobile-app/lib/l10n/app_localizations_id.dart index ed0a95ef..6efa8c4a 100644 --- a/mobile-app/lib/l10n/app_localizations_id.dart +++ b/mobile-app/lib/l10n/app_localizations_id.dart @@ -1341,6 +1341,9 @@ class AppLocalizationsId extends AppLocalizations { @override String get settingsRecoveryPhraseDone => 'Selesai'; + @override + String get settingsRecoveryAlreadyBackedUp => 'Saya sudah mencadangkan dompet saya'; + @override String get settingsResetTitle => 'Reset Dompet'; diff --git a/mobile-app/lib/providers/wallet_providers.dart b/mobile-app/lib/providers/wallet_providers.dart index 52e26f60..51993ceb 100644 --- a/mobile-app/lib/providers/wallet_providers.dart +++ b/mobile-app/lib/providers/wallet_providers.dart @@ -241,17 +241,26 @@ final recoveryPhraseViewedProvider = Provider.family((ref, walletInde return ref.watch(settingsServiceProvider).recoveryPhraseViewed(walletIndex); }); +final walletOriginProvider = Provider.family((ref, walletIndex) { + return ref.watch(settingsServiceProvider).getWalletOrigin(walletIndex); +}); + /// 0.0001 QUAN in raw units; dust below this doesn't warrant a backup nudge. final _backupNudgeBalanceThreshold = BigInt.from(10).pow(AppConstants.decimals - 4); /// Wallet index needing a recovery phrase backup reminder, or null when none. +/// Only nudges mnemonic-backed wallets created on this device (or legacy wallets +/// of unknown origin). Never nudges multisig/entrusted accounts (not +/// [RegularAccount]), hardware (keystone) wallets, or imported wallets. final backupReminderWalletIndexProvider = Provider((ref) { final active = ref.watch(activeAccountProvider).value; if (active is! RegularAccount) return null; + if (active.account.accountType != AccountType.local) return null; final walletIndex = active.account.walletIndex; if (AppConstants.debugAlwaysShowBackupNudge) return walletIndex; + if (ref.watch(walletOriginProvider(walletIndex)) == WalletOrigin.imported) return null; if (ref.watch(recoveryPhraseViewedProvider(walletIndex))) return null; final balance = ref.watch(balanceProvider).value ?? BigInt.zero; diff --git a/mobile-app/lib/services/logout_service.dart b/mobile-app/lib/services/logout_service.dart index 394f4119..8ceffea9 100644 --- a/mobile-app/lib/services/logout_service.dart +++ b/mobile-app/lib/services/logout_service.dart @@ -14,6 +14,7 @@ import 'package:resonance_network_wallet/providers/pending_multisig_executions_p import 'package:resonance_network_wallet/providers/pending_multisig_proposals_provider.dart'; import 'package:resonance_network_wallet/providers/pending_transactions_provider.dart'; import 'package:resonance_network_wallet/providers/remote_config_provider.dart'; +import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/services/firebase_messaging_service.dart'; import 'package:resonance_network_wallet/services/multisig_approval_polling_service.dart'; import 'package:resonance_network_wallet/services/multisig_cancellation_polling_service.dart'; @@ -46,6 +47,8 @@ class LogoutService { _ref.invalidate(miningRewardsProvider); _ref.read(accountsProvider.notifier).reset(); _ref.read(activeAccountProvider.notifier).reset(); + _ref.invalidate(recoveryPhraseViewedProvider); + _ref.invalidate(walletOriginProvider); _ref.read(multisigAccountsProvider.notifier).reset(); _ref.invalidate(discoveredMultisigsProvider); _ref.read(accountAssociationsProvider.notifier).reset(); diff --git a/mobile-app/lib/services/wallet_creation_service.dart b/mobile-app/lib/services/wallet_creation_service.dart index 1d9b0321..48e2a0b5 100644 --- a/mobile-app/lib/services/wallet_creation_service.dart +++ b/mobile-app/lib/services/wallet_creation_service.dart @@ -32,6 +32,7 @@ class WalletCreationService { final hasRoot = existingAccounts.any((a) => a.walletIndex == walletIndex && a.index == 0); if (!hasRoot) { + _settings.setWalletOrigin(walletIndex, WalletOrigin.created); final account = Account(walletIndex: walletIndex, index: 0, name: name, accountId: accountId); await _accounts.addAccount(account); unawaited(_referral.submitAddressToBackend()); diff --git a/mobile-app/lib/v2/screens/home/backup_reminder_banner.dart b/mobile-app/lib/v2/screens/home/backup_reminder_banner.dart index 03fa530f..ef187b54 100644 --- a/mobile-app/lib/v2/screens/home/backup_reminder_banner.dart +++ b/mobile-app/lib/v2/screens/home/backup_reminder_banner.dart @@ -19,7 +19,9 @@ class BackupReminderBanner extends ConsumerWidget { return GestureDetector( onTap: () => Navigator.push( context, - MaterialPageRoute(builder: (_) => RecoveryPhraseConfirmationScreen(walletIndex: walletIndex)), + MaterialPageRoute( + builder: (_) => RecoveryPhraseConfirmationScreen(walletIndex: walletIndex, showAlreadyBackedUp: true), + ), ), child: Container( padding: const EdgeInsets.all(16), diff --git a/mobile-app/lib/v2/screens/import/import_wallet_screen.dart b/mobile-app/lib/v2/screens/import/import_wallet_screen.dart index 10db1dfd..052dab9c 100644 --- a/mobile-app/lib/v2/screens/import/import_wallet_screen.dart +++ b/mobile-app/lib/v2/screens/import/import_wallet_screen.dart @@ -112,8 +112,8 @@ class _ImportWalletScreenV2State extends ConsumerState { ref.invalidate(activeAccountProvider); _settingsService.setReferralCheckCompleted(); _settingsService.setExistingUserSeenPromoVideo(); - _settingsService.setRecoveryPhraseViewed(widget.walletIndex); - ref.invalidate(recoveryPhraseViewedProvider(widget.walletIndex)); + _settingsService.setWalletOrigin(widget.walletIndex, WalletOrigin.imported); + ref.invalidate(walletOriginProvider(widget.walletIndex)); if (ref.read(remoteConfigProvider).enableRemoteNotifications && widget.walletIndex == 0) { ref.read(firebaseMessagingServiceProvider).registerDeviceIfPossible(); diff --git a/mobile-app/lib/v2/screens/settings/recovery_phrase_confirmation_screen.dart b/mobile-app/lib/v2/screens/settings/recovery_phrase_confirmation_screen.dart index 48105c8d..cfa6b1cb 100644 --- a/mobile-app/lib/v2/screens/settings/recovery_phrase_confirmation_screen.dart +++ b/mobile-app/lib/v2/screens/settings/recovery_phrase_confirmation_screen.dart @@ -1,16 +1,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; +import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/v2/screens/settings/recovery_phrase_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_caution_scaffold.dart'; class RecoveryPhraseConfirmationScreen extends ConsumerStatefulWidget { - const RecoveryPhraseConfirmationScreen({super.key, required this.walletIndex}); + const RecoveryPhraseConfirmationScreen({super.key, required this.walletIndex, this.showAlreadyBackedUp = false}); final int walletIndex; + /// When true (the home backup nudge), offers an "I already backed up" action + /// that dismisses the nudge without revealing the phrase. + final bool showAlreadyBackedUp; + @override ConsumerState createState() => _RecoveryPhraseConfirmationScreenState(); } @@ -31,6 +36,12 @@ class _RecoveryPhraseConfirmationScreenState extends ConsumerState { ref.invalidate(accountsProvider); ref.invalidate(activeAccountProvider); + ref.invalidate(walletOriginProvider(_walletIndex)); + ref.invalidate(recoveryPhraseViewedProvider(_walletIndex)); if (ref.read(remoteConfigProvider).enableRemoteNotifications) { ref.read(firebaseMessagingServiceProvider).registerDeviceIfPossible(); diff --git a/quantus_sdk/lib/src/models/account.dart b/quantus_sdk/lib/src/models/account.dart index 726d8f89..79671681 100644 --- a/quantus_sdk/lib/src/models/account.dart +++ b/quantus_sdk/lib/src/models/account.dart @@ -3,6 +3,10 @@ import 'package:quantus_sdk/src/models/base_account.dart'; enum AccountType { local, keystone, external, encrypted } +/// How the mnemonic-backed wallet at a given walletIndex came to exist. +/// Absent (legacy wallets) is treated as unknown. +enum WalletOrigin { created, imported } + @immutable class Account implements BaseAccount { final int walletIndex; diff --git a/quantus_sdk/lib/src/services/settings_service.dart b/quantus_sdk/lib/src/services/settings_service.dart index eebc319d..66be6906 100644 --- a/quantus_sdk/lib/src/services/settings_service.dart +++ b/quantus_sdk/lib/src/services/settings_service.dart @@ -166,6 +166,8 @@ class SettingsService { } await saveAccounts(remaining); await deleteMnemonic(walletIndex); + await _prefs.remove(_walletOriginKey(walletIndex)); + await _prefs.remove(_recoveryPhraseViewedKey(walletIndex)); } Future setActiveAccount(DisplayAccount account) async { @@ -571,6 +573,17 @@ class SettingsService { _prefs.setBool(_recoveryPhraseViewedKey(walletIndex), true); } + String _walletOriginKey(int walletIndex) => 'wallet_origin_$walletIndex'; + + WalletOrigin? getWalletOrigin(int walletIndex) { + final value = _prefs.getString(_walletOriginKey(walletIndex)); + return value == null ? null : WalletOrigin.values.asNameMap()[value]; + } + + void setWalletOrigin(int walletIndex, WalletOrigin origin) { + _prefs.setString(_walletOriginKey(walletIndex), origin.name); + } + bool existingUserSeenPromoVideo() { return _prefs.getBool(existingUserSeenPromoVideoKey) ?? false; }