mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
CW-1581-trezor-fixes-enhancements (#3462)
* feat: add Trezor auto connect * chore: update `trezor_flutter` * fix: correct method name for retrieving Trezor auto-pairing credentials * fix: update device connection prompt text across all languages [skip ci] * feat: add Trezor key image synchronization ui and improve hardware wallet UX * feat: add sync-balance icon asset for settings row visuals [skip ci] * feat: add sync-balance icon asset for settings row visuals [skip ci] * fix: remove unused Monero and sync key images settings actions from menus [skip ci] * fix: re-enable routing for UR QR Animated Page [skip ci]
This commit is contained in:
+3
-1
@@ -434,7 +434,9 @@ Future<void> setup({
|
||||
() => TrezorConnect("cakewallet://trezor_connect", appName: "Cake Wallet"),
|
||||
);
|
||||
|
||||
getIt.registerLazySingleton(() => TrezorConnectViewModel(getIt<TrezorConnect>()));
|
||||
getIt.registerLazySingleton(
|
||||
() => TrezorConnectViewModel(getIt<TrezorConnect>(), getIt<SecureStorage>()),
|
||||
);
|
||||
|
||||
getIt.registerFactory<KeyService>(() => KeyService(getIt.get<SecureStorage>()));
|
||||
|
||||
|
||||
@@ -420,6 +420,12 @@ class CWMonero extends Monero {
|
||||
return moneroWallet.needExportOutputs(amount);
|
||||
}
|
||||
|
||||
@override
|
||||
bool hasUnknownKeyImages(Object wallet) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
return moneroWallet.hasUnknownKeyImages();
|
||||
}
|
||||
|
||||
@override
|
||||
void monerocCheck() => checkIfMoneroCIsFine();
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import "package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_sheet
|
||||
import "package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/modern_button.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/new_future_primary_button.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/picker.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
@@ -42,7 +43,6 @@ import "package:cake_wallet/src/widgets/bottom_sheet/token_selection_bottom_shee
|
||||
import "package:cake_wallet/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/new_list_row/list_item_regular_row_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/primary_button.dart";
|
||||
import "package:cake_wallet/src/widgets/standard_checkbox.dart";
|
||||
import "package:cake_wallet/store/app_store.dart";
|
||||
import "package:cake_wallet/utils/payment_request.dart";
|
||||
@@ -618,9 +618,9 @@ class _NewSendPageState extends State<NewSendPage> {
|
||||
selectedDot: _selectedOutput,
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
builder: (_) => NewFuturePrimaryButton(
|
||||
key: const ValueKey("send_page_send_button_key"),
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
//Request dummy node to get the focus out of the text fields
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
|
||||
@@ -629,11 +629,11 @@ class _NewSendPageState extends State<NewSendPage> {
|
||||
}
|
||||
|
||||
if (widget.mode == SendPageModes.normal) {
|
||||
_handleSend();
|
||||
await _handleSend();
|
||||
} else if (widget.mode ==
|
||||
SendPageModes.lightningDeposit ||
|
||||
widget.mode == SendPageModes.mwebDeposit) {
|
||||
Navigator.of(context).push(
|
||||
await Navigator.of(context).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => Material(
|
||||
child: L2ActionWalletSelector(
|
||||
@@ -652,7 +652,7 @@ class _NewSendPageState extends State<NewSendPage> {
|
||||
} else if (widget.mode ==
|
||||
SendPageModes.lightningWithdrawal ||
|
||||
widget.mode == SendPageModes.mwebWithdrawal) {
|
||||
Navigator.of(context).push(
|
||||
await Navigator.of(context).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => Material(
|
||||
child: L2ActionWalletSelector(
|
||||
@@ -673,13 +673,7 @@ class _NewSendPageState extends State<NewSendPage> {
|
||||
text: S.of(context).continue_text,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
isLoading: widget.sendViewModel.state is IsExecutingState ||
|
||||
widget.sendViewModel.state is TransactionCommitting ||
|
||||
widget.sendViewModel.state
|
||||
is IsAwaitingDeviceResponseState ||
|
||||
widget.sendViewModel.state
|
||||
is LoadingTemplateExecutingState,
|
||||
isDisabled: !widget.sendViewModel.isReadyForSend ||
|
||||
disabled: !widget.sendViewModel.isReadyForSend ||
|
||||
widget.sendViewModel.state is ExecutedSuccessfullyState,
|
||||
),
|
||||
),
|
||||
@@ -858,17 +852,11 @@ class _NewSendPageState extends State<NewSendPage> {
|
||||
for (final item in widget.sendViewModel.outputs) {
|
||||
amount += item.cryptoAmountMoney;
|
||||
}
|
||||
if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) {
|
||||
if (widget.sendViewModel.wallet.hardwareWalletType == HardwareWalletType.trezor) {
|
||||
await monero!.syncTrezor(widget.sendViewModel.wallet);
|
||||
} else {
|
||||
if (mounted) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
Routes.urqrAnimatedPage,
|
||||
arguments: monero!.exportOutputsUR(widget.sendViewModel.wallet),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (monero!.hasUnknownKeyImages(widget.sendViewModel.wallet) && mounted) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
Routes.syncKeyImagesDevices,
|
||||
arguments: monero!.exportOutputsUR(widget.sendViewModel.wallet),
|
||||
);
|
||||
await Future.delayed(const Duration(seconds: 1)); // wait for monero to refresh the state
|
||||
}
|
||||
if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) {
|
||||
|
||||
@@ -9,10 +9,9 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.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_svg/svg.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
bool _trueFunc(DashboardViewModel _) => true;
|
||||
|
||||
@@ -26,10 +25,21 @@ bool _hasMweb(DashboardViewModel vm) => vm.hasMweb;
|
||||
|
||||
bool _hasWalletConnect(DashboardViewModel vm) => vm.hasWalletConnect;
|
||||
|
||||
bool _isCupcake(DashboardViewModel vm) =>
|
||||
vm.wallet.hardwareWalletType == HardwareWalletType.cupcake;
|
||||
bool _requiresKeyImageSync(DashboardViewModel vm) =>
|
||||
vm.wallet.type == WalletType.monero &&
|
||||
[HardwareWalletType.cupcake, HardwareWalletType.trezor].contains(vm.wallet.hardwareWalletType);
|
||||
|
||||
class SettingsListItem {
|
||||
const SettingsListItem(
|
||||
this.iconPath,
|
||||
this.title,
|
||||
this.route, {
|
||||
this.requireAuth = false,
|
||||
this.use2fa = _falseFunc,
|
||||
this.condition = _trueFunc,
|
||||
this.routeArgs,
|
||||
});
|
||||
|
||||
final String iconPath;
|
||||
final String title;
|
||||
final String route;
|
||||
@@ -37,21 +47,15 @@ class SettingsListItem {
|
||||
final bool requireAuth;
|
||||
final bool Function(DashboardViewModel) use2fa;
|
||||
final bool Function(DashboardViewModel) condition;
|
||||
|
||||
const SettingsListItem(this.iconPath, this.title, this.route,
|
||||
{this.routeArgs = null,
|
||||
this.requireAuth = false,
|
||||
this.use2fa = _falseFunc,
|
||||
this.condition = _trueFunc});
|
||||
}
|
||||
|
||||
class SettingsSectionData {
|
||||
const SettingsSectionData(this.title, this.titleIconPath, this.items);
|
||||
|
||||
final String title;
|
||||
final String titleIconPath;
|
||||
final List<SettingsListItem> items;
|
||||
|
||||
const SettingsSectionData(this.title, this.titleIconPath, this.items);
|
||||
|
||||
static SettingsSectionData walletSettings =
|
||||
SettingsSectionData(S.current.wallet_settings, "assets/new-ui/wallet-setting.svg", [
|
||||
SettingsListItem(
|
||||
@@ -71,9 +75,13 @@ class SettingsSectionData {
|
||||
condition: _hasWalletConnect),
|
||||
//SettingsListItem("assets/new-ui/settings_row_icons/silent-payments.svg", S.current.silent_payments_settings, Routes.silentPaymentsSettings, condition: _isBtc),
|
||||
//SettingsListItem("assets/new-ui/settings_row_icons/mweb.svg", S.current.litecoin_mweb_settings, Routes.mwebSettings, condition: _hasMweb),
|
||||
SettingsListItem("assets/new-ui/settings_row_icons/cupcake.svg", S.current.export_outputs,
|
||||
Routes.urqrAnimatedPage,
|
||||
routeArgs: {'export-outputs': 'export-outputs'}, condition: _isCupcake),
|
||||
SettingsListItem(
|
||||
"assets/new-ui/settings_row_icons/sync-balance.svg",
|
||||
S.current.resync_device,
|
||||
Routes.syncKeyImagesDevices,
|
||||
routeArgs: {'export-outputs': 'export-outputs'},
|
||||
condition: _requiresKeyImageSync,
|
||||
),
|
||||
SettingsListItem(
|
||||
"assets/new-ui/settings_row_icons/other.svg", S.current.other, Routes.otherSettingsPage),
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/send_page/directional_switcher.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_two_actions.dart";
|
||||
import "package:cake_wallet/src/widgets/base_alert_dialog.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/primary_button.dart";
|
||||
import "package:cake_wallet/store/app_store.dart";
|
||||
import "package:cake_wallet/utils/show_pop_up.dart";
|
||||
import "package:cake_wallet/view_model/hardware_wallet/trezor_connect_view_model.dart";
|
||||
import "package:cw_core/wallet_base.dart";
|
||||
import "package:cw_core/wallet_info.dart";
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class SyncKeyImagesSheet extends StatefulWidget {
|
||||
const SyncKeyImagesSheet({
|
||||
required this.appStore,
|
||||
required this.trezorConnectVM,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final AppStore appStore;
|
||||
final TrezorConnectViewModelBase trezorConnectVM;
|
||||
|
||||
WalletBase get wallet => appStore.wallet!;
|
||||
|
||||
@override
|
||||
State<SyncKeyImagesSheet> createState() => _HardwareWalletProceedOnDeviceSheetState();
|
||||
}
|
||||
|
||||
class _HardwareWalletProceedOnDeviceSheetState extends State<SyncKeyImagesSheet> {
|
||||
_KeyImageSyncState _state = _KeyImageSyncState.initial;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => PopScope(
|
||||
canPop: false,
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).resync_device,
|
||||
leadingIcon: Icon(
|
||||
Icons.close,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
onLeadingPressed: () => showPopUp(
|
||||
context: context,
|
||||
builder: (context) => AlertWithTwoActions(
|
||||
alertTitle: S.of(context).are_you_sure_exit,
|
||||
alertContent: S.of(context).resync_device_cancel_warning_desc,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
rightButtonText: S.of(context).yes_exit,
|
||||
rightAlertButtonStyle: AlertButtonStyle.error(context),
|
||||
actionLeftButton: Navigator.of(context).pop,
|
||||
actionRightButton: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: hardwareWalletIcon,
|
||||
width: 100,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
DirectionalAnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
child: content,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: AnimatedOpacity(
|
||||
curve: Curves.easeOutQuad,
|
||||
opacity: _state == _KeyImageSyncState.initial ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: PrimaryButton(
|
||||
text: S.of(context).continue_text,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
onPressed: _onContinuePressed,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget get content {
|
||||
if (_state == _KeyImageSyncState.syncing) {
|
||||
return _syncingKeyImages();
|
||||
}
|
||||
|
||||
return _initial();
|
||||
}
|
||||
|
||||
String? get hardwareWalletIcon {
|
||||
switch (widget.wallet.hardwareWalletType) {
|
||||
case HardwareWalletType.bitbox:
|
||||
return "assets/new-ui/hardware_wallets/device_bitbox.svg";
|
||||
case HardwareWalletType.ledger:
|
||||
return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
|
||||
case HardwareWalletType.trezor:
|
||||
return "assets/new-ui/hardware_wallets/device_trezor_safe_7.svg";
|
||||
case HardwareWalletType.cupcake:
|
||||
return "assets/images/cupcake.svg";
|
||||
case HardwareWalletType.coldcard:
|
||||
case HardwareWalletType.seedsigner:
|
||||
case HardwareWalletType.keystone:
|
||||
case null:
|
||||
return "assets/images/hardware_wallet/device_qr.svg";
|
||||
}
|
||||
}
|
||||
|
||||
Widget _initial() => Column(
|
||||
key: const ValueKey(0),
|
||||
spacing: 12,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
S.of(context).resync_device_desc,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _syncingKeyImages() => Column(
|
||||
key: const ValueKey(1),
|
||||
spacing: 12,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).proceed_on_device,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
S.of(context).proceed_on_device_description,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
Future<void> _onContinuePressed() async {
|
||||
setState(() => _state = _KeyImageSyncState.syncing);
|
||||
|
||||
if (!widget.trezorConnectVM.isConnected(widget.wallet.type)) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
Routes.connectDevices,
|
||||
arguments: ConnectDevicePageParams(
|
||||
walletType: widget.wallet.type,
|
||||
hardwareWalletType: widget.wallet.walletInfo.hardwareWalletType!,
|
||||
onConnectDevice: (_, __) {
|
||||
widget.trezorConnectVM.initWallet(widget.wallet);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
isReconnect: false,
|
||||
),
|
||||
);
|
||||
|
||||
// Recheck to handle tap-backs
|
||||
if (!widget.trezorConnectVM.isConnected(widget.wallet.type)) {
|
||||
setState(() => _state = _KeyImageSyncState.initial);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
await widget.trezorConnectVM.initWallet(widget.wallet);
|
||||
}
|
||||
|
||||
final result = await widget.trezorConnectVM.syncKeyImages(widget.wallet);
|
||||
if (result && context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum _KeyImageSyncState {
|
||||
initial,
|
||||
syncing;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_svg/flutter_svg.dart";
|
||||
|
||||
class NewFuturePrimaryButton extends StatefulWidget {
|
||||
const NewFuturePrimaryButton({
|
||||
required this.onPressed,
|
||||
required this.text,
|
||||
required this.color,
|
||||
required this.textColor,
|
||||
this.borderColor = Colors.transparent,
|
||||
this.disabled = false,
|
||||
this.image,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Future<void> Function() onPressed;
|
||||
final bool disabled;
|
||||
final SvgPicture? image;
|
||||
final Color color;
|
||||
final Color textColor;
|
||||
final Color borderColor;
|
||||
final String text;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _NewFuturePrimaryButtonState();
|
||||
}
|
||||
|
||||
class _NewFuturePrimaryButtonState extends State<NewFuturePrimaryButton> {
|
||||
bool isLoading = false;
|
||||
|
||||
Future<void> _onPressed() async {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
setState(() => isLoading = true);
|
||||
try {
|
||||
await widget.onPressed.call();
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => isLoading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => NewPrimaryButton(
|
||||
onPressed: _onPressed,
|
||||
image: widget.image,
|
||||
text: widget.text,
|
||||
color: widget.color,
|
||||
textColor: widget.textColor,
|
||||
isLoading: isLoading,
|
||||
borderColor: widget.borderColor,
|
||||
disabled: widget.disabled,
|
||||
);
|
||||
}
|
||||
+16
-3
@@ -4,7 +4,6 @@ import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
||||
import 'package:cake_wallet/core/new_wallet_arguments.dart';
|
||||
import 'package:cake_wallet/new-ui/new_dashboard.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/about_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_confirm_sheet.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_history_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_network_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_receiving_wallet_page.dart';
|
||||
@@ -13,6 +12,7 @@ import 'package:cake_wallet/new-ui/pages/addresses_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/lightning_username_page.dart';
|
||||
import "package:cake_wallet/new-ui/pages/receive_page.dart";
|
||||
import 'package:cake_wallet/new-ui/pages/send_page.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/hardware_wallet/sync_key_images_sheet.dart';
|
||||
import 'package:cake_wallet/order/order.dart';
|
||||
import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
|
||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||
@@ -51,6 +51,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/store/app_store.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/qr_tools_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart';
|
||||
@@ -126,7 +127,6 @@ import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/ur/animated_ur_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
|
||||
@@ -146,7 +146,7 @@ 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/hardware_wallet/trezor_connect_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';
|
||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||
@@ -908,6 +908,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
case Routes.setup2faInfoPage:
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<Setup2FAInfoPage>());
|
||||
|
||||
case Routes.syncKeyImagesDevices:
|
||||
final hardwareWalletType = getIt<AppStore>().wallet!.hardwareWalletType;
|
||||
if (hardwareWalletType == HardwareWalletType.trezor) {
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => SyncKeyImagesSheet(
|
||||
appStore: getIt<AppStore>(),
|
||||
trezorConnectVM: getIt<TrezorConnectViewModel>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<AnimatedURPage>(param1: settings.arguments));
|
||||
|
||||
case Routes.urqrAnimatedPage:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<AnimatedURPage>(param1: settings.arguments));
|
||||
|
||||
@@ -133,6 +133,7 @@ class Routes {
|
||||
|
||||
static const signPage = '/sign_page';
|
||||
static const connectDevices = '/device/connect';
|
||||
static const syncKeyImagesDevices = '/device/sync_key_images';
|
||||
static const urqrAnimatedPage = '/urqr/animated_page';
|
||||
static const walletGroupsDisplayPage = '/wallet_groups_display_page';
|
||||
static const walletGroupDescription = '/wallet_group_description';
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import "dart:async";
|
||||
import "dart:convert";
|
||||
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/evm/evm.dart';
|
||||
import 'package:cake_wallet/main.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.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:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:trezor_connect/trezor_connect.dart' as connect_sdk;
|
||||
import 'package:trezor_flutter/trezor_flutter.dart' as sdk;
|
||||
import "package:cake_wallet/bitcoin/bitcoin.dart";
|
||||
import "package:cake_wallet/core/secure_storage.dart";
|
||||
import "package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart";
|
||||
import "package:cake_wallet/evm/evm.dart";
|
||||
import "package:cake_wallet/main.dart";
|
||||
import "package:cake_wallet/monero/monero.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/hardware_wallet/proceed_on_device_sheet.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/encryption_file_utils.dart";
|
||||
import "package:cw_core/hardware/device_connection_type.dart";
|
||||
import "package:cw_core/hardware/hardware_wallet_service.dart";
|
||||
import "package:cw_core/key.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_info.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:device_info_plus/device_info_plus.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:mobx/mobx.dart";
|
||||
import "package:permission_handler/permission_handler.dart";
|
||||
import "package:trezor_connect/trezor_connect.dart" as connect_sdk;
|
||||
import "package:trezor_flutter/trezor_flutter.dart" as sdk;
|
||||
|
||||
part 'trezor_connect_view_model.g.dart';
|
||||
part "trezor_connect_view_model.g.dart";
|
||||
|
||||
class TrezorConnectViewModel = TrezorConnectViewModelBase with _$TrezorConnectViewModel;
|
||||
|
||||
abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with Store {
|
||||
final connect_sdk.TrezorConnect trezorConnect;
|
||||
|
||||
late final sdk.TrezorInterface trezorBLE;
|
||||
late final sdk.TrezorInterface trezorUSB;
|
||||
|
||||
sdk.ThpState _state = sdk.ThpState();
|
||||
|
||||
TrezorConnectViewModelBase(this.trezorConnect) {
|
||||
TrezorConnectViewModelBase(this.trezorConnect, this._secureStorage) {
|
||||
if (_doesSupportHardwareWallets) {
|
||||
reaction((_) => isBleEnabled, (_) {
|
||||
if (isBleEnabled) _initBLE();
|
||||
if (isBleEnabled) {
|
||||
_initBLE();
|
||||
}
|
||||
});
|
||||
updateBleState();
|
||||
|
||||
@@ -47,11 +47,21 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
}
|
||||
}
|
||||
|
||||
final connect_sdk.TrezorConnect trezorConnect;
|
||||
final SecureStorage _secureStorage;
|
||||
|
||||
late final sdk.TrezorInterface trezorBLE;
|
||||
late final sdk.TrezorInterface trezorUSB;
|
||||
|
||||
sdk.ThpState? _state;
|
||||
|
||||
bool get _doesSupportHardwareWallets {
|
||||
if (isMoneroOnly) {
|
||||
return DeviceConnectionType.supportedConnectionTypes(
|
||||
WalletType.monero, HardwareWalletType.trezor, Platform.isIOS)
|
||||
.isNotEmpty;
|
||||
WalletType.monero,
|
||||
HardwareWalletType.trezor,
|
||||
Platform.isIOS,
|
||||
).isNotEmpty;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -63,9 +73,11 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
if (isBleEnabled && !_bleIsInitialized) {
|
||||
trezorBLE = sdk.TrezorInterface.ble(
|
||||
onPermissionRequest: (_) async {
|
||||
if (Platform.isMacOS) return true;
|
||||
if (Platform.isMacOS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Map<Permission, PermissionStatus> statuses = await [
|
||||
final Map<Permission, PermissionStatus> statuses = await [
|
||||
Permission.bluetoothScan,
|
||||
Permission.bluetoothConnect,
|
||||
Permission.bluetoothAdvertise,
|
||||
@@ -73,7 +85,7 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
|
||||
return statuses.values.where((status) => status.isDenied).isEmpty;
|
||||
},
|
||||
bleOptions: sdk.BluetoothOptions(maxScanDuration: Duration(minutes: 5)),
|
||||
bleOptions: sdk.BluetoothOptions(maxScanDuration: const Duration(minutes: 5)),
|
||||
);
|
||||
_bleIsInitialized = true;
|
||||
}
|
||||
@@ -99,21 +111,27 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
|
||||
final newState = bleState == sdk.AvailabilityState.poweredOn;
|
||||
|
||||
if (newState != isBleEnabled) isBleEnabled = newState;
|
||||
if (newState != isBleEnabled) {
|
||||
isBleEnabled = newState;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<HardwareWalletDevice> scanForBleDevices() =>
|
||||
trezorBLE.scan().map((d) => TrezorHardwareWalletDevice(d));
|
||||
trezorBLE.scan().map(TrezorHardwareWalletDevice.new);
|
||||
|
||||
@override
|
||||
Future<List<HardwareWalletDevice>> getAllUsbDevices() => trezorUSB.devices
|
||||
.then((devices) => devices.map((d) => TrezorHardwareWalletDevice(d)).toList());
|
||||
Future<List<HardwareWalletDevice>> getAllUsbDevices() =>
|
||||
trezorUSB.devices.then((devices) => devices.map(TrezorHardwareWalletDevice.new).toList());
|
||||
|
||||
@override
|
||||
Future<void> stopScanning() async {
|
||||
if (_bleIsInitialized) await trezorBLE.stopScanning();
|
||||
if (!Platform.isIOS) await trezorUSB.stopScanning();
|
||||
if (_bleIsInitialized) {
|
||||
await trezorBLE.stopScanning();
|
||||
}
|
||||
if (!Platform.isIOS) {
|
||||
await trezorUSB.stopScanning();
|
||||
}
|
||||
}
|
||||
|
||||
sdk.TrezorClient? _client;
|
||||
@@ -127,10 +145,17 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
|
||||
@override
|
||||
@action
|
||||
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type,
|
||||
[bool isRetry = false]) async {
|
||||
if (!(device is TrezorHardwareWalletDevice)) return false;
|
||||
if (isConnecting) return false;
|
||||
Future<bool> connectDevice(
|
||||
HardwareWalletDevice device,
|
||||
WalletType type, [
|
||||
bool isRetry = false,
|
||||
]) async {
|
||||
if (device is! TrezorHardwareWalletDevice) {
|
||||
return false;
|
||||
}
|
||||
if (isConnecting) {
|
||||
return false;
|
||||
}
|
||||
isConnecting = true;
|
||||
paringState = TrezorParingState.initial;
|
||||
|
||||
@@ -140,16 +165,19 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
final connection = await trezorInterface.connect(device.device);
|
||||
|
||||
if (!isRetry) {
|
||||
showModalBottomSheet(
|
||||
context: navigatorKey.currentContext!,
|
||||
isScrollControlled: true,
|
||||
isDismissible: false,
|
||||
enableDrag: false,
|
||||
useSafeArea: true,
|
||||
builder: (context) => HardwareWalletProceedOnDeviceSheet(
|
||||
unawaited(
|
||||
showModalBottomSheet(
|
||||
context: navigatorKey.currentContext!,
|
||||
isScrollControlled: true,
|
||||
isDismissible: false,
|
||||
enableDrag: false,
|
||||
useSafeArea: true,
|
||||
builder: (_) => HardwareWalletProceedOnDeviceSheet(
|
||||
hardwareWalletType: hardwareWalletType,
|
||||
trezorConnectVM: this,
|
||||
onRetry: () => connectDevice(device, type, true)),
|
||||
onRetry: () => connectDevice(device, type, true),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -159,15 +187,18 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
|
||||
final res = await _pinCompleter!.future;
|
||||
paringState = TrezorParingState.verifyingPin;
|
||||
if (res == null) throw Exception();
|
||||
if (res == null) {
|
||||
throw Exception();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
final deviceInfo = await _deviceName;
|
||||
|
||||
_state ??= await _getState();
|
||||
_client = sdk.TrezorClient.getClientForConnection(
|
||||
connection,
|
||||
_state,
|
||||
_state!,
|
||||
"Cake Wallet",
|
||||
deviceInfo,
|
||||
onPinCode,
|
||||
@@ -175,6 +206,16 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
|
||||
await _client!.createChannel();
|
||||
|
||||
if (!_state!.pairingCredentials.any((c) => c.autoconnect == true)) {
|
||||
if (_client case final sdk.TrezorClientV2 clientV2) {
|
||||
try {
|
||||
final auto = await clientV2.getAutoPairingCredentials();
|
||||
_state!.setPairingCredentials([auto]);
|
||||
await _saveState();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
paringState = TrezorParingState.success;
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -192,8 +233,12 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
|
||||
Future<String> get _deviceName async {
|
||||
final deviceInfo = DeviceInfoPlugin();
|
||||
if (Platform.isAndroid) return (await deviceInfo.androidInfo).model;
|
||||
if (Platform.isIOS) return (await deviceInfo.iosInfo).name;
|
||||
if (Platform.isAndroid) {
|
||||
return (await deviceInfo.androidInfo).model;
|
||||
}
|
||||
if (Platform.isIOS) {
|
||||
return (await deviceInfo.iosInfo).name;
|
||||
}
|
||||
return "Computer";
|
||||
}
|
||||
|
||||
@@ -231,9 +276,61 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
|
||||
case WalletType.polygon:
|
||||
return evm!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${wallet.type} for trezor');
|
||||
throw Exception("Unexpected wallet type: ${wallet.type} for trezor");
|
||||
}
|
||||
}
|
||||
|
||||
final EncryptionFileUtils _encryptionFileUtils = encryptionFileUtilsFor(true);
|
||||
static const String _secureStorageKey = "com.cakewallet.trezor/thp_state";
|
||||
|
||||
Future<String> get _thpJsonFile async => "${(await getAppDir()).path}/thp_state.json.enc";
|
||||
|
||||
Future<sdk.ThpState> _getState() async {
|
||||
final password = await _secureStorage.read(key: _secureStorageKey);
|
||||
if (password == null) {
|
||||
return _saveState();
|
||||
}
|
||||
try {
|
||||
final state = await _encryptionFileUtils.read(path: await _thpJsonFile, password: password);
|
||||
return sdk.ThpState.fromJson(state);
|
||||
} catch (_) {
|
||||
return _saveState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<sdk.ThpState> _saveState() async {
|
||||
try {
|
||||
final password = generateKey();
|
||||
await _secureStorage.write(key: _secureStorageKey, value: password);
|
||||
|
||||
final file = File(await _thpJsonFile);
|
||||
if (!file.existsSync()) {
|
||||
file.createSync();
|
||||
}
|
||||
|
||||
final state = _state ?? sdk.ThpState();
|
||||
await _encryptionFileUtils.write(
|
||||
path: file.path,
|
||||
password: password,
|
||||
data: jsonEncode(state.toMap()),
|
||||
);
|
||||
_state = state;
|
||||
return state;
|
||||
} catch (_) {
|
||||
throw Exception("Unable to save Trezor State");
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> syncKeyImages(WalletBase wallet) async {
|
||||
if (wallet.type == WalletType.monero) {
|
||||
try {
|
||||
await monero!.syncTrezor(wallet);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TrezorParingState {
|
||||
@@ -241,6 +338,7 @@ abstract class TrezorParingState {
|
||||
static TrezorParingState enterPin = EnterPinTrezorParingState();
|
||||
static TrezorParingState verifyingPin = VerifyingPinTrezorParingState();
|
||||
static TrezorParingState success = SuccessTrezorParingState();
|
||||
|
||||
static TrezorParingState fail(String message) => FailTrezorParingState(message);
|
||||
}
|
||||
|
||||
@@ -253,7 +351,7 @@ class VerifyingPinTrezorParingState extends TrezorParingState {}
|
||||
class SuccessTrezorParingState extends TrezorParingState {}
|
||||
|
||||
class FailTrezorParingState extends TrezorParingState {
|
||||
final String message;
|
||||
|
||||
FailTrezorParingState(this.message);
|
||||
|
||||
final String message;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user