diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 80924bb0d..787933217 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -257,6 +257,10 @@ void setRecoveringFromSeed({required bool isRecovery}) { final storeMutex = Mutex(); +void refresh() { + currentWallet!.refresh(); +} + int lastStorePointer = 0; int lastStoreHeight = 0; void storeSync({bool force = false}) async { diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 2bdcf0bac..85bc77291 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -150,6 +150,11 @@ void restoreWalletFromSeedSync( _lastOpenedWallet = path; } +void storePassphrase({required String path, required String passphrase}) { + currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: passphrase); + currentWallet!.store(path: path); +} + void restoreWalletFromKeys( {required String path, required String password, diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 16ab89ad6..d0e653907 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -382,22 +382,30 @@ abstract class MoneroWalletBase } MoneroTrezorService? trezorService; + + Future _getTrezor() async { + if (trezorService == null) throw Exception("Trezor not connected"); + + final trezor = Trezor(trezorService!); + await trezor.newPassphraseSession(passphrase); + return trezor; + } + Future syncTrezor() async { if (trezorService == null) throw Exception("Trezor not connected"); final ptr = Pointer.fromAddress(currentWallet!.ffiAddress()); final tdis = monero.Wallet_exportTrezorTdis(ptr); - - final response = await Trezor(trezorService!).keyImageSync(tdis); - + final trezor = await _getTrezor(); + final response = await trezor.keyImageSync(tdis); final success = monero.Wallet_importTrezorEncryptedKeyImagesJson(ptr, response); if (!success) throw Exception(monero.Wallet_errorString(ptr)); } Future signTrezorTransaction(String json) async { - if (trezorService == null) throw Exception("Trezor not connected"); - return Trezor(trezorService!).signTransaction(json); + final trezor = await _getTrezor(); + return trezor.signTransaction(json); } @override diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index cb549a861..e774b47ba 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -44,10 +44,15 @@ class MoneroNewWalletCredentials extends WalletCredentials { } class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials { - MoneroRestoreWalletFromHardwareCredentials( - {required String name, required this.hardwareWalletService, int height = 0, String? password}) - : super(name: name, password: password, height: height); - HardwareWalletService hardwareWalletService; + MoneroRestoreWalletFromHardwareCredentials({ + required String name, + required this.hardwareWalletService, + int height = 0, + String? password, + String? passphrase, + }) : super(name: name, password: password, height: height, passphrase: passphrase); + + final HardwareWalletService hardwareWalletService; } class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials { @@ -306,7 +311,13 @@ class MoneroWalletService extends WalletService< deviceName: 'Ledger', ); } else if (credentials.hardwareWalletService case MoneroTrezorService service) { - final watchCredentials = await Trezor(service).getWatchCredentials(); + final trezor = Trezor(service); + + if (credentials.passphrase != null) { + await trezor.newPassphraseSession(credentials.passphrase); + } + + final watchCredentials = await trezor.getWatchCredentials(); monero_wallet_manager.restoreWalletFromKeys( path: path, @@ -317,6 +328,10 @@ class MoneroWalletService extends WalletService< viewKey: watchCredentials.watchKey, spendKey: "", ); + + if (credentials.passphrase != null) { + monero_wallet_manager.storePassphrase(path: path, passphrase: credentials.passphrase!); + } } final wallet = MoneroWallet( diff --git a/cw_monero/lib/trezor.dart b/cw_monero/lib/trezor.dart index eff953d1b..a9a387912 100644 --- a/cw_monero/lib/trezor.dart +++ b/cw_monero/lib/trezor.dart @@ -21,6 +21,15 @@ class Trezor { Trezor(this.service); + String? _sessionPassphrase; + Future newPassphraseSession(String? passphrase) async { + if (passphrase == null) return; + + if (_sessionPassphrase == passphrase) return; + _sessionPassphrase = passphrase; + return service.client.createChannel(passphrase: _sessionPassphrase); + } + Future getWatchCredentials() async { final credentials = await TrezorMonero(service.client).getWatchCredentials(); diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 79da36944..275e7d4aa 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -921,8 +921,8 @@ packages: dependency: "direct main" description: path: trezor-flutter - ref: d1289dfa4247a93ee57342dfd249957b339915ef - resolved-ref: d1289dfa4247a93ee57342dfd249957b339915ef + ref: "944080d950a62dd48a199b9736237cbe580351de" + resolved-ref: "944080d950a62dd48a199b9736237cbe580351de" url: "https://github.com/cake-tech/trezor-flutter.git" source: git version: "0.1.0" @@ -930,8 +930,8 @@ packages: dependency: transitive description: path: trezor_usb_transport - ref: d1289dfa4247a93ee57342dfd249957b339915ef - resolved-ref: d1289dfa4247a93ee57342dfd249957b339915ef + ref: "944080d950a62dd48a199b9736237cbe580351de" + resolved-ref: "944080d950a62dd48a199b9736237cbe580351de" url: "https://github.com/cake-tech/trezor-flutter.git" source: git version: "0.0.1" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index c84f08b04..692103eba 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: trezor_flutter: git: url: https://github.com/cake-tech/trezor-flutter.git - ref: d1289dfa4247a93ee57342dfd249957b339915ef + ref: 944080d950a62dd48a199b9736237cbe580351de path: trezor-flutter dev_dependencies: diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 1551b95de..7d2b8ec26 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -236,12 +236,14 @@ class CWMonero extends Monero { required String password, required int height, required HardwareWalletService hardwareWalletService, + required String? passphrase, }) => MoneroRestoreWalletFromHardwareCredentials( name: name, password: password, height: height, hardwareWalletService: hardwareWalletService, + passphrase: passphrase, ); @override diff --git a/lib/new-ui/widgets/digit_input.dart b/lib/new-ui/widgets/digit_input.dart index d6889db67..2994a75e9 100644 --- a/lib/new-ui/widgets/digit_input.dart +++ b/lib/new-ui/widgets/digit_input.dart @@ -1,5 +1,7 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import "dart:io"; + +import "package:flutter/material.dart"; +import "package:flutter/services.dart"; class DigitInputController { String _text = ""; @@ -19,38 +21,37 @@ class DigitInputController { } class DigitInputPill extends StatelessWidget { - const DigitInputPill({super.key, this.digit, required this.highlighted}); + const DigitInputPill({required this.highlighted, super.key, this.digit}); final String? digit; final bool highlighted; @override - Widget build(BuildContext context) { - return Container( - width: 51, - height: 84, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerHigh, - borderRadius: BorderRadius.circular(16), - border: - highlighted ? Border.all(color: Theme.of(context).colorScheme.primary, width: 2) : null, - ), - child: Center( - child: Padding( - padding: EdgeInsets.symmetric(vertical: 16), - child: Text(digit ?? " ", style: TextStyle(fontSize: 40)), + Widget build(BuildContext context) => Container( + width: 51, + height: 84, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(16), + border: highlighted + ? Border.all(color: Theme.of(context).colorScheme.primary, width: 2) + : null, ), - ), - ); - } + child: Center( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Text(digit ?? " ", style: const TextStyle(fontSize: 40)), + ), + ), + ); } class DigitInput extends StatefulWidget { const DigitInput({ - super.key, required this.controller, required this.desiredLength, this.breakAt = 3, + super.key, }); final DigitInputController controller; @@ -74,12 +75,9 @@ class _DigitInputState extends State implements TextInputClient { if (_textInputConnection == null || !_textInputConnection!.attached) { _textInputConnection = TextInput.attach( this, - const TextInputConfiguration( - inputType: TextInputType.numberWithOptions( - signed: false, - decimal: false, - ), - inputAction: TextInputAction.none, + TextInputConfiguration( + inputType: TextInputType.number, + inputAction: Platform.isIOS ? TextInputAction.done : TextInputAction.none, ), ); } @@ -102,30 +100,28 @@ class _DigitInputState extends State implements TextInputClient { } @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: _openVirtualKeyboard, - child: SizedBox( - height: 84, - child: ListView.separated( - scrollDirection: Axis.horizontal, - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => DigitInputPill( - digit: widget.controller.text.length > index ? widget.controller.text[index] : null, - highlighted: widget.controller.text.length == index, + Widget build(BuildContext context) => GestureDetector( + onTap: _openVirtualKeyboard, + child: SizedBox( + height: 84, + child: ListView.separated( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => DigitInputPill( + digit: widget.controller.text.length > index ? widget.controller.text[index] : null, + highlighted: widget.controller.text.length == index, + ), + separatorBuilder: (context, index) => + SizedBox(width: (index + 1) % widget.breakAt == 0 ? 16 : 4), + itemCount: widget.desiredLength, ), - separatorBuilder: (context, index) => - SizedBox(width: (index + 1) % widget.breakAt == 0 ? 16 : 4), - itemCount: widget.desiredLength, ), - ), - ); - } + ); @override void updateEditingValue(TextEditingValue value) { - final RegExp numberRegExp = RegExp(r'^\d{0,6}$'); + final RegExp numberRegExp = RegExp(r"^\d{0,6}$"); if (numberRegExp.hasMatch(value.text)) { setState(() => widget.controller.text = value.text); diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart index 21cbd0292..ed1ae3154 100644 --- a/lib/src/screens/connect_device/connect_device_page.dart +++ b/lib/src/screens/connect_device/connect_device_page.dart @@ -142,12 +142,22 @@ class ConnectDevicePageBodyState extends State { super.dispose(); } + var _isRefreshingUsb = false; Future _refreshUsbDevices() async { - final dev = await widget.hardwareWalletVM.getAllUsbDevices(); - - if (usbDevices.length != dev.length) { - setState(() => usbDevices = dev); + if (_isRefreshingUsb) { + return; } + _isRefreshingUsb = true; + try { + final dev = await widget.hardwareWalletVM.getAllUsbDevices(); + + if (usbDevices.length != dev.length) { + setState(() => usbDevices = dev); + } + } catch(e) { + printV(e); + } + _isRefreshingUsb = false; } Future _refreshBleDevices() async { diff --git a/lib/src/screens/connect_device/monero_hardware_wallet_options_page.dart b/lib/src/screens/connect_device/monero_hardware_wallet_options_page.dart index 7e77d4350..e6286a80d 100644 --- a/lib/src/screens/connect_device/monero_hardware_wallet_options_page.dart +++ b/lib/src/screens/connect_device/monero_hardware_wallet_options_page.dart @@ -1,19 +1,22 @@ -import 'package:cake_wallet/core/wallet_name_validator.dart'; -import 'package:cw_core/generate_name.dart'; -import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; -import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart'; -import 'package:cake_wallet/utils/responsive_layout_util.dart'; -import 'package:cake_wallet/utils/show_pop_up.dart'; -import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:mobx/mobx.dart'; +import "dart:async"; + +import "package:cake_wallet/core/wallet_name_validator.dart"; +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/src/screens/base_page.dart"; +import "package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_passphrase_input.dart"; +import "package:cake_wallet/src/widgets/alert_with_one_action.dart"; +import "package:cake_wallet/src/widgets/base_text_form_field.dart"; +import "package:cake_wallet/src/widgets/blockchain_height_widget.dart"; +import "package:cake_wallet/src/widgets/primary_button.dart"; +import "package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart"; +import "package:cake_wallet/utils/responsive_layout_util.dart"; +import "package:cake_wallet/utils/show_pop_up.dart"; +import "package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart"; +import "package:cw_core/generate_name.dart"; +import "package:cw_core/wallet_type.dart"; +import "package:flutter/material.dart"; +import "package:flutter_mobx/flutter_mobx.dart"; +import "package:mobx/mobx.dart"; class MoneroHardwareWalletOptionsPage extends BasePage { MoneroHardwareWalletOptionsPage(this._walletHardwareRestoreVM); @@ -42,13 +45,15 @@ class _MoneroHardwareWalletOptionsFormState extends State<_MoneroHardwareWalletO : _formKey = GlobalKey(), _blockchainHeightKey = GlobalKey(), _blockHeightFocusNode = FocusNode(), - _controller = TextEditingController(); + _walletNameController = TextEditingController(), + _passphraseController = TextEditingController(); final GlobalKey _formKey; final GlobalKey _blockchainHeightKey; final FocusNode _blockHeightFocusNode; final WalletHardwareRestoreViewModel _walletHardwareRestoreVM; - final TextEditingController _controller; + final TextEditingController _walletNameController; + final TextEditingController _passphraseController; @override void initState() { @@ -57,131 +62,159 @@ class _MoneroHardwareWalletOptionsFormState extends State<_MoneroHardwareWalletO } @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.only(top: 24), - child: ScrollableWithBottomSection( - contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), - content: Center( - child: ConstrainedBox( - constraints: - BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: EdgeInsets.only(top: 0), - child: Form( - key: _formKey, - child: Stack( - alignment: Alignment.centerRight, - children: [ - BaseTextFormField( - onChanged: (value) => _walletHardwareRestoreVM.name = value, - controller: _controller, - textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 20.0, - fontWeight: FontWeight.w600, - ), - placeholderTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 18.0, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - hintText: S.of(context).wallet_name, - suffixIcon: Semantics( - label: S.of(context).generate_name, - child: IconButton( - onPressed: _onGenerateName, - icon: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6.0), - color: Theme.of(context).colorScheme.surfaceContainerHighest, + Widget build(BuildContext context) => Padding( + padding: const EdgeInsets.only(top: 24), + child: ScrollableWithBottomSection( + contentPadding: const EdgeInsets.only(left: 24, right: 24, bottom: 24), + content: Center( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.zero, + child: Form( + key: _formKey, + child: Stack( + alignment: Alignment.centerRight, + children: [ + BaseTextFormField( + onChanged: (value) => _walletHardwareRestoreVM.name = value, + controller: _walletNameController, + textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 20, + fontWeight: FontWeight.w600, ), - width: 34, - height: 34, - child: Image.asset( - 'assets/images/refresh_icon.png', - color: Theme.of(context).colorScheme.primary, + placeholderTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + hintText: S.of(context).wallet_name, + suffixIcon: Semantics( + label: S.of(context).generate_name, + child: IconButton( + onPressed: _onGenerateName, + icon: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + color: Theme.of(context).colorScheme.surfaceContainerHighest, + ), + width: 34, + height: 34, + child: Image.asset( + "assets/images/refresh_icon.png", + color: Theme.of(context).colorScheme.primary, + ), ), ), ), + validator: WalletNameValidator(), ), - validator: WalletNameValidator(), - ), - ], + ], + ), ), ), - ), - Padding( - padding: EdgeInsets.only(top: 20), - child: BlockchainHeightWidget( - focusNode: _blockHeightFocusNode, - key: _blockchainHeightKey, - hasDatePicker: true, - walletType: WalletType.monero, + Padding( + padding: const EdgeInsets.only(top: 20), + child: BlockchainHeightWidget( + focusNode: _blockHeightFocusNode, + key: _blockchainHeightKey, + hasDatePicker: true, + walletType: WalletType.monero, + ), ), + ], + ), + ), + ), + bottomSectionPadding: const EdgeInsets.all(24), + bottomSection: Observer( + builder: (_) => Column( + children: [ + if (_walletHardwareRestoreVM.passphraseAvailable) ...[ + TextButton( + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => MoneroHardwareWalletPassphraseInputModal( + controller: _passphraseController, + ), + ); + }, + child: Text(S.of(context).add_a_passphrase), + ), + ], + LoadingPrimaryButton( + onPressed: _confirmForm, + text: S.of(context).seed_language_next, + color: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + isDisabled: _walletHardwareRestoreVM.name.isEmpty, ), ], ), ), ), - bottomSectionPadding: EdgeInsets.all(24), - bottomSection: Observer( - builder: (context) => LoadingPrimaryButton( - onPressed: _confirmForm, - text: S.of(context).seed_language_next, - color: Theme.of(context).colorScheme.primary, - textColor: Theme.of(context).colorScheme.onPrimary, - isDisabled: _walletHardwareRestoreVM.name.isEmpty, - ), - ), - ), - ); - } + ); Future _onGenerateName() async { final rName = await generateName(); FocusManager.instance.primaryFocus?.unfocus(); setState(() { - _controller.text = rName; + _walletNameController.text = rName; _walletHardwareRestoreVM.name = rName; - _controller.selection = - TextSelection.fromPosition(TextPosition(offset: _controller.text.length)); + _walletNameController.selection = + TextSelection.fromPosition(TextPosition(offset: _walletNameController.text.length)); }); } Future _confirmForm() async { - showPopUp( - context: context, - builder: (BuildContext context) => AlertWithOneAction( - alertTitle: S.of(context).proceed_on_device, - alertContent: S.of(context).proceed_on_device_description, - buttonText: S.of(context).cancel, - alertBarrierDismissible: false, - buttonAction: () => Navigator.of(context).pop(), + unawaited( + showPopUp( + context: context, + builder: (context) => AlertWithOneAction( + alertTitle: S.of(context).proceed_on_device, + alertContent: S.of(context).proceed_on_device_description, + buttonText: S.of(context).cancel, + alertBarrierDismissible: false, + buttonAction: () => Navigator.of(context).pop(), + ), ), ); - final options = {'height': _blockchainHeightKey.currentState?.height ?? -1}; + final options = {"height": _blockchainHeightKey.currentState?.height ?? -1}; + + if (_walletHardwareRestoreVM.passphraseAvailable && _passphraseController.text.isNotEmpty) { + options["passphrase"] = _passphraseController.text; + } await _walletHardwareRestoreVM.create(options: options); } bool _effectsInstalled = false; void _setEffects(BuildContext context) { - if (_effectsInstalled) return; + if (_effectsInstalled) { + return; + } - reaction((_) => _walletHardwareRestoreVM.error, (String? error) { + reaction((_) => _walletHardwareRestoreVM.error, (error) { if (error != null) { - if (error == S.current.ledger_connection_error) Navigator.of(context).pop(); + if (error == S.current.ledger_connection_error) { + Navigator.of(context).pop(); + } WidgetsBinding.instance.addPostFrameCallback((_) { showPopUp( context: context, - builder: (BuildContext context) => AlertWithOneAction( + builder: (context) => AlertWithOneAction( alertTitle: S.of(context).error, alertContent: error, buttonText: S.of(context).ok, diff --git a/lib/src/screens/connect_device/monero_hardware_wallet_passphrase_input.dart b/lib/src/screens/connect_device/monero_hardware_wallet_passphrase_input.dart new file mode 100644 index 000000000..6dd277e97 --- /dev/null +++ b/lib/src/screens/connect_device/monero_hardware_wallet_passphrase_input.dart @@ -0,0 +1,54 @@ +import "package:cake_wallet/generated/i18n.dart"; +import "package:cake_wallet/new-ui/widgets/new_primary_button.dart"; +import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart"; +import "package:cake_wallet/src/widgets/base_text_form_field.dart"; +import "package:flutter/material.dart"; + +class MoneroHardwareWalletPassphraseInputModal extends StatelessWidget { + const MoneroHardwareWalletPassphraseInputModal({required this.controller, super.key}); + + final TextEditingController controller; + + @override + Widget build(BuildContext context) => Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.vertical(top: Radius.circular(18)), + color: Theme.of(context).colorScheme.surface, + ), + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ModalTopBar( + title: S.of(context).passphrase_entry, + leadingIcon: const Icon(Icons.close), + onLeadingPressed: Navigator.of(context).pop, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 18), + child: Column( + spacing: 12, + children: [ + BaseTextFormField( + controller: controller, + hintText: S.of(context).passphrase_raw, + ), + const SizedBox(), + NewPrimaryButton( + onPressed: Navigator.of(context).pop, + text: S.of(context).restore_next, + color: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + ), + const SizedBox(), + ], + ), + ), + ], + ), + ), + ), + ); +} diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 2588fd35c..c8f795c5f 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -99,7 +99,7 @@ class WalletRestorePage extends BasePage { children: [ Observer( builder: (context) { - return walletRestoreViewModel.mode == WalletRestoreMode.seed + return walletRestoreViewModel.passphraseAvailable ? StandardCheckbox( captionColor: Theme.of(context).colorScheme.onSecondaryContainer, value: walletRestoreViewModel.hasPassphrase, diff --git a/lib/view_model/wallet_hardware_restore_view_model.dart b/lib/view_model/wallet_hardware_restore_view_model.dart index 37f6467d3..35de089c8 100644 --- a/lib/view_model/wallet_hardware_restore_view_model.dart +++ b/lib/view_model/wallet_hardware_restore_view_model.dart @@ -6,13 +6,13 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart'; -import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/seed_settings_view_model.dart'; import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; @@ -43,6 +43,8 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with @observable String? error = null; + bool get passphraseAvailable => hardwareWalletVM.hardwareWalletType == HardwareWalletType.trezor; + // @observable ObservableList availableAccounts = ObservableList(); @@ -87,6 +89,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with hardwareWalletService: hardwareWalletVM.getHardwareWalletService(type), password: password, height: _options['height'] as int? ?? 0, + passphrase: _options['passphrase'] as String?, ); default: throw Exception('Unexpected type: ${type.toString()}'); diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 0bc32341b..1f9557170 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -108,6 +108,10 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { @observable WalletRestoreMode mode; + @computed + bool get passphraseAvailable => + mode == WalletRestoreMode.seed || hardwareWalletType == HardwareWalletType.trezor; + @observable bool hasPassphrase; diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index e52f35696..e5b669cb6 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -14,6 +14,7 @@ "activeConnectionsPrompt": "Active connections will appear here", "add": "Add", "add_a_note": "Add a note", + "add_a_passphrase": "Add a passphrase", "add_account": "Add Account", "add_contact": "Add contact", "add_contact_to_address_book": "Would you like to add this contact to your address book?", @@ -402,6 +403,7 @@ "enter_code": "Enter code", "enter_greater_than": "Please enter an amount greater than", "enter_less_than": "Please enter an amount less than", + "enter_on_device_instead": "Enter on device instead", "enter_recipient_address": "Please enter recipient address", "enter_seed_phrase": "Enter your seed phrase", "enter_totp_code": "Please enter the TOTP Code.", @@ -745,6 +747,8 @@ "pairing_error": "Pairing Error", "pairingInvalidEvent": "Pairing Invalid Event", "passphrase": "Passphrase (Optional)", + "passphrase_raw": "Passphrase", + "passphrase_entry": "Passphrase entry", "passphrase_view_keys": "Passphrase", "passphrases_doesnt_match": "Passphrases do not match, please try again", "password": "Password", diff --git a/tool/configure.dart b/tool/configure.dart index d6b76d756..1628abbcb 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -455,7 +455,7 @@ abstract class Monero { HardwareWalletType? hardwareWalletType, required int height}); WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic}); - WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required HardwareWalletService hardwareWalletService}); + WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required HardwareWalletService hardwareWalletService, required String? passphrase}); WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required int seedType, required String? passphrase, String? password, String? mnemonic}); Map getKeys(Object wallet); int? getRestoreHeight(Object wallet);