From d348d1f06a378e81e46193b5f24f83fa8af9dfdd Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 29 Jan 2025 15:58:55 +0200 Subject: [PATCH] Update wallet_restore_page.dart --- .../screens/restore/wallet_restore_page.dart | 411 +++++++++++------- 1 file changed, 262 insertions(+), 149 deletions(-) diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index beedee220b..fadc3b1a3b 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -20,64 +20,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:mobx/mobx.dart'; -import 'package:smooth_page_indicator/smooth_page_indicator.dart'; class WalletRestorePage extends BasePage { WalletRestorePage(this.walletRestoreViewModel, this.seedSettingsViewModel) : walletRestoreFromSeedFormKey = GlobalKey(), walletRestoreFromKeysFormKey = GlobalKey(), - _pages = [], - _blockHeightFocusNode = FocusNode(), - _controller = PageController(initialPage: 0) { - walletRestoreViewModel.availableModes.forEach((mode) { - switch (mode) { - case WalletRestoreMode.seed: - _pages.add(WalletRestoreFromSeedForm( - seedSettingsViewModel: seedSettingsViewModel, - displayBlockHeightSelector: - walletRestoreViewModel.hasBlockchainHeightLanguageSelector, - displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector, - type: walletRestoreViewModel.type, - key: walletRestoreFromSeedFormKey, - blockHeightFocusNode: _blockHeightFocusNode, - onHeightOrDateEntered: (value) { - if (_isValidSeed()) { - walletRestoreViewModel.isButtonEnabled = value; - } - }, - onSeedChange: (String seed) { - final isPolyseed = walletRestoreViewModel.isPolyseed(seed); - _validateOnChange(isPolyseed: isPolyseed); - }, - onLanguageChange: (String language) { - final isPolyseed = language.startsWith("POLYSEED_"); - _validateOnChange(isPolyseed: isPolyseed); - }, - displayWalletPassword: walletRestoreViewModel.hasWalletPassword, - onPasswordChange: (String password) => walletRestoreViewModel.walletPassword = password, - onRepeatedPasswordChange: (String repeatedPassword) => walletRestoreViewModel.repeatedWalletPassword = repeatedPassword)); - break; - case WalletRestoreMode.keys: - _pages.add(WalletRestoreFromKeysFrom( - key: walletRestoreFromKeysFormKey, - walletRestoreViewModel: walletRestoreViewModel, - onPrivateKeyChange: (String seed) { - if (walletRestoreViewModel.type == WalletType.nano || - walletRestoreViewModel.type == WalletType.banano) { - walletRestoreViewModel.isButtonEnabled = _isValidSeedKey(); - } - }, - displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey, - displayWalletPassword: walletRestoreViewModel.hasWalletPassword, - onPasswordChange: (String password) => walletRestoreViewModel.walletPassword = password, - onRepeatedPasswordChange: (String repeatedPassword) => walletRestoreViewModel.repeatedWalletPassword = repeatedPassword, - onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value)); - break; - default: - break; - } - }); - } + _blockHeightFocusNode = FocusNode(); + + final WalletRestoreViewModel walletRestoreViewModel; + final SeedSettingsViewModel seedSettingsViewModel; + final GlobalKey walletRestoreFromSeedFormKey; + final GlobalKey walletRestoreFromKeysFormKey; + final FocusNode _blockHeightFocusNode; bool _formProcessing = false; @@ -94,14 +48,6 @@ class WalletRestorePage extends BasePage { color: titleColor(context)), )); - final WalletRestoreViewModel walletRestoreViewModel; - final SeedSettingsViewModel seedSettingsViewModel; - final PageController _controller; - final List _pages; - final GlobalKey walletRestoreFromSeedFormKey; - final GlobalKey walletRestoreFromKeysFormKey; - final FocusNode _blockHeightFocusNode; - // DerivationType derivationType = DerivationType.unknown; // String? derivationPath = null; DerivationInfo? derivationInfo; @@ -170,41 +116,18 @@ class WalletRestorePage extends BasePage { constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), child: Column( - mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( - child: PageView.builder( - onPageChanged: (page) { - walletRestoreViewModel.mode = - page == 0 ? WalletRestoreMode.seed : WalletRestoreMode.keys; - }, - controller: _controller, - itemCount: _pages.length, - itemBuilder: (_, index) => SingleChildScrollView(child: _pages[index]), + child: _WalletRestorePageBody( + walletRestoreViewModel: walletRestoreViewModel, + seedSettingsViewModel: seedSettingsViewModel, + walletRestoreFromSeedFormKey: walletRestoreFromSeedFormKey, + walletRestoreFromKeysFormKey: walletRestoreFromKeysFormKey, + blockHeightFocusNode: _blockHeightFocusNode, + derivationInfo: derivationInfo, + onDerivationInfoChanged: (info) => derivationInfo = info, ), ), - if (_pages.length > 1) - Padding( - padding: EdgeInsets.only(top: 10), - child: Semantics( - button: false, - label: 'Page Indicator', - hint: 'Swipe to change restore mode', - excludeSemantics: true, - child: SmoothPageIndicator( - controller: _controller, - count: _pages.length, - effect: ColorTransitionEffect( - spacing: 6.0, - radius: 6.0, - dotWidth: 6.0, - dotHeight: 6.0, - dotColor: Theme.of(context).hintColor.withOpacity(0.5), - activeDotColor: Theme.of(context).hintColor, - ), - ), - ), - ), Padding( padding: EdgeInsets.only(top: 20, bottom: 24, left: 24, right: 24), child: Column( @@ -213,9 +136,7 @@ class WalletRestorePage extends BasePage { builder: (context) { return LoadingPrimaryButton( key: ValueKey('wallet_restore_seed_or_key_restore_button_key'), - onPressed: () async { - await _confirmForm(context); - }, + onPressed: () async => await _confirmForm(context), text: S.of(context).restore_recover, color: Theme.of(context) .extension()! @@ -253,60 +174,6 @@ class WalletRestorePage extends BasePage { ); } - void _validateOnChange({bool isPolyseed = false}) { - if (!isPolyseed && walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { - final hasHeight = walletRestoreFromSeedFormKey - .currentState?.blockchainHeightKey.currentState?.restoreHeightController.text.isNotEmpty; - - if (hasHeight == true) { - walletRestoreViewModel.isButtonEnabled = _isValidSeed(); - } - } else { - walletRestoreViewModel.isButtonEnabled = _isValidSeed(); - } - } - - bool _isValidSeed() { - final seedPhrase = - walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text; - if (walletRestoreViewModel.isPolyseed(seedPhrase)) return true; - - final seedWords = seedPhrase.split(' '); - - if (seedWords.length == 14 && walletRestoreViewModel.type == WalletType.wownero) return true; - if (seedWords.length == 26 && walletRestoreViewModel.type == WalletType.zano) return true; - - if ((walletRestoreViewModel.type == WalletType.monero || - walletRestoreViewModel.type == WalletType.wownero || - walletRestoreViewModel.type == WalletType.haven) && - seedWords.length != WalletRestoreViewModelBase.moneroSeedMnemonicLength) { - return false; - } - - // bip39: - final validBip39SeedLengths = [12, 18, 24]; - final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven]; - // if it's a bip39 wallet and the length is not valid return false - if (!nonBip39WalletTypes.contains(walletRestoreViewModel.type) && - !(validBip39SeedLengths.contains(seedWords.length))) { - return false; - } - - final words = - walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet(); - return seedWords.toSet().difference(words).toSet().isEmpty; - } - - bool _isValidSeedKey() { - final seedKey = walletRestoreFromKeysFormKey.currentState!.privateKeyController.text; - - if (seedKey.length != 64 && seedKey.length != 128) { - return false; - } - - return true; - } - Map _credentials() { final credentials = {}; @@ -434,3 +301,249 @@ class WalletRestorePage extends BasePage { }); } } + +class _WalletRestorePageBody extends StatefulWidget { + const _WalletRestorePageBody({ + Key? key, + required this.walletRestoreViewModel, + required this.seedSettingsViewModel, + required this.walletRestoreFromSeedFormKey, + required this.walletRestoreFromKeysFormKey, + required this.blockHeightFocusNode, + required this.derivationInfo, + required this.onDerivationInfoChanged, + }) : super(key: key); + + final WalletRestoreViewModel walletRestoreViewModel; + final SeedSettingsViewModel seedSettingsViewModel; + final GlobalKey walletRestoreFromSeedFormKey; + final GlobalKey walletRestoreFromKeysFormKey; + final FocusNode blockHeightFocusNode; + final DerivationInfo? derivationInfo; + final void Function(DerivationInfo?) onDerivationInfoChanged; + + @override + State<_WalletRestorePageBody> createState() => _WalletRestorePageBodyState( + walletRestoreViewModel: walletRestoreViewModel, + seedSettingsViewModel: seedSettingsViewModel, + walletRestoreFromSeedFormKey: walletRestoreFromSeedFormKey, + walletRestoreFromKeysFormKey: walletRestoreFromKeysFormKey, + blockHeightFocusNode: blockHeightFocusNode, + derivationInfo: derivationInfo); +} + +class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> + with SingleTickerProviderStateMixin { + _WalletRestorePageBodyState( + {required this.walletRestoreViewModel, + required this.seedSettingsViewModel, + required this.walletRestoreFromSeedFormKey, + required this.walletRestoreFromKeysFormKey, + required this.blockHeightFocusNode, + required this.derivationInfo}); + + final WalletRestoreViewModel walletRestoreViewModel; + final SeedSettingsViewModel seedSettingsViewModel; + final GlobalKey walletRestoreFromSeedFormKey; + final GlobalKey walletRestoreFromKeysFormKey; + final FocusNode blockHeightFocusNode; + DerivationInfo? derivationInfo; + + late TabController _tabController; + + late bool _hasKeysTab; + + @override + void initState() { + super.initState(); + + _hasKeysTab = widget.walletRestoreViewModel.availableModes.contains(WalletRestoreMode.keys); + final tabCount = _hasKeysTab ? 2 : 1; + _tabController = TabController(length: tabCount, vsync: this); + + _tabController.addListener(() { + if (!_tabController.indexIsChanging) { + widget.walletRestoreViewModel.mode = + _tabController.index == 0 ? WalletRestoreMode.seed : WalletRestoreMode.keys; + } + }); + + reaction( + (_) => widget.walletRestoreViewModel.mode, + (mode) { + final index = mode == WalletRestoreMode.seed ? 0 : 1; + if (_tabController.index != index) { + _tabController.animateTo(index); + } + }, + ); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 22), + child: TabBar( + controller: _tabController, + splashFactory: NoSplash.splashFactory, + indicatorSize: TabBarIndicatorSize.label, + isScrollable: true, + labelStyle: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).appBarTheme.titleTextStyle!.color), + unselectedLabelStyle: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).appBarTheme.titleTextStyle!.color?.withOpacity(0.5)), + labelColor: Theme.of(context).appBarTheme.titleTextStyle!.color, + indicatorColor: Theme.of(context).appBarTheme.titleTextStyle!.color, + indicatorPadding: EdgeInsets.zero, + labelPadding: const EdgeInsets.only(right: 24), + tabAlignment: TabAlignment.start, + dividerColor: Colors.transparent, + padding: EdgeInsets.zero, + tabs: [ + Tab(text: S.of(context).widgets_seed), + if (_hasKeysTab) Tab(text: S.of(context).keys), + ], + ), + ), + const SizedBox(height: 20), + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: _buildWalletRestoreFromSeedTab(), + ), + if (_hasKeysTab) + SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: _buildWalletRestoreFromKeysTab(), + ), + ], + ), + ), + ], + ), + ); + } + + WalletRestoreFromKeysFrom _buildWalletRestoreFromKeysTab() { + return WalletRestoreFromKeysFrom( + key: widget.walletRestoreFromKeysFormKey, + walletRestoreViewModel: widget.walletRestoreViewModel, + displayPrivateKeyField: widget.walletRestoreViewModel.hasRestoreFromPrivateKey, + displayWalletPassword: widget.walletRestoreViewModel.hasWalletPassword, + onPrivateKeyChange: (String seed) { + // For nano/banano, set button state if valid seed key + if (widget.walletRestoreViewModel.type == WalletType.nano || + widget.walletRestoreViewModel.type == WalletType.banano) { + widget.walletRestoreViewModel.isButtonEnabled = _isValidSeedKey(); + } + }, + onPasswordChange: (String password) => + widget.walletRestoreViewModel.walletPassword = password, + onRepeatedPasswordChange: (String repeatedPassword) => + widget.walletRestoreViewModel.repeatedWalletPassword = repeatedPassword, + onHeightOrDateEntered: (value) => widget.walletRestoreViewModel.isButtonEnabled = value, + ); + } + + WalletRestoreFromSeedForm _buildWalletRestoreFromSeedTab() { + return WalletRestoreFromSeedForm( + key: widget.walletRestoreFromSeedFormKey, + seedSettingsViewModel: widget.seedSettingsViewModel, + displayBlockHeightSelector: widget.walletRestoreViewModel.hasBlockchainHeightLanguageSelector, + displayLanguageSelector: widget.walletRestoreViewModel.hasSeedLanguageSelector, + type: widget.walletRestoreViewModel.type, + blockHeightFocusNode: widget.blockHeightFocusNode, + onHeightOrDateEntered: (value) { + // set button state + if (_isValidSeed()) { + widget.walletRestoreViewModel.isButtonEnabled = value; + } + }, + onSeedChange: (String seed) { + final isPolyseed = widget.walletRestoreViewModel.isPolyseed(seed); + _validateOnChange(isPolyseed: isPolyseed); + }, + onLanguageChange: (String language) { + final isPolyseed = language.startsWith("POLYSEED_"); + _validateOnChange(isPolyseed: isPolyseed); + }, + displayWalletPassword: widget.walletRestoreViewModel.hasWalletPassword, + onPasswordChange: (String password) => + widget.walletRestoreViewModel.walletPassword = password, + onRepeatedPasswordChange: (String repeatedPassword) => + widget.walletRestoreViewModel.repeatedWalletPassword = repeatedPassword, + ); + } + + void _validateOnChange({bool isPolyseed = false}) { + if (!isPolyseed && walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { + final hasHeight = walletRestoreFromSeedFormKey + .currentState?.blockchainHeightKey.currentState?.restoreHeightController.text.isNotEmpty; + + if (hasHeight == true) { + walletRestoreViewModel.isButtonEnabled = _isValidSeed(); + } + } else { + walletRestoreViewModel.isButtonEnabled = _isValidSeed(); + } + } + + bool _isValidSeed() { + final seedPhrase = + walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text; + if (walletRestoreViewModel.isPolyseed(seedPhrase)) return true; + + final seedWords = seedPhrase.split(' '); + + if (seedWords.length == 14 && walletRestoreViewModel.type == WalletType.wownero) return true; + if (seedWords.length == 26 && walletRestoreViewModel.type == WalletType.zano) return true; + + if ((walletRestoreViewModel.type == WalletType.monero || + walletRestoreViewModel.type == WalletType.wownero || + walletRestoreViewModel.type == WalletType.haven) && + seedWords.length != WalletRestoreViewModelBase.moneroSeedMnemonicLength) { + return false; + } + + // bip39: + final validBip39SeedLengths = [12, 18, 24]; + final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven]; + // if it's a bip39 wallet and the length is not valid return false + if (!nonBip39WalletTypes.contains(walletRestoreViewModel.type) && + !(validBip39SeedLengths.contains(seedWords.length))) { + return false; + } + + final words = + walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet(); + return seedWords.toSet().difference(words).toSet().isEmpty; + } + + bool _isValidSeedKey() { + final seedKey = walletRestoreFromKeysFormKey.currentState!.privateKeyController.text; + + if (seedKey.length != 64 && seedKey.length != 128) { + return false; + } + + return true; + } +}