Qr code passphrase restoration flow fix (#1694)
* add passphrase to credentials * prevent double restoring * fix conflict, simplify restricting user from tapping restore multiple times --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
Serhii committed
Sep 24, 2024 at 06:40 UTC
cf1e8a306c3117feb3be42c81c5abe0fd2fe7769
5 files changed
+100
-42
lib/src/screens/restore/restore_options_page.dart
+61
-35
@@ -31,30 +31,45 @@ class RestoreOptionsPage extends BasePage {
31
32
final bool isNewInstall;
33
34
+ @override
35
+ Widget body(BuildContext context) {
36
+ return _RestoreOptionsBody(isNewInstall: isNewInstall, themeType: currentTheme.type);
37
+ }
38
+}
39
+
40
+class _RestoreOptionsBody extends StatefulWidget {
41
+ const _RestoreOptionsBody({required this.isNewInstall, required this.themeType});
42
+
43
+ final bool isNewInstall;
44
+ final ThemeType themeType;
45
+
46
+ @override
47
+ _RestoreOptionsBodyState createState() => _RestoreOptionsBodyState();
48
+}
49
+
50
+class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
51
+ bool isRestoring = false;
52
+
53
bool get _doesSupportHardwareWallets {
54
if (!DeviceInfo.instance.isMobile) {
55
return false;
56
}
57
58
if (isMoneroOnly) {
40
- return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
41
- .isNotEmpty;
59
+ return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS).isNotEmpty;
60
}
61
62
return true;
63
}
64
65
@override
48
- Widget body(BuildContext context) {
66
+ Widget build(BuildContext context) {
67
final mainImageColor = Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor;
68
final brightImageColor = Theme.of(context).extension<InfoTheme>()!.textColor;
51
- final imageColor = currentTheme.type == ThemeType.bright ? brightImageColor : mainImageColor;
69
+ final imageColor = widget.themeType == ThemeType.bright ? brightImageColor : mainImageColor;
70
final imageLedger = Image.asset('assets/images/ledger_nano.png', width: 40, color: imageColor);
71
final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor);
72
final imageBackup = Image.asset('assets/images/backup.png', color: imageColor);
55
- final qrCode = Image.asset('assets/images/restore_qr.png', color: imageColor);
56
-
57
-
73
74
return Center(
75
child: Container(
@@ -66,16 +81,17 @@ class RestoreOptionsPage extends BasePage {
81
children: <Widget>[
82
OptionTile(
83
key: ValueKey('restore_options_from_seeds_button_key'),
69
- onPressed: () => Navigator.pushNamed(
70
- context,
71
- Routes.restoreWalletFromSeedKeys,
72
- arguments: isNewInstall,
73
- ),
84
+ onPressed: () =>
85
+ Navigator.pushNamed(
86
+ context,
87
+ Routes.restoreWalletFromSeedKeys,
88
+ arguments: widget.isNewInstall,
89
+ ),
90
image: imageSeedKeys,
91
title: S.of(context).restore_title_from_seed_keys,
92
description: S.of(context).restore_description_from_seed_keys,
93
),
78
- if (isNewInstall)
94
+ if (widget.isNewInstall)
95
Padding(
96
padding: EdgeInsets.only(top: 24),
97
child: OptionTile(
@@ -91,9 +107,8 @@ class RestoreOptionsPage extends BasePage {
107
padding: EdgeInsets.only(top: 24),
108
child: OptionTile(
109
key: ValueKey('restore_options_from_hardware_wallet_button_key'),
94
- onPressed: () => Navigator.pushNamed(
95
- context, Routes.restoreWalletFromHardwareWallet,
96
- arguments: isNewInstall),
110
+ onPressed: () => Navigator.pushNamed(context, Routes.restoreWalletFromHardwareWallet,
111
+ arguments: widget.isNewInstall),
112
image: imageLedger,
113
title: S.of(context).restore_title_from_hardware_wallet,
114
description: S.of(context).restore_description_from_hardware_wallet,
@@ -119,36 +134,47 @@ class RestoreOptionsPage extends BasePage {
134
}
135
136
void _onWalletCreateFailure(BuildContext context, String error) {
122
- showPopUp<void>(
123
- context: context,
124
- builder: (BuildContext context) {
125
- return AlertWithOneAction(
126
- alertTitle: S.current.error,
127
- alertContent: error,
128
- buttonText: S.of(context).ok,
129
- buttonAction: () => Navigator.of(context).pop());
130
- });
137
+ setState(() {
138
+ isRestoring = false;
139
+ });
140
+
141
+ WidgetsBinding.instance.addPostFrameCallback((_) {
142
+ showPopUp<void>(
143
+ context: context,
144
+ builder: (BuildContext context) {
145
+ return AlertWithOneAction(
146
+ alertTitle: S.current.error,
147
+ alertContent: error,
148
+ buttonText: S.of(context).ok,
149
+ buttonAction: () => Navigator.of(context).pop());
150
+ });
151
+ });
152
+
153
}
154
155
Future<void> _onScanQRCode(BuildContext context) async {
134
- final isCameraPermissionGranted =
135
- await PermissionHandler.checkPermission(Permission.camera, context);
156
+ final isCameraPermissionGranted = await PermissionHandler.checkPermission(Permission.camera, context);
157
158
if (!isCameraPermissionGranted) return;
159
bool isPinSet = false;
139
- if (isNewInstall) {
160
+ if (widget.isNewInstall) {
161
await Navigator.pushNamed(context, Routes.setupPin,
162
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
142
- setupPinContext.close();
143
- isPinSet = true;
144
- });
163
+ setupPinContext.close();
164
+ isPinSet = true;
165
+ });
166
}
146
- if (!isNewInstall || isPinSet) {
167
+ if (!widget.isNewInstall || isPinSet) {
168
try {
169
+ if (isRestoring) {
170
+ return;
171
+ }
172
+ setState(() {
173
+ isRestoring = true;
174
+ });
175
final restoreWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
176
150
- final restoreFromQRViewModel =
151
- getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
177
+ final restoreFromQRViewModel = getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
178
179
await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
180
if (restoreFromQRViewModel.state is FailureState) {
@@ -160,4 +186,4 @@ class RestoreOptionsPage extends BasePage {
186
}
187
}
188
}
163
-}
189
+}
\ No newline at end of file
lib/src/widgets/alert_with_no_action.dart.dart
new
+30
@@ -0,0 +1,30 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
3
+
4
+class AlertWithNoAction extends BaseAlertDialog {
5
+ AlertWithNoAction({
6
+ required this.alertTitle,
7
+ required this.alertContent,
8
+ this.alertBarrierDismissible = true,
9
+ Key? key,
10
+ });
11
+
12
+ final String alertTitle;
13
+ final String alertContent;
14
+ final bool alertBarrierDismissible;
15
+
16
+ @override
17
+ String get titleText => alertTitle;
18
+
19
+ @override
20
+ String get contentText => alertContent;
21
+
22
+ @override
23
+ bool get barrierDismissible => alertBarrierDismissible;
24
+
25
+ @override
26
+ bool get isBottomDividerExists => false;
27
+
28
+ @override
29
+ Widget actionButtons(BuildContext context) => Container(height: 60);
30
+}
lib/src/widgets/base_alert_dialog.dart
+3
-1
@@ -17,6 +17,8 @@ class BaseAlertDialog extends StatelessWidget {
17
18
bool get isDividerExists => false;
19
20
+ bool get isBottomDividerExists => true;
21
+
22
VoidCallback get actionLeft => () {};
23
24
VoidCallback get actionRight => () {};
@@ -205,7 +207,7 @@ class BaseAlertDialog extends StatelessWidget {
207
)
208
],
209
),
208
- const HorizontalSectionDivider(),
210
+ if (isBottomDividerExists) const HorizontalSectionDivider(),
211
ClipRRect(
212
borderRadius: BorderRadius.all(Radius.circular(30)),
213
child: actionButtons(context))
lib/view_model/restore/restore_from_qr_vm.dart
+2
-6
@@ -56,12 +56,8 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
56
WalletCredentials getCredentialsFromRestoredWallet(
57
dynamic options, RestoredWallet restoreWallet) {
58
final password = generateWalletPassword();
59
- String? passphrase;
59
+
60
DerivationInfo? derivationInfo;
61
- if (options != null) {
62
- derivationInfo = options["derivationInfo"] as DerivationInfo?;
63
- passphrase = options["passphrase"] as String?;
64
- }
61
derivationInfo ??= getDefaultCreateDerivation();
62
63
switch (restoreWallet.restoreMode) {
@@ -119,7 +115,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
115
name: name,
116
mnemonic: restoreWallet.mnemonicSeed ?? '',
117
password: password,
122
- passphrase: passphrase,
118
+ passphrase: restoreWallet.passphrase,
119
derivationType: derivationInfo!.derivationType!,
120
derivationPath: derivationInfo.derivationPath!,
121
);
lib/view_model/restore/restore_wallet.dart
+4
@@ -10,6 +10,7 @@ class RestoredWallet {
10
this.spendKey,
11
this.viewKey,
12
this.mnemonicSeed,
13
+ this.passphrase,
14
this.txAmount,
15
this.txDescription,
16
this.recipientName,
@@ -23,6 +24,7 @@ class RestoredWallet {
24
final String? spendKey;
25
final String? viewKey;
26
final String? mnemonicSeed;
27
+ final String? passphrase;
28
final String? txAmount;
29
final String? txDescription;
30
final String? recipientName;
@@ -46,11 +48,13 @@ class RestoredWallet {
48
final height = json['height'] as String?;
49
final mnemonic_seed = json['mnemonic_seed'] as String?;
50
final seed = json['seed'] as String? ?? json['hexSeed'] as String?;
51
+ final passphrase = json['passphrase'] as String?;
52
return RestoredWallet(
53
restoreMode: json['mode'] as WalletRestoreMode,
54
type: json['type'] as WalletType,
55
address: json['address'] as String?,
56
mnemonicSeed: mnemonic_seed ?? seed,
57
+ passphrase: passphrase,
58
height: height != null ? int.parse(height) : 0,
59
);
60
}