Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mobile-app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
1 change: 1 addition & 0 deletions mobile-app/lib/l10n/app_id.arb
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@

"settingsRecoveryPhraseTitle": "Frasa Pemulihan",
"settingsRecoveryPhraseDone": "Selesai",
"settingsRecoveryAlreadyBackedUp": "Saya sudah mencadangkan dompet saya",

"settingsResetTitle": "Reset Dompet",
"settingsResetAuthReason": "Autentikasi untuk mereset dompet",
Expand Down
6 changes: 6 additions & 0 deletions mobile-app/lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions mobile-app/lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
3 changes: 3 additions & 0 deletions mobile-app/lib/l10n/app_localizations_id.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
9 changes: 9 additions & 0 deletions mobile-app/lib/providers/wallet_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -241,17 +241,26 @@ final recoveryPhraseViewedProvider = Provider.family<bool, int>((ref, walletInde
return ref.watch(settingsServiceProvider).recoveryPhraseViewed(walletIndex);
});

final walletOriginProvider = Provider.family<WalletOrigin?, int>((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<int?>((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;
Expand Down
3 changes: 3 additions & 0 deletions mobile-app/lib/services/logout_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions mobile-app/lib/services/wallet_creation_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
4 changes: 3 additions & 1 deletion mobile-app/lib/v2/screens/home/backup_reminder_banner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
4 changes: 2 additions & 2 deletions mobile-app/lib/v2/screens/import/import_wallet_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ class _ImportWalletScreenV2State extends ConsumerState<ImportWalletScreenV2> {
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this necessary? we can just not displaying it at all if user already back up, can't we?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an option on the screen because the screen is also used from settings. So when users open it from settings the button doesn't make sense.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh okayy


@override
ConsumerState<RecoveryPhraseConfirmationScreen> createState() => _RecoveryPhraseConfirmationScreenState();
}
Expand All @@ -31,6 +36,12 @@ class _RecoveryPhraseConfirmationScreenState extends ConsumerState<RecoveryPhras
}
}

void _onAlreadyBackedUp() {
ref.read(settingsServiceProvider).setRecoveryPhraseViewed(widget.walletIndex);
ref.invalidate(recoveryPhraseViewedProvider(widget.walletIndex));
Navigator.of(context).pop();
}

@override
Widget build(BuildContext context) {
final l10n = ref.watch(l10nProvider);
Expand All @@ -40,6 +51,8 @@ class _RecoveryPhraseConfirmationScreenState extends ConsumerState<RecoveryPhras
data: SettingsCautionScaffoldData.recoveryPhrase(l10n),
continueLabel: l10n.commonContinue,
onContinue: _onContinue,
secondaryLabel: widget.showAlreadyBackedUp ? l10n.settingsRecoveryAlreadyBackedUp : null,
onSecondary: widget.showAlreadyBackedUp ? _onAlreadyBackedUp : null,
);
}
}
14 changes: 14 additions & 0 deletions mobile-app/lib/v2/screens/settings/settings_caution_scaffold.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class SettingsCautionScaffold extends StatelessWidget {
final SettingsCautionScaffoldData data;
final SettingsDividerStyle betweenBulletsStyle;
final bool continueButtonLoading;
final String? secondaryLabel;
final VoidCallback? onSecondary;

const SettingsCautionScaffold({
super.key,
Expand All @@ -57,6 +59,8 @@ class SettingsCautionScaffold extends StatelessWidget {
this.onCheckboxChanged,
this.betweenBulletsStyle = SettingsDividerStyle.list,
this.continueButtonLoading = false,
this.secondaryLabel,
this.onSecondary,
});

@override
Expand Down Expand Up @@ -90,6 +94,8 @@ class SettingsCautionScaffold extends StatelessWidget {
onCheckboxChanged: onCheckboxChanged,
onContinue: onContinue,
continueButtonLoading: continueButtonLoading,
secondaryLabel: secondaryLabel,
onSecondary: onSecondary,
),
);
}
Expand All @@ -103,6 +109,8 @@ class _SettingsCautionBottom extends StatelessWidget {
required this.onCheckboxChanged,
required this.onContinue,
required this.continueButtonLoading,
required this.secondaryLabel,
required this.onSecondary,
});

final String? checkboxLabel;
Expand All @@ -111,6 +119,8 @@ class _SettingsCautionBottom extends StatelessWidget {
final VoidCallback? onCheckboxChanged;
final VoidCallback onContinue;
final bool continueButtonLoading;
final String? secondaryLabel;
final VoidCallback? onSecondary;

@override
Widget build(BuildContext context) {
Expand All @@ -129,6 +139,10 @@ class _SettingsCautionBottom extends StatelessWidget {
isDisabled: checkboxLabel != null && !checked,
isLoading: continueButtonLoading,
),
if (secondaryLabel != null) ...[
const SizedBox(height: 12),
QuantusButton.simple(label: secondaryLabel!, onTap: onSecondary, variant: ButtonVariant.transparent),
],
],
),
);
Expand Down
3 changes: 3 additions & 0 deletions mobile-app/lib/v2/screens/welcome/welcome_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:quantus_sdk/quantus_sdk.dart';
import 'package:resonance_network_wallet/providers/account_providers.dart';
import 'package:resonance_network_wallet/providers/l10n_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/wallet_creation_service.dart';
import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart';
Expand Down Expand Up @@ -48,6 +49,8 @@ class _WelcomeScreenV2State extends ConsumerState<WelcomeScreenV2> {

ref.invalidate(accountsProvider);
ref.invalidate(activeAccountProvider);
ref.invalidate(walletOriginProvider(_walletIndex));
ref.invalidate(recoveryPhraseViewedProvider(_walletIndex));

if (ref.read(remoteConfigProvider).enableRemoteNotifications) {
ref.read(firebaseMessagingServiceProvider).registerDeviceIfPossible();
Expand Down
4 changes: 4 additions & 0 deletions quantus_sdk/lib/src/models/account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions quantus_sdk/lib/src/services/settings_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ class SettingsService {
}
await saveAccounts(remaining);
await deleteMnemonic(walletIndex);
await _prefs.remove(_walletOriginKey(walletIndex));
await _prefs.remove(_recoveryPhraseViewedKey(walletIndex));
}

Future<void> setActiveAccount(DisplayAccount account) async {
Expand Down Expand Up @@ -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;
}
Expand Down
Loading