mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Cw 604 integrate bitcoin ledger (#1407)
* CW-503 Prepare Ledger integration * CW-503 Revert Tor ignore * CW-503 Add Connect Device Page * CW-503 Add createWalletFromDevice for monero * CW-503 Add Connect Device Page * CW-503 Add Connect Device Page * CW-503 Add Debug Options * CW-503 Add proper hardware wallet selection screen * CW-503 Minor design changes on connect_device_page * CW-503 Add Create Wallet from Ledger * CW-503 Spent Ledger ETH Funds * CW-503 Minor fixes * CW-503 Fix Merge conflicts * CW-503 Fix Merge conflicts * CW-503 Fix Merge conflicts * CW-503 Fix minor conflicts * CW-503 Improve Ledger BLE Communication * CW-503 Improve Ledger BLE Communication * CW-503 Rollback Monero Test code * CW-503 Fix Execution failed for task :app:checkReleaseDuplicateClasses * CW-503 Better Error-Exceptions * CW-503 Add SetPinScreen before restore from hardware-wallet * CW-503 override web3dart to use cake's git hosted version * CW-503 Implement ledger sign messages * CW-503 Implement ledger sign messages and send erc20 tokens * CW-503 Fix merge conflicts * CW-503 Fix merge conflicts * CW-503 Use dep override for ledger_flutter * CW-503 Ledger ERC20 finalisation * CW-503 More graceful error handling * CW-503 Even more graceful error handling & remove debug code * CW-503 Minor Changes for Vik * CW-503 Fix USB connection * CW-503 Maybe this overrides web3dart finally * Indicate Loading on the first 5 Wallet Accounts * Prepare Ledger Bitcoin * Fix conflicts with main * Add Bluetooth permission to iOS [skip ci] * add privacyinfo for ios [skip ci] * update PrivacyInfo.xcprivacy [scip ci] * ios shit [skip ci] * Improve bitcoin xpub * Resolve open Todos regarding iOS Support * Minor debug * Remove erc20 dependency to have more granular control over the tx UX * Create Bitcoin Wallets using xpub * Create Bitcoin Wallets using xpub * Better error handling * Improve Ledger account handling * Add Bitcoin Support for Ledger * Add Bitcoin Support for Ledger * Add Bitcoin Support for Ledger * Implement requested Changes * Implement requested Changes * Minor fix * Implement requested Changes * Implement requested Changes * Add comment to remind me * Enable RBF for Bitcoin Ledger * Fix merge conflicts * Update wallet_info.dart * Cw 503 ledger support (#1310) * CW-503 Prepare Ledger integration * CW-503 Revert Tor ignore * CW-503 Add Connect Device Page * CW-503 Add createWalletFromDevice for monero * CW-503 Add Connect Device Page * CW-503 Add Connect Device Page * CW-503 Add Debug Options * CW-503 Add proper hardware wallet selection screen * CW-503 Minor design changes on connect_device_page * CW-503 Add Create Wallet from Ledger * CW-503 Spent Ledger ETH Funds * CW-503 Minor fixes * CW-503 Fix Merge conflicts * CW-503 Fix Merge conflicts * CW-503 Fix Merge conflicts * CW-503 Fix minor conflicts * CW-503 Improve Ledger BLE Communication * CW-503 Improve Ledger BLE Communication * CW-503 Rollback Monero Test code * CW-503 Fix Execution failed for task :app:checkReleaseDuplicateClasses * CW-503 Better Error-Exceptions * CW-503 Add SetPinScreen before restore from hardware-wallet * CW-503 override web3dart to use cake's git hosted version * CW-503 Implement ledger sign messages * CW-503 Implement ledger sign messages and send erc20 tokens * CW-503 Fix merge conflicts * CW-503 Fix merge conflicts * CW-503 Use dep override for ledger_flutter * CW-503 Ledger ERC20 finalisation * CW-503 More graceful error handling * CW-503 Even more graceful error handling & remove debug code * CW-503 Minor Changes for Vik * CW-503 Fix USB connection * CW-503 Maybe this overrides web3dart finally * Indicate Loading on the first 5 Wallet Accounts * Fix conflicts with main * Add Bluetooth permission to iOS [skip ci] * add privacyinfo for ios [skip ci] * update PrivacyInfo.xcprivacy [scip ci] * ios shit [skip ci] * Resolve open Todos regarding iOS Support * Remove erc20 dependency to have more granular control over the tx UX * Better error handling * Improve Ledger account handling * Implement requested Changes * Implement requested Changes * Implement requested Changes * Implement requested Changes * Fix merge conflicts * Update wallet_info.dart --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Fix merge conflicts * Fix merge conflicts * Minor Fix to derivations * Update cw_bitcoin/lib/bitcoin_wallet.dart [skip ci] * Update cw_bitcoin/lib/bitcoin_wallet.dart [skip ci] * Update cw_bitcoin/lib/electrum_wallet.dart [skip ci] * Fix backward compatibility issues * Merge Tron * Fix Tron with HW changes * fix ble on iOS fix tron address validation --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Omar Hatem
parent
043d7d7c8b
commit
5eabdcdca1
@@ -0,0 +1,221 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:ledger_flutter/ledger_flutter.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel);
|
||||
|
||||
class ConnectDevicePageParams {
|
||||
final WalletType walletType;
|
||||
final OnConnectDevice onConnectDevice;
|
||||
|
||||
ConnectDevicePageParams({required this.walletType, required this.onConnectDevice});
|
||||
}
|
||||
|
||||
class ConnectDevicePage extends BasePage {
|
||||
final WalletType walletType;
|
||||
final OnConnectDevice onConnectDevice;
|
||||
final LedgerViewModel ledgerVM;
|
||||
|
||||
ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
|
||||
: walletType = params.walletType,
|
||||
onConnectDevice = params.onConnectDevice;
|
||||
|
||||
@override
|
||||
String get title => S.current.restore_title_from_hardware_wallet;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM);
|
||||
}
|
||||
|
||||
class ConnectDevicePageBody extends StatefulWidget {
|
||||
final WalletType walletType;
|
||||
final OnConnectDevice onConnectDevice;
|
||||
final LedgerViewModel ledgerVM;
|
||||
|
||||
const ConnectDevicePageBody(this.walletType, this.onConnectDevice, this.ledgerVM);
|
||||
|
||||
@override
|
||||
ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState();
|
||||
}
|
||||
|
||||
class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
final imageLedger = 'assets/images/ledger_nano.png';
|
||||
|
||||
final ledger = Ledger(
|
||||
options: LedgerOptions(
|
||||
scanMode: ScanMode.balanced,
|
||||
maxScanDuration: const Duration(minutes: 5),
|
||||
),
|
||||
onPermissionRequest: (_) async {
|
||||
Map<Permission, PermissionStatus> statuses = await [
|
||||
Permission.bluetoothScan,
|
||||
Permission.bluetoothConnect,
|
||||
Permission.bluetoothAdvertise,
|
||||
].request();
|
||||
|
||||
return statuses.values.where((status) => status.isDenied).isEmpty;
|
||||
},
|
||||
);
|
||||
|
||||
var bleIsEnabled = true;
|
||||
var bleDevices = <LedgerDevice>[];
|
||||
var usbDevices = <LedgerDevice>[];
|
||||
|
||||
late Timer? _usbRefreshTimer = null;
|
||||
late Timer? _bleRefreshTimer = null;
|
||||
late StreamSubscription<LedgerDevice>? _bleRefresh = null;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.delayed(
|
||||
Duration(seconds: 1),
|
||||
() => _bleRefresh = ledger.scan().listen((device) => setState(() => bleDevices.add(device))),
|
||||
);
|
||||
// _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
_usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_bleRefreshTimer?.cancel();
|
||||
_usbRefreshTimer?.cancel();
|
||||
_bleRefresh?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _refreshUsbDevices() async {
|
||||
final dev = await ledger.listUsbDevices();
|
||||
if (usbDevices.length != dev.length) setState(() => usbDevices = dev);
|
||||
}
|
||||
|
||||
Future<void> _refreshBleDevices() async {
|
||||
final isBleEnabled = await Permission.bluetooth.serviceStatus.isEnabled;
|
||||
|
||||
setState(() => bleIsEnabled = isBleEnabled);
|
||||
|
||||
if (isBleEnabled) {
|
||||
_bleRefresh = ledger.scan().listen((device) => setState(() => bleDevices.add(device)));
|
||||
_bleRefreshTimer?.cancel();
|
||||
_bleRefreshTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _connectToDevice(LedgerDevice device) async {
|
||||
await widget.ledgerVM.connectLedger(device);
|
||||
widget.onConnectDevice(context, widget.ledgerVM);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Container(
|
||||
width: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
|
||||
height: double.infinity,
|
||||
padding: EdgeInsets.symmetric(vertical: 24, horizontal: 24),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
|
||||
child: Text(
|
||||
Platform.isIOS
|
||||
? S.of(context).connect_your_hardware_wallet_ios
|
||||
: S.of(context).connect_your_hardware_wallet,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
if (!bleIsEnabled)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
|
||||
child: Text(
|
||||
S.of(context).ledger_please_enable_bluetooth,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
if (bleDevices.length > 0) ...[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
S.of(context).bluetooth,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
...bleDevices
|
||||
.map(
|
||||
(device) => Padding(
|
||||
padding: EdgeInsets.only(bottom: 20),
|
||||
child: DeviceTile(
|
||||
onPressed: () => _connectToDevice(device),
|
||||
title: device.name,
|
||||
leading: imageLedger,
|
||||
connectionType: device.connectionType,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
],
|
||||
if (usbDevices.length > 0) ...[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
S.of(context).usb,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
...usbDevices
|
||||
.map(
|
||||
(device) => Padding(
|
||||
padding: EdgeInsets.only(bottom: 20),
|
||||
child: DeviceTile(
|
||||
onPressed: () => _connectToDevice(device),
|
||||
title: device.name,
|
||||
leading: imageLedger,
|
||||
connectionType: device.connectionType,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// import 'dart:convert';
|
||||
//
|
||||
// import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
// import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
|
||||
// import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
// import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
// import 'package:convert/convert.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||
// import 'package:ledger_flutter/ledger_flutter.dart';
|
||||
// import 'package:permission_handler/permission_handler.dart';
|
||||
// import 'package:polyseed/polyseed.dart';
|
||||
//
|
||||
// class DebugDevicePage extends BasePage {
|
||||
// @override
|
||||
// String get title => "Connect Ledger";
|
||||
//
|
||||
// @override
|
||||
// Widget body(BuildContext context) => DebugDevicePageBody();
|
||||
// }
|
||||
//
|
||||
// class DebugDevicePageBody extends StatefulWidget {
|
||||
// @override
|
||||
// DebugDevicePageBodyState createState() => DebugDevicePageBodyState();
|
||||
// }
|
||||
//
|
||||
// class DebugDevicePageBodyState extends State<DebugDevicePageBody> {
|
||||
// final imageLedger = Image.asset(
|
||||
// 'assets/images/ledger_icon_black.png',
|
||||
// width: 40,
|
||||
// );
|
||||
// final ledger = Ledger(
|
||||
// options: LedgerOptions(
|
||||
// scanMode: ScanMode.balanced,
|
||||
// maxScanDuration: const Duration(milliseconds: 5000),
|
||||
// ),
|
||||
// onPermissionRequest: (status) async {
|
||||
// Map<Permission, PermissionStatus> statuses = await [
|
||||
// // Permission.location,
|
||||
// Permission.bluetoothScan,
|
||||
// Permission.bluetoothConnect,
|
||||
// Permission.bluetoothAdvertise,
|
||||
// ].request();
|
||||
//
|
||||
// if (status != BleStatus.ready) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// return statuses.values.where((status) => status.isDenied).isEmpty;
|
||||
// },
|
||||
// );
|
||||
//
|
||||
// late BitcoinLedgerApp btc;
|
||||
// var devices = <LedgerDevice>[];
|
||||
// var status = "";
|
||||
// var counter = 0;
|
||||
// LedgerDevice? selectedDevice = null;
|
||||
//
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// btc = BitcoinLedgerApp(ledger);
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// void dispose() {
|
||||
// super.dispose();
|
||||
// ledger.close(ConnectionType.ble);
|
||||
// ledger.close(ConnectionType.usb);
|
||||
// }
|
||||
//
|
||||
// Future<void> reconnectCurrentDevice() async {
|
||||
// // await ledger.disconnect(selectedDevice!);
|
||||
// // await ledger.connect(selectedDevice!);
|
||||
// }
|
||||
//
|
||||
// Future<void> disconnectCurrentDevice() async {
|
||||
// await ledger.disconnect(selectedDevice!);
|
||||
// setState(() => selectedDevice = null);
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// final imageLedger = 'assets/images/ledger_nano.png';
|
||||
//
|
||||
// return Center(
|
||||
// child: Container(
|
||||
// width: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
|
||||
// height: double.infinity,
|
||||
// padding: EdgeInsets.symmetric(vertical: 24, horizontal: 24),
|
||||
// child: SingleChildScrollView(
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Padding(
|
||||
// padding: EdgeInsets.only(top: 20),
|
||||
// child: Text(status),
|
||||
// ),
|
||||
// if (selectedDevice != null) ...[
|
||||
// DebugButton(
|
||||
// title: "Get Version",
|
||||
// method: "Version",
|
||||
// func: () async => await btc.getVersion(selectedDevice!),
|
||||
// ),
|
||||
// DebugButton(
|
||||
// title: "Get Master Fingerprint",
|
||||
// method: "Master Fingerprint",
|
||||
// func: () async => hex.encode(await btc.getMasterFingerprint(selectedDevice!)),
|
||||
// ),
|
||||
// DebugButton(
|
||||
// title: "Get XPub",
|
||||
// method: "XPub",
|
||||
// func: () async => await btc.getXPubKey(selectedDevice!, derivationPath: "m/84'/0'/$counter'"),
|
||||
// ),
|
||||
// DebugButton(
|
||||
// title: "Get Wallet Address",
|
||||
// method: "Wallet Address",
|
||||
// func: () async {
|
||||
// setState(() => counter++);
|
||||
// final derivationPath = "m/84'/0'/$counter'/0/0";
|
||||
// return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath);
|
||||
// // return await ethereum!.getHardwareWalletAccounts(selectedDevice!);
|
||||
// },
|
||||
// ),
|
||||
// DebugButton(
|
||||
// title: "Send Money",
|
||||
// method: "Sig",
|
||||
// func: () async {
|
||||
// final psbt = PsbtV2();
|
||||
// final psbtBuf = base64.decode(
|
||||
// "cHNidP8BAgQCAAAAAQQBAQEFAQIAAQ4gTW6k/cwKKu1u7m9oKr5ob7VcAC0IPkfaDitRi/FkD7sBDwQAAAAAARAE/////wEA/ekBAQAAAAABA9AYVQLI722H0osKMa/4dvMucrnKV1Myxtlp0l0BoOBDAQAAAAD/////ku6r2ABaHt9N26f/P4eMljX8t1f4lBcFfEwuNm/uXYoBAAAAAP////+YeAl8arEGKOcyrWJAYwSboyCstkhHN8zn7/vy7pkYTAEAAAAA/////wHlHgAAAAAAABYAFKdq0umSucBGVkl2MpT6Hgo/0a/xAkcwRAIgMkiJmNFbEi2I3CQYOwyV/JepCnFQRvj4xghkySpFcJMCIGAypkkWltfj+ucvqUIu27tusDAIAAB+rBhX/GV7hPlEASEDyLmWyTLjLfC9kn8pnW42jW5N6EJo5fObjWWEyfLDu9UCSDBFAiEAg9crVtwBPF+sWk+Th6pLwzDjJGItwsUCvoBPtmMTEb4CIDGuM7WOguV0TP21oidF3bSUZlEAjUHWfWzxLKw+3LofASEDfN16xKb70UZSeQyX5Tlh8iRq7np5Nlz9GYdcSU50sKwCSDBFAiEAvotOblaEiBptRWkvb6bj2MGyRjTphKLBLiHYmrRMTCgCIEKJH+z65uPSSz1NIb0d/u3bU9l0xcWk0idEsXjB+BIiASEDrAEiEtrSNKxbh6F/KPaCTafF2LVjCzb75WB+x4xSuoQAAAAAAQEf5R4AAAAAAAAWABSnatLpkrnARlZJdjKU+h4KP9Gv8SIGA3xMuxmPsBAm9aMEUBs3N46DB+Kdts3bZR/Wxt+uM0H4GKtN6bpUAACAAAAAgAAAAIAAAAAAAAAAAAABBBTk7bEOxYcdXDi1eeWraYDufm6eJgEDCOgDAAAAAAAAAAEEFDX3g/pnDXIfsRw8shK42NZn+SdpAQMIiBMAAAAAAAAiAgN8TLsZj7AQJvWjBFAbNzeOgwfinbbN22Uf1sbfrjNB+BirTem6VAAAgAAAAIAAAACAAAAAAAAAAAAA"
|
||||
// );
|
||||
// psbt.deserialize(psbtBuf);
|
||||
// final result = await btc.signPsbt(selectedDevice!, psbt: psbt);
|
||||
// return result.toHexString();
|
||||
// },
|
||||
// ),
|
||||
// Padding(
|
||||
// padding: EdgeInsets.only(top: 20),
|
||||
// child: PrimaryButton(
|
||||
// text: "Disconnect",
|
||||
// onPressed: () => disconnectCurrentDevice(),
|
||||
// color: Theme.of(context).primaryColor,
|
||||
// textColor: Colors.white),
|
||||
// ),
|
||||
// ],
|
||||
// if (selectedDevice == null) ...[
|
||||
// ...devices
|
||||
// .map(
|
||||
// (device) => Padding(
|
||||
// padding: EdgeInsets.only(bottom: 20),
|
||||
// child: DeviceTile(
|
||||
// onPressed: () {
|
||||
// setState(() => selectedDevice = device);
|
||||
// ledger.connect(device);
|
||||
// },
|
||||
// title: device.name,
|
||||
// leading: imageLedger,
|
||||
// connectionType: device.connectionType,
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// .toList(),
|
||||
// PrimaryButton(
|
||||
// text: "Refresh BLE",
|
||||
// onPressed: () async {
|
||||
// setState(() => devices = []);
|
||||
// ledger.scan().listen((device) => setState(() {
|
||||
// devices.add(device);
|
||||
// }));
|
||||
// },
|
||||
// color: Theme.of(context).primaryColor,
|
||||
// textColor: Colors.white),
|
||||
// Padding(
|
||||
// padding: EdgeInsets.only(top: 20),
|
||||
// child: PrimaryButton(
|
||||
// text: "Use USB",
|
||||
// onPressed: () async {
|
||||
// final dev = await ledger.listUsbDevices();
|
||||
// setState(() => devices = dev);
|
||||
// },
|
||||
// color: Theme.of(context).primaryColor,
|
||||
// textColor: Colors.white),
|
||||
// ),
|
||||
// ],
|
||||
// ],
|
||||
// ),
|
||||
// )),
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// Widget DebugButton(
|
||||
// {required String title, required String method, required Future<dynamic> Function() func}) {
|
||||
// return Padding(
|
||||
// padding: EdgeInsets.only(top: 20),
|
||||
// child: PrimaryButton(
|
||||
// text: title,
|
||||
// onPressed: () async {
|
||||
// try {
|
||||
// setState(() => status = "Sending...");
|
||||
// final acc = await func();
|
||||
// setState(() => status = "$method: $acc");
|
||||
// print("$method: $acc");
|
||||
// } on LedgerException catch (ex) {
|
||||
// setState(() => status = "${ex.errorCode.toRadixString(16)} ${ex.message}");
|
||||
// print("${ex.errorCode.toRadixString(16)} ${ex.message}");
|
||||
// }
|
||||
// },
|
||||
// color: Theme.of(context).primaryColor,
|
||||
// textColor: Colors.white),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,256 @@
|
||||
import 'package:cake_wallet/core/wallet_name_validator.dart';
|
||||
import 'package:cake_wallet/entities/generate_name.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.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';
|
||||
|
||||
class SelectHardwareWalletAccountPage extends BasePage {
|
||||
SelectHardwareWalletAccountPage(this._walletHardwareRestoreVM);
|
||||
|
||||
final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
|
||||
|
||||
@override
|
||||
String get title => S.current.restore_title_from_hardware_wallet;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => SelectHardwareWalletAccountForm(_walletHardwareRestoreVM);
|
||||
}
|
||||
|
||||
class SelectHardwareWalletAccountForm extends StatefulWidget {
|
||||
SelectHardwareWalletAccountForm(this._walletHardwareRestoreVM);
|
||||
|
||||
final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
|
||||
|
||||
@override
|
||||
_SelectHardwareWalletAccountFormState createState() =>
|
||||
_SelectHardwareWalletAccountFormState(_walletHardwareRestoreVM);
|
||||
}
|
||||
|
||||
class _SelectHardwareWalletAccountFormState extends State<SelectHardwareWalletAccountForm> {
|
||||
_SelectHardwareWalletAccountFormState(this._walletHardwareRestoreVM)
|
||||
: _formKey = GlobalKey<FormState>(),
|
||||
_controller = TextEditingController();
|
||||
|
||||
final GlobalKey<FormState> _formKey;
|
||||
final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
|
||||
final TextEditingController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_setEffects(context);
|
||||
if (_walletHardwareRestoreVM.availableAccounts.length == 0) _loadMoreAccounts();
|
||||
}
|
||||
|
||||
@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: [
|
||||
TextFormField(
|
||||
onChanged: (value) => _walletHardwareRestoreVM.name = value,
|
||||
controller: _controller,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<NewWalletTheme>()!.hintTextColor,
|
||||
),
|
||||
hintText: S.of(context).wallet_name,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color:
|
||||
Theme.of(context).extension<NewWalletTheme>()!.underlineColor,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color:
|
||||
Theme.of(context).extension<NewWalletTheme>()!.underlineColor,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
suffixIcon: Semantics(
|
||||
label: S.of(context).generate_name,
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
final rName = await generateName();
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
setState(() {
|
||||
_controller.text = rName;
|
||||
_walletHardwareRestoreVM.name = rName;
|
||||
_controller.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: _controller.text.length));
|
||||
});
|
||||
},
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: Image.asset(
|
||||
'assets/images/refresh_icon.png',
|
||||
color: Theme.of(context)
|
||||
.extension<SendPageTheme>()!
|
||||
.textFieldButtonIconColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
validator: WalletNameValidator(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
"Available accounts",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (context) => Column(
|
||||
children: _walletHardwareRestoreVM.availableAccounts.map((acc) {
|
||||
final address = acc.address;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: SelectButton(
|
||||
image: Image.asset(
|
||||
walletTypeToCryptoCurrency(_walletHardwareRestoreVM.type).iconPath ??
|
||||
'',
|
||||
height: 24,
|
||||
width: 24,
|
||||
),
|
||||
text:
|
||||
"${address.substring(0, 6)}...${address.substring(address.length - 6)}",
|
||||
showTrailingIcon: false,
|
||||
height: 54,
|
||||
isSelected: _walletHardwareRestoreVM.selectedAccount == acc,
|
||||
onTap: () =>
|
||||
setState(() => _walletHardwareRestoreVM.selectedAccount = acc),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Observer(builder: (context) {
|
||||
return LoadingPrimaryButton(
|
||||
onPressed: _loadMoreAccounts,
|
||||
text: S.of(context).load_more,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
isLoading: _walletHardwareRestoreVM.isLoadingMoreAccounts,
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.all(24),
|
||||
bottomSection: Observer(
|
||||
builder: (context) {
|
||||
return LoadingPrimaryButton(
|
||||
onPressed: _confirmForm,
|
||||
text: S.of(context).seed_language_next,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white,
|
||||
isDisabled: _walletHardwareRestoreVM.name.isEmpty,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadMoreAccounts() async {
|
||||
_walletHardwareRestoreVM.isLoadingMoreAccounts = true;
|
||||
_walletHardwareRestoreVM.getNextAvailableAccounts(5);
|
||||
}
|
||||
|
||||
Future<void> _confirmForm() async {
|
||||
await _walletHardwareRestoreVM.create();
|
||||
}
|
||||
|
||||
bool _effectsInstalled = false;
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
if (_effectsInstalled) return;
|
||||
|
||||
reaction((_) => _walletHardwareRestoreVM.error, (String? error) {
|
||||
|
||||
if (error != null) {
|
||||
|
||||
if (error == S.current.ledger_connection_error)
|
||||
Navigator.of(context).pop();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () {
|
||||
_walletHardwareRestoreVM.error = null;
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_effectsInstalled = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:ledger_flutter/ledger_flutter.dart';
|
||||
|
||||
class DeviceTile extends StatelessWidget {
|
||||
const DeviceTile({
|
||||
required this.onPressed,
|
||||
required this.title,
|
||||
this.leading,
|
||||
this.connectionType,
|
||||
});
|
||||
|
||||
final VoidCallback onPressed;
|
||||
final String title;
|
||||
final String? leading;
|
||||
final ConnectionType? connectionType;
|
||||
|
||||
String? get connectionTypeIcon {
|
||||
switch (connectionType) {
|
||||
case ConnectionType.ble:
|
||||
return 'assets/images/bluetooth.png';
|
||||
case ConnectionType.usb:
|
||||
return 'assets/images/usb.png';
|
||||
case null:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onPressed,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(24),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
color: Theme.of(context).cardColor,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
if (leading != null)
|
||||
Image.asset(
|
||||
leading!,
|
||||
height: 30,
|
||||
color: Theme.of(context).extension<OptionTileTheme>()!.titleColor,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<OptionTileTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (connectionTypeIcon != null)
|
||||
Center(
|
||||
child: Image.asset(
|
||||
connectionTypeIcon!,
|
||||
height: 25,
|
||||
color: Theme.of(context).extension<OptionTileTheme>()!.titleColor,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ class SideMenuItem extends StatelessWidget {
|
||||
? Icon(
|
||||
icon,
|
||||
color: _setColor(context),
|
||||
size: 30,
|
||||
)
|
||||
: Image.asset(
|
||||
imagePath ?? '',
|
||||
|
||||
@@ -82,7 +82,8 @@ class DesktopSidebarWrapper extends BasePage {
|
||||
width: sideMenuWidth,
|
||||
topItems: [
|
||||
SideMenuItem(
|
||||
imagePath: 'assets/images/wallet_outline.png',
|
||||
// imagePath: 'assets/images/wallet_outline.png',
|
||||
icon: Icons.home,
|
||||
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.dashboard,
|
||||
onTap: () {
|
||||
desktopSidebarViewModel.onPageChange(SidebarItem.dashboard);
|
||||
|
||||
@@ -148,6 +148,17 @@ class CryptoBalanceWidget extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (dashboardViewModel.wallet.isHardwareWallet)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Image.asset(
|
||||
'assets/images/ledger_nano.png',
|
||||
width: 24,
|
||||
color: Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.pageTitleTextColor,
|
||||
),
|
||||
),
|
||||
if (dashboardViewModel
|
||||
.balanceViewModel.isHomeScreenSettingsEnabled)
|
||||
InkWell(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
|
||||
@@ -10,14 +12,20 @@ import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/wallet_types.g.dart';
|
||||
import 'package:cw_core/hardware/device_connection_type.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NewWalletTypePage extends BasePage {
|
||||
NewWalletTypePage({required this.onTypeSelected, required this.isCreate});
|
||||
NewWalletTypePage({
|
||||
required this.onTypeSelected,
|
||||
required this.isCreate,
|
||||
required this.isHardwareWallet,
|
||||
});
|
||||
|
||||
final void Function(BuildContext, WalletType) onTypeSelected;
|
||||
final bool isCreate;
|
||||
final bool isHardwareWallet;
|
||||
|
||||
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
|
||||
final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png');
|
||||
@@ -31,15 +39,22 @@ class NewWalletTypePage extends BasePage {
|
||||
onTypeSelected: onTypeSelected,
|
||||
walletImage: currentTheme.type == ThemeType.dark ? walletTypeImage : walletTypeLightImage,
|
||||
isCreate: isCreate,
|
||||
isHardwareWallet: isHardwareWallet,
|
||||
);
|
||||
}
|
||||
|
||||
class WalletTypeForm extends StatefulWidget {
|
||||
WalletTypeForm({required this.onTypeSelected, required this.walletImage, required this.isCreate});
|
||||
WalletTypeForm({
|
||||
required this.onTypeSelected,
|
||||
required this.walletImage,
|
||||
required this.isCreate,
|
||||
required this.isHardwareWallet,
|
||||
});
|
||||
|
||||
final void Function(BuildContext, WalletType) onTypeSelected;
|
||||
final Image walletImage;
|
||||
final bool isCreate;
|
||||
final bool isHardwareWallet;
|
||||
|
||||
@override
|
||||
WalletTypeFormState createState() => WalletTypeFormState();
|
||||
@@ -58,7 +73,11 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
types = filteredTypes = availableWalletTypes;
|
||||
types = filteredTypes = availableWalletTypes
|
||||
.where((element) =>
|
||||
!widget.isHardwareWallet ||
|
||||
DeviceConnectionType.supportedConnectionTypes(element, Platform.isIOS).isNotEmpty)
|
||||
.toList();
|
||||
super.initState();
|
||||
|
||||
searchController.addListener(() {
|
||||
@@ -74,76 +93,81 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 48),
|
||||
child: Text(
|
||||
S.of(context).choose_wallet_currency,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 48),
|
||||
child: Text(
|
||||
S.of(context).choose_wallet_currency,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 12),
|
||||
child: SearchBarWidget(searchController: searchController, borderRadius: 24),
|
||||
),
|
||||
Expanded(
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
...filteredTypes.map((type) => Padding(
|
||||
padding: EdgeInsets.only(top: 12),
|
||||
child: SelectButton(
|
||||
image: Image.asset(
|
||||
walletTypeToCryptoCurrency(type).iconPath ?? '',
|
||||
height: 24,
|
||||
width: 24),
|
||||
text: walletTypeToDisplayName(type),
|
||||
showTrailingIcon: false,
|
||||
height: 54,
|
||||
isSelected: selected == type,
|
||||
onTap: () => setState(() => selected = type)),
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 24, 24, 12),
|
||||
child: SearchBarWidget(searchController: searchController, borderRadius: 24),
|
||||
),
|
||||
Expanded(
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
...filteredTypes.map(
|
||||
(type) => Padding(
|
||||
padding: EdgeInsets.only(top: 12),
|
||||
child: SelectButton(
|
||||
image: Image.asset(
|
||||
walletTypeToCryptoCurrency(type).iconPath ?? '',
|
||||
height: 24,
|
||||
width: 24,
|
||||
),
|
||||
text: walletTypeToDisplayName(type),
|
||||
showTrailingIcon: false,
|
||||
height: 54,
|
||||
isSelected: selected == type,
|
||||
onTap: () => setState(() => selected = type),
|
||||
deviceConnectionTypes: widget.isHardwareWallet
|
||||
? DeviceConnectionType.supportedConnectionTypes(type, Platform.isIOS)
|
||||
: [],
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSection: PrimaryButton(
|
||||
onPressed: () => onTypeSelected(),
|
||||
text: S.of(context).seed_language_next,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
isDisabled: selected == null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)));
|
||||
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSection: PrimaryButton(
|
||||
onPressed: () => onTypeSelected(),
|
||||
text: S.of(context).seed_language_next,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
isDisabled: selected == null,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onTypeSelected() async {
|
||||
if (selected == null) {
|
||||
throw Exception('Wallet Type is not selected yet.');
|
||||
}
|
||||
if (selected == null) throw Exception('Wallet Type is not selected yet.');
|
||||
|
||||
if (selected == WalletType.haven && widget.isCreate) {
|
||||
return await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return PopUpCancellableAlertDialog(
|
||||
contentText: S.of(context).pause_wallet_creation,
|
||||
actionButtonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
);
|
||||
},
|
||||
builder: (BuildContext context) => PopUpCancellableAlertDialog(
|
||||
contentText: S.of(context).pause_wallet_creation,
|
||||
actionButtonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
|
||||
import 'package:cw_core/hardware/device_connection_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SelectButton extends StatelessWidget {
|
||||
SelectButton({
|
||||
@@ -16,6 +17,7 @@ class SelectButton extends StatelessWidget {
|
||||
this.textColor,
|
||||
this.arrowColor,
|
||||
this.borderColor,
|
||||
this.deviceConnectionTypes,
|
||||
});
|
||||
|
||||
final Image? image;
|
||||
@@ -24,6 +26,7 @@ class SelectButton extends StatelessWidget {
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
final bool showTrailingIcon;
|
||||
final List<DeviceConnectionType>? deviceConnectionTypes;
|
||||
final double height;
|
||||
final Color? color;
|
||||
final Color? textColor;
|
||||
@@ -33,15 +36,26 @@ class SelectButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final backgroundColor = color ?? (isSelected ? Colors.green : Theme.of(context).cardColor);
|
||||
final effectiveTextColor = textColor ?? (isSelected
|
||||
? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor);
|
||||
final effectiveArrowColor = arrowColor ?? (isSelected
|
||||
? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
|
||||
: Theme.of(context).extension<FilterTheme>()!.titlesColor);
|
||||
final effectiveTextColor = textColor ??
|
||||
(isSelected
|
||||
? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor);
|
||||
final effectiveArrowColor = arrowColor ??
|
||||
(isSelected
|
||||
? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
|
||||
: Theme.of(context).extension<FilterTheme>()!.titlesColor);
|
||||
|
||||
final selectArrowImage = Image.asset('assets/images/select_arrow.png',
|
||||
color: effectiveArrowColor);
|
||||
final trailingIcons = <Image>[];
|
||||
final selectArrowImage =
|
||||
Image.asset('assets/images/select_arrow.png', color: effectiveArrowColor);
|
||||
|
||||
deviceConnectionTypes?.forEach((element) => trailingIcons.add(Image.asset(
|
||||
element.iconString,
|
||||
color: effectiveArrowColor,
|
||||
height: 24,
|
||||
)));
|
||||
|
||||
if (showTrailingIcon) trailingIcons.add(selectArrowImage);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
@@ -54,34 +68,33 @@ class SelectButton extends StatelessWidget {
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
color: backgroundColor,
|
||||
border: borderColor != null ? Border.all(color: borderColor!) : null,
|
||||
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image ?? Offstage(),
|
||||
Padding(
|
||||
padding: image != null
|
||||
? EdgeInsets.only(left: 15)
|
||||
: EdgeInsets.only(left: 0),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: textSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: effectiveTextColor,
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
image ?? Offstage(),
|
||||
Padding(
|
||||
padding: image != null ? EdgeInsets.only(left: 15) : EdgeInsets.only(left: 0),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: textSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: effectiveTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (showTrailingIcon) selectArrowImage
|
||||
...trailingIcons
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/option_tile.dart';
|
||||
import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
|
||||
import 'package:cake_wallet/utils/permission_handler.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/restore/restore_from_qr_vm.dart';
|
||||
import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:cake_wallet/utils/permission_handler.dart';
|
||||
|
||||
class RestoreOptionsPage extends BasePage {
|
||||
RestoreOptionsPage({required this.isNewInstall});
|
||||
@@ -22,12 +22,15 @@ class RestoreOptionsPage extends BasePage {
|
||||
String get title => S.current.restore_restore_wallet;
|
||||
|
||||
final bool isNewInstall;
|
||||
final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png');
|
||||
final imageBackup = Image.asset('assets/images/backup.png');
|
||||
final qrCode = Image.asset('assets/images/restore_qr.png');
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final imageColor = Theme.of(context).extension<OptionTileTheme>()!.titleColor;
|
||||
final imageLedger = Image.asset('assets/images/ledger_nano.png', width: 40, color: imageColor);
|
||||
final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor);
|
||||
final imageBackup = Image.asset('assets/images/backup.png', color: imageColor);
|
||||
final qrCode = Image.asset('assets/images/restore_qr.png', color: imageColor);
|
||||
|
||||
return Center(
|
||||
child: Container(
|
||||
width: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
|
||||
@@ -37,53 +40,37 @@ class RestoreOptionsPage extends BasePage {
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
OptionTile(
|
||||
onPressed: () => Navigator.pushNamed(context, Routes.restoreWalletFromSeedKeys,
|
||||
arguments: isNewInstall),
|
||||
image: imageSeedKeys,
|
||||
title: S.of(context).restore_title_from_seed_keys,
|
||||
description: S.of(context).restore_description_from_seed_keys),
|
||||
onPressed: () => Navigator.pushNamed(context, Routes.restoreWalletFromSeedKeys,
|
||||
arguments: isNewInstall),
|
||||
image: imageSeedKeys,
|
||||
title: S.of(context).restore_title_from_seed_keys,
|
||||
description: S.of(context).restore_description_from_seed_keys,
|
||||
),
|
||||
if (isNewInstall)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: OptionTile(
|
||||
onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup),
|
||||
image: imageBackup,
|
||||
title: S.of(context).restore_title_from_backup,
|
||||
description: S.of(context).restore_description_from_backup),
|
||||
onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup),
|
||||
image: imageBackup,
|
||||
title: S.of(context).restore_title_from_backup,
|
||||
description: S.of(context).restore_description_from_backup,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: OptionTile(
|
||||
onPressed: () async {
|
||||
bool isCameraPermissionGranted =
|
||||
await PermissionHandler.checkPermission(Permission.camera, context);
|
||||
if (!isCameraPermissionGranted) return;
|
||||
bool isPinSet = false;
|
||||
if (isNewInstall) {
|
||||
await Navigator.pushNamed(context, Routes.setupPin,
|
||||
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
|
||||
setupPinContext.close();
|
||||
isPinSet = true;
|
||||
});
|
||||
}
|
||||
if (!isNewInstall || isPinSet) {
|
||||
try {
|
||||
final restoreWallet =
|
||||
await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
|
||||
|
||||
final restoreFromQRViewModel =
|
||||
getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
|
||||
|
||||
await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
|
||||
if (restoreFromQRViewModel.state is FailureState) {
|
||||
_onWalletCreateFailure(context,
|
||||
'Create wallet state: ${(restoreFromQRViewModel.state as FailureState).error}');
|
||||
}
|
||||
} catch (e) {
|
||||
_onWalletCreateFailure(context, e.toString());
|
||||
}
|
||||
}
|
||||
},
|
||||
onPressed: () => Navigator.pushNamed(
|
||||
context, Routes.restoreWalletFromHardwareWallet,
|
||||
arguments: isNewInstall),
|
||||
image: imageLedger,
|
||||
title: S.of(context).restore_title_from_hardware_wallet,
|
||||
description: S.of(context).restore_description_from_hardware_wallet,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: OptionTile(
|
||||
onPressed: () => _onScanQRCode(context),
|
||||
image: qrCode,
|
||||
title: S.of(context).scan_qr_code,
|
||||
description: S.of(context).cold_or_recover_wallet),
|
||||
@@ -105,4 +92,35 @@ class RestoreOptionsPage extends BasePage {
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onScanQRCode(BuildContext context) async {
|
||||
final isCameraPermissionGranted =
|
||||
await PermissionHandler.checkPermission(Permission.camera, context);
|
||||
|
||||
if (!isCameraPermissionGranted) return;
|
||||
bool isPinSet = false;
|
||||
if (isNewInstall) {
|
||||
await Navigator.pushNamed(context, Routes.setupPin,
|
||||
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
|
||||
setupPinContext.close();
|
||||
isPinSet = true;
|
||||
});
|
||||
}
|
||||
if (!isNewInstall || isPinSet) {
|
||||
try {
|
||||
final restoreWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
|
||||
|
||||
final restoreFromQRViewModel =
|
||||
getIt.get<WalletRestorationFromQRVM>(param1: restoreWallet.type);
|
||||
|
||||
await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
|
||||
if (restoreFromQRViewModel.state is FailureState) {
|
||||
_onWalletCreateFailure(context,
|
||||
'Create wallet state: ${(restoreFromQRViewModel.state as FailureState).error}');
|
||||
}
|
||||
} catch (e) {
|
||||
_onWalletCreateFailure(context, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/template.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/send_card.dart';
|
||||
import 'package:cake_wallet/src/widgets/add_template_button.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/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/src/widgets/template_tile.dart';
|
||||
import 'package:cake_wallet/src/widgets/trail_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/seed_widget_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/utils/request_review_handler.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/send/output.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/src/widgets/trail_button.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
|
||||
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
class SendPage extends BasePage {
|
||||
SendPage({
|
||||
@@ -369,6 +370,21 @@ class SendPage extends BasePage {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sendViewModel.wallet.isHardwareWallet) {
|
||||
if (!sendViewModel.ledgerViewModel.isConnected) {
|
||||
await Navigator.of(context).pushNamed(Routes.connectDevices,
|
||||
arguments: ConnectDevicePageParams(
|
||||
walletType: sendViewModel.walletType,
|
||||
onConnectDevice: (BuildContext context, _) {
|
||||
sendViewModel.ledgerViewModel.setLedger(sendViewModel.wallet);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
));
|
||||
} else {
|
||||
sendViewModel.ledgerViewModel.setLedger(sendViewModel.wallet);
|
||||
}
|
||||
}
|
||||
|
||||
final check = sendViewModel.shouldDisplayTotp();
|
||||
authService.authenticateAction(
|
||||
context,
|
||||
@@ -384,7 +400,8 @@ class SendPage extends BasePage {
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
isLoading: sendViewModel.state is IsExecutingState ||
|
||||
sendViewModel.state is TransactionCommitting,
|
||||
sendViewModel.state is TransactionCommitting ||
|
||||
sendViewModel.state is IsAwaitingDeviceResponseState,
|
||||
isDisabled: !sendViewModel.isReadyForSend,
|
||||
);
|
||||
},
|
||||
@@ -395,12 +412,20 @@ class SendPage extends BasePage {
|
||||
);
|
||||
}
|
||||
|
||||
BuildContext? dialogContext;
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
if (_effectsInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
reaction((_) => sendViewModel.state, (ExecutionState state) {
|
||||
|
||||
if (dialogContext != null && dialogContext?.mounted == true) {
|
||||
Navigator.of(dialogContext!).pop();
|
||||
}
|
||||
|
||||
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
@@ -510,6 +535,21 @@ class SendPage extends BasePage {
|
||||
sendViewModel.clearOutputs();
|
||||
});
|
||||
}
|
||||
|
||||
if (state is IsAwaitingDeviceResponseState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
dialogContext = context;
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).proceed_on_device,
|
||||
alertContent: S.of(context).proceed_on_device_description,
|
||||
buttonText: S.of(context).cancel,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_effectsInstalled = true;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/wallet_connect_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
@@ -12,10 +16,6 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
|
||||
import 'package:cw_core/battery_optimization_native.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/routes.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_two_actions.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class ConnectionSyncPage extends BasePage {
|
||||
@@ -106,7 +106,8 @@ class ConnectionSyncPage extends BasePage {
|
||||
);
|
||||
},
|
||||
),
|
||||
if (isWalletConnectCompatibleChain(dashboardViewModel.wallet.type)) ...[
|
||||
if (isWalletConnectCompatibleChain(dashboardViewModel.wallet.type) &&
|
||||
!dashboardViewModel.wallet.isHardwareWallet) ...[ // ToDo: Remove this line once WalletConnect is implemented
|
||||
WalletConnectTile(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.walletConnectConnectionsListing),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user