mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-20 06:08:34 +02:00
CW-1135-add-support-for-bitbox-hww (#2476)
* chore: add ffi and ffigen dependencies to pubspec.yaml * refactor: replace method channel with FFI implementation for MWeb module * fix: correct `cd` command in build_mwebd.sh script * chore: add go.sum file with dependency checksums * fix: ensure ANDROID_NDK_VERSION is set before building and dynamically update NDK_BIN path * fix: update NDK_BIN path to dynamically detect platform in build_mwebd.sh script * refactor: remove unused mwebd dependencies and clean up CwMwebPlugin code * Update scripts/android/build_mwebd.sh Co-authored-by: cyan <cyjan@mrcyjanek.net> * refactor: replace iOS MoneroWallet framework with Mwebd.xcframework and clean up plugin implementation [skip-ci] * refactor: extract hardware wallet service abstraction * refactor: extend BitcoinHardwareWalletService from HardwareWalletService and override account retrieval method * feat: add BitBox hardware wallet support and implement manufacturer selection page with custom UI components * refactor: update manufacturer selection UI and integrate custom gradient styles for ManufacturerOptionTile widget * fix: mwebd ffi ios * feat: add BitBox hardware wallet support, update derivation paths, and refactor hardware wallet credential implementations * feat: add bitbox support and refactor the old ledger implementation * feat: add BitBox hardware wallet support and refactor hardware wallet service implementations * feat: refactor hardware wallet support to introduce abstraction for BitBox and Ledger; update device scanning, initialization, and UI integration * fix: BitBox hardware wallet ethereum transaction signing * chore: update BitBox Flutter path references and include build steps in CI workflows * chore: update CI workflows to include `--dont-install` flag for BitBox Flutter builds * refactor: replace if-else logic with switch-case for HardwareWalletViewModel factory * feat: refactor hardware wallet services to introduce shared interface, improve ledger and bitbox implementations, and support additional transaction signing operations * feat: add SVG assets for hardware wallet manufacturers (Foundation, Keystone, SeedSigner) * feat: redesign hardware wallet restoration flow with improved routing, UI updates, and manufacturer-specific device selection handling * fix: validate required BIP353 fields in transaction builder to prevent potential null value errors * feat: improve BitBox transaction signing logs with detailed PSBT information * refactor: update MWebFfi class to use non-nullable `lib` and improve address debug logging * feat: add BitBox coin type constant and improve hardware wallet service extensibility * chore: remove unused iOS/Android build scripts for mwebd * feat: add BitBox SVG asset and update localization for hardware wallet connection instructions * feat: update manufacturer tags and add Toast Easter egg for device selection interactions * refactor: replace LedgerViewModel with HardwareWalletViewModel across providers and improve hardware wallet handling * refactor: replace mweb.so with libmweb.so, use sync methods in MWebFfi, and update iOS/Android frameworks/scripts accordingly * feat: add Ledger Nano X SVG asset to hardware wallet resources * chore: unify mwebd build process in workflows, remove unused gomobile init command * refactor: remove redundant print statement and unnecessary whitespace * chore: add `gomobile init` to Android PR test build workflow * chore: update Android PR test build workflow and add file existence check in `build_bitbox_flutter.sh` * chore: override `ledger_usb_plus` dependency in pubspec_base.yaml * feat: add verbose logging for BitBox connection and initialization process * Update lib/view_model/hardware_wallet/bitbox_view_model.dart [skip ci] * CW-1206-refactor-hardware-wallets-code (#2507) * feat: add support for new hardware wallet types (Cupcake, Coldcard, SeedSigner) and improve QR Code handling - Introduced new hardware wallet types (Cupcake, Coldcard, SeedSigner) with corresponding assets and localization updates. - Updated QR Code format selection for hardware wallets with new formats (BCUR, BBQR) specific to devices. - Improved hardware wallet restoration flow to include airgapped wallet handling via QR Code scanning. - Refactored routing, view models, and dependency injection to accommodate new hardware wallet types. - Enhanced UI for device manufacturer selection and QR Code format dialogs. * feat: update hardware wallet assets and restoration flow * feat: add changing airgapped wallet type * feat: add Cupcake Man SVG asset to hardware wallet resources * refactor: update Litecoin receive options to require wallet parameter and handle hardware wallets * fix: debugging keystone integration feat: support for zpub view only restore * fix: seed signer restore * fix: disable bitbox restoring (for now) [skip-ci] * refactor: rename `isCupcake` to `isBitcoinViewOnly` for clarity [skip ci] --------- Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * refactor: standardize hardware wallet connection handling across wallet types --------- Co-authored-by: cyan <cyjan@mrcyjanek.net> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Czarek Nakamoto
Omar Hatem
parent
e1bb0e4a69
commit
e4672c75b7
+22
-21
@@ -23,8 +23,10 @@ class CWBitcoin extends Bitcoin {
|
||||
required String name,
|
||||
required String password,
|
||||
required String xpub,
|
||||
HardwareWalletType? hardwareWalletType,
|
||||
}) =>
|
||||
BitcoinWalletFromKeysCredentials(name: name, password: password, xpub: xpub);
|
||||
BitcoinWalletFromKeysCredentials(
|
||||
name: name, password: password, xpub: xpub, hardwareWalletType: hardwareWalletType);
|
||||
|
||||
@override
|
||||
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
|
||||
@@ -315,8 +317,12 @@ class CWBitcoin extends Bitcoin {
|
||||
}
|
||||
|
||||
@override
|
||||
List<ReceivePageOption> getLitecoinReceivePageOptions() {
|
||||
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
||||
List<ReceivePageOption> getLitecoinReceivePageOptions(Object wallet) {
|
||||
final litecoinWallet = wallet as ElectrumWallet;
|
||||
if (Platform.isLinux ||
|
||||
Platform.isMacOS ||
|
||||
Platform.isWindows ||
|
||||
litecoinWallet.isHardwareWallet) {
|
||||
return BitcoinReceivePageOption.allLitecoin
|
||||
.where((element) => element != BitcoinReceivePageOption.mweb)
|
||||
.toList();
|
||||
@@ -536,27 +542,22 @@ class CWBitcoin extends Bitcoin {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<HardwareAccountData>> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM,
|
||||
{int index = 0, int limit = 5}) async {
|
||||
final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.connection);
|
||||
try {
|
||||
return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
|
||||
} catch (err) {
|
||||
printV(err);
|
||||
throw err;
|
||||
}
|
||||
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
|
||||
(wallet as ElectrumWallet).hardwareWalletService = service;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<HardwareAccountData>> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM,
|
||||
{int index = 0, int limit = 5}) async {
|
||||
final hardwareWalletService = LitecoinHardwareWalletService(ledgerVM.connection);
|
||||
try {
|
||||
return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
|
||||
} catch (err) {
|
||||
printV(err);
|
||||
throw err;
|
||||
}
|
||||
HardwareWalletService getLedgerHardwareWalletService(
|
||||
ledger.LedgerConnection connection, bool isBitcoin) {
|
||||
if (isBitcoin) return BitcoinLedgerService(connection);
|
||||
return LitecoinLedgerService(connection);
|
||||
}
|
||||
|
||||
@override
|
||||
HardwareWalletService getBitboxHardwareWalletService(
|
||||
bitbox.BitboxManager manager, bool isBitcoin) {
|
||||
if (isBitcoin) return BitcoinBitboxService(manager);
|
||||
return LitecoinBitboxService(manager);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:cake_wallet/order/order.dart';
|
||||
import 'package:cake_wallet/buy/pairs_utils.dart';
|
||||
import 'package:cake_wallet/buy/payment_method.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -13,14 +13,14 @@ abstract class BuyProvider {
|
||||
BuyProvider({
|
||||
required this.wallet,
|
||||
required this.isTestEnvironment,
|
||||
required this.ledgerVM,
|
||||
required this.hardwareWalletVM,
|
||||
required this.supportedCryptoList,
|
||||
required this.supportedFiatList
|
||||
});
|
||||
|
||||
final WalletBase wallet;
|
||||
final bool isTestEnvironment;
|
||||
final LedgerViewModel? ledgerVM;
|
||||
final HardwareWalletViewModel? hardwareWalletVM;
|
||||
final List<TradePair<dynamic, dynamic>> supportedCryptoList;
|
||||
final List<TradePair<dynamic, dynamic>> supportedFiatList;
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
@@ -24,16 +24,16 @@ class DFXBuyProvider extends BuyProvider {
|
||||
DFXBuyProvider({
|
||||
required WalletBase wallet,
|
||||
bool isTestEnvironment = false,
|
||||
LedgerViewModel? ledgerVM,
|
||||
HardwareWalletViewModel? hardwareWalletVM,
|
||||
}) : super(
|
||||
wallet: wallet,
|
||||
isTestEnvironment: isTestEnvironment,
|
||||
ledgerVM: ledgerVM,
|
||||
supportedCryptoList: supportedCryptoToFiatPairs(
|
||||
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
|
||||
supportedFiatList: supportedFiatToCryptoPairs(
|
||||
notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto)
|
||||
);
|
||||
wallet: wallet,
|
||||
isTestEnvironment: isTestEnvironment,
|
||||
hardwareWalletVM: hardwareWalletVM,
|
||||
supportedCryptoList: supportedCryptoToFiatPairs(
|
||||
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
|
||||
supportedFiatList: supportedFiatToCryptoPairs(
|
||||
notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto),
|
||||
);
|
||||
|
||||
static const _baseUrl = 'api.dfx.swiss';
|
||||
|
||||
@@ -104,8 +104,7 @@ class DFXBuyProvider extends BuyProvider {
|
||||
// }
|
||||
|
||||
Future<String> auth(String walletAddress) async {
|
||||
final signMessage = await getSignature(
|
||||
await getSignMessage(walletAddress), walletAddress);
|
||||
final signMessage = await getSignature(await getSignMessage(walletAddress), walletAddress);
|
||||
|
||||
final requestBody = jsonEncode({
|
||||
'wallet': walletName,
|
||||
@@ -119,7 +118,6 @@ class DFXBuyProvider extends BuyProvider {
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: requestBody,
|
||||
);
|
||||
|
||||
|
||||
if (response.statusCode == 201) {
|
||||
final responseBody = jsonDecode(response.body);
|
||||
@@ -139,14 +137,13 @@ class DFXBuyProvider extends BuyProvider {
|
||||
case WalletType.polygon:
|
||||
case WalletType.solana:
|
||||
case WalletType.tron:
|
||||
final r = await wallet.signMessage(message);
|
||||
return r;
|
||||
return wallet.signMessage(message);
|
||||
case WalletType.monero:
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.bitcoinCash:
|
||||
case WalletType.zano:
|
||||
return await wallet.signMessage(message, address: walletAddress);
|
||||
return wallet.signMessage(message, address: walletAddress);
|
||||
default:
|
||||
throw Exception("WalletType is not available for DFX ${wallet.type}");
|
||||
}
|
||||
@@ -336,16 +333,17 @@ class DFXBuyProvider extends BuyProvider {
|
||||
required String cryptoCurrencyAddress,
|
||||
String? countryCode}) async {
|
||||
if (wallet.isHardwareWallet) {
|
||||
if (!ledgerVM!.isConnected) {
|
||||
if (!hardwareWalletVM!.isConnected) {
|
||||
await Navigator.of(context).pushNamed(Routes.connectDevices,
|
||||
arguments: ConnectDevicePageParams(
|
||||
walletType: wallet.walletInfo.type,
|
||||
onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) {
|
||||
ledgerVM.setLedger(wallet);
|
||||
hardwareWalletType: wallet.walletInfo.hardwareWalletType!,
|
||||
onConnectDevice: (context, hwwVM) {
|
||||
hwwVM.initWallet(wallet);
|
||||
Navigator.of(context).pop();
|
||||
}));
|
||||
} else {
|
||||
ledgerVM!.setLedger(wallet);
|
||||
hardwareWalletVM!.initWallet(wallet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,14 +368,13 @@ class DFXBuyProvider extends BuyProvider {
|
||||
}
|
||||
} catch (e) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: "DFX.swiss",
|
||||
alertContent: S.of(context).buy_provider_unavailable + ': $e',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
context: context,
|
||||
builder: (context) => AlertWithOneAction(
|
||||
alertTitle: "DFX.swiss",
|
||||
alertContent: '${S.of(context).buy_provider_unavailable}: $e',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class KryptonimBuyProvider extends BuyProvider {
|
||||
: super(
|
||||
wallet: wallet,
|
||||
isTestEnvironment: isTestEnvironment,
|
||||
ledgerVM: null,
|
||||
hardwareWalletVM: null,
|
||||
supportedCryptoList: supportedCryptoToFiatPairs(
|
||||
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
|
||||
supportedFiatList: supportedFiatToCryptoPairs(
|
||||
|
||||
@@ -22,7 +22,7 @@ class MeldBuyProvider extends BuyProvider {
|
||||
: super(
|
||||
wallet: wallet,
|
||||
isTestEnvironment: isTestEnvironment,
|
||||
ledgerVM: null,
|
||||
hardwareWalletVM: null,
|
||||
supportedCryptoList: supportedCryptoToFiatPairs(
|
||||
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
|
||||
supportedFiatList: supportedFiatToCryptoPairs(
|
||||
|
||||
@@ -36,7 +36,7 @@ class MoonPayProvider extends BuyProvider {
|
||||
super(
|
||||
wallet: wallet,
|
||||
isTestEnvironment: isTestEnvironment,
|
||||
ledgerVM: null,
|
||||
hardwareWalletVM: null,
|
||||
supportedCryptoList: supportedCryptoToFiatPairs(
|
||||
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
|
||||
supportedFiatList: supportedFiatToCryptoPairs(
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:cake_wallet/buy/payment_method.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/core/theme_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
@@ -22,7 +21,7 @@ class OnRamperBuyProvider extends BuyProvider {
|
||||
{required WalletBase wallet, bool isTestEnvironment = false})
|
||||
: super(wallet: wallet,
|
||||
isTestEnvironment: isTestEnvironment,
|
||||
ledgerVM: null,
|
||||
hardwareWalletVM: null,
|
||||
supportedCryptoList: supportedCryptoToFiatPairs(
|
||||
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
|
||||
supportedFiatList: supportedFiatToCryptoPairs(
|
||||
|
||||
@@ -11,9 +11,9 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
@@ -22,16 +22,19 @@ import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class RobinhoodBuyProvider extends BuyProvider {
|
||||
RobinhoodBuyProvider(
|
||||
{required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
|
||||
: super(
|
||||
wallet: wallet,
|
||||
isTestEnvironment: isTestEnvironment,
|
||||
ledgerVM: ledgerVM,
|
||||
supportedCryptoList: supportedCryptoToFiatPairs(
|
||||
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
|
||||
supportedFiatList: supportedFiatToCryptoPairs(
|
||||
notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto));
|
||||
RobinhoodBuyProvider({
|
||||
required WalletBase wallet,
|
||||
bool isTestEnvironment = false,
|
||||
HardwareWalletViewModel? hardwareWalletVM,
|
||||
}) : super(
|
||||
wallet: wallet,
|
||||
isTestEnvironment: isTestEnvironment,
|
||||
hardwareWalletVM: hardwareWalletVM,
|
||||
supportedCryptoList: supportedCryptoToFiatPairs(
|
||||
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
|
||||
supportedFiatList: supportedFiatToCryptoPairs(
|
||||
notSupportedFiat: _notSupportedFiat, notSupportedCrypto: _notSupportedCrypto),
|
||||
);
|
||||
|
||||
static const _baseUrl = 'applink.robinhood.com';
|
||||
static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
|
||||
@@ -69,8 +72,8 @@ class RobinhoodBuyProvider extends BuyProvider {
|
||||
final uri = Uri.https(_apiBaseUrl, '$_assetsPath', {'applicationId': _applicationId});
|
||||
|
||||
try {
|
||||
final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'accept': 'application/json'});
|
||||
|
||||
final response =
|
||||
await ProxyWrapper().get(clearnetUri: uri, headers: {'accept': 'application/json'});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final responseData = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
@@ -104,11 +107,11 @@ class RobinhoodBuyProvider extends BuyProvider {
|
||||
case WalletType.polygon:
|
||||
case WalletType.solana:
|
||||
case WalletType.tron:
|
||||
return await wallet.signMessage(message);
|
||||
return wallet.signMessage(message);
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.bitcoinCash:
|
||||
return await wallet.signMessage(message, address: wallet.walletAddresses.address);
|
||||
return wallet.signMessage(message, address: wallet.walletAddresses.address);
|
||||
default:
|
||||
throw Exception("Wallet Type ${wallet.type.name} is not available for Robinhood");
|
||||
}
|
||||
@@ -123,14 +126,14 @@ class RobinhoodBuyProvider extends BuyProvider {
|
||||
|
||||
final uri = Uri.https(_cIdBaseUrl, "/api/robinhood");
|
||||
|
||||
var response = await ProxyWrapper().post(
|
||||
final response = await ProxyWrapper().post(
|
||||
clearnetUri: uri,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode({'valid_until': valid_until, 'wallet': walletAddress, 'signature': signature}),
|
||||
body: json
|
||||
.encode({'valid_until': valid_until, 'wallet': walletAddress, 'signature': signature}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
return (jsonDecode(response.body) as Map<String, dynamic>)['connectId'] as String;
|
||||
} else {
|
||||
throw Exception('Provider currently unavailable. Status: ${response.statusCode}');
|
||||
@@ -158,16 +161,17 @@ class RobinhoodBuyProvider extends BuyProvider {
|
||||
required String cryptoCurrencyAddress,
|
||||
String? countryCode}) async {
|
||||
if (wallet.isHardwareWallet) {
|
||||
if (!ledgerVM!.isConnected) {
|
||||
if (!hardwareWalletVM!.isConnected) {
|
||||
await Navigator.of(context).pushNamed(Routes.connectDevices,
|
||||
arguments: ConnectDevicePageParams(
|
||||
walletType: wallet.walletInfo.type,
|
||||
onConnectDevice: (BuildContext context, LedgerViewModel ledgerVM) {
|
||||
ledgerVM.setLedger(wallet);
|
||||
hardwareWalletType: wallet.walletInfo.hardwareWalletType!,
|
||||
onConnectDevice: (context, hwwVM) {
|
||||
hwwVM.initWallet(wallet);
|
||||
Navigator.of(context).pop();
|
||||
}));
|
||||
} else {
|
||||
ledgerVM!.setLedger(wallet);
|
||||
hardwareWalletVM!.initWallet(wallet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,14 +180,13 @@ class RobinhoodBuyProvider extends BuyProvider {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
} catch (e) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: "Robinhood Connect",
|
||||
alertContent: e.toString(),
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
context: context,
|
||||
builder: (context) => AlertWithOneAction(
|
||||
alertTitle: "Robinhood Connect",
|
||||
alertContent: '${S.of(context).buy_provider_unavailable}: $e',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class WyreBuyProvider extends BuyProvider {
|
||||
super(
|
||||
wallet: wallet,
|
||||
isTestEnvironment: isTestEnvironment,
|
||||
ledgerVM: null,
|
||||
hardwareWalletVM: null,
|
||||
supportedCryptoList: supportedCryptoToFiatPairs(
|
||||
notSupportedCrypto: _notSupportedCrypto, notSupportedFiat: _notSupportedFiat),
|
||||
supportedFiatList: supportedFiatToCryptoPairs(
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NewWalletTypeArguments {
|
||||
final void Function(BuildContext, WalletType)? onTypeSelected;
|
||||
final bool isCreate;
|
||||
final bool isHardwareWallet;
|
||||
final HardwareWalletType? hardwareWalletType;
|
||||
|
||||
NewWalletTypeArguments({
|
||||
required this.onTypeSelected,
|
||||
required this.isCreate,
|
||||
required this.isHardwareWallet,
|
||||
this.hardwareWalletType,
|
||||
});
|
||||
}
|
||||
|
||||
+53
-20
@@ -36,6 +36,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/network_requests.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/exchange_provider_logs_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
|
||||
@@ -53,6 +54,8 @@ import 'package:cake_wallet/themes/core/theme_store.dart';
|
||||
import 'package:cake_wallet/view_model/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/view_model/dev/secure_preferences.dart';
|
||||
import 'package:cake_wallet/view_model/dev/shared_preferences.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/bitbox_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
@@ -393,8 +396,22 @@ Future<void> setup({
|
||||
AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
|
||||
getIt.registerSingleton<SeedSettingsStore>(SeedSettingsStore());
|
||||
|
||||
getIt.registerFactoryParam<HardwareWalletViewModel, HardwareWalletType, void>((type, _) {
|
||||
switch(type) {
|
||||
case HardwareWalletType.bitbox: return getIt<BitboxViewModel>();
|
||||
case HardwareWalletType.ledger: return getIt<LedgerViewModel>();
|
||||
case HardwareWalletType.cupcake:
|
||||
case HardwareWalletType.coldcard:
|
||||
case HardwareWalletType.seedsigner:
|
||||
case HardwareWalletType.keystone:
|
||||
throw Exception("This should not have happened, because airgapped Wallets don't need View Models");
|
||||
}
|
||||
});
|
||||
|
||||
getIt.registerLazySingleton(() => LedgerViewModel());
|
||||
|
||||
getIt.registerLazySingleton(() => BitboxViewModel());
|
||||
|
||||
final secretStore = await SecretStoreBase.load(getIt.get<SecureStorage>());
|
||||
|
||||
getIt.registerSingleton<SecretStore>(secretStore);
|
||||
@@ -505,9 +522,9 @@ Future<void> setup({
|
||||
walletType: args.walletType ?? currentWalletType);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<WalletHardwareRestoreViewModel, WalletType, void>((type, _) =>
|
||||
WalletHardwareRestoreViewModel(
|
||||
getIt.get<LedgerViewModel>(),
|
||||
getIt.registerFactoryParam<WalletHardwareRestoreViewModel, WalletType, HardwareWalletViewModel>(
|
||||
(type, hardwareWalletVM) => WalletHardwareRestoreViewModel(
|
||||
hardwareWalletVM,
|
||||
getIt.get<AppStore>(),
|
||||
getIt.get<WalletCreationService>(param1: type),
|
||||
_walletInfoSource,
|
||||
@@ -816,7 +833,10 @@ Future<void> setup({
|
||||
getIt.get<BalanceViewModel>(),
|
||||
getIt.get<ContactListViewModel>(),
|
||||
_transactionDescriptionBox,
|
||||
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null,
|
||||
getIt.get<AppStore>().wallet!.isHardwareWallet
|
||||
? getIt<HardwareWalletViewModel>(
|
||||
param1: getIt.get<AppStore>().wallet!.hardwareWalletType!)
|
||||
: null,
|
||||
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.nonMweb,
|
||||
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom),
|
||||
getIt.get<FeesViewModel>(),
|
||||
@@ -1077,14 +1097,18 @@ Future<void> setup({
|
||||
isSelected: isSelected));
|
||||
|
||||
getIt.registerFactory<RobinhoodBuyProvider>(() => RobinhoodBuyProvider(
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
ledgerVM:
|
||||
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null));
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
hardwareWalletVM: getIt<AppStore>().wallet!.isHardwareWallet
|
||||
? getIt<HardwareWalletViewModel>(param1: getIt<AppStore>().wallet!.hardwareWalletType!)
|
||||
: null,
|
||||
));
|
||||
|
||||
getIt.registerFactory<DFXBuyProvider>(() => DFXBuyProvider(
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
ledgerVM:
|
||||
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null));
|
||||
wallet: getIt<AppStore>().wallet!,
|
||||
hardwareWalletVM: getIt<AppStore>().wallet!.isHardwareWallet
|
||||
? getIt<HardwareWalletViewModel>(param1: getIt<AppStore>().wallet!.hardwareWalletType!)
|
||||
: null,
|
||||
));
|
||||
|
||||
getIt.registerFactory<MoonPayProvider>(() => MoonPayProvider(
|
||||
appStore: getIt.get<AppStore>(),
|
||||
@@ -1235,19 +1259,25 @@ Future<void> setup({
|
||||
|
||||
getIt.registerFactory(() => FaqPage(getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, RestoredWallet?>((WalletType type,
|
||||
restoredWallet) =>
|
||||
WalletRestoreViewModel(getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type),
|
||||
_walletInfoSource, getIt.get<SeedSettingsViewModel>(),
|
||||
type: type, restoredWallet: restoredWallet));
|
||||
getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, Map<String, dynamic>?>(
|
||||
(type, additionalParams) {
|
||||
final restoredWallet = additionalParams?['restoredWallet'] as RestoredWallet?;
|
||||
final hardwareWalletType = additionalParams?['hardwareWalletType'] as HardwareWalletType?;
|
||||
|
||||
getIt.registerFactoryParam<WalletRestorePage, WalletType, RestoredWallet?>((WalletType type,
|
||||
restoredWallet) {
|
||||
return WalletRestorePage(
|
||||
getIt.get<WalletRestoreViewModel>(param1: type, param2: restoredWallet),
|
||||
getIt.get<SeedSettingsViewModel>());
|
||||
return WalletRestoreViewModel(
|
||||
getIt.get<AppStore>(),
|
||||
getIt.get<WalletCreationService>(param1: type),
|
||||
_walletInfoSource,
|
||||
getIt.get<SeedSettingsViewModel>(),
|
||||
type: type,
|
||||
restoredWallet: restoredWallet,
|
||||
hardwareWalletType: hardwareWalletType);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<WalletRestorePage, WalletType, Map<String, dynamic>?>((type, additionalParams) =>
|
||||
WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type, param2: additionalParams),
|
||||
getIt.get<SeedSettingsViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<WalletRestoreChooseDerivationViewModel, List<DerivationInfo>, void>(
|
||||
(derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
|
||||
|
||||
@@ -1565,9 +1595,12 @@ Future<void> setup({
|
||||
getIt.registerFactory(() => DevBackgroundSyncLogsPage(getIt.get<BackgroundSyncLogsViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => SocketHealthLogsViewModel());
|
||||
|
||||
getIt.registerFactory(() => DevSocketHealthLogsPage(getIt.get<SocketHealthLogsViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => DevNetworkRequests());
|
||||
|
||||
getIt.registerFactory(() => DevQRToolsPage());
|
||||
|
||||
getIt.registerFactory(() => ExchangeProviderLogsViewModel());
|
||||
getIt.registerFactory(() => DevExchangeProviderLogsPage(getIt.get<ExchangeProviderLogsViewModel>()));
|
||||
|
||||
@@ -1,22 +1,44 @@
|
||||
import 'package:bitbox_flutter/usb/bitbox_device.dart' as bitbox;
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
|
||||
|
||||
class HardwareWalletDevice {
|
||||
final String name;
|
||||
final HardwareWalletDeviceType type;
|
||||
final HardwareWalletConnectionType connectionType;
|
||||
abstract class HardwareWalletDevice {
|
||||
String get name;
|
||||
|
||||
const HardwareWalletDevice({
|
||||
required this.name,
|
||||
required this.type,
|
||||
required this.connectionType,
|
||||
});
|
||||
HardwareWalletDeviceType get type;
|
||||
|
||||
factory HardwareWalletDevice.fromLedgerDevice(ledger.LedgerDevice device) =>
|
||||
HardwareWalletDevice(
|
||||
name: device.name,
|
||||
type: device.deviceInfo.toGeneric(),
|
||||
connectionType: device.connectionType.toGeneric(),
|
||||
);
|
||||
HardwareWalletConnectionType get connectionType;
|
||||
}
|
||||
|
||||
class LedgerHardwareWalletDevice extends HardwareWalletDevice {
|
||||
final ledger.LedgerDevice device;
|
||||
|
||||
LedgerHardwareWalletDevice(this.device);
|
||||
|
||||
@override
|
||||
String get name => device.name;
|
||||
|
||||
@override
|
||||
HardwareWalletDeviceType get type => device.deviceInfo.toGeneric();
|
||||
|
||||
@override
|
||||
HardwareWalletConnectionType get connectionType => device.connectionType.toGeneric();
|
||||
}
|
||||
|
||||
class BitboxHardwareWalletDevice extends HardwareWalletDevice {
|
||||
final bitbox.BitboxDevice device;
|
||||
|
||||
BitboxHardwareWalletDevice(this.device);
|
||||
|
||||
@override
|
||||
String get name => device.productName;
|
||||
|
||||
@override
|
||||
HardwareWalletDeviceType get type => name.contains("BitBox02 Nova")
|
||||
? HardwareWalletDeviceType.BitBox02Nova
|
||||
: HardwareWalletDeviceType.BitBox02;
|
||||
|
||||
@override
|
||||
HardwareWalletConnectionType get connectionType => HardwareWalletConnectionType.usb;
|
||||
}
|
||||
|
||||
enum HardwareWalletDeviceType {
|
||||
@@ -25,13 +47,16 @@ enum HardwareWalletDeviceType {
|
||||
ledgerNanoX,
|
||||
ledgerNanoSPlus,
|
||||
ledgerStax,
|
||||
ledgerFlex;
|
||||
ledgerFlex,
|
||||
BitBox02,
|
||||
BitBox02Nova;
|
||||
}
|
||||
|
||||
enum HardwareWalletConnectionType {
|
||||
usb,
|
||||
ble,
|
||||
nfc;
|
||||
nfc,
|
||||
qr;
|
||||
}
|
||||
|
||||
extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
|
||||
|
||||
@@ -181,22 +181,22 @@ class CWEthereum extends Ethereum {
|
||||
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
|
||||
|
||||
@override
|
||||
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
|
||||
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials)
|
||||
.setLedgerConnection(connection, wallet.walletInfo.derivationInfo?.derivationPath);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
|
||||
{int index = 0, int limit = 5}) async {
|
||||
final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
|
||||
try {
|
||||
return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
|
||||
} catch (err) {
|
||||
printV(err);
|
||||
throw err;
|
||||
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
|
||||
if (service is EVMChainLedgerService) {
|
||||
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
|
||||
service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath);
|
||||
} else if (service is EVMChainBitboxService) {
|
||||
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
|
||||
.setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
|
||||
}
|
||||
}
|
||||
@override
|
||||
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
|
||||
EVMChainLedgerService(connection);
|
||||
|
||||
@override
|
||||
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
|
||||
EVMChainBitboxService(manager);
|
||||
|
||||
@override
|
||||
List<String> getDefaultTokenContractAddresses() {
|
||||
|
||||
+14
-18
@@ -199,27 +199,23 @@ class CWPolygon extends Polygon {
|
||||
);
|
||||
|
||||
@override
|
||||
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
|
||||
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
|
||||
connection,
|
||||
wallet.walletInfo.derivationInfo?.derivationPath,
|
||||
);
|
||||
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
|
||||
if (service is EVMChainLedgerService) {
|
||||
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
|
||||
service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath);
|
||||
} else if (service is EVMChainBitboxService) {
|
||||
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
|
||||
.setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<HardwareAccountData>> getHardwareWalletAccounts(
|
||||
LedgerViewModel ledgerVM, {
|
||||
int index = 0,
|
||||
int limit = 5,
|
||||
}) async {
|
||||
final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
|
||||
try {
|
||||
return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
|
||||
} catch (err) {
|
||||
printV(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
|
||||
EVMChainLedgerService(connection);
|
||||
|
||||
@override
|
||||
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
|
||||
EVMChainBitboxService(manager, chainId: 137);
|
||||
|
||||
@override
|
||||
List<String> getDefaultTokenContractAddresses() =>
|
||||
|
||||
@@ -14,6 +14,8 @@ import 'package:cake_wallet/store/authentication_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/utils/exception_handler.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
@@ -60,8 +62,10 @@ void startAuthenticationStateChange(
|
||||
(route) => false,
|
||||
arguments: ConnectDevicePageParams(
|
||||
walletType: WalletType.monero,
|
||||
hardwareWalletType: HardwareWalletType.ledger,
|
||||
onConnectDevice: (context, ledgerVM) async {
|
||||
monero!.setGlobalLedgerConnection(ledgerVM.connection);
|
||||
if (ledgerVM is LedgerViewModel)
|
||||
monero!.setGlobalLedgerConnection(ledgerVM.connection);
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertWithOneAction(
|
||||
|
||||
+45
-18
@@ -23,6 +23,7 @@ import 'package:cake_wallet/src/screens/buy/webview_page.dart';
|
||||
import 'package:cake_wallet/cake_pay/cake_pay.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_options_page.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/select_device_manufacturer_page.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart';
|
||||
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
|
||||
@@ -39,6 +40,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/network_requests.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart';
|
||||
@@ -130,6 +132,7 @@ import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
@@ -213,7 +216,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
arguments: NewWalletArguments(type: type),
|
||||
),
|
||||
isCreate: true,
|
||||
isHardwareWallet: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -245,12 +247,15 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
case Routes.chooseHardwareWalletAccount:
|
||||
final arguments = settings.arguments as List<dynamic>;
|
||||
final type = arguments[0] as WalletType;
|
||||
final walletVM = getIt.get<WalletHardwareRestoreViewModel>(param1: type);
|
||||
final hardwareWallet = arguments [1] as HardwareWalletType;
|
||||
|
||||
final walletVM = getIt.get<WalletHardwareRestoreViewModel>(
|
||||
param1: type, param2: getIt<HardwareWalletViewModel>(param1: hardwareWallet));
|
||||
|
||||
if (type == WalletType.monero)
|
||||
return CupertinoPageRoute<void>(builder: (_) => MoneroHardwareWalletOptionsPage(walletVM));
|
||||
return handleRouteWithPlatformAwareness((_) => MoneroHardwareWalletOptionsPage(walletVM));
|
||||
|
||||
return CupertinoPageRoute<void>(builder: (_) => SelectHardwareWalletAccountPage(walletVM));
|
||||
return handleRouteWithPlatformAwareness((_) => SelectHardwareWalletAccountPage(walletVM));
|
||||
|
||||
case Routes.setupPin:
|
||||
Function(PinCodeState<PinCodeWidget>, String)? callback;
|
||||
@@ -272,7 +277,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: arg);
|
||||
},
|
||||
isCreate: false,
|
||||
isHardwareWallet: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -301,41 +305,59 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: arg);
|
||||
},
|
||||
isCreate: false,
|
||||
isHardwareWallet: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
case Routes.restoreWalletFromHardwareWallet:
|
||||
final arguments = settings.arguments as Map<String, dynamic>?;
|
||||
final showUnavailable = (arguments?['showUnavailable'] as bool?) ?? true;
|
||||
final onSelect = arguments?['onSelect'] as void Function(BuildContext, HardwareWalletType)?;
|
||||
final availableHardwareWalletTypes =
|
||||
arguments?['availableHardwareWalletTypes'] as List<HardwareWalletType>?;
|
||||
|
||||
return handleRouteWithPlatformAwareness((_) => SelectDeviceManufacturerPage(
|
||||
showUnavailable: showUnavailable,
|
||||
onSelect: onSelect,
|
||||
availableHardwareWalletTypes: availableHardwareWalletTypes,
|
||||
));
|
||||
|
||||
case Routes.connectHardwareWallet:
|
||||
final arguments = settings.arguments as List<dynamic>;
|
||||
final hardwareWalletType = (arguments[0] as HardwareWalletType?) ?? HardwareWalletType.ledger;
|
||||
|
||||
if (isSingleCoin) {
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => ConnectDevicePage(
|
||||
return handleRouteWithPlatformAwareness(
|
||||
(_) => ConnectDevicePage(
|
||||
ConnectDevicePageParams(
|
||||
walletType: availableWalletTypes.first,
|
||||
hardwareWalletType: hardwareWalletType,
|
||||
onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed(
|
||||
Routes.chooseHardwareWalletAccount,
|
||||
arguments: [availableWalletTypes.first]),
|
||||
arguments: [availableWalletTypes.first, hardwareWalletType]),
|
||||
isReconnect: false,
|
||||
),
|
||||
getIt.get<LedgerViewModel>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<NewWalletTypePage>(
|
||||
return handleRouteWithPlatformAwareness(
|
||||
(_) => getIt.get<NewWalletTypePage>(
|
||||
param1: NewWalletTypeArguments(
|
||||
onTypeSelected: (BuildContext context, WalletType type) {
|
||||
final arguments = ConnectDevicePageParams(
|
||||
walletType: type,
|
||||
onConnectDevice: (BuildContext context, _) => Navigator.of(context)
|
||||
.pushNamed(Routes.chooseHardwareWalletAccount, arguments: [type]),
|
||||
hardwareWalletType: hardwareWalletType,
|
||||
onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed(
|
||||
Routes.chooseHardwareWalletAccount,
|
||||
arguments: [type, hardwareWalletType]),
|
||||
isReconnect: false,
|
||||
);
|
||||
|
||||
Navigator.of(context).pushNamed(Routes.connectDevices, arguments: arguments);
|
||||
},
|
||||
isCreate: false,
|
||||
isHardwareWallet: true,
|
||||
hardwareWalletType: hardwareWalletType,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -347,7 +369,6 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
onTypeSelected: (BuildContext context, WalletType type) =>
|
||||
Navigator.of(context).pop(type),
|
||||
isCreate: false,
|
||||
isHardwareWallet: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -360,9 +381,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
case Routes.restoreWallet:
|
||||
final args = settings.arguments as Map<String, dynamic>?;
|
||||
final walletType = args?['walletType'] as WalletType;
|
||||
final restoredWallet = args?['restoredWallet'] as RestoredWallet?;
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<WalletRestorePage>(param1: walletType, param2: restoredWallet));
|
||||
builder: (_) => getIt.get<WalletRestorePage>(param1: walletType, param2: args));
|
||||
|
||||
case Routes.restoreWalletChooseDerivation:
|
||||
return MaterialPageRoute<void>(
|
||||
@@ -873,8 +893,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
|
||||
case Routes.connectDevices:
|
||||
final params = settings.arguments as ConnectDevicePageParams;
|
||||
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => ConnectDevicePage(params, getIt.get<LedgerViewModel>()));
|
||||
builder: (_) => ConnectDevicePage(
|
||||
params, getIt.get<HardwareWalletViewModel>(param1: params.hardwareWalletType)));
|
||||
|
||||
case Routes.walletGroupDescription:
|
||||
final walletType = settings.arguments as WalletType;
|
||||
@@ -920,6 +942,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
builder: (_) => getIt.get<DevSocketHealthLogsPage>(),
|
||||
);
|
||||
|
||||
case Routes.devQRTools:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<DevQRToolsPage>(),
|
||||
);
|
||||
|
||||
case Routes.devNetworkRequests:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<DevNetworkRequests>(),
|
||||
|
||||
@@ -11,6 +11,7 @@ class Routes {
|
||||
static const restoreWalletFromHardwareWallet = '/restore/hardware_wallet';
|
||||
static const restoreWalletTypeFromQR = '/restore_wallet_from_qr_code';
|
||||
static const restoreWalletChooseDerivation = '/restore_wallet_choose_derivation';
|
||||
static const connectHardwareWallet = '/restore/hardware_wallet/devices';
|
||||
static const chooseHardwareWalletAccount = '/restore/hardware_wallet/accounts';
|
||||
static const dashboard = '/dashboard';
|
||||
static const send = '/send';
|
||||
@@ -123,6 +124,7 @@ class Routes {
|
||||
static const devBackgroundSyncLogs = '/dev/background_sync_logs';
|
||||
static const devSocketHealthLogs = '/dev/socket_health_logs';
|
||||
static const devNetworkRequests = '/dev/network_requests';
|
||||
static const devQRTools = '/dev/qr_tools';
|
||||
static const devExchangeProviderLogs = '/dev/exchange_provider_logs';
|
||||
|
||||
static const signPage = '/sign_page';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
@@ -9,23 +10,25 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_wid
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_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:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||
|
||||
typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel);
|
||||
typedef OnConnectDevice = void Function(BuildContext, HardwareWalletViewModel);
|
||||
|
||||
class ConnectDevicePageParams {
|
||||
final WalletType walletType;
|
||||
final OnConnectDevice onConnectDevice;
|
||||
final bool allowChangeWallet;
|
||||
final bool isReconnect;
|
||||
final HardwareWalletType hardwareWalletType;
|
||||
|
||||
ConnectDevicePageParams({
|
||||
required this.walletType,
|
||||
required this.hardwareWalletType,
|
||||
required this.onConnectDevice,
|
||||
this.allowChangeWallet = false,
|
||||
this.isReconnect = true,
|
||||
@@ -37,9 +40,9 @@ class ConnectDevicePage extends BasePage {
|
||||
final OnConnectDevice onConnectDevice;
|
||||
final bool allowChangeWallet;
|
||||
final bool isReconnect;
|
||||
final LedgerViewModel ledgerVM;
|
||||
final HardwareWalletViewModel hardwareWalletVM;
|
||||
|
||||
ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
|
||||
ConnectDevicePage(ConnectDevicePageParams params, this.hardwareWalletVM)
|
||||
: walletType = params.walletType,
|
||||
onConnectDevice = params.onConnectDevice,
|
||||
allowChangeWallet = params.allowChangeWallet,
|
||||
@@ -51,8 +54,7 @@ class ConnectDevicePage extends BasePage {
|
||||
: S.current.restore_title_from_hardware_wallet;
|
||||
|
||||
@override
|
||||
Widget? leading(BuildContext context) =>
|
||||
!isReconnect ? super.leading(context) : null;
|
||||
Widget? leading(BuildContext context) => !isReconnect ? super.leading(context) : null;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => PopScope(
|
||||
@@ -61,7 +63,7 @@ class ConnectDevicePage extends BasePage {
|
||||
walletType,
|
||||
onConnectDevice,
|
||||
allowChangeWallet,
|
||||
ledgerVM,
|
||||
hardwareWalletVM,
|
||||
currentTheme,
|
||||
));
|
||||
}
|
||||
@@ -70,14 +72,14 @@ class ConnectDevicePageBody extends StatefulWidget {
|
||||
final WalletType walletType;
|
||||
final OnConnectDevice onConnectDevice;
|
||||
final bool allowChangeWallet;
|
||||
final LedgerViewModel ledgerVM;
|
||||
final HardwareWalletViewModel hardwareWalletVM;
|
||||
final MaterialThemeBase currentTheme;
|
||||
|
||||
const ConnectDevicePageBody(
|
||||
this.walletType,
|
||||
this.onConnectDevice,
|
||||
this.allowChangeWallet,
|
||||
this.ledgerVM,
|
||||
this.hardwareWalletVM,
|
||||
this.currentTheme,
|
||||
);
|
||||
|
||||
@@ -86,13 +88,13 @@ class ConnectDevicePageBody extends StatefulWidget {
|
||||
}
|
||||
|
||||
class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
var bleDevices = <LedgerDevice>[];
|
||||
var usbDevices = <LedgerDevice>[];
|
||||
var bleDevices = <HardwareWalletDevice>[];
|
||||
var usbDevices = <HardwareWalletDevice>[];
|
||||
|
||||
late Timer? _usbRefreshTimer = null;
|
||||
late Timer? _bleRefreshTimer = null;
|
||||
late Timer? _bleStateTimer = null;
|
||||
late StreamSubscription<LedgerDevice>? _bleRefresh = null;
|
||||
late StreamSubscription<HardwareWalletDevice>? _bleRefresh = null;
|
||||
|
||||
bool longWait = false;
|
||||
Timer? _longWaitTimer;
|
||||
@@ -101,21 +103,21 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_bleStateTimer = Timer.periodic(
|
||||
Duration(seconds: 1), (_) => widget.ledgerVM.updateBleState());
|
||||
_bleStateTimer =
|
||||
Timer.periodic(Duration(seconds: 1), (_) => widget.hardwareWalletVM.updateBleState());
|
||||
|
||||
_bleRefreshTimer =
|
||||
Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
|
||||
_bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
_usbRefreshTimer =
|
||||
Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
|
||||
_usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
|
||||
}
|
||||
|
||||
_longWaitTimer = Timer(Duration(seconds: 10), () {
|
||||
if (widget.ledgerVM.bleIsEnabled && bleDevices.isEmpty)
|
||||
setState(() => longWait = true);
|
||||
});
|
||||
if (widget.hardwareWalletVM.hasBluetooth) {
|
||||
_longWaitTimer = Timer(Duration(seconds: 10), () {
|
||||
if (widget.hardwareWalletVM.isBleEnabled && bleDevices.isEmpty)
|
||||
setState(() => longWait = true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -127,35 +129,25 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
_bleRefresh?.cancel();
|
||||
_longWaitTimer?.cancel();
|
||||
|
||||
widget.ledgerVM.stopScanning();
|
||||
widget.hardwareWalletVM.stopScanning();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _refreshUsbDevices() async {
|
||||
final dev = await widget.ledgerVM.ledgerPlusUSB.devices;
|
||||
final dev = await widget.hardwareWalletVM.getAllUsbDevices();
|
||||
if (usbDevices.length != dev.length) setState(() => usbDevices = dev);
|
||||
// _usbRefresh = widget.ledgerVM
|
||||
// .scanForUsbDevices()
|
||||
// .listen((device) => setState(() => usbDevices.add(device)))
|
||||
// ..onError((e) {
|
||||
// throw e.toString();
|
||||
// });
|
||||
// Keep polling until the lfp lib gets updated
|
||||
// _usbRefreshTimer?.cancel();
|
||||
// _usbRefreshTimer = null;
|
||||
}
|
||||
|
||||
Future<void> _refreshBleDevices() async {
|
||||
try {
|
||||
if (widget.ledgerVM.bleIsEnabled) {
|
||||
_bleRefresh =
|
||||
widget.ledgerVM.scanForBleDevices().listen((device) => setState(() {
|
||||
bleDevices.add(device);
|
||||
if (longWait) longWait = false;
|
||||
}))
|
||||
..onError((e) {
|
||||
throw e.toString();
|
||||
});
|
||||
if (widget.hardwareWalletVM.isBleEnabled) {
|
||||
_bleRefresh = widget.hardwareWalletVM.scanForBleDevices().listen((device) => setState(() {
|
||||
bleDevices.add(device);
|
||||
if (longWait) longWait = false;
|
||||
}))
|
||||
..onError((e) {
|
||||
throw e.toString();
|
||||
});
|
||||
_bleRefreshTimer?.cancel();
|
||||
_bleRefreshTimer = null;
|
||||
}
|
||||
@@ -164,25 +156,35 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _connectToDevice(LedgerDevice device) async {
|
||||
final isConnected =
|
||||
await widget.ledgerVM.connectLedger(device, widget.walletType);
|
||||
if (isConnected) widget.onConnectDevice(context, widget.ledgerVM);
|
||||
Future<void> _connectToDevice(HardwareWalletDevice device) async {
|
||||
final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType);
|
||||
if (isConnected) widget.onConnectDevice(context, widget.hardwareWalletVM);
|
||||
}
|
||||
|
||||
String _getDeviceTileLeading(LedgerDeviceType deviceInfo) {
|
||||
switch (deviceInfo) {
|
||||
case LedgerDeviceType.nanoX:
|
||||
String _getDeviceTileLeading(HardwareWalletDeviceType deviceType) {
|
||||
switch (deviceType) {
|
||||
case HardwareWalletDeviceType.ledgerNanoX:
|
||||
return 'assets/images/hardware_wallet/ledger_nano_x.png';
|
||||
case LedgerDeviceType.stax:
|
||||
case HardwareWalletDeviceType.ledgerStax:
|
||||
return 'assets/images/hardware_wallet/ledger_stax.png';
|
||||
case LedgerDeviceType.flex:
|
||||
case HardwareWalletDeviceType.ledgerFlex:
|
||||
return 'assets/images/hardware_wallet/ledger_flex.png';
|
||||
case HardwareWalletDeviceType.BitBox02:
|
||||
return 'assets/images/hardware_wallet/device_bitbox.svg';
|
||||
|
||||
default:
|
||||
return 'assets/images/hardware_wallet/ledger_nano_x.png';
|
||||
}
|
||||
}
|
||||
|
||||
String get description {
|
||||
if (!Platform.isAndroid) return S.of(context).connect_your_hardware_wallet_ble;
|
||||
|
||||
if (widget.hardwareWalletVM.hasBluetooth) return S.of(context).connect_your_hardware_wallet;
|
||||
|
||||
return S.of(context).connect_your_hardware_wallet_usb;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
@@ -197,11 +199,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
|
||||
child: Text(
|
||||
Platform.isAndroid
|
||||
? S.of(context).connect_your_hardware_wallet
|
||||
: S.of(context).connect_your_hardware_wallet_ios,
|
||||
style: Theme.of(context)
|
||||
.textTheme.titleMedium,
|
||||
description,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
@@ -211,22 +210,20 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
|
||||
child: Text(
|
||||
S.of(context).if_you_dont_see_your_device,
|
||||
style: Theme.of(context)
|
||||
.textTheme.titleMedium,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => Offstage(
|
||||
offstage: widget.ledgerVM.bleIsEnabled,
|
||||
offstage: widget.hardwareWalletVM.isBleEnabled ||
|
||||
!widget.hardwareWalletVM.hasBluetooth,
|
||||
child: Padding(
|
||||
padding:
|
||||
EdgeInsets.only(left: 20, right: 20, bottom: 20),
|
||||
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
|
||||
child: Text(
|
||||
S.of(context).ledger_please_enable_bluetooth,
|
||||
style: Theme.of(context)
|
||||
.textTheme.titleMedium,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
@@ -239,8 +236,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
S.of(context).bluetooth,
|
||||
style: Theme.of(context)
|
||||
.textTheme.bodyMedium,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -251,7 +247,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
child: DeviceTile(
|
||||
onPressed: () => _connectToDevice(device),
|
||||
title: device.name,
|
||||
leading: _getDeviceTileLeading(device.deviceInfo),
|
||||
leading: _getDeviceTileLeading(device.type),
|
||||
connectionType: device.connectionType,
|
||||
),
|
||||
),
|
||||
@@ -265,8 +261,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
S.of(context).usb,
|
||||
style: Theme.of(context)
|
||||
.textTheme.bodyMedium,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -277,7 +272,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
child: DeviceTile(
|
||||
onPressed: () => _connectToDevice(device),
|
||||
title: device.name,
|
||||
leading: _getDeviceTileLeading(device.deviceInfo),
|
||||
leading: _getDeviceTileLeading(device.type),
|
||||
connectionType: device.connectionType,
|
||||
),
|
||||
),
|
||||
@@ -287,10 +282,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
if (widget.allowChangeWallet) ...[
|
||||
PrimaryButton(
|
||||
text: S.of(context).wallets,
|
||||
color: Theme.of(context)
|
||||
.colorScheme.primary,
|
||||
textColor: Theme.of(context)
|
||||
.colorScheme.onPrimary,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
onPressed: _onChangeWallet,
|
||||
)
|
||||
],
|
||||
@@ -312,8 +305,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
void _onChangeWallet() {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.walletList,
|
||||
arguments: (BuildContext context) => Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false),
|
||||
arguments: (BuildContext context) =>
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(Routes.dashboard, (route) => false),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
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/widgets/dotted_divider.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/widgets/manufacturer_option_tile.dart';
|
||||
import 'package:cake_wallet/utils/permission_handler.dart';
|
||||
import 'package:cake_wallet/view_model/restore/wallet_restore_from_qr_code.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class SelectDeviceManufacturerPage extends BasePage {
|
||||
SelectDeviceManufacturerPage({
|
||||
this.showUnavailable = true,
|
||||
this.onSelect,
|
||||
this.availableHardwareWalletTypes,
|
||||
});
|
||||
|
||||
final bool showUnavailable;
|
||||
final void Function(BuildContext, HardwareWalletType)? onSelect;
|
||||
final List<HardwareWalletType>? availableHardwareWalletTypes;
|
||||
|
||||
@override
|
||||
String get title => S.current.select_manufacturer_title;
|
||||
|
||||
@override
|
||||
AppBarStyle get appBarStyle => AppBarStyle.regular;
|
||||
|
||||
List<_DeviceManufacturer> get availableManufacturers => [
|
||||
_DeviceManufacturer(
|
||||
image: SvgPicture.asset('assets/images/hardware_wallet/ledger_man.svg', height: 25),
|
||||
hardwareWalletType: HardwareWalletType.ledger,
|
||||
),
|
||||
// _DeviceManufacturer(
|
||||
// image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
|
||||
// hardwareWalletType: HardwareWalletType.bitbox,
|
||||
// tag: S.current.new_tag,
|
||||
// ),
|
||||
_DeviceManufacturer(
|
||||
image: SvgPicture.asset('assets/images/hardware_wallet/cupcake_man.svg', height: 25),
|
||||
hardwareWalletType: HardwareWalletType.cupcake,
|
||||
tag: S.current.new_tag,
|
||||
),
|
||||
_DeviceManufacturer(
|
||||
image: SvgPicture.asset('assets/images/hardware_wallet/coldcard_man.svg', height: 25),
|
||||
hardwareWalletType: HardwareWalletType.coldcard,
|
||||
tag: S.current.new_tag,
|
||||
),
|
||||
// _DeviceManufacturer(
|
||||
// image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
|
||||
// hardwareWalletType: HardwareWalletType.seedsigner,
|
||||
// tag: S.current.new_tag,
|
||||
// ),
|
||||
]
|
||||
.where((e) => availableHardwareWalletTypes == null
|
||||
? ![HardwareWalletType.cupcake].contains(e.hardwareWalletType)
|
||||
: availableHardwareWalletTypes!.contains(e.hardwareWalletType))
|
||||
.toList();
|
||||
|
||||
List<_DeviceManufacturer> get comingManufacturers => [
|
||||
_DeviceManufacturer(
|
||||
image: SvgPicture.asset('assets/images/hardware_wallet/trezor_man.svg', height: 25),
|
||||
tag: S.current.coming_soon_tag,
|
||||
),
|
||||
_DeviceManufacturer(
|
||||
image: SvgPicture.asset('assets/images/hardware_wallet/bitbox_man.svg', height: 25),
|
||||
hardwareWalletType: HardwareWalletType.bitbox,
|
||||
tag: S.current.coming_soon_tag,
|
||||
),
|
||||
_DeviceManufacturer(
|
||||
image: SvgPicture.asset('assets/images/hardware_wallet/seedsigner_man.svg', height: 25),
|
||||
hardwareWalletType: HardwareWalletType.seedsigner,
|
||||
tag: S.current.coming_soon_tag,
|
||||
),
|
||||
_DeviceManufacturer(
|
||||
image: SvgPicture.asset('assets/images/hardware_wallet/foundation_man.svg', height: 25),
|
||||
tag: S.current.coming_soon_tag,
|
||||
),
|
||||
_DeviceManufacturer(
|
||||
image: SvgPicture.asset('assets/images/hardware_wallet/keystone_man.svg', height: 25),
|
||||
tag: S.current.coming_soon_tag,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => Container(
|
||||
child: Center(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
...availableManufacturers.map(
|
||||
(manufacturer) => Padding(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: ManufacturerOptionTile(
|
||||
image: manufacturer.image,
|
||||
tag: manufacturer.tag,
|
||||
onPressed: () {
|
||||
if (onSelect != null)
|
||||
return onSelect!.call(context, manufacturer.hardwareWalletType!);
|
||||
|
||||
if (isAirgappedWallet(manufacturer.hardwareWalletType)) {
|
||||
_onScanQRCode(context, manufacturer.hardwareWalletType!);
|
||||
} else if (manufacturer.hardwareWalletType != null) {
|
||||
Navigator.pushNamed(context, Routes.connectHardwareWallet,
|
||||
arguments: [manufacturer.hardwareWalletType]);
|
||||
}
|
||||
},
|
||||
isDarkTheme: currentTheme.isDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showUnavailable) ...[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20, bottom: 10),
|
||||
child: DottedDivider(color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
),
|
||||
...comingManufacturers.map(
|
||||
(manufacturer) => Padding(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: ManufacturerOptionTile(
|
||||
image: manufacturer.image,
|
||||
tag: manufacturer.tag,
|
||||
onPressed: () =>
|
||||
Fluttertoast.showToast(msg: 'One more tap and it might work'),
|
||||
// Ester egg
|
||||
isDarkTheme: currentTheme.isDark,
|
||||
isUnavailable: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
bool isAirgappedWallet(HardwareWalletType? type) => [
|
||||
HardwareWalletType.cupcake,
|
||||
HardwareWalletType.coldcard,
|
||||
HardwareWalletType.seedsigner,
|
||||
HardwareWalletType.keystone,
|
||||
].contains(type);
|
||||
|
||||
bool isRestoring = false;
|
||||
|
||||
Future<void> _onScanQRCode(BuildContext context, HardwareWalletType type) async {
|
||||
final isCameraPermissionGranted =
|
||||
await PermissionHandler.checkPermission(Permission.camera, context);
|
||||
|
||||
if (!isCameraPermissionGranted) return;
|
||||
try {
|
||||
if (isRestoring) return;
|
||||
|
||||
isRestoring = true;
|
||||
|
||||
final restoredWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
|
||||
|
||||
final params = {
|
||||
'walletType': restoredWallet.type,
|
||||
'restoredWallet': restoredWallet,
|
||||
'hardwareWalletType': type,
|
||||
};
|
||||
|
||||
Navigator.pushNamed(context, Routes.restoreWallet, arguments: params)
|
||||
.then((_) => isRestoring = false);
|
||||
} catch (e) {
|
||||
printV(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _DeviceManufacturer {
|
||||
final SvgPicture image;
|
||||
final HardwareWalletType? hardwareWalletType;
|
||||
final String? tag;
|
||||
|
||||
_DeviceManufacturer({required this.image, this.hardwareWalletType, this.tag});
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class DeviceTile extends StatelessWidget {
|
||||
const DeviceTile({
|
||||
@@ -12,15 +13,15 @@ class DeviceTile extends StatelessWidget {
|
||||
final VoidCallback onPressed;
|
||||
final String title;
|
||||
final String? leading;
|
||||
final ConnectionType? connectionType;
|
||||
final HardwareWalletConnectionType? connectionType;
|
||||
|
||||
String? get connectionTypeIcon {
|
||||
switch (connectionType) {
|
||||
case ConnectionType.ble:
|
||||
case HardwareWalletConnectionType.ble:
|
||||
return 'assets/images/bluetooth.png';
|
||||
case ConnectionType.usb:
|
||||
case HardwareWalletConnectionType.usb:
|
||||
return 'assets/images/usb.png';
|
||||
case null:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -42,12 +43,14 @@ class DeviceTile extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
if (leading != null)
|
||||
if (leading != null && !leading!.endsWith(".svg"))
|
||||
Image.asset(
|
||||
leading!,
|
||||
height: 30,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
if (leading != null && leading!.endsWith(".svg"))
|
||||
SvgPicture.asset(leading!, height: 30),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 16),
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DottedDivider extends StatelessWidget {
|
||||
final Color color;
|
||||
|
||||
const DottedDivider({super.key, required this.color});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => SizedBox(
|
||||
width: double.infinity,
|
||||
child: CustomPaint(
|
||||
painter: _DashedLinePainter(color: color),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _DashedLinePainter extends CustomPainter {
|
||||
final Color color;
|
||||
|
||||
const _DashedLinePainter({required this.color});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
double dashWidth = 9, dashSpace = 5, startX = 0;
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = 1;
|
||||
while (startX < size.width) {
|
||||
canvas.drawLine(Offset(startX, 0), Offset(startX + dashWidth, 0), paint);
|
||||
startX += dashWidth + dashSpace;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import 'package:cake_wallet/themes/utils/custom_theme_colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ManufacturerOptionTile extends StatelessWidget {
|
||||
const ManufacturerOptionTile({
|
||||
required this.onPressed,
|
||||
required this.image,
|
||||
required this.isDarkTheme,
|
||||
this.tag,
|
||||
this.isUnavailable = false,
|
||||
});
|
||||
|
||||
final VoidCallback onPressed;
|
||||
final Widget image;
|
||||
final String? tag;
|
||||
final bool isUnavailable;
|
||||
final bool isDarkTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => GestureDetector(
|
||||
onTap: onPressed,
|
||||
child: Opacity(
|
||||
opacity: isUnavailable ? 0.5 : 1,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
isDarkTheme
|
||||
? CustomThemeColors.cardGradientColorPrimaryDark
|
||||
: CustomThemeColors.cardGradientColorPrimaryLight,
|
||||
isDarkTheme
|
||||
? CustomThemeColors.cardGradientColorSecondaryDark
|
||||
: CustomThemeColors.cardGradientColorSecondaryLight,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
Container(height: 25, child: image),
|
||||
if (tag != null)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.transparent),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).colorScheme.surfaceContainerLow),
|
||||
padding: EdgeInsets.symmetric(horizontal: 5),
|
||||
margin: EdgeInsets.only(left: 5),
|
||||
child: Text(tag!))
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import 'package:cake_wallet/src/widgets/standard_switch.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@@ -27,6 +28,23 @@ class CryptoBalanceWidget extends StatelessWidget {
|
||||
final btcLockLight = 'assets/images/btc_lock_light.png';
|
||||
final btcLockDark = 'assets/images/btc_lock_dark.png';
|
||||
|
||||
String? get hardwareWalletIcon {
|
||||
switch (dashboardViewModel.wallet.hardwareWalletType) {
|
||||
case null:
|
||||
return null;
|
||||
case HardwareWalletType.ledger:
|
||||
return "assets/images/hardware_wallet/device_ledger_nano_x.svg";
|
||||
case HardwareWalletType.bitbox:
|
||||
return "assets/images/hardware_wallet/device_bitbox.svg";
|
||||
case HardwareWalletType.cupcake:
|
||||
return "assets/images/cupcake.svg";
|
||||
case HardwareWalletType.coldcard:
|
||||
case HardwareWalletType.seedsigner:
|
||||
case HardwareWalletType.keystone:
|
||||
return "assets/images/hardware_wallet/device_qr.svg";
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
@@ -86,10 +104,10 @@ class CryptoBalanceWidget extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (dashboardViewModel.wallet.isHardwareWallet)
|
||||
if (hardwareWalletIcon != null)
|
||||
Container(
|
||||
child: Image.asset(
|
||||
'assets/images/hardware_wallet/ledger_nano_x.png',
|
||||
child: SvgPicture.asset(
|
||||
hardwareWalletIcon!,
|
||||
width: 24,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/moneroc_cache_debug.dart';
|
||||
import 'package:cake_wallet/view_model/dev/qr_tools_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class DevQRToolsPage extends BasePage {
|
||||
@override
|
||||
String? get title => "[dev] *QR tools";
|
||||
|
||||
final QRToolsViewModel viewModel = QRToolsViewModel();
|
||||
|
||||
late final textCtrl = TextEditingController(text: viewModel.input);
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Container(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: textCtrl,
|
||||
maxLines: 8,
|
||||
onChanged: (value) {
|
||||
viewModel.input = value;
|
||||
}
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return Expanded(
|
||||
child: JsonExplorer(data: viewModel.data, title: "result"),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:cake_wallet/core/new_wallet_arguments.dart';
|
||||
import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/core/new_wallet_arguments.dart';
|
||||
import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_utils.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
@@ -11,12 +11,12 @@ import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_aler
|
||||
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/search_bar_widget.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_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/new_wallet_type_view_model.dart';
|
||||
import 'package:cake_wallet/wallet_types.g.dart';
|
||||
import 'package:cw_core/hardware/device_connection_type.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -51,7 +51,7 @@ class NewWalletTypePage extends BasePage {
|
||||
isCreate: newWalletTypeArguments.isCreate,
|
||||
newWalletTypeViewModel: newWalletTypeViewModel,
|
||||
onTypeSelected: newWalletTypeArguments.onTypeSelected,
|
||||
isHardwareWallet: newWalletTypeArguments.isHardwareWallet,
|
||||
hardwareWalletType: newWalletTypeArguments.hardwareWalletType,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,14 +61,16 @@ class WalletTypeForm extends StatefulWidget {
|
||||
required this.isCreate,
|
||||
required this.newWalletTypeViewModel,
|
||||
this.onTypeSelected,
|
||||
required this.isHardwareWallet,
|
||||
this.hardwareWalletType,
|
||||
});
|
||||
|
||||
final bool isCreate;
|
||||
final Image walletImage;
|
||||
final NewWalletTypeViewModel newWalletTypeViewModel;
|
||||
final void Function(BuildContext, WalletType)? onTypeSelected;
|
||||
final bool isHardwareWallet;
|
||||
final HardwareWalletType? hardwareWalletType;
|
||||
|
||||
bool get isHardwareWallet => hardwareWalletType != null;
|
||||
|
||||
@override
|
||||
WalletTypeFormState createState() => WalletTypeFormState();
|
||||
@@ -90,7 +92,9 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
||||
types = filteredTypes = availableWalletTypes
|
||||
.where((element) =>
|
||||
!widget.isHardwareWallet ||
|
||||
DeviceConnectionType.supportedConnectionTypes(element, Platform.isIOS).isNotEmpty)
|
||||
DeviceConnectionType.supportedConnectionTypes(
|
||||
element, widget.hardwareWalletType!, Platform.isIOS)
|
||||
.isNotEmpty)
|
||||
.toList();
|
||||
super.initState();
|
||||
|
||||
@@ -148,7 +152,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
||||
isSelected: selected == type,
|
||||
onTap: () => setState(() => selected = type),
|
||||
deviceConnectionTypes: widget.isHardwareWallet
|
||||
? DeviceConnectionType.supportedConnectionTypes(type, Platform.isIOS)
|
||||
? DeviceConnectionType.supportedConnectionTypes(
|
||||
type, widget.hardwareWalletType!, Platform.isIOS)
|
||||
: [],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -111,7 +111,7 @@ class QRWidget extends StatelessWidget {
|
||||
),
|
||||
if (addressListViewModel.isPayjoinUnavailable &&
|
||||
!addressListViewModel.isSilentPayments &&
|
||||
!addressListViewModel.isCupcake) ...[
|
||||
!addressListViewModel.isBitcoinViewOnly) ...[
|
||||
GestureDetector(
|
||||
onTap: () => _onPayjoinInactivePressed(context),
|
||||
child: Row(
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
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/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/option_tile.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_theme.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/utils/permission_handler.dart';
|
||||
@@ -12,11 +11,9 @@ 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/wallet_restore_from_qr_code.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
import 'package:cw_core/hardware/device_connection_type.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_theme.dart';
|
||||
|
||||
class RestoreOptionsPage extends BasePage {
|
||||
RestoreOptionsPage({required this.isNewInstall});
|
||||
@@ -27,9 +24,8 @@ class RestoreOptionsPage extends BasePage {
|
||||
final bool isNewInstall;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return _RestoreOptionsBody(isNewInstall: isNewInstall, themeType: currentTheme.type);
|
||||
}
|
||||
Widget body(BuildContext context) =>
|
||||
_RestoreOptionsBody(isNewInstall: isNewInstall, themeType: currentTheme.type);
|
||||
}
|
||||
|
||||
class _RestoreOptionsBody extends StatefulWidget {
|
||||
@@ -45,41 +41,25 @@ class _RestoreOptionsBody extends StatefulWidget {
|
||||
class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
|
||||
bool isRestoring = false;
|
||||
|
||||
bool get _doesSupportHardwareWallets {
|
||||
if (isMoneroOnly) {
|
||||
return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
|
||||
.isNotEmpty;
|
||||
}
|
||||
String get imageRestoreHWPath => widget.themeType == ThemeType.dark
|
||||
? 'assets/images/restore_hw_dark.png'
|
||||
: 'assets/images/restore_hw.png';
|
||||
|
||||
return true;
|
||||
}
|
||||
String get imageRestoreCupcakePath => widget.themeType == ThemeType.dark
|
||||
? 'assets/images/restore_cupcake_dark.png'
|
||||
: 'assets/images/restore_cupcake.png';
|
||||
|
||||
String get imageRestoreHWPath =>
|
||||
widget.themeType == ThemeType.dark
|
||||
? 'assets/images/restore_hw_dark.png'
|
||||
: 'assets/images/restore_hw.png';
|
||||
String get imageRestoreQRPath => widget.themeType == ThemeType.dark
|
||||
? 'assets/images/restore_qr_dark.png'
|
||||
: 'assets/images/restore_qr.png';
|
||||
|
||||
String get imageRestoreCupcakePath =>
|
||||
widget.themeType == ThemeType.dark
|
||||
? 'assets/images/restore_cupcake_dark.png'
|
||||
: 'assets/images/restore_cupcake.png';
|
||||
String get imageRestoreHotWalletPath => widget.themeType == ThemeType.dark
|
||||
? 'assets/images/restore_hot_wallet_dark.png'
|
||||
: 'assets/images/restore_hot_wallet.png';
|
||||
|
||||
String get imageRestoreQRPath =>
|
||||
widget.themeType == ThemeType.dark
|
||||
? 'assets/images/restore_qr_dark.png'
|
||||
: 'assets/images/restore_qr.png';
|
||||
|
||||
|
||||
String get imageRestoreHotWalletPath =>
|
||||
widget.themeType == ThemeType.dark
|
||||
? 'assets/images/restore_hot_wallet_dark.png'
|
||||
: 'assets/images/restore_hot_wallet.png';
|
||||
|
||||
|
||||
String get imageRestoreBackupPath =>
|
||||
widget.themeType == ThemeType.dark
|
||||
? 'assets/images/restore_backup_dark.png'
|
||||
: 'assets/images/restore_backup.png';
|
||||
String get imageRestoreBackupPath => widget.themeType == ThemeType.dark
|
||||
? 'assets/images/restore_backup_dark.png'
|
||||
: 'assets/images/restore_backup.png';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -116,18 +96,20 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
|
||||
tag: S.of(context).new_tag,
|
||||
),
|
||||
),
|
||||
if (_doesSupportHardwareWallets)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: OptionTile(
|
||||
key: ValueKey('restore_options_from_hardware_wallet_button_key'),
|
||||
onPressed: () =>
|
||||
Navigator.pushNamed(context, Routes.restoreWalletFromHardwareWallet),
|
||||
image: imageRestoreHW,
|
||||
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(
|
||||
key: ValueKey('restore_options_from_hardware_wallet_button_key'),
|
||||
onPressed: () => Navigator.pushNamed(
|
||||
context,
|
||||
isSingleCoin
|
||||
? Routes.connectHardwareWallet
|
||||
: Routes.restoreWalletFromHardwareWallet),
|
||||
image: imageRestoreHW,
|
||||
title: S.of(context).restore_title_from_hardware_wallet,
|
||||
description: S.of(context).restore_description_from_hardware_wallet,
|
||||
),
|
||||
),
|
||||
if (widget.isNewInstall)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
@@ -143,11 +125,11 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: OptionTile(
|
||||
key: ValueKey('restore_options_from_qr_button_key'),
|
||||
onPressed: () => _onScanQRCode(context),
|
||||
image: imageRestoreQR,
|
||||
title: S.of(context).scan_qr_code,
|
||||
description: S.of(context).cold_or_recover_wallet,
|
||||
key: ValueKey('restore_options_from_qr_button_key'),
|
||||
onPressed: () => _onScanQRCode(context),
|
||||
image: imageRestoreQR,
|
||||
title: S.of(context).scan_qr_code,
|
||||
description: S.of(context).cold_or_recover_wallet,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -184,7 +166,11 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
|
||||
|
||||
final restoredWallet = await WalletRestoreFromQRCode.scanQRCodeForRestoring(context);
|
||||
|
||||
final params = {'walletType': restoredWallet.type, 'restoredWallet': restoredWallet};
|
||||
final params = {
|
||||
'walletType': restoredWallet.type,
|
||||
'restoredWallet': restoredWallet,
|
||||
'hardwareWalletType': HardwareWalletType.cupcake,
|
||||
};
|
||||
|
||||
Navigator.pushNamed(context, Routes.restoreWallet, arguments: params).then((_) {
|
||||
if (mounted) setState(() => isRestoring = false);
|
||||
|
||||
@@ -440,18 +440,21 @@ class SendPage extends BasePage {
|
||||
}
|
||||
|
||||
if (sendViewModel.wallet.isHardwareWallet) {
|
||||
if (!sendViewModel.ledgerViewModel!.isConnected) {
|
||||
if (!sendViewModel.hardwareWalletViewModel!.isConnected) {
|
||||
await Navigator.of(context).pushNamed(Routes.connectDevices,
|
||||
arguments: ConnectDevicePageParams(
|
||||
walletType: sendViewModel.walletType,
|
||||
hardwareWalletType:
|
||||
sendViewModel.wallet.walletInfo.hardwareWalletType!,
|
||||
onConnectDevice: (BuildContext context, _) {
|
||||
sendViewModel.ledgerViewModel!
|
||||
.setLedger(sendViewModel.wallet);
|
||||
sendViewModel.hardwareWalletViewModel!
|
||||
.initWallet(sendViewModel.wallet);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
));
|
||||
} else {
|
||||
sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
|
||||
sendViewModel.hardwareWalletViewModel!
|
||||
.initWallet(sendViewModel.wallet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.da
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
@@ -26,16 +27,14 @@ class OtherSettingsPage extends BasePage {
|
||||
final OtherSettingsViewModel _otherSettingsViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
return Container(
|
||||
Widget body(BuildContext context) => Observer(
|
||||
builder: (_) => Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
if (_otherSettingsViewModel.displayTransactionPriority)
|
||||
_otherSettingsViewModel.walletType == WalletType.bitcoin ?
|
||||
SettingsPriorityPickerCell(
|
||||
_otherSettingsViewModel.walletType == WalletType.bitcoin
|
||||
? SettingsPriorityPickerCell(
|
||||
title: S.current.settings_fee_priority,
|
||||
items: priorityForWalletType(_otherSettingsViewModel.walletType),
|
||||
displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
|
||||
@@ -44,8 +43,8 @@ class OtherSettingsPage extends BasePage {
|
||||
onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
|
||||
customValue: _otherSettingsViewModel.customBitcoinFeeRate,
|
||||
maxValue: _otherSettingsViewModel.maxCustomFeeRate?.toDouble(),
|
||||
) :
|
||||
SettingsPickerCell(
|
||||
)
|
||||
: SettingsPickerCell(
|
||||
title: S.current.settings_fee_priority,
|
||||
items: priorityForWalletType(_otherSettingsViewModel.walletType),
|
||||
displayItem: _otherSettingsViewModel.getDisplayPriority,
|
||||
@@ -55,75 +54,96 @@ class OtherSettingsPage extends BasePage {
|
||||
if (_otherSettingsViewModel.changeRepresentativeEnabled)
|
||||
SettingsCellWithArrow(
|
||||
title: S.current.change_rep,
|
||||
handler: (BuildContext context) =>
|
||||
Navigator.of(context).pushNamed(Routes.changeRep),
|
||||
handler: (context) => Navigator.of(context).pushNamed(Routes.changeRep),
|
||||
),
|
||||
if (_otherSettingsViewModel.changeHardwareWalletTypeEnabled)
|
||||
SettingsCellWithArrow(
|
||||
title: "Hardware wallet manufacturer",
|
||||
handler: (context) => Navigator.of(context)
|
||||
.pushNamed(Routes.restoreWalletFromHardwareWallet, arguments: {
|
||||
"showUnavailable": false,
|
||||
"availableHardwareWalletTypes": [
|
||||
HardwareWalletType.cupcake,
|
||||
HardwareWalletType.coldcard,
|
||||
HardwareWalletType.seedsigner,
|
||||
],
|
||||
"onSelect": (BuildContext context, HardwareWalletType hwType) async {
|
||||
await _otherSettingsViewModel.onHardwareWalletTypeChanged(hwType);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
}),
|
||||
),
|
||||
SettingsCellWithArrow(
|
||||
title: S.current.settings_terms_and_conditions,
|
||||
handler: (BuildContext context) =>
|
||||
Navigator.of(context).pushNamed(Routes.readDisclaimer),
|
||||
handler: (context) => Navigator.of(context).pushNamed(Routes.readDisclaimer),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions && _otherSettingsViewModel.walletType == WalletType.monero)
|
||||
if (FeatureFlag.hasDevOptions &&
|
||||
_otherSettingsViewModel.walletType == WalletType.monero)
|
||||
SettingsCellWithArrow(
|
||||
title: '[dev] monero background sync',
|
||||
handler: (BuildContext context) =>
|
||||
handler: (context) =>
|
||||
Navigator.of(context).pushNamed(Routes.devMoneroBackgroundSync),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions && [WalletType.monero, WalletType.wownero, WalletType.zano].contains(_otherSettingsViewModel.walletType))
|
||||
if (FeatureFlag.hasDevOptions &&
|
||||
[WalletType.monero, WalletType.wownero, WalletType.zano]
|
||||
.contains(_otherSettingsViewModel.walletType))
|
||||
SettingsCellWithArrow(
|
||||
title: '[dev] xmr call profiler',
|
||||
handler: (BuildContext context) =>
|
||||
handler: (context) =>
|
||||
Navigator.of(context).pushNamed(Routes.devMoneroCallProfiler),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions && [WalletType.monero].contains(_otherSettingsViewModel.walletType))
|
||||
if (FeatureFlag.hasDevOptions &&
|
||||
[WalletType.monero].contains(_otherSettingsViewModel.walletType))
|
||||
SettingsCellWithArrow(
|
||||
title: '[dev] xmr wallet cache debug',
|
||||
handler: (BuildContext context) =>
|
||||
handler: (context) =>
|
||||
Navigator.of(context).pushNamed(Routes.devMoneroWalletCacheDebug),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions)
|
||||
SettingsCellWithArrow(
|
||||
title: '[dev] shared preferences',
|
||||
handler: (BuildContext context) =>
|
||||
handler: (context) =>
|
||||
Navigator.of(context).pushNamed(Routes.devSharedPreferences),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions)
|
||||
SettingsCellWithArrow(
|
||||
title: '[dev] secure storage preferences',
|
||||
handler: (BuildContext context) =>
|
||||
handler: (context) =>
|
||||
Navigator.of(context).pushNamed(Routes.devSecurePreferences),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions)
|
||||
SettingsCellWithArrow(
|
||||
title: '[dev] background sync logs',
|
||||
handler: (BuildContext context) =>
|
||||
handler: (context) =>
|
||||
Navigator.of(context).pushNamed(Routes.devBackgroundSyncLogs),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions)
|
||||
SettingsCellWithArrow(
|
||||
title: '[dev] socket health logs',
|
||||
handler: (BuildContext context) =>
|
||||
Navigator.of(context).pushNamed(Routes.devSocketHealthLogs),
|
||||
handler: (context) => Navigator.of(context).pushNamed(Routes.devSocketHealthLogs),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions)
|
||||
SettingsCellWithArrow(
|
||||
title: '[dev] network requests logs',
|
||||
handler: (BuildContext context) =>
|
||||
Navigator.of(context).pushNamed(Routes.devNetworkRequests),
|
||||
handler: (context) => Navigator.of(context).pushNamed(Routes.devNetworkRequests),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions)
|
||||
SettingsCellWithArrow(
|
||||
title: '[dev] exchange provider logs',
|
||||
handler: (BuildContext context) =>
|
||||
handler: (context) =>
|
||||
Navigator.of(context).pushNamed(Routes.devExchangeProviderLogs),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions)
|
||||
SettingsCellWithArrow(
|
||||
title: '[dev] *QR tools',
|
||||
handler: (context) => Navigator.of(context).pushNamed(Routes.devQRTools),
|
||||
),
|
||||
Spacer(),
|
||||
SettingsVersionCell(
|
||||
title: S.of(context).version(_otherSettingsViewModel.currentVersion)),
|
||||
title: S.of(context).version(_otherSettingsViewModel.currentVersion),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ class AnimatedURPage extends BasePage {
|
||||
child: URQR(
|
||||
urqr: urQr,
|
||||
walletType: animatedURmodel.wallet.type,
|
||||
hardwareWalletType: animatedURmodel.wallet.hardwareWalletType,
|
||||
),
|
||||
),
|
||||
if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt", BBQR.header]
|
||||
@@ -125,4 +126,4 @@ class AnimatedURPage extends BasePage {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
|
||||
@@ -7,67 +6,65 @@ import 'package:cake_wallet/src/screens/ur/widgets/qr_selection_dialog.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class URQR extends StatefulWidget {
|
||||
URQR({super.key, required this.urqr, required this.walletType});
|
||||
const URQR({super.key, required this.urqr, required this.walletType, this.hardwareWalletType});
|
||||
|
||||
final Map<String, String> urqr;
|
||||
final WalletType walletType;
|
||||
final HardwareWalletType? hardwareWalletType;
|
||||
|
||||
@override
|
||||
// ignore: library_private_types_in_public_api
|
||||
_URQRState createState() => _URQRState();
|
||||
}
|
||||
|
||||
const urFrameTime = 1000 ~/ 5;
|
||||
|
||||
class _URQRState extends State<URQR> {
|
||||
Timer? t;
|
||||
Timer? _timer;
|
||||
int frame = 0;
|
||||
int selectedInt = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
setState(() {
|
||||
t = Timer.periodic(const Duration(milliseconds: urFrameTime), (timer) {
|
||||
_nextFrame();
|
||||
});
|
||||
});
|
||||
}
|
||||
final keys = widget.urqr.keys.toList();
|
||||
if (widget.hardwareWalletType == HardwareWalletType.coldcard) {
|
||||
selectedInt = 1;
|
||||
selected = keys[(selectedInt) % keys.length];
|
||||
} else if (widget.hardwareWalletType == HardwareWalletType.seedsigner) {
|
||||
selectedInt = 2;
|
||||
selected = keys[(selectedInt) % keys.length];
|
||||
}
|
||||
|
||||
void _nextFrame() {
|
||||
setState(() {
|
||||
frame++;
|
||||
});
|
||||
_timer = Timer.periodic(const Duration(milliseconds: urFrameTime), (_) => _nextFrame());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
t?.cancel();
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
late String selected =
|
||||
(widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first;
|
||||
int selectedInt = 0;
|
||||
|
||||
List<String> get frames {
|
||||
return widget.urqr[selected]?.split("\n") ?? [];
|
||||
}
|
||||
|
||||
late String nextLabel =
|
||||
widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length];
|
||||
|
||||
void next() {
|
||||
String get nextLabel => widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length];
|
||||
void next() => setState(() {
|
||||
final keys = widget.urqr.keys.toList();
|
||||
setState(() {
|
||||
selectedInt++;
|
||||
nextLabel = keys[(selectedInt + 1) % keys.length];
|
||||
selected = keys[(selectedInt) % keys.length];
|
||||
});
|
||||
}
|
||||
|
||||
selectedInt++;
|
||||
selected = keys[(selectedInt) % keys.length];
|
||||
});
|
||||
|
||||
late String selected = (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first;
|
||||
|
||||
|
||||
List<String> get frames => widget.urqr[selected]?.split("\n") ?? [];
|
||||
void _nextFrame() => setState(() => frame++);
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -107,45 +104,44 @@ All frames:
|
||||
);
|
||||
}
|
||||
|
||||
Widget _legacySwitch(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: PrimaryButton(
|
||||
onPressed: next,
|
||||
text: nextLabel,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
Widget _legacySwitch(BuildContext context) => SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: PrimaryButton(
|
||||
onPressed: next,
|
||||
text: nextLabel,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Widget _newSwitch(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(height: 32),
|
||||
const SizedBox(height: 32),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.qr_code,
|
||||
color: Theme.of(context).colorScheme.primary, size: 24),
|
||||
SizedBox(width: 4),
|
||||
Text("QR Code Format",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
)),
|
||||
Icon(Icons.qr_code, color: Theme.of(context).colorScheme.primary, size: 24),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
"QR Code Format",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
@@ -176,7 +172,7 @@ All frames:
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -193,10 +189,7 @@ All frames:
|
||||
_getCurrentFormatSubtitle(),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.6),
|
||||
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -204,10 +197,7 @@ All frames:
|
||||
),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.4),
|
||||
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
size: 24,
|
||||
),
|
||||
],
|
||||
@@ -216,7 +206,7 @@ All frames:
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
const SizedBox(height: 16),
|
||||
GestureDetector(
|
||||
onTap: () => _showInfoDialog(context),
|
||||
child: Row(
|
||||
@@ -226,35 +216,32 @@ All frames:
|
||||
"What's this for?",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
size: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _getCurrentFormatName() {
|
||||
if (widget.urqr.isEmpty) return "Unknown";
|
||||
final currentKey =
|
||||
widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
|
||||
final currentKey = widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
|
||||
return currentKey.split(' ')[0];
|
||||
}
|
||||
|
||||
String _getCurrentFormatSubtitle() {
|
||||
if (widget.urqr.isEmpty) return "";
|
||||
final currentKey =
|
||||
widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
|
||||
final currentKey = widget.urqr.keys.elementAt(selectedInt % widget.urqr.length);
|
||||
return currentKey.substring(currentKey.indexOf(' ') + 1);
|
||||
}
|
||||
|
||||
@@ -264,30 +251,25 @@ All frames:
|
||||
final keys = widget.urqr.keys.toList();
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return QRFormatSelectionDialog(
|
||||
formats: keys,
|
||||
currentIndex: selectedInt,
|
||||
onFormatSelected: (index) {
|
||||
Navigator.pop(context);
|
||||
setState(() {
|
||||
selectedInt = index;
|
||||
selected = keys[index];
|
||||
nextLabel = keys[(index + 1) % keys.length];
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
context: context,
|
||||
builder: (context) => QRFormatSelectionDialog(
|
||||
formats: keys,
|
||||
currentIndex: selectedInt,
|
||||
onFormatSelected: (index) {
|
||||
Navigator.pop(context);
|
||||
setState(() {
|
||||
selectedInt = index;
|
||||
selected = keys[index];
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showInfoDialog(BuildContext context) async {
|
||||
await showModalBottomSheet(
|
||||
Future<void> _showInfoDialog(BuildContext context) => showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (BuildContext context) {
|
||||
return QRFormatInfoBottomSheet();
|
||||
});
|
||||
}
|
||||
builder: (_) => QRFormatInfoBottomSheet(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,9 +21,11 @@ import 'package:cake_wallet/utils/exception_handler.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
@@ -477,8 +479,9 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||
Routes.connectDevices,
|
||||
arguments: ConnectDevicePageParams(
|
||||
walletType: WalletType.monero,
|
||||
hardwareWalletType: HardwareWalletType.ledger,
|
||||
onConnectDevice: (context, ledgerVM) async {
|
||||
monero!.setGlobalLedgerConnection(ledgerVM.connection);
|
||||
if (ledgerVM is LedgerViewModel) monero!.setGlobalLedgerConnection(ledgerVM.connection);
|
||||
didConnect = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@ abstract class ReceiveOptionViewModelBase with Store {
|
||||
break;
|
||||
case WalletType.litecoin:
|
||||
_options = [
|
||||
...bitcoin!.getLitecoinReceivePageOptions(),
|
||||
...bitcoin!.getLitecoinReceivePageOptions(_wallet),
|
||||
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
|
||||
];
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:ur/cbor_lite.dart';
|
||||
import 'package:ur/ur.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:ur/ur_decoder.dart';
|
||||
|
||||
part 'qr_tools_view_model.g.dart';
|
||||
|
||||
class QRToolsViewModel = QRToolsViewModelBase with _$QRToolsViewModel;
|
||||
|
||||
enum DevQrType {
|
||||
zpub,
|
||||
xpub,
|
||||
bbqr,
|
||||
bcur,
|
||||
unknown,
|
||||
}
|
||||
|
||||
abstract class QRToolsViewModelBase with Store {
|
||||
@observable
|
||||
String input = '';
|
||||
|
||||
|
||||
|
||||
@computed
|
||||
Map<String, dynamic> get data => _getResult(input);
|
||||
|
||||
Uint8List _decodeCBOR(Uint8List cbor) {
|
||||
final cborDecoder = CBORDecoder(cbor);
|
||||
final out = cborDecoder.decodeBytes();
|
||||
return out.$1;
|
||||
}
|
||||
|
||||
|
||||
Map<String, dynamic> _getResult(String input) {
|
||||
try {
|
||||
final decoder = URDecoder();
|
||||
for (var part in input.split('\n')) {
|
||||
part = part.toLowerCase();
|
||||
part = part.trim();
|
||||
if (part.startsWith('-')) {
|
||||
part = part.substring(1).trim();
|
||||
}
|
||||
if (!part.startsWith('ur:')) {
|
||||
continue;
|
||||
}
|
||||
decoder.receivePart(part);
|
||||
}
|
||||
return {
|
||||
"result": (decoder.result != null) ? switch (decoder.result.runtimeType) {
|
||||
UR => {
|
||||
"cbor": (decoder.result as UR).cbor,
|
||||
"_cbor.decode.base64": base64.encode(_decodeCBOR((decoder.result as UR).cbor)),
|
||||
"type": (decoder.result as UR).type,
|
||||
"toString": (decoder.result as UR).toString(),
|
||||
},
|
||||
_ => "unknown type: ${decoder.result.runtimeType}"
|
||||
} : null,
|
||||
"expectedType": decoder.expectedType,
|
||||
"estimatedPercentComplete": decoder.estimatedPercentComplete(),
|
||||
"processedPartsCount": decoder.processedPartsCount(),
|
||||
"expectedPartCount": decoder.expectedPartCount(),
|
||||
"isComplete": decoder.isComplete(),
|
||||
"isFailure": decoder.isFailure(),
|
||||
"isSuccess": decoder.isSuccess(),
|
||||
"lastPartIndexes": decoder.lastPartIndexes(),
|
||||
"receivedPartIndexes": decoder.receivedPartIndexes(),
|
||||
"resultError": decoder.resultError(),
|
||||
"resultMessage": decoder.resultMessage(),
|
||||
"toString": decoder.toString(),
|
||||
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
"error": e.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
import 'package:cw_core/hardware/hardware_wallet_service.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:bitbox_flutter/bitbox_flutter.dart' as sdk;
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'bitbox_view_model.g.dart';
|
||||
|
||||
class BitboxViewModel = BitboxViewModelBase with _$BitboxViewModel;
|
||||
|
||||
abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
|
||||
late final sdk.BitboxManager bitboxManager;
|
||||
|
||||
BitboxViewModelBase() {
|
||||
if (!Platform.isIOS && !isMoneroOnly) {
|
||||
bitboxManager = sdk.BitboxManager();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@observable
|
||||
bool isBleEnabled = false;
|
||||
|
||||
@override
|
||||
bool get hasBluetooth => Platform.isIOS && false; // TODO: remove when we enable bluetooth
|
||||
|
||||
@override
|
||||
Future<void> updateBleState() async {}
|
||||
|
||||
@override
|
||||
Stream<HardwareWalletDevice> scanForBleDevices() => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<List<HardwareWalletDevice>> getAllUsbDevices() => bitboxManager.devices
|
||||
.then((devices) => devices.map((d) => BitboxHardwareWalletDevice(d)).toList());
|
||||
|
||||
@override
|
||||
Future<void> stopScanning() async {}
|
||||
|
||||
@override
|
||||
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async {
|
||||
if (!(device is BitboxHardwareWalletDevice)) return false;
|
||||
if (_isConnecting) return false;
|
||||
_isConnecting = true;
|
||||
|
||||
try {
|
||||
final appDocDir = await getAppDir();
|
||||
|
||||
await bitboxManager.connect(device.device);
|
||||
printV("Got Connection", file: '${appDocDir.path}/error.txt');
|
||||
await bitboxManager.initBitBox();
|
||||
printV("Bitbox initialized!", file: '${appDocDir.path}/error.txt');
|
||||
await bitboxManager.channelHashVerify();
|
||||
printV("Bitbox channel-hash verified!", file: '${appDocDir.path}/error.txt');
|
||||
_isConnecting = false;
|
||||
return true;
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
}
|
||||
_isConnecting = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _isConnecting = false;
|
||||
|
||||
@override
|
||||
bool get isConnected => false;
|
||||
|
||||
@override
|
||||
HardwareWalletService getHardwareWalletService(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
return bitcoin!.getBitboxHardwareWalletService(bitboxManager, true);
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.getBitboxHardwareWalletService(bitboxManager, false);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.getBitboxHardwareWalletService(bitboxManager);
|
||||
case WalletType.polygon:
|
||||
return polygon!.getBitboxHardwareWalletService(bitboxManager);
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initWallet(WalletBase wallet) {
|
||||
switch (wallet.type) {
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
case WalletType.polygon:
|
||||
return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${wallet.type}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
|
||||
import 'package:cw_core/hardware/hardware_wallet_service.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
abstract class HardwareWalletViewModel {
|
||||
bool get isConnected;
|
||||
bool get isBleEnabled;
|
||||
bool get hasBluetooth;
|
||||
|
||||
Future<void> updateBleState();
|
||||
|
||||
Stream<HardwareWalletDevice> scanForBleDevices();
|
||||
|
||||
Future<List<HardwareWalletDevice>> getAllUsbDevices();
|
||||
|
||||
Future<void> stopScanning();
|
||||
|
||||
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type);
|
||||
|
||||
HardwareWalletService getHardwareWalletService(WalletType type);
|
||||
|
||||
void initWallet(WalletBase wallet);
|
||||
|
||||
String? interpretErrorCode(String error) => null;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/main.dart';
|
||||
@@ -9,10 +10,13 @@ import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
import 'package:cw_core/hardware/device_connection_type.dart';
|
||||
import 'package:cw_core/hardware/hardware_wallet_service.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk;
|
||||
@@ -23,15 +27,14 @@ part 'ledger_view_model.g.dart';
|
||||
|
||||
class LedgerViewModel = LedgerViewModelBase with _$LedgerViewModel;
|
||||
|
||||
abstract class LedgerViewModelBase with Store {
|
||||
// late final Ledger ledger;
|
||||
abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
|
||||
late final sdk.LedgerInterface ledgerPlusBLE;
|
||||
late final sdk.LedgerInterface ledgerPlusUSB;
|
||||
|
||||
bool get _doesSupportHardwareWallets {
|
||||
if (isMoneroOnly) {
|
||||
return DeviceConnectionType.supportedConnectionTypes(
|
||||
WalletType.monero, Platform.isIOS)
|
||||
WalletType.monero, HardwareWalletType.ledger, Platform.isIOS)
|
||||
.isNotEmpty;
|
||||
}
|
||||
|
||||
@@ -40,8 +43,8 @@ abstract class LedgerViewModelBase with Store {
|
||||
|
||||
LedgerViewModelBase() {
|
||||
if (_doesSupportHardwareWallets) {
|
||||
reaction((_) => bleIsEnabled, (_) {
|
||||
if (bleIsEnabled) _initBLE();
|
||||
reaction((_) => isBleEnabled, (_) {
|
||||
if (isBleEnabled) _initBLE();
|
||||
});
|
||||
updateBleState();
|
||||
|
||||
@@ -51,13 +54,17 @@ abstract class LedgerViewModelBase with Store {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@observable
|
||||
bool bleIsEnabled = false;
|
||||
bool isBleEnabled = false;
|
||||
|
||||
@override
|
||||
bool get hasBluetooth => true;
|
||||
|
||||
bool _bleIsInitialized = false;
|
||||
|
||||
Future<void> _initBLE() async {
|
||||
if (bleIsEnabled && !_bleIsInitialized) {
|
||||
if (isBleEnabled && !_bleIsInitialized) {
|
||||
ledgerPlusBLE = sdk.LedgerInterface.ble(
|
||||
onPermissionRequest: (_) async {
|
||||
if (Platform.isMacOS) return true;
|
||||
@@ -76,24 +83,32 @@ abstract class LedgerViewModelBase with Store {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBleState() async {
|
||||
final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState();
|
||||
|
||||
final newState = bleState == sdk.AvailabilityState.poweredOn;
|
||||
|
||||
if (newState != bleIsEnabled) bleIsEnabled = newState;
|
||||
if (newState != isBleEnabled) isBleEnabled = newState;
|
||||
}
|
||||
|
||||
Stream<sdk.LedgerDevice> scanForBleDevices() => ledgerPlusBLE.scan();
|
||||
@override
|
||||
Stream<HardwareWalletDevice> scanForBleDevices() =>
|
||||
ledgerPlusBLE.scan().map((d) => LedgerHardwareWalletDevice(d));
|
||||
|
||||
Stream<sdk.LedgerDevice> scanForUsbDevices() => ledgerPlusUSB.scan();
|
||||
@override
|
||||
Future<List<HardwareWalletDevice>> getAllUsbDevices() => ledgerPlusUSB.devices
|
||||
.then((devices) => devices.map((d) => LedgerHardwareWalletDevice(d)).toList());
|
||||
|
||||
@override
|
||||
Future<void> stopScanning() async {
|
||||
if (_bleIsInitialized) await ledgerPlusBLE.stopScanning();
|
||||
if (!Platform.isIOS) await ledgerPlusUSB.stopScanning();
|
||||
}
|
||||
|
||||
Future<bool> connectLedger(sdk.LedgerDevice device, WalletType type) async {
|
||||
@override
|
||||
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async {
|
||||
if (!(device is LedgerHardwareWalletDevice)) return false;
|
||||
if (_isConnecting) return false;
|
||||
_isConnecting = true;
|
||||
_connectingWalletType = type;
|
||||
@@ -103,18 +118,18 @@ abstract class LedgerViewModelBase with Store {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
final ledger = device.connectionType == sdk.ConnectionType.ble
|
||||
final ledger = device.connectionType == HardwareWalletConnectionType.ble
|
||||
? ledgerPlusBLE
|
||||
: ledgerPlusUSB;
|
||||
|
||||
if (_connectionChangeSubscription == null) {
|
||||
_connectionChangeSubscription = ledger
|
||||
.deviceStateChanges(device.id)
|
||||
.deviceStateChanges(device.device.id)
|
||||
.listen(_connectionChangeListener);
|
||||
}
|
||||
|
||||
try {
|
||||
_connection = await ledger.connect(device);
|
||||
_connection = await ledger.connect(device.device);
|
||||
_isConnecting = false;
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -140,6 +155,7 @@ abstract class LedgerViewModelBase with Store {
|
||||
Routes.connectDevices,
|
||||
arguments: ConnectDevicePageParams(
|
||||
walletType: WalletType.monero,
|
||||
hardwareWalletType: HardwareWalletType.ledger,
|
||||
allowChangeWallet: true,
|
||||
isReconnect: true,
|
||||
onConnectDevice: (context, ledgerVM) async {
|
||||
@@ -153,26 +169,47 @@ abstract class LedgerViewModelBase with Store {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isConnected => _connection != null && !(_connection!.isDisconnected);
|
||||
|
||||
sdk.LedgerConnection get connection => _connection!;
|
||||
|
||||
void setLedger(WalletBase wallet) {
|
||||
@override
|
||||
void initWallet(WalletBase wallet) {
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
return monero!.setLedgerConnection(wallet, connection);
|
||||
case WalletType.bitcoin:
|
||||
return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.setLedgerConnection(wallet, connection);
|
||||
return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.setLedgerConnection(wallet, connection);
|
||||
return ethereum!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
case WalletType.polygon:
|
||||
return polygon!.setLedgerConnection(wallet, connection);
|
||||
return polygon!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${wallet.type}');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
HardwareWalletService getHardwareWalletService(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
return bitcoin!.getLedgerHardwareWalletService(connection, true);
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.getLedgerHardwareWalletService(connection, false);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.getLedgerHardwareWalletService(connection);
|
||||
case WalletType.polygon:
|
||||
return polygon!.getLedgerHardwareWalletService(connection);
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
String? interpretErrorCode(String error) {
|
||||
if (error.contains("Make sure no other program is communicating with the Ledger")) {
|
||||
return error;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
|
||||
import 'package:cw_core/utils/zpub.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
class RestoredWallet {
|
||||
@@ -44,6 +45,9 @@ class RestoredWallet {
|
||||
} catch (e) {
|
||||
// fine, we don't care, it is only for monero anyway
|
||||
}
|
||||
if (json['zpub'] != null) {
|
||||
json['xpub'] = convertZpubToXpub(json['zpub'] as String);
|
||||
}
|
||||
json['view_key'] ??= json['xpub'];
|
||||
final height = json['height'] as String?;
|
||||
return RestoredWallet(
|
||||
|
||||
@@ -58,6 +58,7 @@ class WalletRestoreFromQRCode {
|
||||
final extracted = sortedKeys.firstWhereOrNull((key) => code.toLowerCase().contains(key));
|
||||
|
||||
if (code.startsWith("xpub")) return WalletType.bitcoin;
|
||||
if (code.startsWith("zpub")) return WalletType.bitcoin;
|
||||
|
||||
if (extracted == null) {
|
||||
// Special case for view-only monero wallet
|
||||
@@ -109,9 +110,11 @@ class WalletRestoreFromQRCode {
|
||||
if (code == null) throw Exception("Unexpected scan QR code value: aborted");
|
||||
if (code.isEmpty) throw Exception('Unexpected scan QR code value: value is empty');
|
||||
|
||||
if (code.startsWith("[")) code = code.substring(code.indexOf("]") + 1);
|
||||
|
||||
String formattedUri = '';
|
||||
WalletType? walletType = _extractWalletType(code);
|
||||
|
||||
final prefix = code.startsWith('xpub') ? 'xpub' : code.startsWith('zpub') ? 'zpub' : '????';
|
||||
if (walletType == null) {
|
||||
await _specifyWalletAssets(context, "Can't determine wallet type, please pick it manually");
|
||||
walletType =
|
||||
@@ -122,14 +125,14 @@ class WalletRestoreFromQRCode {
|
||||
|
||||
formattedUri = seedPhrase != null
|
||||
? '$walletType:?seed=$seedPhrase'
|
||||
: code.startsWith('xpub')
|
||||
? '$walletType:?xpub=$code'
|
||||
: code.startsWith(prefix)
|
||||
? '$walletType:?$prefix=$code'
|
||||
: throw Exception('Failed to determine valid seed phrase');
|
||||
} else {
|
||||
final index = code.indexOf(':');
|
||||
final query = code.substring(index + 1).replaceAll('?', '&');
|
||||
formattedUri = code.startsWith('xpub')
|
||||
? '$walletType:?xpub=$code'
|
||||
formattedUri = code.startsWith(prefix)
|
||||
? '$walletType:?$prefix=$code'
|
||||
:'$walletType:?$query';
|
||||
}
|
||||
|
||||
@@ -167,7 +170,8 @@ class WalletRestoreFromQRCode {
|
||||
throw Exception('Unexpected restore mode: tx_payment_id is invalid');
|
||||
}
|
||||
|
||||
if (credentials.containsKey("xpub")) {
|
||||
if (credentials.containsKey("xpub") ||
|
||||
credentials.containsKey("zpub")) {
|
||||
return WalletRestoreMode.keys;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
@@ -93,7 +94,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
this.balanceViewModel,
|
||||
this.contactListViewModel,
|
||||
this.transactionDescriptionBox,
|
||||
this.ledgerViewModel,
|
||||
this.hardwareWalletViewModel,
|
||||
this.unspentCoinsListViewModel,
|
||||
this.feesViewModel,
|
||||
this.walletInfoSource, {
|
||||
@@ -368,7 +369,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
final SendTemplateViewModel sendTemplateViewModel;
|
||||
final BalanceViewModel balanceViewModel;
|
||||
final ContactListViewModel contactListViewModel;
|
||||
final LedgerViewModel? ledgerViewModel;
|
||||
final HardwareWalletViewModel? hardwareWalletViewModel;
|
||||
final FeesViewModel feesViewModel;
|
||||
final FiatConversionStore _fiatConversationStore;
|
||||
final Box<TransactionDescription> transactionDescriptionBox;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/provider_types.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/utils/package_info.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
// import 'package:package_info/package_info.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
@@ -58,20 +56,28 @@ abstract class OtherSettingsViewModelBase with Store {
|
||||
|
||||
@computed
|
||||
bool get changeRepresentativeEnabled =>
|
||||
_wallet.type == WalletType.nano || _wallet.type == WalletType.banano;
|
||||
[WalletType.nano, WalletType.banano].contains(_wallet.type);
|
||||
|
||||
@computed
|
||||
bool get displayTransactionPriority => !(changeRepresentativeEnabled ||
|
||||
_wallet.type == WalletType.solana ||
|
||||
_wallet.type == WalletType.tron);
|
||||
bool get changeHardwareWalletTypeEnabled => [
|
||||
HardwareWalletType.cupcake,
|
||||
HardwareWalletType.coldcard,
|
||||
HardwareWalletType.seedsigner,
|
||||
].contains(_wallet.hardwareWalletType);
|
||||
|
||||
@computed
|
||||
bool get displayTransactionPriority =>
|
||||
!(changeRepresentativeEnabled || [WalletType.solana, WalletType.tron].contains(_wallet.type));
|
||||
|
||||
String getDisplayPriority(dynamic priority) {
|
||||
final _priority = priority as TransactionPriority;
|
||||
|
||||
if (_wallet.type == WalletType.bitcoin ||
|
||||
_wallet.type == WalletType.litecoin ||
|
||||
_wallet.type == WalletType.bitcoinCash ||
|
||||
_wallet.type == WalletType.dogecoin) {
|
||||
if ([
|
||||
WalletType.bitcoin,
|
||||
WalletType.litecoin,
|
||||
WalletType.bitcoinCash,
|
||||
WalletType.dogecoin,
|
||||
].contains(_wallet.type)) {
|
||||
final rate = bitcoin!.getFeeRate(_wallet, _priority);
|
||||
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate);
|
||||
}
|
||||
@@ -82,10 +88,12 @@ abstract class OtherSettingsViewModelBase with Store {
|
||||
String getDisplayBitcoinPriority(dynamic priority, int customValue) {
|
||||
final _priority = priority as TransactionPriority;
|
||||
|
||||
if (_wallet.type == WalletType.bitcoin ||
|
||||
_wallet.type == WalletType.litecoin ||
|
||||
_wallet.type == WalletType.bitcoinCash ||
|
||||
_wallet.type == WalletType.dogecoin) {
|
||||
if ([
|
||||
WalletType.bitcoin,
|
||||
WalletType.litecoin,
|
||||
WalletType.bitcoinCash,
|
||||
WalletType.dogecoin,
|
||||
].contains(_wallet.type)) {
|
||||
final rate = bitcoin!.getFeeRate(_wallet, _priority);
|
||||
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate,
|
||||
customRate: customValue);
|
||||
@@ -105,6 +113,12 @@ abstract class OtherSettingsViewModelBase with Store {
|
||||
_settingsStore.priority[_wallet.type] = priority;
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> onHardwareWalletTypeChanged(HardwareWalletType hwType) async {
|
||||
_wallet.walletInfo.hardwareWalletType = hwType;
|
||||
return _wallet.walletInfo.save();
|
||||
}
|
||||
|
||||
@computed
|
||||
double get customBitcoinFeeRate =>
|
||||
_settingsStore.customBitcoinFeeRate.toDouble();
|
||||
|
||||
@@ -473,7 +473,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
|
||||
|
||||
@computed
|
||||
bool get isCupcake =>
|
||||
bool get isBitcoinViewOnly =>
|
||||
wallet.type == WalletType.bitcoin &&
|
||||
(bitcoin!.getWalletKeys(wallet)["privateKey"] ?? "").isEmpty;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
|
||||
@@ -24,12 +25,12 @@ class WalletHardwareRestoreViewModel = WalletHardwareRestoreViewModelBase
|
||||
with _$WalletHardwareRestoreViewModel;
|
||||
|
||||
abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with Store {
|
||||
final LedgerViewModel ledgerViewModel;
|
||||
final HardwareWalletViewModel hardwareWalletVM;
|
||||
|
||||
int _nextIndex = 0;
|
||||
|
||||
WalletHardwareRestoreViewModelBase(
|
||||
this.ledgerViewModel,
|
||||
this.hardwareWalletVM,
|
||||
AppStore appStore,
|
||||
WalletCreationService walletCreationService,
|
||||
Box<WalletInfo> walletInfoSource,
|
||||
@@ -56,33 +57,16 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
|
||||
@action
|
||||
Future<void> getNextAvailableAccounts(int limit) async {
|
||||
try {
|
||||
List<HardwareAccountData> accounts;
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
accounts = await bitcoin!
|
||||
.getHardwareWalletBitcoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
|
||||
break;
|
||||
case WalletType.litecoin:
|
||||
accounts = await bitcoin!
|
||||
.getHardwareWalletLitecoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
accounts = await ethereum!
|
||||
.getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
|
||||
break;
|
||||
case WalletType.polygon:
|
||||
accounts = await polygon!
|
||||
.getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
List<HardwareAccountData> accounts = await hardwareWalletVM
|
||||
.getHardwareWalletService(type)
|
||||
.getAvailableAccounts(index: _nextIndex, limit: limit);
|
||||
|
||||
availableAccounts.addAll(accounts);
|
||||
_nextIndex += limit;
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
error = ledgerViewModel.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error;
|
||||
error =
|
||||
hardwareWalletVM.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error;
|
||||
}
|
||||
|
||||
isLoadingMoreAccounts = false;
|
||||
@@ -110,7 +94,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
|
||||
|
||||
credentials = monero!.createMoneroRestoreWalletFromHardwareCredentials(
|
||||
name: name,
|
||||
ledgerConnection: ledgerViewModel.connection,
|
||||
ledgerConnection: (hardwareWalletVM as LedgerViewModel).connection,
|
||||
password: password,
|
||||
height: _options['height'] as int? ?? 0,
|
||||
);
|
||||
@@ -118,7 +102,10 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
|
||||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
}
|
||||
|
||||
credentials.hardwareWalletType = HardwareWalletType.ledger;
|
||||
if (hardwareWalletVM is LedgerViewModel)
|
||||
credentials.hardwareWalletType = HardwareWalletType.ledger;
|
||||
else
|
||||
credentials.hardwareWalletType = HardwareWalletType.bitbox;
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreVi
|
||||
abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
|
||||
Box<WalletInfo> walletInfoSource, SeedSettingsViewModel seedSettingsViewModel,
|
||||
{required WalletType type, this.restoredWallet})
|
||||
{required WalletType type, this.restoredWallet, this.hardwareWalletType})
|
||||
: isButtonEnabled = restoredWallet != null,
|
||||
hasPassphrase = false,
|
||||
mode = restoredWallet?.restoreMode ?? WalletRestoreMode.seed,
|
||||
@@ -102,6 +102,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||
].contains(type);
|
||||
|
||||
final RestoredWallet? restoredWallet;
|
||||
final HardwareWalletType? hardwareWalletType;
|
||||
|
||||
@observable
|
||||
WalletRestoreMode mode;
|
||||
@@ -226,6 +227,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||
name: name,
|
||||
password: password,
|
||||
xpub: viewKey!,
|
||||
hardwareWalletType: hardwareWalletType,
|
||||
);
|
||||
|
||||
case WalletType.monero:
|
||||
|
||||
Reference in New Issue
Block a user