From 8d157e620748aa64ee4f3a8dbe9b9cd8db18cdf0 Mon Sep 17 00:00:00 2001 From: Rafael Saes <76502841+saltrafael@users.noreply.github.com> Date: Fri, 14 Jul 2023 17:28:01 -0300 Subject: [PATCH] Linux: fixes for wallet rename (#1000) * fix: missing MarketPlaceViewModel register * fix: no auth route on wallet list _loadWallet * fix: fixes for wallet rename with password flow * fix: missing monero condition * fix: navigator pop * fix: going back case --- cw_monero/lib/monero_wallet.dart | 58 +++++ lib/core/wallet_loading_service.dart | 8 + lib/di.dart | 4 + lib/src/screens/wallet/wallet_edit_page.dart | 206 ++++++++++++++++++ .../screens/wallet_list/wallet_list_page.dart | 16 ++ .../wallet_unlock_arguments.dart | 14 +- .../wallet_unlock/wallet_unlock_page.dart | 21 +- .../wallet_list/wallet_edit_view_model.dart | 56 +++++ .../wallet_unlock_loadable_view_model.dart | 41 ++-- .../wallet_unlock_verifiable_view_model.dart | 42 ++-- lib/view_model/wallet_unlock_view_model.dart | 4 +- 11 files changed, 430 insertions(+), 40 deletions(-) create mode 100644 lib/src/screens/wallet/wallet_edit_page.dart create mode 100644 lib/view_model/wallet_list/wallet_edit_view_model.dart diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index cdb7767cd..5bd6e8e6c 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'dart:io'; +import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_monero/monero_transaction_creation_exception.dart'; @@ -274,6 +276,62 @@ abstract class MoneroWalletBase extends WalletBase renameWalletFiles(String newWalletName) async { + final currentWalletDirPath = await pathForWalletDir(name: name, type: type); + + try { + // -- rename the waller folder -- + final currentWalletDir = + Directory(await pathForWalletDir(name: name, type: type)); + final newWalletDirPath = + await pathForWalletDir(name: newWalletName, type: type); + await currentWalletDir.rename(newWalletDirPath); + + // -- use new waller folder to rename files with old names still -- + final renamedWalletPath = newWalletDirPath + '/$name'; + + final currentCacheFile = File(renamedWalletPath); + final currentKeysFile = File('$renamedWalletPath.keys'); + final currentAddressListFile = File('$renamedWalletPath.address.txt'); + + final newWalletPath = + await pathForWallet(name: newWalletName, type: type); + + if (currentCacheFile.existsSync()) { + await currentCacheFile.rename(newWalletPath); + } + if (currentKeysFile.existsSync()) { + await currentKeysFile.rename('$newWalletPath.keys'); + } + if (currentAddressListFile.existsSync()) { + await currentAddressListFile.rename('$newWalletPath.address.txt'); + } + } catch (e) { + final currentWalletPath = await pathForWallet(name: name, type: type); + + final currentCacheFile = File(currentWalletPath); + final currentKeysFile = File('$currentWalletPath.keys'); + final currentAddressListFile = File('$currentWalletPath.address.txt'); + + final newWalletPath = + await pathForWallet(name: newWalletName, type: type); + + // Copies current wallet files into new wallet name's dir and files + if (currentCacheFile.existsSync()) { + await currentCacheFile.copy(newWalletPath); + } + if (currentKeysFile.existsSync()) { + await currentKeysFile.copy('$newWalletPath.keys'); + } + if (currentAddressListFile.existsSync()) { + await currentAddressListFile.copy('$newWalletPath.address.txt'); + } + + // Delete old name's dir and files + await Directory(currentWalletDirPath).delete(recursive: true); + } + } + @override Future changePassword(String password) async { monero_wallet.setPasswordSync(password); diff --git a/lib/core/wallet_loading_service.dart b/lib/core/wallet_loading_service.dart index 4b156d586..17f132ddb 100644 --- a/lib/core/wallet_loading_service.dart +++ b/lib/core/wallet_loading_service.dart @@ -25,6 +25,14 @@ class WalletLoadingService { await keyService.deleteWalletPassword(walletName: name); await walletService.rename(name, walletPassword, newName); + + // set shared preferences flag based on previous wallet name + if (type == WalletType.monero) { + final oldNameKey = PreferencesKey.moneroWalletUpdateV1Key(name); + final isPasswordUpdated = sharedPreferences.getBool(oldNameKey) ?? false; + final newNameKey = PreferencesKey.moneroWalletUpdateV1Key(newName); + await sharedPreferences.setBool(newNameKey, isPasswordUpdated); + } } Future load(WalletType type, String name, {String? password}) async { diff --git a/lib/di.dart b/lib/di.dart index fa2ba54ba..5fc86e6a0 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -904,6 +904,8 @@ Future setup({ getIt.registerFactory(() => IoniaAnyPay( getIt.get(), getIt.get(), getIt.get().wallet!)); + getIt.registerFactory(()=> MarketPlaceViewModel(getIt.get())); + getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get())); getIt.registerFactory(()=> MarketPlaceViewModel(getIt.get())); @@ -1075,6 +1077,7 @@ Future setup({ return WalletUnlockPage( getIt.get(param1: args), args.callback, + args.authPasswordHandler, closable: closable); }, instanceName: 'wallet_unlock_loadable'); @@ -1082,6 +1085,7 @@ Future setup({ return WalletUnlockPage( getIt.get(param1: args), args.callback, + args.authPasswordHandler, closable: closable); }, instanceName: 'wallet_unlock_verifiable'); diff --git a/lib/src/screens/wallet/wallet_edit_page.dart b/lib/src/screens/wallet/wallet_edit_page.dart new file mode 100644 index 000000000..d656fb838 --- /dev/null +++ b/lib/src/screens/wallet/wallet_edit_page.dart @@ -0,0 +1,206 @@ +import 'package:another_flushbar/flushbar.dart'; +import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/core/wallet_name_validator.dart'; +import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/auth/auth_page.dart'; +import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart'; +import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; +import 'package:cake_wallet/view_model/wallet_new_vm.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; + +class WalletEditPage extends BasePage { + WalletEditPage( + {required this.walletEditViewModel, + required this.editingWallet, + required this.walletNewVM, + required this.authService}) + : _formKey = GlobalKey(), + _labelController = TextEditingController(), + super() { + _labelController.text = editingWallet.name; + _labelController.addListener(() => walletEditViewModel.newName = _labelController.text); + } + + final GlobalKey _formKey; + final TextEditingController _labelController; + + final WalletEditViewModel walletEditViewModel; + final WalletNewVM walletNewVM; + final WalletListItem editingWallet; + final AuthService authService; + + @override + String get title => S.current.wallet_list_edit_wallet; + + Flushbar? _progressBar; + + @override + Widget body(BuildContext context) { + return Form( + key: _formKey, + child: Container( + padding: EdgeInsets.all(24.0), + child: Column( + children: [ + Expanded( + child: Center( + child: BaseTextFormField( + controller: _labelController, + hintText: S.of(context).wallet_list_wallet_name, + validator: WalletNameValidator()))), + Observer( + builder: (_) { + final isLoading = walletEditViewModel.state is WalletEditRenamePending || + walletEditViewModel.state is WalletEditDeletePending; + + return Row( + children: [ + Flexible( + child: Container( + padding: EdgeInsets.only(right: 8.0), + child: LoadingPrimaryButton( + isDisabled: isLoading, + onPressed: () => _removeWallet(context), + text: S.of(context).delete, + color: Palette.red, + textColor: Colors.white), + ), + ), + Flexible( + child: Container( + padding: EdgeInsets.only(left: 8.0), + child: LoadingPrimaryButton( + onPressed: () async { + if (_formKey.currentState?.validate() ?? false) { + if (walletNewVM.nameExists(walletEditViewModel.newName)) { + showPopUp( + context: context, + builder: (_) { + return AlertWithOneAction( + alertTitle: '', + alertContent: S.of(context).wallet_name_exists, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } else { + try { + bool confirmed = false; + + if (SettingsStoreBase + .walletPasswordDirectInput) { + await Navigator.of(context).pushNamed( + Routes.walletUnlockLoadable, + arguments: WalletUnlockArguments( + authPasswordHandler: + (String password) async { + await walletEditViewModel + .changeName(editingWallet, + password: password); + }, + callback: (bool + isAuthenticatedSuccessfully, + AuthPageState auth) async { + if (isAuthenticatedSuccessfully) { + auth.close(); + confirmed = true; + } + }, + walletName: editingWallet.name, + walletType: editingWallet.type)); + } else { + await walletEditViewModel + .changeName(editingWallet); + confirmed = true; + } + + if (confirmed) Navigator.of(context).pop(); + } catch (e) {} + } + } + }, + text: S.of(context).save, + color: Theme.of(context).accentTextTheme.bodyLarge!.color!, + textColor: Colors.white, + isDisabled: walletEditViewModel.newName.isEmpty || isLoading, + ), + ), + ) + ], + ); + }, + ) + ], + ), + ), + ); + } + + Future _removeWallet(BuildContext context) async { + authService.authenticateAction(context, onAuthSuccess: (isAuthenticatedSuccessfully) async { + if (!isAuthenticatedSuccessfully) { + return; + } + + _onSuccessfulAuth(context); + }); + } + + void _onSuccessfulAuth(BuildContext context) async { + bool confirmed = false; + + await showPopUp( + context: context, + builder: (BuildContext dialogContext) { + return AlertWithTwoActions( + alertTitle: S.of(context).delete_wallet, + alertContent: S.of(context).delete_wallet_confirm_message(editingWallet.name), + leftButtonText: S.of(context).cancel, + rightButtonText: S.of(context).delete, + actionLeftButton: () => Navigator.of(dialogContext).pop(), + actionRightButton: () { + confirmed = true; + Navigator.of(dialogContext).pop(); + }); + }); + + if (confirmed) { + Navigator.of(context).pop(); + + try { + changeProcessText(context, S.of(context).wallet_list_removing_wallet(editingWallet.name)); + await walletEditViewModel.remove(editingWallet); + hideProgressText(); + } catch (e) { + changeProcessText( + context, + S.of(context).wallet_list_failed_to_remove(editingWallet.name, e.toString()), + ); + } + } + } + + void changeProcessText(BuildContext context, String text) { + _progressBar = createBar(text, duration: null)..show(context); + } + + Future hideProgressText() async { + await Future.delayed(Duration(milliseconds: 50), () { + _progressBar?.dismiss(); + _progressBar = null; + }); + } +} diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 35366455c..043b3314e 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -1,7 +1,9 @@ import 'package:cake_wallet/main.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -205,6 +207,20 @@ class WalletListBodyState extends State { } Future _loadWallet(WalletListItem wallet) async { + if (SettingsStoreBase.walletPasswordDirectInput) { + Navigator.of(context).pushNamed( + Routes.walletUnlockLoadable, + arguments: WalletUnlockArguments( + callback: (bool isAuthenticatedSuccessfully, AuthPageState auth) async { + if (isAuthenticatedSuccessfully) { + auth.close(); + setState(() {}); + } + }, walletName: wallet.name, + walletType: wallet.type)); + return; + } + await widget.authService.authenticateAction(context, onAuthSuccess: (isAuthenticatedSuccessfully) async { if (!isAuthenticatedSuccessfully) { diff --git a/lib/src/screens/wallet_unlock/wallet_unlock_arguments.dart b/lib/src/screens/wallet_unlock/wallet_unlock_arguments.dart index 34b26548c..5b6d4dd16 100644 --- a/lib/src/screens/wallet_unlock/wallet_unlock_arguments.dart +++ b/lib/src/screens/wallet_unlock/wallet_unlock_arguments.dart @@ -1,13 +1,17 @@ import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cw_core/wallet_type.dart'; +typedef AuthPasswordHandler = Future Function(String); + class WalletUnlockArguments { - WalletUnlockArguments({ - required this.callback, - this.walletName, - this.walletType}); + WalletUnlockArguments( + {required this.callback, + this.walletName, + this.walletType, + this.authPasswordHandler}); final OnAuthenticationFinished callback; + final AuthPasswordHandler? authPasswordHandler; final String? walletName; final WalletType? walletType; -} \ No newline at end of file +} diff --git a/lib/src/screens/wallet_unlock/wallet_unlock_page.dart b/lib/src/screens/wallet_unlock/wallet_unlock_page.dart index f7e1968cf..42f5464c7 100644 --- a/lib/src/screens/wallet_unlock/wallet_unlock_page.dart +++ b/lib/src/screens/wallet_unlock/wallet_unlock_page.dart @@ -2,10 +2,10 @@ import 'package:another_flushbar/flushbar.dart'; import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart'; +import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; -import 'package:cake_wallet/view_model/wallet_unlock_verifiable_view_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/view_model/wallet_unlock_view_model.dart'; @@ -17,10 +17,12 @@ class WalletUnlockPage extends StatefulWidget { WalletUnlockPage( this.walletUnlockViewModel, this.onAuthenticationFinished, + this.authPasswordHandler, {required this.closable}); final WalletUnlockViewModel walletUnlockViewModel; final OnAuthenticationFinished onAuthenticationFinished; + final AuthPasswordHandler? authPasswordHandler; final bool closable; @override @@ -204,7 +206,20 @@ class WalletUnlockPageState extends AuthPageState { child: Observer( builder: (_) => LoadingPrimaryButton( - onPressed: () => widget.walletUnlockViewModel.unlock(), + onPressed: () async { + if (widget.authPasswordHandler != null) { + try { + await widget.authPasswordHandler!(widget + .walletUnlockViewModel.password); + widget.walletUnlockViewModel.success(); + } catch (e) { + widget.walletUnlockViewModel.failure(e); + } + return; + } + + widget.walletUnlockViewModel.unlock(); + }, text: S.of(context).unlock, color: Colors.green, textColor: Colors.white, @@ -213,4 +228,4 @@ class WalletUnlockPageState extends AuthPageState { ])) )); } -} \ No newline at end of file +} diff --git a/lib/view_model/wallet_list/wallet_edit_view_model.dart b/lib/view_model/wallet_list/wallet_edit_view_model.dart new file mode 100644 index 000000000..806d76022 --- /dev/null +++ b/lib/view_model/wallet_list/wallet_edit_view_model.dart @@ -0,0 +1,56 @@ +import 'package:cake_wallet/core/wallet_loading_service.dart'; +import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/di.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; + +part 'wallet_edit_view_model.g.dart'; + +class WalletEditViewModel = WalletEditViewModelBase with _$WalletEditViewModel; + +abstract class WalletEditViewModelState {} + +class WalletEditViewModelInitialState extends WalletEditViewModelState {} + +class WalletEditRenamePending extends WalletEditViewModelState {} + +class WalletEditDeletePending extends WalletEditViewModelState {} + +abstract class WalletEditViewModelBase with Store { + WalletEditViewModelBase(this._walletListViewModel, this._walletLoadingService) + : state = WalletEditViewModelInitialState(), + newName = ''; + + @observable + WalletEditViewModelState state; + + @observable + String newName; + + final WalletListViewModel _walletListViewModel; + final WalletLoadingService _walletLoadingService; + + @action + Future changeName(WalletListItem walletItem, {String? password}) async { + state = WalletEditRenamePending(); + await _walletLoadingService.renameWallet( + walletItem.type, walletItem.name, newName, + password: password); + resetState(); + } + + @action + Future remove(WalletListItem wallet) async { + state = WalletEditDeletePending(); + final walletService = getIt.get(param1: wallet.type); + await walletService.remove(wallet.name); + resetState(); + } + + @action + void resetState() { + _walletListViewModel.updateList(); + state = WalletEditViewModelInitialState(); + } +} diff --git a/lib/view_model/wallet_unlock_loadable_view_model.dart b/lib/view_model/wallet_unlock_loadable_view_model.dart index e8d328ade..e1e5d5238 100644 --- a/lib/view_model/wallet_unlock_loadable_view_model.dart +++ b/lib/view_model/wallet_unlock_loadable_view_model.dart @@ -7,16 +7,15 @@ import 'package:cake_wallet/view_model/wallet_unlock_view_model.dart'; part 'wallet_unlock_loadable_view_model.g.dart'; -class WalletUnlockLoadableViewModel = WalletUnlockLoadableViewModelBase with _$WalletUnlockLoadableViewModel; +class WalletUnlockLoadableViewModel = WalletUnlockLoadableViewModelBase + with _$WalletUnlockLoadableViewModel; - abstract class WalletUnlockLoadableViewModelBase extends WalletUnlockViewModel with Store { - WalletUnlockLoadableViewModelBase( - this._appStore, - this._walletLoadingService, { - required this.walletName, - required this.walletType}) - : password = '', - state = InitialExecutionState(); +abstract class WalletUnlockLoadableViewModelBase extends WalletUnlockViewModel + with Store { + WalletUnlockLoadableViewModelBase(this._appStore, this._walletLoadingService, + {required this.walletName, required this.walletType}) + : password = '', + state = InitialExecutionState(); final String walletName; @@ -43,14 +42,24 @@ class WalletUnlockLoadableViewModel = WalletUnlockLoadableViewModelBase with _$W Future unlock() async { try { state = InitialExecutionState(); - final wallet = await _walletLoadingService.load( - walletType, - walletName, - password: password); + final wallet = await _walletLoadingService.load(walletType, walletName, + password: password); _appStore.changeCurrentWallet(wallet); - state = ExecutedSuccessfullyState(); - } catch(e) { - state = FailureState(e.toString()); + success(); + } catch (e) { + failure(e.toString()); } } + + @override + @action + void success() { + state = ExecutedSuccessfullyState(); + } + + @override + @action + void failure(e) { + state = FailureState(e.toString()); + } } diff --git a/lib/view_model/wallet_unlock_verifiable_view_model.dart b/lib/view_model/wallet_unlock_verifiable_view_model.dart index ebfec0ff6..11fe3b674 100644 --- a/lib/view_model/wallet_unlock_verifiable_view_model.dart +++ b/lib/view_model/wallet_unlock_verifiable_view_model.dart @@ -7,15 +7,15 @@ import 'package:cake_wallet/view_model/wallet_unlock_view_model.dart'; part 'wallet_unlock_verifiable_view_model.g.dart'; -class WalletUnlockVerifiableViewModel = WalletUnlockVerifiableViewModelBase with _$WalletUnlockVerifiableViewModel; +class WalletUnlockVerifiableViewModel = WalletUnlockVerifiableViewModelBase + with _$WalletUnlockVerifiableViewModel; -abstract class WalletUnlockVerifiableViewModelBase extends WalletUnlockViewModel with Store { - WalletUnlockVerifiableViewModelBase( - this.appStore, { - required this.walletName, - required this.walletType}) - : password = '', - state = InitialExecutionState(); +abstract class WalletUnlockVerifiableViewModelBase extends WalletUnlockViewModel + with Store { + WalletUnlockVerifiableViewModelBase(this.appStore, + {required this.walletName, required this.walletType}) + : password = '', + state = InitialExecutionState(); final String walletName; @@ -38,12 +38,24 @@ abstract class WalletUnlockVerifiableViewModelBase extends WalletUnlockViewModel @override @action Future unlock() async { - try { - state = appStore.wallet!.password == password - ? ExecutedSuccessfullyState() - : FailureState(S.current.invalid_password) ; - } catch(e) { - state = FailureState('${S.current.invalid_password}\n${e.toString()}'); - } + try { + state = appStore.wallet!.password == password + ? ExecutedSuccessfullyState() + : FailureState(S.current.invalid_password); + } catch (e) { + failure('${S.current.invalid_password}\n${e.toString()}'); + } + } + + @override + @action + void success() { + state = ExecutedSuccessfullyState(); + } + + @override + @action + void failure(e) { + state = FailureState(e.toString()); } } diff --git a/lib/view_model/wallet_unlock_view_model.dart b/lib/view_model/wallet_unlock_view_model.dart index aff3a89d0..f0131c61f 100644 --- a/lib/view_model/wallet_unlock_view_model.dart +++ b/lib/view_model/wallet_unlock_view_model.dart @@ -6,4 +6,6 @@ abstract class WalletUnlockViewModel { void setPassword(String password); ExecutionState get state; Future unlock(); -} \ No newline at end of file + void success(); + void failure(dynamic e); +}