CW-1045-Ledger-Bugs-Enhancements (#2278)

* feat: prepare ledger.dart to use callbacks

* feat: set ledger callback using monero_c

* fix(cw_monero): async ledger

* build: Bump monero_c dependencies

* feat: Add "How to connect" to HW Device selection screen

* refactor: use monero_c free to clean pointer

* fix: use new monero_c deps

* fix: merge conflicts regarding new theming

* feat: add status bottomsheet indicating an ongoing signing process

* fix: getLastLedgerCommand monero.dart generation

* reformat send_view_model.dart [skip ci]

---------

Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
Konstantin Ullrich
2025-05-26 18:38:51 +03:00
committed by GitHub
co-authored by Czarek Nakamoto Omar Hatem
parent e03dcc7fe9
commit 90aee053cd
46 changed files with 660 additions and 315 deletions
@@ -5,7 +5,9 @@ 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/device_tile.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_steps_bottom_sheet_widget.dart';
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:cw_core/utils/print_verbose.dart';
@@ -60,6 +62,7 @@ class ConnectDevicePage extends BasePage {
onConnectDevice,
allowChangeWallet,
ledgerVM,
currentTheme,
));
}
@@ -68,12 +71,14 @@ class ConnectDevicePageBody extends StatefulWidget {
final OnConnectDevice onConnectDevice;
final bool allowChangeWallet;
final LedgerViewModel ledgerVM;
final MaterialThemeBase currentTheme;
const ConnectDevicePageBody(
this.walletType,
this.onConnectDevice,
this.allowChangeWallet,
this.ledgerVM,
this.currentTheme,
);
@override
@@ -179,109 +184,126 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
height: double.infinity,
padding: EdgeInsets.symmetric(vertical: 24, horizontal: 24),
child: SingleChildScrollView(
child: Column(
children: [
Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
Platform.isIOS
? S.of(context).connect_your_hardware_wallet_ios
: S.of(context).connect_your_hardware_wallet,
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
Offstage(
offstage: !longWait,
child: Padding(
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,
textAlign: TextAlign.center,
),
),
),
Observer(
builder: (_) => Offstage(
offstage: widget.ledgerVM.bleIsEnabled,
child: Padding(
return Container(
width: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
padding: EdgeInsets.symmetric(vertical: 24, horizontal: 24),
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
S.of(context).ledger_please_enable_bluetooth,
style: Theme.of(context).textTheme.titleMedium,
Platform.isIOS
? S.of(context).connect_your_hardware_wallet_ios
: S.of(context).connect_your_hardware_wallet,
style: Theme.of(context)
.textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
),
Offstage(
offstage: !longWait,
child: Padding(
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,
textAlign: TextAlign.center,
),
),
),
Observer(
builder: (_) => Offstage(
offstage: widget.ledgerVM.bleIsEnabled,
child: Padding(
padding:
EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(
S.of(context).ledger_please_enable_bluetooth,
style: Theme.of(context)
.textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
),
),
if (bleDevices.length > 0) ...[
Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Container(
width: double.infinity,
child: Text(
S.of(context).bluetooth,
style: Theme.of(context)
.textTheme.bodyMedium,
),
),
),
...bleDevices
.map(
(device) => Padding(
padding: EdgeInsets.only(bottom: 20),
child: DeviceTile(
onPressed: () => _connectToDevice(device),
title: device.name,
leading: _getDeviceTileLeading(device.deviceInfo),
connectionType: device.connectionType,
),
),
)
.toList()
],
if (usbDevices.length > 0) ...[
Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Container(
width: double.infinity,
child: Text(
S.of(context).usb,
style: Theme.of(context)
.textTheme.bodyMedium,
),
),
),
...usbDevices
.map(
(device) => Padding(
padding: EdgeInsets.only(bottom: 20),
child: DeviceTile(
onPressed: () => _connectToDevice(device),
title: device.name,
leading: _getDeviceTileLeading(device.deviceInfo),
connectionType: device.connectionType,
),
),
)
.toList(),
],
if (widget.allowChangeWallet) ...[
PrimaryButton(
text: S.of(context).wallets,
color: Theme.of(context)
.colorScheme.primary,
textColor: Theme.of(context)
.colorScheme.onPrimary,
onPressed: _onChangeWallet,
)
],
],
),
if (bleDevices.length > 0) ...[
Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Container(
width: double.infinity,
child: Text(
S.of(context).bluetooth,
style: Theme.of(context).textTheme.bodyMedium,
),
),
),
...bleDevices
.map(
(device) => Padding(
padding: EdgeInsets.only(bottom: 20),
child: DeviceTile(
onPressed: () => _connectToDevice(device),
title: device.name,
leading: _getDeviceTileLeading(device.deviceInfo),
connectionType: device.connectionType,
),
),
)
.toList()
],
if (usbDevices.length > 0) ...[
Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Container(
width: double.infinity,
child: Text(
S.of(context).usb,
style: Theme.of(context).textTheme.bodyMedium,
),
),
),
...usbDevices
.map(
(device) => Padding(
padding: EdgeInsets.only(bottom: 20),
child: DeviceTile(
onPressed: () => _connectToDevice(device),
title: device.name,
leading: _getDeviceTileLeading(device.deviceInfo),
connectionType: device.connectionType,
),
),
)
.toList(),
],
if (widget.allowChangeWallet) ...[
PrimaryButton(
text: S.of(context).wallets,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
onPressed: _onChangeWallet,
)
],
],
),
),
),
PrimaryButton(
text: S.of(context).how_to_connect,
color: Theme.of(context).colorScheme.surfaceContainer,
textColor: Theme.of(context).colorScheme.onSecondaryContainer,
onPressed: () => _onHowToConnect(context),
)
],
),
);
}
@@ -293,4 +315,21 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false),
);
}
void _onHowToConnect(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext bottomSheetContext) => InfoStepsBottomSheet(
titleText: S.of(context).how_to_connect,
currentTheme: widget.currentTheme,
steps: [
InfoStep('${S.of(context).step} 1', S.of(context).connect_hw_info_step_1),
InfoStep('${S.of(context).step} 2', S.of(context).connect_hw_info_step_2),
InfoStep('${S.of(context).step} 3', S.of(context).connect_hw_info_step_3),
InfoStep('${S.of(context).step} 4', S.of(context).connect_hw_info_step_4),
],
),
);
}
}
+32 -14
View File
@@ -684,29 +684,47 @@ class SendPage extends BasePage {
});
}
if (state is IsAwaitingDeviceResponseState) {
if (state is IsDeviceSigningResponseState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!context.mounted) return;
showModalBottomSheet<void>(
context: context,
isDismissible: false,
builder: (BuildContext bottomSheetContext) => InfoBottomSheet(
currentTheme: currentTheme,
titleText: S.of(bottomSheetContext).proceed_on_device,
contentImage: 'assets/images/hardware_wallet/ledger_nano_x.png',
contentImageColor: Theme.of(context).colorScheme.onSurface,
content: S.of(bottomSheetContext).proceed_on_device_description,
isTwoAction: false,
actionButtonText: S.of(context).cancel,
actionButton: () {
sendViewModel.state = InitialExecutionState();
Navigator.of(bottomSheetContext).pop();
},
),
builder: (context) {
dialogContext = context;
return LoadingBottomSheet(titleText: S.of(context).device_is_signing);
},
);
});
}
if (state is IsAwaitingDeviceResponseState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!context.mounted) return;
showModalBottomSheet<void>(
context: context,
isDismissible: false,
builder: (context) {
dialogContext = context;
return InfoBottomSheet(
currentTheme: currentTheme,
titleText: S.of(context).proceed_on_device,
contentImage:
'assets/images/hardware_wallet/ledger_nano_x.png',
contentImageColor: Theme.of(context).colorScheme.onSurface,
content: S.of(context).proceed_on_device_description,
isTwoAction: false,
actionButtonText: S.of(context).cancel,
actionButton: () {
sendViewModel.state = InitialExecutionState();
Navigator.of(context).pop();
},
);
});
});
}
});
_effectsInstalled = true;
@@ -0,0 +1,114 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:flutter/material.dart';
import 'base_bottom_sheet_widget.dart';
class InfoStep {
final String title;
final String description;
const InfoStep(this.title, this.description);
}
class InfoStepsBottomSheet extends BaseBottomSheet {
final MaterialThemeBase currentTheme;
final List<InfoStep> steps;
InfoStepsBottomSheet({
required String titleText,
required this.steps,
String? titleIconPath,
required this.currentTheme,
}) : super(titleText: titleText, titleIconPath: titleIconPath);
@override
Widget contentWidget(BuildContext context) => SizedBox(
height: 500,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: steps
.map((step) => Container(
margin: EdgeInsets.only(
bottom: 15, left: 20, right: 20),
padding: EdgeInsets.all(10),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Theme.of(context).cardColor,
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 16),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
step.title,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.colorScheme
.onSurface,
),
),
Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
step.description,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
),
),
)
],
),
),
)
],
),
))
.toList(),
),
),
),
Padding(
padding: const EdgeInsets.all(16),
child: PrimaryButton(
text: S.of(context).close,
color: Theme.of(context).colorScheme.primary,
textColor: currentTheme.isDark
? Theme.of(context).colorScheme.onSurfaceVariant
: Theme.of(context).colorScheme.onPrimary,
onPressed: () => Navigator.of(context).pop(),
),
)
],
),
);
@override
Widget footerWidget(BuildContext context) => SizedBox.shrink();
}