mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
trezor-monero-passphrase-modal (#3337)
* trezor monero passphrase modal * feat: add Trezor passphrase support for wallet restoration process * revert: send page changes * chore: format code * chore: format code * chore: reorganize imports and improve code formatting in send_page.dart [skip ci] * auto-reformat * fix: trezor usb device refresh race condition * fix: ui bugs in relation to trezor * fix: only show passphrase option for trezor devices --------- Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Robert Malikowski
Omar Hatem
parent
03edd658a5
commit
9d685d4645
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -382,22 +382,30 @@ abstract class MoneroWalletBase
|
||||
}
|
||||
|
||||
MoneroTrezorService? trezorService;
|
||||
|
||||
Future<Trezor> _getTrezor() async {
|
||||
if (trezorService == null) throw Exception("Trezor not connected");
|
||||
|
||||
final trezor = Trezor(trezorService!);
|
||||
await trezor.newPassphraseSession(passphrase);
|
||||
return trezor;
|
||||
}
|
||||
|
||||
Future<void> syncTrezor() async {
|
||||
if (trezorService == null) throw Exception("Trezor not connected");
|
||||
|
||||
final ptr = Pointer<Void>.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<String> 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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -21,6 +21,15 @@ class Trezor {
|
||||
|
||||
Trezor(this.service);
|
||||
|
||||
String? _sessionPassphrase;
|
||||
Future<void> newPassphraseSession(String? passphrase) async {
|
||||
if (passphrase == null) return;
|
||||
|
||||
if (_sessionPassphrase == passphrase) return;
|
||||
_sessionPassphrase = passphrase;
|
||||
return service.client.createChannel(passphrase: _sessionPassphrase);
|
||||
}
|
||||
|
||||
Future<MoneroTrezorWatchCredentials> getWatchCredentials() async {
|
||||
final credentials = await TrezorMonero(service.client).getWatchCredentials();
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<DigitInput> 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<DigitInput> 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);
|
||||
|
||||
@@ -142,12 +142,22 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
var _isRefreshingUsb = false;
|
||||
Future<void> _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<void> _refreshBleDevices() async {
|
||||
|
||||
@@ -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<FormState>(),
|
||||
_blockchainHeightKey = GlobalKey<BlockchainHeightState>(),
|
||||
_blockHeightFocusNode = FocusNode(),
|
||||
_controller = TextEditingController();
|
||||
_walletNameController = TextEditingController(),
|
||||
_passphraseController = TextEditingController();
|
||||
|
||||
final GlobalKey<FormState> _formKey;
|
||||
final GlobalKey<BlockchainHeightState> _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<void> _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<void> _confirmForm() async {
|
||||
showPopUp<void>(
|
||||
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<void>(
|
||||
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 = <String, dynamic>{"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<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertWithOneAction(
|
||||
builder: (context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: error,
|
||||
buttonText: S.of(context).ok,
|
||||
|
||||
@@ -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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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<HardwareAccountData> 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()}');
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
+1
-1
@@ -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<String, String> getKeys(Object wallet);
|
||||
int? getRestoreHeight(Object wallet);
|
||||
|
||||
Reference in New Issue
Block a user