From 7436709fa77411fba8c83026382bd35fd47420e6 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 5 Dec 2022 13:55:50 -0600 Subject: [PATCH] desktop forgot password flow and functionality --- lib/hive/db.dart | 122 +++--- lib/pages/intro_view.dart | 20 +- .../restore_from_file_view.dart | 1 - .../stack_restore_progress_view.dart | 31 +- .../password/create_password_view.dart | 34 +- .../delete_password_warning_view.dart | 193 +++++++++ .../forgot_password_desktop_view.dart | 17 +- ...forgotten_passphrase_restore_from_swb.dart | 406 ++++++++++++++++++ lib/route_generator.dart | 33 ++ lib/utilities/text_styles.dart | 26 ++ pubspec.lock | 45 +- 11 files changed, 813 insertions(+), 115 deletions(-) create mode 100644 lib/pages_desktop_specific/password/delete_password_warning_view.dart create mode 100644 lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart diff --git a/lib/hive/db.dart b/lib/hive/db.dart index 1a52d64df..557f4ef30 100644 --- a/lib/hive/db.dart +++ b/lib/hive/db.dart @@ -40,8 +40,6 @@ class DB { String boxNameUsedSerialsCache({required Coin coin}) => "${coin.name}_usedSerialsCache"; - static bool _initialized = false; - Box? _boxAddressBook; Box? _boxDebugInfo; Box? _boxNodeModels; @@ -88,69 +86,65 @@ class DB { // open hive boxes Future init() async { - if (!_initialized) { - if (Hive.isBoxOpen(boxNameDBInfo)) { - _boxDBInfo = Hive.box(boxNameDBInfo); - } else { - _boxDBInfo = await Hive.openBox(boxNameDBInfo); - } - await Hive.openBox(boxNameWalletsToDeleteOnStart); - - if (Hive.isBoxOpen(boxNamePrefs)) { - _boxPrefs = Hive.box(boxNamePrefs); - } else { - _boxPrefs = await Hive.openBox(boxNamePrefs); - } - - _boxAddressBook = await Hive.openBox(boxNameAddressBook); - _boxDebugInfo = await Hive.openBox(boxNameDebugInfo); - - if (Hive.isBoxOpen(boxNameNodeModels)) { - _boxNodeModels = Hive.box(boxNameNodeModels); - } else { - _boxNodeModels = await Hive.openBox(boxNameNodeModels); - } - - if (Hive.isBoxOpen(boxNamePrimaryNodes)) { - _boxPrimaryNodes = Hive.box(boxNamePrimaryNodes); - } else { - _boxPrimaryNodes = await Hive.openBox(boxNamePrimaryNodes); - } - - if (Hive.isBoxOpen(boxNameAllWalletsData)) { - _boxAllWalletsData = Hive.box(boxNameAllWalletsData); - } else { - _boxAllWalletsData = await Hive.openBox(boxNameAllWalletsData); - } - - if (Hive.isBoxOpen(boxNameDesktopData)) { - _boxDesktopData = Hive.box(boxNameDesktopData); - } else { - _boxDesktopData = await Hive.openBox(boxNameDesktopData); - } - - _boxNotifications = - await Hive.openBox(boxNameNotifications); - _boxWatchedTransactions = - await Hive.openBox(boxNameWatchedTransactions); - _boxWatchedTrades = - await Hive.openBox(boxNameWatchedTrades); - _boxTrades = await Hive.openBox(boxNameTrades); - _boxTradesV2 = await Hive.openBox(boxNameTradesV2); - _boxTradeNotes = await Hive.openBox(boxNameTradeNotes); - _boxTradeLookup = - await Hive.openBox(boxNameTradeLookup); - _walletInfoSource = - await Hive.openBox(xmr.WalletInfo.boxName); - _boxFavoriteWallets = await Hive.openBox(boxNameFavoriteWallets); - - await Future.wait([ - Hive.openBox(boxNamePriceCache), - _loadWalletBoxes(), - _loadSharedCoinCacheBoxes(), - ]); - _initialized = true; + if (Hive.isBoxOpen(boxNameDBInfo)) { + _boxDBInfo = Hive.box(boxNameDBInfo); + } else { + _boxDBInfo = await Hive.openBox(boxNameDBInfo); } + await Hive.openBox(boxNameWalletsToDeleteOnStart); + + if (Hive.isBoxOpen(boxNamePrefs)) { + _boxPrefs = Hive.box(boxNamePrefs); + } else { + _boxPrefs = await Hive.openBox(boxNamePrefs); + } + + _boxAddressBook = await Hive.openBox(boxNameAddressBook); + _boxDebugInfo = await Hive.openBox(boxNameDebugInfo); + + if (Hive.isBoxOpen(boxNameNodeModels)) { + _boxNodeModels = Hive.box(boxNameNodeModels); + } else { + _boxNodeModels = await Hive.openBox(boxNameNodeModels); + } + + if (Hive.isBoxOpen(boxNamePrimaryNodes)) { + _boxPrimaryNodes = Hive.box(boxNamePrimaryNodes); + } else { + _boxPrimaryNodes = await Hive.openBox(boxNamePrimaryNodes); + } + + if (Hive.isBoxOpen(boxNameAllWalletsData)) { + _boxAllWalletsData = Hive.box(boxNameAllWalletsData); + } else { + _boxAllWalletsData = await Hive.openBox(boxNameAllWalletsData); + } + + if (Hive.isBoxOpen(boxNameDesktopData)) { + _boxDesktopData = Hive.box(boxNameDesktopData); + } else { + _boxDesktopData = await Hive.openBox(boxNameDesktopData); + } + + _boxNotifications = + await Hive.openBox(boxNameNotifications); + _boxWatchedTransactions = + await Hive.openBox(boxNameWatchedTransactions); + _boxWatchedTrades = + await Hive.openBox(boxNameWatchedTrades); + _boxTrades = await Hive.openBox(boxNameTrades); + _boxTradesV2 = await Hive.openBox(boxNameTradesV2); + _boxTradeNotes = await Hive.openBox(boxNameTradeNotes); + _boxTradeLookup = await Hive.openBox(boxNameTradeLookup); + _walletInfoSource = + await Hive.openBox(xmr.WalletInfo.boxName); + _boxFavoriteWallets = await Hive.openBox(boxNameFavoriteWallets); + + await Future.wait([ + Hive.openBox(boxNamePriceCache), + _loadWalletBoxes(), + _loadSharedCoinCacheBoxes(), + ]); } Future _loadWalletBoxes() async { diff --git a/lib/pages/intro_view.dart b/lib/pages/intro_view.dart index be0a9b82a..9218c0610 100644 --- a/lib/pages/intro_view.dart +++ b/lib/pages/intro_view.dart @@ -2,12 +2,14 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/stack_privacy_calls.dart'; +import 'package:stackwallet/pages_desktop_specific/password/create_password_view.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:url_launcher/url_launcher.dart'; class IntroView extends StatefulWidget { @@ -135,6 +137,20 @@ class _IntroViewState extends State { GetStartedButton( isDesktop: isDesktop, ), + if (isDesktop) + const SizedBox( + height: 20, + ), + if (isDesktop) + SecondaryButton( + label: "Restore from Stack backup", + onPressed: () { + Navigator.of(context).pushNamed( + CreatePasswordView.routeName, + arguments: true, + ); + }, + ), const Spacer( flex: 65, ), @@ -257,7 +273,7 @@ class GetStartedButton extends StatelessWidget { ), ) : SizedBox( - width: 328, + width: double.infinity, height: 70, child: TextButton( style: Theme.of(context) @@ -270,7 +286,7 @@ class GetStartedButton extends StatelessWidget { ); }, child: Text( - "Get started", + "Create new Stack", style: STextStyles.button(context).copyWith(fontSize: 20), ), ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index 6350feb52..4b0d1e044 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -12,7 +12,6 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart index 360feb9f2..40a0b0f9f 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart @@ -9,6 +9,9 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/stack_backup_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_item_card.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_menu.dart'; +import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/stack_restore/stack_restoring_ui_state_provider.dart'; @@ -20,25 +23,23 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; -import '../../../../../pages_desktop_specific/desktop_home_view.dart'; -import '../../../../../pages_desktop_specific/desktop_menu.dart'; -import '../../../../../providers/desktop/current_desktop_menu_item.dart'; -import '../../../../../widgets/desktop/primary_button.dart'; - class StackRestoreProgressView extends ConsumerStatefulWidget { const StackRestoreProgressView({ Key? key, required this.jsonString, this.fromFile = false, + this.shouldPushToHome = false, }) : super(key: key); final String jsonString; final bool fromFile; + final bool shouldPushToHome; @override ConsumerState createState() => @@ -696,11 +697,21 @@ class _StackRestoreProgressViewState .state) .state = keyID; - Navigator.of(context, rootNavigator: true) - .popUntil( - ModalRoute.withName( - DesktopHomeView.routeName), - ); + if (widget.shouldPushToHome) { + unawaited( + Navigator.of(context) + .pushNamedAndRemoveUntil( + DesktopHomeView.routeName, + (route) => false, + ), + ); + } else { + Navigator.of(context, rootNavigator: true) + .popUntil( + ModalRoute.withName( + DesktopHomeView.routeName), + ); + } }, ) : SecondaryButton( diff --git a/lib/pages_desktop_specific/password/create_password_view.dart b/lib/pages_desktop_specific/password/create_password_view.dart index 22179a3bc..2f440ec92 100644 --- a/lib/pages_desktop_specific/password/create_password_view.dart +++ b/lib/pages_desktop_specific/password/create_password_view.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; +import 'package:stackwallet/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart'; import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; @@ -23,9 +24,11 @@ import 'package:zxcvbn/zxcvbn.dart'; class CreatePasswordView extends ConsumerStatefulWidget { const CreatePasswordView({ Key? key, + this.restoreFromSWB = false, }) : super(key: key); static const String routeName = "/createPasswordDesktop"; + final bool restoreFromSWB; @override ConsumerState createState() => _CreatePasswordViewState(); @@ -84,7 +87,9 @@ class _CreatePasswordViewState extends ConsumerState { // load default nodes now as node service requires storage handler to exist - await ref.read(nodeServiceChangeNotifierProvider).updateDefaults(); + if (!widget.restoreFromSWB) { + await ref.read(nodeServiceChangeNotifierProvider).updateDefaults(); + } } catch (e) { unawaited(showFloatingFlushBar( type: FlushBarType.warning, @@ -95,15 +100,28 @@ class _CreatePasswordViewState extends ConsumerState { } if (mounted) { - unawaited(Navigator.of(context) - .pushReplacementNamed(DesktopHomeView.routeName)); + if (widget.restoreFromSWB) { + unawaited( + Navigator.of(context).pushNamed( + ForgottenPassphraseRestoreFromSWB.routeName, + ), + ); + } else { + unawaited( + Navigator.of(context).pushReplacementNamed( + DesktopHomeView.routeName, + ), + ); + } } - unawaited(showFloatingFlushBar( - type: FlushBarType.success, - message: "Your password is set up", - context: context, - )); + if (!widget.restoreFromSWB) { + unawaited(showFloatingFlushBar( + type: FlushBarType.success, + message: "Your password is set up", + context: context, + )); + } } @override diff --git a/lib/pages_desktop_specific/password/delete_password_warning_view.dart b/lib/pages_desktop_specific/password/delete_password_warning_view.dart new file mode 100644 index 000000000..c11fb0815 --- /dev/null +++ b/lib/pages_desktop_specific/password/delete_password_warning_view.dart @@ -0,0 +1,193 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:hive/hive.dart'; +import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/intro_view.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/stack_file_system.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; + +class DeletePasswordWarningView extends ConsumerStatefulWidget { + const DeletePasswordWarningView({ + Key? key, + required this.shouldCreateNew, + }) : super(key: key); + + static const String routeName = "/deletePasswordWarning"; + + final bool shouldCreateNew; + + @override + ConsumerState createState() => + _ForgotPasswordDesktopViewState(); +} + +class _ForgotPasswordDesktopViewState + extends ConsumerState { + bool _deleteInProgress = false; + + Future _deleteStack() async { + final appRoot = await StackFileSystem.applicationRootDirectory(); + + try { + await Hive.close(); + await appRoot.delete(recursive: true); + await DB.instance.init(); + } catch (e, s) { + Logging.instance.log( + "$e\n$s", + level: LogLevel.Fatal, + ); + return false; + } + + return true; + } + + @override + Widget build(BuildContext context) { + return DesktopScaffold( + appBar: DesktopAppBar( + leading: AppBarBackButton( + onPressed: () async { + if (mounted && !_deleteInProgress) { + Navigator.of(context).pop(); + } + }, + ), + isCompactHeight: false, + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 480, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + Assets.svg.stackIcon(context), + width: 100, + ), + const SizedBox( + height: 42, + ), + Text( + "Warning!", + style: STextStyles.desktopH1(context), + ), + const SizedBox( + height: 24, + ), + SizedBox( + width: 480, + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + children: [ + TextSpan( + text: "To ", + style: STextStyles.desktopTextSmall(context), + ), + TextSpan( + text: "create a new Stack", + style: STextStyles.desktopTextSmallBold(context), + ), + TextSpan( + text: ", we need to ", + style: STextStyles.desktopTextSmall(context), + ), + TextSpan( + text: "delete your old wallets", + style: STextStyles.desktopTextSmallBold(context), + ), + TextSpan( + text: + ". All wallets will be lost. If you have not written down your recovery phrase for EACH wallet, you may be in danger of losing funds. Continue?", + style: STextStyles.desktopTextSmall(context), + ), + ], + ), + ), + ), + const SizedBox( + height: 48, + ), + PrimaryButton( + label: "Delete and continue", + enabled: !_deleteInProgress, + onPressed: () async { + final shouldDelete = !_deleteInProgress; + setState(() { + _deleteInProgress = true; + }); + + if (shouldDelete) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Deleting wallet...", + context: context, + ), + ); + + final success = await _deleteStack(); + + if (success) { + await showFloatingFlushBar( + type: FlushBarType.success, + message: "Wallet deleted", + context: context, + ); + if (mounted) { + await Navigator.of(context).pushNamedAndRemoveUntil( + IntroView.routeName, + (_) => false, + ); + } + } else { + await showFloatingFlushBar( + type: FlushBarType.warning, + message: "Something broke badly. Contact developer", + context: context, + ); + + setState(() { + _deleteInProgress = false; + }); + } + } + }, + ), + const SizedBox( + height: 24, + ), + SecondaryButton( + label: "Take me back!", + enabled: !_deleteInProgress, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + const SizedBox( + height: kDesktopAppBarHeight, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/password/forgot_password_desktop_view.dart b/lib/pages_desktop_specific/password/forgot_password_desktop_view.dart index d501cbd38..db3e40a16 100644 --- a/lib/pages_desktop_specific/password/forgot_password_desktop_view.dart +++ b/lib/pages_desktop_specific/password/forgot_password_desktop_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages_desktop_specific/password/delete_password_warning_view.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -74,18 +75,26 @@ class _ForgotPasswordDesktopViewState extends State { height: 48, ), PrimaryButton( - label: "Create new wallet", + label: "Create new Stack", onPressed: () { - // // todo delete everything and start fresh? + const shouldCreateNew = true; + Navigator.of(context).pushNamed( + DeletePasswordWarningView.routeName, + arguments: shouldCreateNew, + ); }, ), const SizedBox( height: 24, ), SecondaryButton( - label: "Restore from backup", + label: "Restore from Stack backup", onPressed: () { - // todo SWB restore + const shouldCreateNew = false; + Navigator.of(context).pushNamed( + DeletePasswordWarningView.routeName, + arguments: shouldCreateNew, + ); }, ), const SizedBox( diff --git a/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart b/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart new file mode 100644 index 000000000..60dad82f1 --- /dev/null +++ b/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart @@ -0,0 +1,406 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart'; +import 'package:stackwallet/pages_desktop_specific/password/create_password_view.dart'; +import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:tuple/tuple.dart'; + +class ForgottenPassphraseRestoreFromSWB extends ConsumerStatefulWidget { + const ForgottenPassphraseRestoreFromSWB({Key? key}) : super(key: key); + + static const String routeName = "/forgottenPassphraseRestoreFromSWB"; + + @override + ConsumerState createState() => + _ForgottenPassphraseRestoreFromSWBState(); +} + +class _ForgottenPassphraseRestoreFromSWBState + extends ConsumerState { + late final TextEditingController fileLocationController; + late final TextEditingController passwordController; + + late final FocusNode passwordFocusNode; + + late final SWBFileSystem stackFileSystem; + + bool hidePassword = true; + + bool _enableButton = false; + + Future restore() async { + final String fileToRestore = fileLocationController.text; + final String passphrase = passwordController.text; + + if (!(await File(fileToRestore).exists())) { + await showFloatingFlushBar( + type: FlushBarType.warning, + message: "Backup file does not exist", + context: context, + ); + return; + } + + bool shouldPop = false; + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => WillPopScope( + onWillPop: () async { + return shouldPop; + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Material( + color: Colors.transparent, + child: Center( + child: Text( + "Decrypting Stack backup file", + style: STextStyles.pageTitleH2(context).copyWith( + color: + Theme.of(context).extension()!.textWhite, + ), + ), + ), + ), + const SizedBox( + height: 64, + ), + const Center( + child: LoadingIndicator( + width: 100, + ), + ), + ], + ), + ), + ), + ); + + final String? jsonString = await compute( + SWB.decryptStackWalletWithPassphrase, + Tuple2(fileToRestore, passphrase), + debugLabel: "stack wallet decryption compute", + ); + + if (mounted) { + // pop LoadingIndicator + shouldPop = true; + Navigator.of(context).pop(); + + passwordController.text = ""; + + if (jsonString == null) { + await showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to decrypt backup file", + context: context, + ); + return; + } + + ref.read(walletsChangeNotifierProvider); + + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Restoring Stack wallet", + style: STextStyles.desktopH3(context), + ), + ], + ), + const SizedBox( + height: 44, + ), + StackRestoreProgressView( + jsonString: jsonString, + shouldPushToHome: true, + ), + ], + ), + ), + ); + }, + ); + // await Navigator.of(context).push( + // RouteGenerator.getRoute( + // builder: (_) => StackRestoreProgressView( + // jsonString: jsonString, + // ), + // ), + // ); + } + } + + @override + void initState() { + stackFileSystem = SWBFileSystem(); + fileLocationController = TextEditingController(); + passwordController = TextEditingController(); + + passwordFocusNode = FocusNode(); + + super.initState(); + } + + @override + void dispose() { + fileLocationController.dispose(); + passwordController.dispose(); + + passwordFocusNode.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DesktopScaffold( + appBar: DesktopAppBar( + leading: AppBarBackButton( + onPressed: () async { + await (ref.read(secureStoreProvider).store as DesktopSecureStore) + .close(); + ref.refresh(secureStoreProvider); + ref.refresh(storageCryptoHandlerProvider); + await Hive.deleteBoxFromDisk(DB.boxNameDesktopData); + await DB.instance.init(); + if (mounted) { + Navigator.of(context) + .popUntil(ModalRoute.withName(CreatePasswordView.routeName)); + Navigator.of(context).pop(); + } + }, + ), + isCompactHeight: false, + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 480, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Restore from backup", + style: STextStyles.desktopH1(context), + ), + const SizedBox( + height: 32, + ), + Text( + "Use your Stack wallet backup file to restore your wallets, address book, and wallet preferences.", + textAlign: TextAlign.center, + style: STextStyles.desktopTextSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + const SizedBox( + height: 40, + ), + GestureDetector( + onTap: () async { + try { + await stackFileSystem.prepareStorage(); + if (mounted) { + await stackFileSystem.openFile(context); + } + + if (mounted) { + setState(() { + fileLocationController.text = + stackFileSystem.filePath ?? ""; + }); + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + } + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: IgnorePointer( + child: TextField( + autocorrect: false, + enableSuggestions: false, + controller: fileLocationController, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Choose file...", + hintStyle: STextStyles.desktopTextFieldLabel(context), + suffixIcon: SizedBox( + height: 70, + child: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 24, + ), + SvgPicture.asset( + Assets.svg.folder, + color: Theme.of(context) + .extension()! + .textDark3, + width: 24, + height: 24, + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + ), + key: const Key("restoreFromFileLocationTextFieldKey"), + readOnly: true, + toolbarOptions: const ToolbarOptions( + copy: true, + cut: false, + paste: false, + selectAll: false, + ), + onChanged: (newValue) { + setState(() { + _enableButton = + passwordController.text.isNotEmpty && + fileLocationController.text.isNotEmpty; + }); + }, + ), + ), + ), + ), + const SizedBox( + height: 16, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("restoreFromFilePasswordFieldKey"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.desktopTextMedium(context).copyWith( + height: 2, + ), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Enter passphrase", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: SizedBox( + height: 70, + child: Row( + children: [ + const SizedBox( + width: 24, + ), + GestureDetector( + key: const Key( + "restoreFromFilePasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 24, + height: 24, + ), + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + ), + onChanged: (newValue) { + setState(() { + _enableButton = passwordController.text.isNotEmpty && + fileLocationController.text.isNotEmpty; + }); + }, + ), + ), + const SizedBox( + height: 24, + ), + PrimaryButton( + label: "Restore", + enabled: _enableButton, + onPressed: () { + restore(); + }, + ), + const SizedBox( + height: kDesktopAppBarHeight, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 2e7c63b53..ed676c437 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -98,7 +98,9 @@ import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart'; import 'package:stackwallet/pages_desktop_specific/notifications/desktop_notifications_view.dart'; import 'package:stackwallet/pages_desktop_specific/password/create_password_view.dart'; +import 'package:stackwallet/pages_desktop_specific/password/delete_password_warning_view.dart'; import 'package:stackwallet/pages_desktop_specific/password/forgot_password_desktop_view.dart'; +import 'package:stackwallet/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart'; import 'package:stackwallet/pages_desktop_specific/settings/desktop_settings_view.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings.dart'; @@ -999,6 +1001,17 @@ class RouteGenerator { // == Desktop specific routes ============================================ case CreatePasswordView.routeName: + if (args is bool) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => CreatePasswordView( + restoreFromSWB: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => const CreatePasswordView(), @@ -1010,6 +1023,26 @@ class RouteGenerator { builder: (_) => const ForgotPasswordDesktopView(), settings: RouteSettings(name: settings.name)); + case ForgottenPassphraseRestoreFromSWB.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const ForgottenPassphraseRestoreFromSWB(), + settings: RouteSettings(name: settings.name)); + + case DeletePasswordWarningView.routeName: + if (args is bool) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => DeletePasswordWarningView( + shouldCreateNew: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case DesktopHomeView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, diff --git a/lib/utilities/text_styles.dart b/lib/utilities/text_styles.dart index c9dd15e1d..56c313219 100644 --- a/lib/utilities/text_styles.dart +++ b/lib/utilities/text_styles.dart @@ -997,6 +997,32 @@ class STextStyles { } } + static TextStyle desktopTextSmallBold(BuildContext context) { + switch (_theme(context).themeType) { + case ThemeType.light: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w700, + fontSize: 18, + height: 27 / 18, + ); + case ThemeType.oceanBreeze: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w700, + fontSize: 18, + height: 27 / 18, + ); + case ThemeType.dark: + return GoogleFonts.inter( + color: _theme(context).buttonTextPrimaryDisabled, + fontWeight: FontWeight.w700, + fontSize: 18, + height: 27 / 18, + ); + } + } + static TextStyle desktopTextExtraSmall(BuildContext context) { switch (_theme(context).themeType) { case ThemeType.light: diff --git a/pubspec.lock b/pubspec.lock index f12b25487..ffd644d05 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,7 +42,7 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.1.11" + version: "3.3.0" args: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" barcode_scan2: dependency: "direct main" description: @@ -190,14 +190,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" checked_yaml: dependency: transitive description: @@ -218,7 +211,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: @@ -288,7 +281,7 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.5.0" cross_file: dependency: transitive description: @@ -442,7 +435,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: "direct main" description: @@ -871,21 +864,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" mime: dependency: transitive description: @@ -997,7 +990,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" path_drawing: dependency: transitive description: @@ -1373,7 +1366,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -1417,7 +1410,7 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" string_validator: dependency: "direct main" description: @@ -1431,35 +1424,35 @@ packages: name: sync_http url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.3.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test: dependency: transitive description: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.21.1" + version: "1.21.4" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.13" + version: "0.4.16" time: dependency: transitive description: @@ -1508,7 +1501,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" universal_io: dependency: transitive description: @@ -1592,7 +1585,7 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "8.2.2" + version: "9.0.0" wakelock: dependency: "direct main" description: