CW-1291-ledger-fixes (#3390)

* feat: add connecting indicator in hardware wallet connect screen * chore: bump ledger related dependencies * chore: bump ledger related dependencies * feat: add additional guard against gLedger being null * auto-reformat * feat: improve ledger connection and state management * fix: connect_device_page padding on iOS * chore: update universal_ble dependency reference --------- Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

Konstantin Ullrich committed Jul 16, 2026 at 20:39 UTC ed540bae04e3f0c7e911de14a33402ae5d8b36ee
12 files changed +420 -285
lib/di.dart
+7 -4
@@ -421,16 +421,19 @@ Future<void> setup({
421 case HardwareWalletType.seedsigner:
422 case HardwareWalletType.keystone:
423 throw Exception(
424 - "This should not have happened, because airgapped Wallets don't need View Models");
424 + "This should not have happened, because airgapped Wallets don't need View Models",
425 + );
426 }
427 });
428
428 - getIt.registerLazySingleton(() => LedgerViewModel());
429 + getIt.registerLazySingleton(() => LedgerViewModel(getIt<AppStore>()));
430
430 - getIt.registerLazySingleton(() => BitboxViewModel());
431 + getIt.registerLazySingleton(BitboxViewModel.new);
432
433 getIt.registerLazySingleton(
433 - () => TrezorConnect("cakewallet://trezor_connect", appName: "Cake Wallet"));
434 + () => TrezorConnect("cakewallet://trezor_connect", appName: "Cake Wallet"),
435 + );
436 +
437 getIt.registerLazySingleton(() => TrezorConnectViewModel(getIt<TrezorConnect>()));
438
439 getIt.registerFactory<KeyService>(() => KeyService(getIt.get<SecureStorage>()));
lib/monero/cw_monero.dart
+3
@@ -433,6 +433,9 @@ class CWMonero extends Monero {
433 @override
434 void setGlobalLedgerConnection(ledger.LedgerConnection connection) => gLedger = connection;
435
436 + @override
437 + bool hasGlobalLedgerConnection() => gLedger != null;
438 +
439 @override
440 String? getLastLedgerCommand() => latestLedgerCommand;
441
lib/reactions/on_authentication_state_change.dart
+28 -7
@@ -75,6 +75,23 @@ void startAuthenticationStateChange(
75 );
76 return;
77 }
78 +
79 + ledgerVM.suppressMoneroReconnectPrompt = true;
80 +
81 + if (!monero!.hasGlobalLedgerConnection()) {
82 + await showPopUp<void>(
83 + context: context,
84 + builder: (context) => AlertWithOneAction(
85 + alertTitle: "Unexpected Error",
86 + alertContent:
87 + "Unexpected Error while trying to connect to the device (has no global LedgerConnection)",
88 + buttonText: S.of(context).try_again,
89 + buttonAction: Navigator.of(context).pop,
90 + ),
91 + );
92 + return;
93 + }
94 +
95 showPopUp<void>(
96 context: context,
97 builder: (context) => AlertWithOneAction(
@@ -85,14 +102,17 @@ void startAuthenticationStateChange(
102 buttonAction: () => Navigator.of(context).pop(),
103 ),
104 );
88 - bool tryOpening = true;
105 +
106 + var isConnected = false;
107 + var tryOpening = true;
108 while (tryOpening) {
109 try {
110 await loadCurrentWallet();
111 + isConnected = true;
112 tryOpening = false;
113 } on Exception catch (e) {
114 final ledgerErrorMessage = ledgerVM.interpretErrorCode(e.toString());
95 - if (ledgerErrorMessage != null) {
115 + if (ledgerErrorMessage != null && context.mounted) {
116 await showPopUp<void>(
117 context: context,
118 builder: (context) => AlertWithTwoActions(
@@ -116,16 +136,17 @@ void startAuthenticationStateChange(
136 }
137
138 getIt.get<BottomSheetService>().showNext();
119 - await navigatorKey.currentState!
120 - .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
139 + if (isConnected) {
140 + ledgerVM.suppressMoneroReconnectPrompt = false;
141 + await navigatorKey.currentState!
142 + .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
143 + }
144 },
145 allowChangeWallet: true,
146 ),
147 );
125 -
126 - // await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.connectDevices, (route) => false, arguments: ConnectDevicePageParams(walletType: walletType, onConnectDevice: onConnectDevice));
148 } else {
128 - navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
149 + await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
150 }
151 WidgetsBinding.instance.addPostFrameCallback((_) {
152 final linkViewModel = getIt.get<LinkViewModel>();
lib/src/screens/connect_device/connect_device_page.dart
+206 -149
@@ -1,54 +1,54 @@
1 -import 'dart:async';
2 -import 'dart:io';
3 -
4 -import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
5 -import 'package:cake_wallet/generated/i18n.dart';
6 -import 'package:cake_wallet/main.dart';
7 -import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
8 -import 'package:cake_wallet/routes.dart';
9 -import 'package:cake_wallet/src/screens/base_page.dart';
10 -import 'package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart';
11 -import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
12 -import 'package:cake_wallet/src/widgets/primary_button.dart';
13 -import 'package:cake_wallet/themes/core/material_base_theme.dart';
14 -import 'package:cake_wallet/utils/responsive_layout_util.dart';
15 -import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
16 -import 'package:cw_core/utils/print_verbose.dart';
17 -import 'package:cw_core/wallet_info.dart';
18 -import 'package:cw_core/wallet_type.dart';
19 -import 'package:flutter/material.dart';
20 -import 'package:flutter_mobx/flutter_mobx.dart';
1 +import "dart:async";
2 +import "dart:io";
3 +
4 +import "package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart";
5 +import "package:cake_wallet/generated/i18n.dart";
6 +import "package:cake_wallet/main.dart";
7 +import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
8 +import "package:cake_wallet/routes.dart";
9 +import "package:cake_wallet/src/screens/base_page.dart";
10 +import "package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart";
11 +import "package:cake_wallet/src/widgets/cake_image_widget.dart";
12 +import "package:cake_wallet/src/widgets/primary_button.dart";
13 +import "package:cake_wallet/themes/core/material_base_theme.dart";
14 +import "package:cake_wallet/utils/responsive_layout_util.dart";
15 +import "package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart";
16 +import "package:cw_core/utils/print_verbose.dart";
17 +import "package:cw_core/wallet_info.dart";
18 +import "package:cw_core/wallet_type.dart";
19 +import "package:flutter/cupertino.dart";
20 +import "package:flutter/material.dart";
21 +import "package:flutter_mobx/flutter_mobx.dart";
22
23 typedef OnConnectDevice = void Function(BuildContext, HardwareWalletViewModel);
24
25 class ConnectDevicePageParams {
25 - final WalletType walletType;
26 - final OnConnectDevice onConnectDevice;
27 - final bool allowChangeWallet;
28 - final bool isReconnect;
29 - final HardwareWalletType hardwareWalletType;
30 -
31 - ConnectDevicePageParams({
26 + const ConnectDevicePageParams({
27 required this.walletType,
28 required this.hardwareWalletType,
29 required this.onConnectDevice,
30 this.allowChangeWallet = false,
31 this.isReconnect = true,
32 });
38 -}
33
40 -class ConnectDevicePage extends BasePage {
34 final WalletType walletType;
35 final OnConnectDevice onConnectDevice;
36 final bool allowChangeWallet;
37 final bool isReconnect;
45 - final HardwareWalletViewModel hardwareWalletVM;
38 + final HardwareWalletType hardwareWalletType;
39 +}
40
41 +class ConnectDevicePage extends BasePage {
42 ConnectDevicePage(ConnectDevicePageParams params, this.hardwareWalletVM)
43 : walletType = params.walletType,
44 onConnectDevice = params.onConnectDevice,
45 allowChangeWallet = params.allowChangeWallet,
46 isReconnect = params.isReconnect;
47 + final WalletType walletType;
48 + final OnConnectDevice onConnectDevice;
49 + final bool allowChangeWallet;
50 + final bool isReconnect;
51 + final HardwareWalletViewModel hardwareWalletVM;
52
53 @override
54 String get title => isReconnect
@@ -60,31 +60,32 @@ class ConnectDevicePage extends BasePage {
60
61 @override
62 Widget body(BuildContext context) => PopScope(
63 - canPop: !isReconnect,
64 - child: ConnectDevicePageBody(
65 - walletType,
66 - onConnectDevice,
67 - allowChangeWallet,
68 - hardwareWalletVM,
69 - currentTheme,
70 - ));
63 + canPop: !isReconnect,
64 + child: ConnectDevicePageBody(
65 + walletType,
66 + onConnectDevice,
67 + hardwareWalletVM,
68 + currentTheme,
69 + allowChangeWallet: allowChangeWallet,
70 + ),
71 + );
72 }
73
74 class ConnectDevicePageBody extends StatefulWidget {
75 + const ConnectDevicePageBody(
76 + this.walletType,
77 + this.onConnectDevice,
78 + this.hardwareWalletVM,
79 + this.currentTheme, {
80 + this.allowChangeWallet = false,
81 + });
82 +
83 final WalletType walletType;
84 final OnConnectDevice onConnectDevice;
85 final bool allowChangeWallet;
86 final HardwareWalletViewModel hardwareWalletVM;
87 final MaterialThemeBase currentTheme;
88
80 - const ConnectDevicePageBody(
81 - this.walletType,
82 - this.onConnectDevice,
83 - this.allowChangeWallet,
84 - this.hardwareWalletVM,
85 - this.currentTheme,
86 - );
87 -
89 @override
90 ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState();
91 }
@@ -95,10 +96,10 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
96
97 List<HardwareWalletDevice> get allDevices => [...bleDevices, ...usbDevices];
98
98 - late Timer? _usbRefreshTimer = null;
99 - late Timer? _bleRefreshTimer = null;
100 - late Timer? _bleStateTimer = null;
101 - late StreamSubscription<HardwareWalletDevice>? _bleRefresh = null;
99 + Timer? _usbRefreshTimer;
100 + Timer? _bleRefreshTimer;
101 + Timer? _bleStateTimer;
102 + StreamSubscription<HardwareWalletDevice>? _bleRefresh;
103
104 bool longWait = false;
105 Timer? _longWaitTimer;
@@ -107,19 +108,22 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
108 void initState() {
109 super.initState();
110 WidgetsBinding.instance.addPostFrameCallback((_) {
110 - _bleStateTimer =
111 - Timer.periodic(Duration(seconds: 1), (_) => widget.hardwareWalletVM.updateBleState());
111 + _bleStateTimer = Timer.periodic(
112 + const Duration(seconds: 1),
113 + (_) => widget.hardwareWalletVM.updateBleState(),
114 + );
115
113 - _bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
116 + _bleRefreshTimer = Timer.periodic(const Duration(seconds: 1), (_) => _refreshBleDevices());
117
118 if (Platform.isAndroid) {
116 - _usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
119 + _usbRefreshTimer = Timer.periodic(const Duration(seconds: 1), (_) => _refreshUsbDevices());
120 }
121
122 if (widget.hardwareWalletVM.hasBluetooth) {
120 - _longWaitTimer = Timer(Duration(seconds: 10), () {
121 - if (widget.hardwareWalletVM.isBleEnabled && bleDevices.isEmpty)
123 + _longWaitTimer = Timer(const Duration(seconds: 10), () {
124 + if (widget.hardwareWalletVM.isBleEnabled && bleDevices.isEmpty) {
125 setState(() => longWait = true);
126 + }
127 });
128 }
129 });
@@ -134,22 +138,29 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
138 _longWaitTimer?.cancel();
139
140 widget.hardwareWalletVM.stopScanning();
141 + _isConnectPressed = false;
142 super.dispose();
143 }
144
145 Future<void> _refreshUsbDevices() async {
146 final dev = await widget.hardwareWalletVM.getAllUsbDevices();
142 - if (usbDevices.length != dev.length) setState(() => usbDevices = dev);
147 +
148 + if (usbDevices.length != dev.length) {
149 + setState(() => usbDevices = dev);
150 + }
151 }
152
153 Future<void> _refreshBleDevices() async {
154 try {
155 if (widget.hardwareWalletVM.isBleEnabled) {
148 - _bleRefresh = widget.hardwareWalletVM.scanForBleDevices().listen((device) => setState(() {
149 - bleDevices.add(device);
150 - if (longWait) longWait = false;
151 - }))
152 - ..onError((Object e) {
156 + _bleRefresh = widget.hardwareWalletVM.scanForBleDevices().listen(
157 + (device) => setState(() {
158 + bleDevices.add(device);
159 + if (longWait) {
160 + longWait = false;
161 + }
162 + }),
163 + )..onError((e) {
164 printV(e);
165 });
166 _bleRefreshTimer?.cancel();
@@ -160,48 +171,66 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
171 }
172 }
173
174 + var _isConnectPressed = false;
175 +
176 Future<void> _connectToDevice(HardwareWalletDevice device) async {
164 - final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType);
177 + if (_isConnectPressed) {
178 + return;
179 + }
180
166 - WidgetsBinding.instance.addPostFrameCallback((_) {
167 - if (isConnected)
168 - widget.onConnectDevice(navigatorKey.currentContext!, widget.hardwareWalletVM);
169 - });
181 + _isConnectPressed = true;
182 + try {
183 + final isConnected = await widget.hardwareWalletVM.connectDevice(device, widget.walletType);
184 + _isConnectPressed = false;
185 +
186 + WidgetsBinding.instance.addPostFrameCallback((_) {
187 + if (isConnected) {
188 + widget.onConnectDevice(navigatorKey.currentContext!, widget.hardwareWalletVM);
189 + }
190 + });
191 + } catch (_) {
192 + _isConnectPressed = false;
193 + }
194 }
195
196 String _getDeviceTileLeading(HardwareWalletDeviceType deviceType) {
197 + printV(deviceType);
198 switch (deviceType) {
199 case HardwareWalletDeviceType.ledgerNanoX:
175 - return 'assets/new-ui/hardware_wallets/ledger_nano_x.svg';
200 + return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
201 case HardwareWalletDeviceType.ledgerNanoGen5:
177 - return 'assets/new-ui/hardware_wallets/device_ledger_nano_gen_5.svg';
202 + return "assets/new-ui/hardware_wallets/device_ledger_nano_gen_5.svg";
203 case HardwareWalletDeviceType.ledgerStax:
179 - return 'assets/new-ui/hardware_wallets/ledger_stax.svg';
204 + return "assets/new-ui/hardware_wallets/device_ledger_stax.svg";
205 case HardwareWalletDeviceType.ledgerFlex:
181 - return 'assets/new-ui/hardware_wallets/ledger_flex.svg';
206 + return "assets/new-ui/hardware_wallets/device_ledger_flex.svg";
207 case HardwareWalletDeviceType.BitBox02:
208 case HardwareWalletDeviceType.BitBox02Nova:
184 - return 'assets/new-ui/hardware_wallets/device_bitbox.svg';
209 + return "assets/new-ui/hardware_wallets/device_bitbox.svg";
210 case HardwareWalletDeviceType.trezorModelOne:
186 - return 'assets/new-ui/hardware_wallets/device_trezor_model_one.svg';
211 + return "assets/new-ui/hardware_wallets/device_trezor_model_one.svg";
212 case HardwareWalletDeviceType.trezorModelT:
188 - return 'assets/new-ui/hardware_wallets/device_trezor_model_t.svg';
213 + return "assets/new-ui/hardware_wallets/device_trezor_model_t.svg";
214 case HardwareWalletDeviceType.trezorSafe3:
190 - return 'assets/new-ui/hardware_wallets/device_trezor_safe_3.svg';
215 + return "assets/new-ui/hardware_wallets/device_trezor_safe_3.svg";
216 case HardwareWalletDeviceType.trezorSafe5:
192 - return 'assets/new-ui/hardware_wallets/device_trezor_safe_5.svg';
217 + return "assets/new-ui/hardware_wallets/device_trezor_safe_5.svg";
218 case HardwareWalletDeviceType.trezorSafe7:
194 - return 'assets/new-ui/hardware_wallets/device_trezor_safe_7.svg';
219 + return "assets/new-ui/hardware_wallets/device_trezor_safe_7.svg";
220
221 default:
197 - return 'assets/new-ui/hardware_wallets/ledger_nano_x.svg';
222 + return "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg";
223 }
224 }
225
226 String get description {
202 - if (!Platform.isAndroid) return S.of(context).connect_your_hardware_wallet_ble;
227 + if (!Platform.isAndroid) {
228 + return S.of(context).connect_your_hardware_wallet_ble;
229 + }
230
204 - if (widget.hardwareWalletVM.hasBluetooth) return S.of(context).connect_your_hardware_wallet;
231 + if (widget.hardwareWalletVM.hasBluetooth) {
232 + return S.of(context).connect_your_hardware_wallet;
233 + }
234
235 return S.of(context).connect_your_hardware_wallet_usb;
236 }
@@ -210,7 +239,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
239 Widget build(BuildContext context) => Center(
240 child: Container(
241 width: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
213 - padding: EdgeInsets.symmetric(vertical: 24, horizontal: 24),
242 + padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 24),
243 child: Column(
244 children: [
245 Expanded(
@@ -218,9 +247,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
247 child: Column(
248 spacing: 24,
249 children: [
221 - CakeImageWidget(
222 - imageUrl: "assets/new-ui/hardware_wallet.svg",
223 - ),
250 + const CakeImageWidget(imageUrl: "assets/new-ui/hardware_wallet.svg"),
251 Text(
252 description,
253 style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
@@ -231,7 +258,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
258 offstage: widget.hardwareWalletVM.isBleEnabled ||
259 !widget.hardwareWalletVM.hasBluetooth,
260 child: Padding(
234 - padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
261 + padding: const EdgeInsets.only(left: 20, right: 20, bottom: 20),
262 child: Text(
263 S.of(context).ledger_please_enable_bluetooth,
264 style: Theme.of(context).textTheme.titleMedium,
@@ -242,67 +269,89 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
269 ),
270 Container(
271 decoration: BoxDecoration(
245 - color: Theme.of(context).colorScheme.surfaceContainer,
246 - borderRadius: BorderRadius.circular(18)),
272 + color: Theme.of(context).colorScheme.surfaceContainer,
273 + borderRadius: BorderRadius.circular(18),
274 + ),
275 child: ListView.separated(
248 - shrinkWrap: true,
249 - itemBuilder: (context, index) {
250 - final item = allDevices[index];
251 - return GestureDetector(
252 - onTap: () => _connectToDevice(item),
253 - behavior: HitTestBehavior.opaque,
254 - child: SizedBox(
255 - height: 48,
256 - child: Padding(
257 - padding: const EdgeInsets.symmetric(horizontal: 12.0),
258 - child: Row(
259 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
260 - children: [
261 - Row(
262 - spacing: 12,
263 - children: [
276 + shrinkWrap: true,
277 + padding: EdgeInsets.zero,
278 + itemBuilder: (context, index) {
279 + final item = allDevices[index];
280 + return GestureDetector(
281 + onTap: () => _connectToDevice(item),
282 + behavior: HitTestBehavior.opaque,
283 + child: SizedBox(
284 + height: 48,
285 + child: Padding(
286 + padding: const EdgeInsets.symmetric(horizontal: 12),
287 + child: Row(
288 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
289 + children: [
290 + Row(
291 + spacing: 12,
292 + children: [
293 + Observer(
294 + builder: (_) {
295 + if (widget.hardwareWalletVM.isConnecting) {
296 + return CupertinoActivityIndicator(
297 + animating: true,
298 + color: Theme.of(context)
299 + .colorScheme
300 + .onSurfaceVariant,
301 + radius: 12,
302 + );
303 + }
304 +
305 + return CakeImageWidget(
306 + imageUrl: _getDeviceTileLeading(item.type),
307 + width: 24,
308 + height: 24,
309 + colorFilter: ColorFilter.mode(
310 + Theme.of(context).colorScheme.onSurfaceVariant,
311 + BlendMode.srcIn,
312 + ),
313 + );
314 + },
315 + ),
316 + Text(item.name),
317 + ],
318 + ),
319 + Row(
320 + spacing: 12,
321 + children: [
322 + if (item.connectionType ==
323 + HardwareWalletConnectionType.ble)
324 CakeImageWidget(
265 - imageUrl: _getDeviceTileLeading(item.type),
325 + imageUrl: "assets/new-ui/bluetooth.svg",
326 width: 24,
327 height: 24,
328 colorFilter: ColorFilter.mode(
269 - Theme.of(context).colorScheme.onSurfaceVariant,
270 - BlendMode.srcIn),
271 - ),
272 - Text(item.name)
273 - ],
274 - ),
275 - Row(
276 - spacing: 12,
277 - children: [
278 - if (item.connectionType ==
279 - HardwareWalletConnectionType.ble)
280 - CakeImageWidget(
281 - imageUrl: "assets/new-ui/bluetooth.svg",
282 - width: 24,
283 - height: 24,
284 - colorFilter: ColorFilter.mode(
285 - Theme.of(context).colorScheme.primary,
286 - BlendMode.srcIn),
329 + Theme.of(context).colorScheme.primary,
330 + BlendMode.srcIn,
331 ),
288 - CakeImageWidget(
289 - imageUrl: "assets/new-ui/arrow_forward.svg",
290 - height: 16,
291 - colorFilter: ColorFilter.mode(
292 - Theme.of(context).colorScheme.onSurfaceVariant,
293 - BlendMode.srcIn))
294 - ],
295 - )
296 - ],
297 - ),
332 + ),
333 + CakeImageWidget(
334 + imageUrl: "assets/new-ui/arrow_forward.svg",
335 + height: 16,
336 + colorFilter: ColorFilter.mode(
337 + Theme.of(context).colorScheme.onSurfaceVariant,
338 + BlendMode.srcIn,
339 + ),
340 + ),
341 + ],
342 + ),
343 + ],
344 ),
345 ),
300 - );
301 - },
302 - separatorBuilder: (context, index) => Container(
303 - height: 1,
304 - color: Theme.of(context).colorScheme.surfaceContainerHigh),
305 - itemCount: allDevices.length),
346 + ),
347 + );
348 + },
349 + separatorBuilder: (context, index) => Container(
350 + height: 1,
351 + color: Theme.of(context).colorScheme.surfaceContainerHigh,
352 + ),
353 + itemCount: allDevices.length,
354 + ),
355 ),
356 if (widget.allowChangeWallet) ...[
357 PrimaryButton(
@@ -310,7 +359,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
359 color: Theme.of(context).colorScheme.primary,
360 textColor: Theme.of(context).colorScheme.onPrimary,
361 onPressed: _onChangeWallet,
313 - )
362 + ),
363 ],
364 ],
365 ),
@@ -321,7 +370,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
370 color: Theme.of(context).colorScheme.surfaceContainer,
371 textColor: Theme.of(context).colorScheme.primary,
372 onPressed: () => _onHowToConnect(context),
324 - )
373 + ),
374 ],
375 ),
376 ),
@@ -339,17 +388,25 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
388 showModalBottomSheet(
389 context: context,
390 isScrollControlled: true,
342 - builder: (BuildContext bottomSheetContext) => InfoStepsBottomSheet(
391 + builder: (_) => InfoStepsBottomSheet(
392 titleText: S.of(context).how_to_connect,
393 steps: [
345 - InfoStep('assets/images/wallet_connect_step_icons/step1_power.svg',
346 - S.of(context).connect_hw_info_step_1),
347 - InfoStep('assets/images/wallet_connect_step_icons/step2_connect.svg',
348 - S.of(context).connect_hw_info_step_2),
349 - InfoStep('assets/images/wallet_connect_step_icons/step3_unlock.svg',
350 - S.of(context).connect_hw_info_step_3),
351 - InfoStep('assets/images/wallet_connect_step_icons/step4_select.svg',
352 - S.of(context).connect_hw_info_step_4),
394 + InfoStep(
395 + "assets/images/wallet_connect_step_icons/step1_power.svg",
396 + S.of(context).connect_hw_info_step_1,
397 + ),
398 + InfoStep(
399 + "assets/images/wallet_connect_step_icons/step2_connect.svg",
400 + S.of(context).connect_hw_info_step_2,
401 + ),
402 + InfoStep(
403 + "assets/images/wallet_connect_step_icons/step3_unlock.svg",
404 + S.of(context).connect_hw_info_step_3,
405 + ),
406 + InfoStep(
407 + "assets/images/wallet_connect_step_icons/step4_select.svg",
408 + S.of(context).connect_hw_info_step_4,
409 + ),
410 ],
411 ),
412 );
lib/store/app_store.dart
+26 -25
@@ -1,25 +1,25 @@
1 -import 'dart:async';
2 -
3 -import 'package:cake_wallet/core/amount_parsing_proxy.dart';
4 -import 'package:cake_wallet/di.dart';
5 -import 'package:cake_wallet/entities/preferences_key.dart';
6 -import 'package:cake_wallet/reactions/wallet_connect.dart';
7 -import 'package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart';
8 -import 'package:cake_wallet/themes/core/theme_store.dart';
9 -import 'package:cake_wallet/utils/exception_handler.dart';
10 -import 'package:cw_core/transaction_info.dart';
11 -import 'package:cw_core/utils/print_verbose.dart';
12 -import 'package:cw_core/wallet_type.dart';
13 -import 'package:mobx/mobx.dart';
14 -import 'package:cw_core/balance.dart';
15 -import 'package:cw_core/wallet_base.dart';
16 -import 'package:cw_core/transaction_history.dart';
17 -import 'package:cake_wallet/store/wallet_list_store.dart';
18 -import 'package:cake_wallet/store/authentication_store.dart';
19 -import 'package:cake_wallet/store/settings_store.dart';
20 -import 'package:shared_preferences/shared_preferences.dart';
21 -
22 -part 'app_store.g.dart';
1 +import "dart:async";
2 +
3 +import "package:cake_wallet/core/amount_parsing_proxy.dart";
4 +import "package:cake_wallet/di.dart";
5 +import "package:cake_wallet/entities/preferences_key.dart";
6 +import "package:cake_wallet/reactions/wallet_connect.dart";
7 +import "package:cake_wallet/src/screens/wallet_connect/services/walletkit_service.dart";
8 +import "package:cake_wallet/store/authentication_store.dart";
9 +import "package:cake_wallet/store/settings_store.dart";
10 +import "package:cake_wallet/store/wallet_list_store.dart";
11 +import "package:cake_wallet/themes/core/theme_store.dart";
12 +import "package:cake_wallet/utils/exception_handler.dart";
13 +import "package:cw_core/balance.dart";
14 +import "package:cw_core/transaction_history.dart";
15 +import "package:cw_core/transaction_info.dart";
16 +import "package:cw_core/utils/print_verbose.dart";
17 +import "package:cw_core/wallet_base.dart";
18 +import "package:cw_core/wallet_type.dart";
19 +import "package:mobx/mobx.dart";
20 +import "package:shared_preferences/shared_preferences.dart";
21 +
22 +part "app_store.g.dart";
23
24 class AppStore = AppStoreBase with _$AppStore;
25
@@ -59,8 +59,9 @@ abstract class AppStoreBase with Store {
59 @action
60 Future<void> changeCurrentWallet(
61 WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet) async {
62 - bool changingToSameWalletType = this.wallet?.type == wallet.type;
63 - this.wallet?.close(shouldCleanup: !changingToSameWalletType);
62 + final changingToSameWalletType = this.wallet?.type == wallet.type;
63 +
64 + await this.wallet?.close(shouldCleanup: !changingToSameWalletType);
65 this.wallet = wallet;
66 this.wallet!.setExceptionHandler(ExceptionHandler.onError);
67
@@ -80,7 +81,7 @@ abstract class AppStoreBase with Store {
81 wcService.create();
82 await wcService.init();
83 } catch (e, s) {
83 - printV('WalletConnect setup failed: $e\n$s');
84 + printV("WalletConnect setup failed: $e\n$s");
85 }
86 }
87 }
lib/view_model/hardware_wallet/bitbox_view_model.dart
+9 -6
@@ -35,6 +35,10 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
35 @observable
36 bool isBleEnabled = false;
37
38 + @override
39 + @observable
40 + bool isConnecting = false;
41 +
42 @override
43 bool get hasBluetooth => Platform.isIOS && false; // TODO: remove when we enable bluetooth
44
@@ -52,10 +56,11 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
56 Future<void> stopScanning() async {}
57
58 @override
59 + @action
60 Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async {
61 if (!(device is BitboxHardwareWalletDevice)) return false;
57 - if (_isConnecting) return false;
58 - _isConnecting = true;
62 + if (isConnecting) return false;
63 + isConnecting = true;
64
65 try {
66 final appDocDir = await getAppDir();
@@ -66,17 +71,15 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
71 printV("Bitbox initialized!", file: '${appDocDir.path}/error.txt');
72 await bitboxManager.channelHashVerify();
73 printV("Bitbox channel-hash verified!", file: '${appDocDir.path}/error.txt');
69 - _isConnecting = false;
74 + isConnecting = false;
75 return true;
76 } catch (e) {
77 printV(e);
78 }
74 - _isConnecting = false;
79 + isConnecting = false;
80 return false;
81 }
82
78 - bool _isConnecting = false;
79 -
83 @override
84 bool isConnected(WalletType type) => false;
85
lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
+8 -5
@@ -1,13 +1,14 @@
1 -import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
2 -import 'package:cw_core/hardware/hardware_wallet_service.dart';
3 -import 'package:cw_core/wallet_base.dart';
4 -import 'package:cw_core/wallet_info.dart';
5 -import 'package:cw_core/wallet_type.dart';
1 +import "package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart";
2 +import "package:cw_core/hardware/hardware_wallet_service.dart";
3 +import "package:cw_core/wallet_base.dart";
4 +import "package:cw_core/wallet_info.dart";
5 +import "package:cw_core/wallet_type.dart";
6
7 abstract class HardwareWalletViewModel {
8 HardwareWalletType get hardwareWalletType;
9 bool get isBleEnabled;
10 bool get hasBluetooth;
11 + bool get isConnecting;
12
13 bool isConnected(WalletType type);
14
@@ -26,4 +27,6 @@ abstract class HardwareWalletViewModel {
27 Future<void> initWallet(WalletBase wallet);
28
29 String? interpretErrorCode(String error) => null;
30 +
31 + Future<void> close() async {}
32 }
lib/view_model/hardware_wallet/ledger_view_model.dart
+119 -73
@@ -1,50 +1,47 @@
1 -import 'dart:async';
2 -import 'dart:io';
3 -
4 -import 'package:cake_wallet/bitcoin/bitcoin.dart';
5 -import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
6 -import 'package:cake_wallet/evm/evm.dart';
7 -import 'package:cake_wallet/generated/i18n.dart';
8 -import 'package:cake_wallet/main.dart';
9 -import 'package:cake_wallet/monero/monero.dart';
10 -import 'package:cake_wallet/routes.dart';
11 -import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
12 -import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
13 -import 'package:cake_wallet/wallet_type_utils.dart';
14 -import 'package:cw_core/hardware/device_connection_type.dart';
15 -import 'package:cw_core/hardware/hardware_wallet_service.dart';
16 -import 'package:cw_core/utils/print_verbose.dart';
17 -import 'package:cw_core/wallet_base.dart';
18 -import 'package:cw_core/wallet_info.dart';
19 -import 'package:cw_core/wallet_type.dart';
20 -import 'package:flutter/widgets.dart';
21 -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk;
22 -import 'package:mobx/mobx.dart';
23 -import 'package:permission_handler/permission_handler.dart';
24 -
25 -part 'ledger_view_model.g.dart';
1 +import "dart:async";
2 +import "dart:io";
3 +
4 +import "package:cake_wallet/bitcoin/bitcoin.dart";
5 +import "package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart";
6 +import "package:cake_wallet/evm/evm.dart";
7 +import "package:cake_wallet/generated/i18n.dart";
8 +import "package:cake_wallet/main.dart";
9 +import "package:cake_wallet/monero/monero.dart";
10 +import "package:cake_wallet/routes.dart";
11 +import "package:cake_wallet/src/screens/connect_device/connect_device_page.dart";
12 +import "package:cake_wallet/store/app_store.dart";
13 +import "package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart";
14 +import "package:cake_wallet/wallet_type_utils.dart";
15 +import "package:cw_core/hardware/device_connection_type.dart";
16 +import "package:cw_core/hardware/hardware_wallet_service.dart";
17 +import "package:cw_core/utils/print_verbose.dart";
18 +import "package:cw_core/wallet_base.dart";
19 +import "package:cw_core/wallet_info.dart";
20 +import "package:cw_core/wallet_type.dart";
21 +import "package:flutter/widgets.dart";
22 +import "package:ledger_flutter_plus/ledger_flutter_plus.dart" as sdk;
23 +import "package:mobx/mobx.dart";
24 +import "package:permission_handler/permission_handler.dart";
25 +
26 +part "ledger_view_model.g.dart";
27
28 class LedgerViewModel = LedgerViewModelBase with _$LedgerViewModel;
29
30 abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
30 - late final sdk.LedgerInterface ledgerPlusBLE;
31 - late final sdk.LedgerInterface ledgerPlusUSB;
32 -
33 - bool get _doesSupportHardwareWallets {
34 - if (isMoneroOnly) {
35 - return DeviceConnectionType.supportedConnectionTypes(
36 - WalletType.monero, HardwareWalletType.ledger, Platform.isIOS)
37 - .isNotEmpty;
38 - }
39 -
40 - return true;
41 - }
42 -
43 - LedgerViewModelBase() {
31 + LedgerViewModelBase(AppStore appStore) {
32 if (_doesSupportHardwareWallets) {
33 reaction((_) => isBleEnabled, (_) {
46 - if (isBleEnabled) _initBLE();
34 + if (isBleEnabled) {
35 + _initBLE();
36 + }
37 });
38 +
39 + reaction((_) => appStore.wallet, (wallet) async {
40 + if (wallet != null && wallet.type != _connectingWalletType) {
41 + await close();
42 + }
43 + });
44 +
45 updateBleState();
46
47 if (!Platform.isIOS) {
@@ -53,6 +50,21 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
50 }
51 }
52
53 + late final sdk.LedgerInterface ledgerPlusBLE;
54 + late final sdk.LedgerInterface ledgerPlusUSB;
55 +
56 + bool get _doesSupportHardwareWallets {
57 + if (isMoneroOnly) {
58 + return DeviceConnectionType.supportedConnectionTypes(
59 + WalletType.monero,
60 + HardwareWalletType.ledger,
61 + Platform.isIOS,
62 + ).isNotEmpty;
63 + }
64 +
65 + return true;
66 + }
67 +
68 @override
69 HardwareWalletType get hardwareWalletType => HardwareWalletType.ledger;
70
@@ -60,6 +72,10 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
72 @observable
73 bool isBleEnabled = false;
74
75 + @override
76 + @observable
77 + bool isConnecting = false;
78 +
79 @override
80 bool get hasBluetooth => true;
81
@@ -69,9 +85,11 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
85 if (isBleEnabled && !_bleIsInitialized) {
86 ledgerPlusBLE = sdk.LedgerInterface.ble(
87 onPermissionRequest: (_) async {
72 - if (Platform.isMacOS) return true;
88 + if (Platform.isMacOS) {
89 + return true;
90 + }
91
74 - Map<Permission, PermissionStatus> statuses = await [
92 + final statuses = await [
93 Permission.bluetoothScan,
94 Permission.bluetoothConnect,
95 Permission.bluetoothAdvertise,
@@ -79,7 +97,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
97
98 return statuses.values.where((status) => status.isDenied).isEmpty;
99 },
82 - bleOptions: sdk.BluetoothOptions(maxScanDuration: Duration(minutes: 5)),
100 + bleOptions: sdk.BluetoothOptions(maxScanDuration: const Duration(minutes: 5)),
101 );
102 _bleIsInitialized = true;
103 }
@@ -91,28 +109,41 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
109
110 final newState = bleState == sdk.AvailabilityState.poweredOn;
111
94 - if (newState != isBleEnabled) isBleEnabled = newState;
112 + if (newState != isBleEnabled) {
113 + isBleEnabled = newState;
114 + }
115 }
116
117 @override
118 Stream<HardwareWalletDevice> scanForBleDevices() =>
99 - ledgerPlusBLE.scan().map((d) => LedgerHardwareWalletDevice(d));
119 + ledgerPlusBLE.scan().map(LedgerHardwareWalletDevice.new);
120
121 @override
102 - Future<List<HardwareWalletDevice>> getAllUsbDevices() => ledgerPlusUSB.devices
103 - .then((devices) => devices.map((d) => LedgerHardwareWalletDevice(d)).toList());
122 + Future<List<HardwareWalletDevice>> getAllUsbDevices() =>
123 + ledgerPlusUSB.devices.then((devices) => devices.map(LedgerHardwareWalletDevice.new).toList());
124
125 @override
126 Future<void> stopScanning() async {
107 - if (_bleIsInitialized) await ledgerPlusBLE.stopScanning();
108 - if (!Platform.isIOS) await ledgerPlusUSB.stopScanning();
127 + if (_bleIsInitialized) {
128 + await ledgerPlusBLE.stopScanning();
129 + }
130 + if (!Platform.isIOS) {
131 + await ledgerPlusUSB.stopScanning();
132 + }
133 }
134
135 @override
136 + @action
137 Future<bool> connectDevice(HardwareWalletDevice device, WalletType type) async {
113 - if (!(device is LedgerHardwareWalletDevice)) return false;
114 - if (_isConnecting) return false;
115 - _isConnecting = true;
138 + if (device is! LedgerHardwareWalletDevice) {
139 + return false;
140 + }
141 +
142 + if (isConnecting) {
143 + return false;
144 + }
145 +
146 + isConnecting = true;
147 _connectingWalletType = type;
148 if (isConnected(type)) {
149 try {
@@ -123,34 +154,36 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
154 final ledger =
155 device.connectionType == HardwareWalletConnectionType.ble ? ledgerPlusBLE : ledgerPlusUSB;
156
126 - if (_connectionChangeSubscription == null) {
127 - _connectionChangeSubscription =
128 - ledger.deviceStateChanges(device.device.id).listen(_connectionChangeListener);
129 - }
157 + _connectionChangeSubscription ??=
158 + ledger.deviceStateChanges(device.device.id).listen(_connectionChangeListener);
159
160 try {
161 _connection = await ledger.connect(device.device);
133 - _isConnecting = false;
162 + isConnecting = false;
163 return true;
164 } catch (e) {
165 printV(e);
166 }
138 - _isConnecting = false;
167 + isConnecting = false;
168 return false;
169 }
170
171 StreamSubscription<sdk.BleConnectionState>? _connectionChangeSubscription;
172 sdk.LedgerConnection? _connection;
144 - bool _isConnecting = false;
173 WalletType? _connectingWalletType;
174 + bool suppressMoneroReconnectPrompt = false;
175
176 void _connectionChangeListener(sdk.BleConnectionState event) {
148 - printV('Ledger Device State Changed: $event');
149 - if (event == sdk.BleConnectionState.disconnected && !_isConnecting) {
177 + printV("Ledger Device State Changed: $event");
178 + if (event == sdk.BleConnectionState.disconnected && !isConnecting) {
179 _connection = null;
180 if (_connectingWalletType == WalletType.monero) {
181 monero!.resetLedgerConnection();
182
183 + if (suppressMoneroReconnectPrompt) {
184 + return;
185 + }
186 +
187 Navigator.of(navigatorKey.currentContext!).pushNamed(
188 Routes.connectDevices,
189 arguments: ConnectDevicePageParams(
@@ -158,7 +191,7 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
191 hardwareWalletType: HardwareWalletType.ledger,
192 allowChangeWallet: true,
193 isReconnect: true,
161 - onConnectDevice: (context, ledgerVM) async {
194 + onConnectDevice: (context, ledgerVM) {
195 if (context.mounted && Navigator.of(context).canPop()) {
196 Navigator.of(context).pop();
197 }
@@ -170,26 +203,24 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
203 }
204
205 @override
173 - bool isConnected(WalletType type) => _connection != null && !(_connection!.isDisconnected);
206 + bool isConnected(WalletType type) => _connection != null && !_connection!.isDisconnected;
207
208 sdk.LedgerConnection get connection => _connection!;
209
210 @override
178 - Future<void> initWallet(WalletBase wallet) async {
211 + Future<void> initWallet(WalletBase wallet) {
212 switch (wallet.type) {
213 case WalletType.monero:
214 return monero!.setLedgerConnection(wallet, connection);
215 case WalletType.bitcoin:
183 - return bitcoin!
184 - .setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
216 + return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
217 case WalletType.litecoin:
186 - return bitcoin!
187 - .setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
218 + return bitcoin!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
219 case WalletType.ethereum:
220 case WalletType.polygon:
190 - return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
221 + return evm!.setHardwareWalletService(wallet, getHardwareWalletService(wallet.type));
222 default:
192 - throw Exception('Unexpected wallet type: ${wallet.type} for ledger');
223 + throw Exception("Unexpected wallet type: ${wallet.type} for ledger");
224 }
225 }
226
@@ -216,16 +247,31 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
247 return error;
248 }
249
219 - var errorRegex = RegExp(r'(?:0x\S*?|[0-9a-f]{4})(?= )').firstMatch(error.toString());
250 + final errorRegex = RegExp(r"(?:0x\S*?|[0-9a-f]{4})(?= )").firstMatch(error.toString());
251
221 - String errorCode = errorRegex?.group(0).toString().replaceAll("0x", "") ?? "";
252 + final errorCode = errorRegex?.group(0).toString().replaceAll("0x", "") ?? "";
253 if (errorCode.contains("6985")) {
254 return S.current.ledger_error_tx_rejected_by_user;
255 } else if (errorCode.contains("5515")) {
256 return S.current.ledger_error_device_locked;
226 - } else if (["6e01", "6a87", "6d02", "6511", "6e00"].any((e) => errorCode.contains(e))) {
257 + } else if (["6e01", "6a87", "6d02", "6511", "6e00"].any(errorCode.contains)) {
258 return S.current.ledger_error_wrong_app;
259 }
260 return null;
261 }
262 +
263 + @override
264 + Future<void> close() async {
265 + try {
266 + printV("Resetting ledger VM");
267 + await _connectionChangeSubscription?.cancel();
268 + await _connection?.disconnect().catchError((_) {});
269 +
270 + _connectingWalletType = null;
271 + _connectionChangeSubscription = null;
272 + _connection = null;
273 + isConnecting = false;
274 + await stopScanning();
275 + } catch (_) {}
276 + }
277 }
lib/view_model/hardware_wallet/trezor_connect_view_model.dart
+8 -4
@@ -82,6 +82,10 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
82 @override
83 HardwareWalletType get hardwareWalletType => HardwareWalletType.trezor;
84
85 + @override
86 + @observable
87 + bool isConnecting = false;
88 +
89 @override
90 @observable
91 bool isBleEnabled = false;
@@ -112,7 +116,6 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
116 if (!Platform.isIOS) await trezorUSB.stopScanning();
117 }
118
115 - bool _isConnecting = false;
119 sdk.TrezorClient? _client;
120
121 Completer<String?>? _pinCompleter;
@@ -123,11 +126,12 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
126 void setParingPin(String pin) => _pinCompleter?.complete(pin);
127
128 @override
129 + @action
130 Future<bool> connectDevice(HardwareWalletDevice device, WalletType type,
131 [bool isRetry = false]) async {
132 if (!(device is TrezorHardwareWalletDevice)) return false;
129 - if (_isConnecting) return false;
130 - _isConnecting = true;
133 + if (isConnecting) return false;
134 + isConnecting = true;
135 paringState = TrezorParingState.initial;
136
137 try {
@@ -182,7 +186,7 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
186 printV(e);
187 return false;
188 } finally {
185 - _isConnecting = false;
189 + isConnecting = false;
190 }
191 }
192
lib/view_model/wallet_list/wallet_list_view_model.dart
+1 -2
@@ -77,8 +77,7 @@ abstract class WalletListViewModelBase with Store {
77 if (walletItem.type == WalletType.haven) {
78 return;
79 }
80 - // bool switchingToSameWalletType = walletItem.type == _appStore.wallet?.type;
81 - // await _appStore.wallet?.close(shouldCleanup: !switchingToSameWalletType);
80 +
81 final wallet = await _walletLoadingService.load(walletItem.type, walletItem.name);
82 await _appStore.changeCurrentWallet(wallet);
83 updateList();
pubspec_base.yaml
+4 -10
@@ -125,7 +125,7 @@ dependencies:
125 ledger_flutter_plus:
126 git:
127 url: https://github.com/vespr-wallet/ledger-flutter-plus
128 - ref: 0ccfbe26298e8974dfd4e891f34719c8348562f9
128 + ref: eaf49764ad0863e8dec97ad30ff6aea0a1d1bd28
129 trezor_connect:
130 git:
131 url: https://github.com/cake-tech/trezor_connect
@@ -201,14 +201,8 @@ dependency_overrides:
201 ffi: 2.1.0
202 ledger_flutter_plus:
203 git:
204 - url: https://github.com/cake-tech/ledger-flutter-plus
205 - ref: 7bcb00380783f8611f68f5cd63fec96261fea2c2
206 - ledger_usb_plus:
207 - git:
208 - # url: https://github.com/konstantinullrich/ledger-usb-plus
209 - # ref: dad482c847c0d0eedceabbb40d3478e64b65e630
210 - url: https://github.com/cake-tech/ledger-usb-plus
211 - ref: 734c3d6f8267ba52ad73b6e1e78e570c07298890
204 + url: https://github.com/vespr-wallet/ledger-flutter-plus
205 + ref: eaf49764ad0863e8dec97ad30ff6aea0a1d1bd28
206 socks5_proxy:
207 git:
208 url: https://github.com/LacticWhale/socks_dart.git
@@ -231,7 +225,7 @@ dependency_overrides:
225 universal_ble:
226 git:
227 url: https://github.com/cake-tech/universal_ble.git
234 - ref: f7044558ad111a156987614971e08c583c80175c
228 + ref: 6735c1bdf7f42c501d51c20564eb957c3a12b981
229
230 flutter_icons:
231 image_path: "assets/images/app_logo.png"
tool/configure.dart
+1
@@ -475,6 +475,7 @@ WalletCredentials createMoneroNewWalletCredentials({required String name, requir
475 Future<void> setLedgerConnection(Object wallet, ledger.LedgerConnection connection);
476 void resetLedgerConnection();
477 void setGlobalLedgerConnection(ledger.LedgerConnection connection);
478 + bool hasGlobalLedgerConnection();
479 String? getLastLedgerCommand();
480 void setHardwareWalletService(Object wallet, HardwareWalletService service);
481 HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);