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:
Konstantin Ullrich
2024-05-05 04:44:50 +03:00
committed by GitHub
co-authored by Omar Hatem
parent 043d7d7c8b
commit 5eabdcdca1
118 changed files with 3363 additions and 579 deletions
@@ -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());
}
}
}
}
+54 -14
View File
@@ -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),
),