mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-16 09:17:35 +00:00
Qr code passphrase restoration flow fix (#1694)
Some checks are pending
Cache Dependencies / test (push) Waiting to run
Some checks are pending
Cache Dependencies / test (push) Waiting to run
* 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>
This commit is contained in:
parent
094b5ec82e
commit
cf1e8a306c
5 changed files with 100 additions and 42 deletions
|
@ -31,30 +31,45 @@ class RestoreOptionsPage extends BasePage {
|
||||||
|
|
||||||
final bool isNewInstall;
|
final bool isNewInstall;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget body(BuildContext context) {
|
||||||
|
return _RestoreOptionsBody(isNewInstall: isNewInstall, themeType: currentTheme.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RestoreOptionsBody extends StatefulWidget {
|
||||||
|
const _RestoreOptionsBody({required this.isNewInstall, required this.themeType});
|
||||||
|
|
||||||
|
final bool isNewInstall;
|
||||||
|
final ThemeType themeType;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_RestoreOptionsBodyState createState() => _RestoreOptionsBodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
|
||||||
|
bool isRestoring = false;
|
||||||
|
|
||||||
bool get _doesSupportHardwareWallets {
|
bool get _doesSupportHardwareWallets {
|
||||||
if (!DeviceInfo.instance.isMobile) {
|
if (!DeviceInfo.instance.isMobile) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMoneroOnly) {
|
if (isMoneroOnly) {
|
||||||
return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
|
return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS).isNotEmpty;
|
||||||
.isNotEmpty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final mainImageColor = Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor;
|
final mainImageColor = Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor;
|
||||||
final brightImageColor = Theme.of(context).extension<InfoTheme>()!.textColor;
|
final brightImageColor = Theme.of(context).extension<InfoTheme>()!.textColor;
|
||||||
final imageColor = currentTheme.type == ThemeType.bright ? brightImageColor : mainImageColor;
|
final imageColor = widget.themeType == ThemeType.bright ? brightImageColor : mainImageColor;
|
||||||
final imageLedger = Image.asset('assets/images/ledger_nano.png', width: 40, color: imageColor);
|
final imageLedger = Image.asset('assets/images/ledger_nano.png', width: 40, color: imageColor);
|
||||||
final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor);
|
final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor);
|
||||||
final imageBackup = Image.asset('assets/images/backup.png', color: imageColor);
|
final imageBackup = Image.asset('assets/images/backup.png', color: imageColor);
|
||||||
final qrCode = Image.asset('assets/images/restore_qr.png', color: imageColor);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -66,16 +81,17 @@ class RestoreOptionsPage extends BasePage {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
OptionTile(
|
OptionTile(
|
||||||
key: ValueKey('restore_options_from_seeds_button_key'),
|
key: ValueKey('restore_options_from_seeds_button_key'),
|
||||||
onPressed: () => Navigator.pushNamed(
|
onPressed: () =>
|
||||||
context,
|
Navigator.pushNamed(
|
||||||
Routes.restoreWalletFromSeedKeys,
|
context,
|
||||||
arguments: isNewInstall,
|
Routes.restoreWalletFromSeedKeys,
|
||||||
),
|
arguments: widget.isNewInstall,
|
||||||
|
),
|
||||||
image: imageSeedKeys,
|
image: imageSeedKeys,
|
||||||
title: S.of(context).restore_title_from_seed_keys,
|
title: S.of(context).restore_title_from_seed_keys,
|
||||||
description: S.of(context).restore_description_from_seed_keys,
|
description: S.of(context).restore_description_from_seed_keys,
|
||||||
),
|
),
|
||||||
if (isNewInstall)
|
if (widget.isNewInstall)
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 24),
|
padding: EdgeInsets.only(top: 24),
|
||||||
child: OptionTile(
|
child: OptionTile(
|
||||||
|
@ -91,9 +107,8 @@ class RestoreOptionsPage extends BasePage {
|
||||||
padding: EdgeInsets.only(top: 24),
|
padding: EdgeInsets.only(top: 24),
|
||||||
child: OptionTile(
|
child: OptionTile(
|
||||||
key: ValueKey('restore_options_from_hardware_wallet_button_key'),
|
key: ValueKey('restore_options_from_hardware_wallet_button_key'),
|
||||||
onPressed: () => Navigator.pushNamed(
|
onPressed: () => Navigator.pushNamed(context, Routes.restoreWalletFromHardwareWallet,
|
||||||
context, Routes.restoreWalletFromHardwareWallet,
|
arguments: widget.isNewInstall),
|
||||||
arguments: isNewInstall),
|
|
||||||
image: imageLedger,
|
image: imageLedger,
|
||||||
title: S.of(context).restore_title_from_hardware_wallet,
|
title: S.of(context).restore_title_from_hardware_wallet,
|
||||||
description: S.of(context).restore_description_from_hardware_wallet,
|
description: S.of(context).restore_description_from_hardware_wallet,
|
||||||
|
@ -119,36 +134,47 @@ class RestoreOptionsPage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onWalletCreateFailure(BuildContext context, String error) {
|
void _onWalletCreateFailure(BuildContext context, String error) {
|
||||||
showPopUp<void>(
|
setState(() {
|
||||||
context: context,
|
isRestoring = false;
|
||||||
builder: (BuildContext context) {
|
});
|
||||||
return AlertWithOneAction(
|
|
||||||
alertTitle: S.current.error,
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
alertContent: error,
|
showPopUp<void>(
|
||||||
buttonText: S.of(context).ok,
|
context: context,
|
||||||
buttonAction: () => Navigator.of(context).pop());
|
builder: (BuildContext context) {
|
||||||
});
|
return AlertWithOneAction(
|
||||||
|
alertTitle: S.current.error,
|
||||||
|
alertContent: error,
|
||||||
|
buttonText: S.of(context).ok,
|
||||||
|
buttonAction: () => Navigator.of(context).pop());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onScanQRCode(BuildContext context) async {
|
Future<void> _onScanQRCode(BuildContext context) async {
|
||||||
final isCameraPermissionGranted =
|
final isCameraPermissionGranted = await PermissionHandler.checkPermission(Permission.camera, context);
|
||||||
await PermissionHandler.checkPermission(Permission.camera, context);
|
|
||||||
|
|
||||||
if (!isCameraPermissionGranted) return;
|
if (!isCameraPermissionGranted) return;
|
||||||
bool isPinSet = false;
|
bool isPinSet = false;
|
||||||
if (isNewInstall) {
|
if (widget.isNewInstall) {
|
||||||
await Navigator.pushNamed(context, Routes.setupPin,
|
await Navigator.pushNamed(context, Routes.setupPin,
|
||||||
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
|
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
|
||||||
setupPinContext.close();
|
setupPinContext.close();
|
||||||
isPinSet = true;
|
isPinSet = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!isNewInstall || isPinSet) {
|
if (!widget.isNewInstall || isPinSet) {
|
||||||
try {
|
try {
|
||||||
|
if (isRestoring) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
isRestoring = true;
|
||||||
|
});
|
||||||
final restoreWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
|
final restoreWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
|
||||||
|
|
||||||
final restoreFromQRViewModel =
|
final restoreFromQRViewModel = getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
|
||||||
getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
|
|
||||||
|
|
||||||
await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
|
await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
|
||||||
if (restoreFromQRViewModel.state is FailureState) {
|
if (restoreFromQRViewModel.state is FailureState) {
|
||||||
|
@ -160,4 +186,4 @@ class RestoreOptionsPage extends BasePage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
30
lib/src/widgets/alert_with_no_action.dart.dart
Normal file
30
lib/src/widgets/alert_with_no_action.dart.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
|
||||||
|
|
||||||
|
class AlertWithNoAction extends BaseAlertDialog {
|
||||||
|
AlertWithNoAction({
|
||||||
|
required this.alertTitle,
|
||||||
|
required this.alertContent,
|
||||||
|
this.alertBarrierDismissible = true,
|
||||||
|
Key? key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String alertTitle;
|
||||||
|
final String alertContent;
|
||||||
|
final bool alertBarrierDismissible;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get titleText => alertTitle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get contentText => alertContent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get barrierDismissible => alertBarrierDismissible;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isBottomDividerExists => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget actionButtons(BuildContext context) => Container(height: 60);
|
||||||
|
}
|
|
@ -17,6 +17,8 @@ class BaseAlertDialog extends StatelessWidget {
|
||||||
|
|
||||||
bool get isDividerExists => false;
|
bool get isDividerExists => false;
|
||||||
|
|
||||||
|
bool get isBottomDividerExists => true;
|
||||||
|
|
||||||
VoidCallback get actionLeft => () {};
|
VoidCallback get actionLeft => () {};
|
||||||
|
|
||||||
VoidCallback get actionRight => () {};
|
VoidCallback get actionRight => () {};
|
||||||
|
@ -205,7 +207,7 @@ class BaseAlertDialog extends StatelessWidget {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const HorizontalSectionDivider(),
|
if (isBottomDividerExists) const HorizontalSectionDivider(),
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||||
child: actionButtons(context))
|
child: actionButtons(context))
|
||||||
|
|
|
@ -56,12 +56,8 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
||||||
WalletCredentials getCredentialsFromRestoredWallet(
|
WalletCredentials getCredentialsFromRestoredWallet(
|
||||||
dynamic options, RestoredWallet restoreWallet) {
|
dynamic options, RestoredWallet restoreWallet) {
|
||||||
final password = generateWalletPassword();
|
final password = generateWalletPassword();
|
||||||
String? passphrase;
|
|
||||||
DerivationInfo? derivationInfo;
|
DerivationInfo? derivationInfo;
|
||||||
if (options != null) {
|
|
||||||
derivationInfo = options["derivationInfo"] as DerivationInfo?;
|
|
||||||
passphrase = options["passphrase"] as String?;
|
|
||||||
}
|
|
||||||
derivationInfo ??= getDefaultCreateDerivation();
|
derivationInfo ??= getDefaultCreateDerivation();
|
||||||
|
|
||||||
switch (restoreWallet.restoreMode) {
|
switch (restoreWallet.restoreMode) {
|
||||||
|
@ -119,7 +115,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
||||||
name: name,
|
name: name,
|
||||||
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
mnemonic: restoreWallet.mnemonicSeed ?? '',
|
||||||
password: password,
|
password: password,
|
||||||
passphrase: passphrase,
|
passphrase: restoreWallet.passphrase,
|
||||||
derivationType: derivationInfo!.derivationType!,
|
derivationType: derivationInfo!.derivationType!,
|
||||||
derivationPath: derivationInfo.derivationPath!,
|
derivationPath: derivationInfo.derivationPath!,
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,6 +10,7 @@ class RestoredWallet {
|
||||||
this.spendKey,
|
this.spendKey,
|
||||||
this.viewKey,
|
this.viewKey,
|
||||||
this.mnemonicSeed,
|
this.mnemonicSeed,
|
||||||
|
this.passphrase,
|
||||||
this.txAmount,
|
this.txAmount,
|
||||||
this.txDescription,
|
this.txDescription,
|
||||||
this.recipientName,
|
this.recipientName,
|
||||||
|
@ -23,6 +24,7 @@ class RestoredWallet {
|
||||||
final String? spendKey;
|
final String? spendKey;
|
||||||
final String? viewKey;
|
final String? viewKey;
|
||||||
final String? mnemonicSeed;
|
final String? mnemonicSeed;
|
||||||
|
final String? passphrase;
|
||||||
final String? txAmount;
|
final String? txAmount;
|
||||||
final String? txDescription;
|
final String? txDescription;
|
||||||
final String? recipientName;
|
final String? recipientName;
|
||||||
|
@ -46,11 +48,13 @@ class RestoredWallet {
|
||||||
final height = json['height'] as String?;
|
final height = json['height'] as String?;
|
||||||
final mnemonic_seed = json['mnemonic_seed'] as String?;
|
final mnemonic_seed = json['mnemonic_seed'] as String?;
|
||||||
final seed = json['seed'] as String? ?? json['hexSeed'] as String?;
|
final seed = json['seed'] as String? ?? json['hexSeed'] as String?;
|
||||||
|
final passphrase = json['passphrase'] as String?;
|
||||||
return RestoredWallet(
|
return RestoredWallet(
|
||||||
restoreMode: json['mode'] as WalletRestoreMode,
|
restoreMode: json['mode'] as WalletRestoreMode,
|
||||||
type: json['type'] as WalletType,
|
type: json['type'] as WalletType,
|
||||||
address: json['address'] as String?,
|
address: json['address'] as String?,
|
||||||
mnemonicSeed: mnemonic_seed ?? seed,
|
mnemonicSeed: mnemonic_seed ?? seed,
|
||||||
|
passphrase: passphrase,
|
||||||
height: height != null ? int.parse(height) : 0,
|
height: height != null ? int.parse(height) : 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue