What changed, and why it matters
This commit is a routine UI refactor. It removes the app's own transaction-details popup and starts using a shared one from a related 'wallet-core' library. It also adds a couple of new translated labels, such as 'Change' and 'Copied to clipboard'. There is nothing in the diff that looks like a security fix or vulnerability.
No security action required. Treat as normal code-review/QA for UI behavior and localization completeness.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change deletes the local TxDetailsDialog implementation in lib/widgets/tx_details.dart and replaces it with a thin adapter that calls wallet_ui.TxDetailsDialog.show from the wallet-core monorepo. A new helper showTxDetailsDialog in wallet_core_glue.dart resolves the neutral display TxDetails to the engine wallet’s wallet_domain TxDetails by tx hash, then hands the richer object to the shared dialog. Localization files gain txDetailsChangeRecipientLabel and copiedToClipboard strings. pubspec.yaml/lock add the wallet_ui path dependency. No cryptographic, input-validation, or permission changes are visible.
Changed components
lib/widgets/tx_details.dartlib/wallet_core_glue.dartlib/screens/wallet_home.dartpubspec.yamlpubspec.lockInspect captured patch +78 / −219
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 9e354dc..e80f21e 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -155,6 +155,8 @@
"txDetailsConfirmationsLabel": "Confirmations",
"txDetailsViewKeyLabel": "View Key",
"txDetailsRecipientsLabel": "Recipients",
+ "txDetailsChangeRecipientLabel": "Change",
+ "copiedToClipboard": "Copied to clipboard",
"lwsKeysTitle": "LWS Keys",
"lwsKeysPrimaryAddress": "Primary Address",
"lwsKeysRestoreHeight": "Restore Height",
diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart
index 743188c..da7dd5f 100644
--- a/lib/l10n/app_localizations.dart
+++ b/lib/l10n/app_localizations.dart
@@ -939,6 +939,18 @@ abstract class AppLocalizations {
/// **'Recipients'**
String get txDetailsRecipientsLabel;
+ /// No description provided for @txDetailsChangeRecipientLabel.
+ ///
+ /// In en, this message translates to:
+ /// **'Change'**
+ String get txDetailsChangeRecipientLabel;
+
+ /// No description provided for @copiedToClipboard.
+ ///
+ /// In en, this message translates to:
+ /// **'Copied to clipboard'**
+ String get copiedToClipboard;
+
/// No description provided for @lwsKeysTitle.
///
/// In en, this message translates to:
diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart
index 35a7a37..8b7c438 100644
--- a/lib/l10n/app_localizations_en.dart
+++ b/lib/l10n/app_localizations_en.dart
@@ -456,6 +456,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get txDetailsRecipientsLabel => 'Recipients';
+ @override
+ String get txDetailsChangeRecipientLabel => 'Change';
+
+ @override
+ String get copiedToClipboard => 'Copied to clipboard';
+
@override
String get lwsKeysTitle => 'LWS Keys';
diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart
index 76fcd8b..cbaf425 100644
--- a/lib/l10n/app_localizations_pt.dart
+++ b/lib/l10n/app_localizations_pt.dart
@@ -456,6 +456,12 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get txDetailsRecipientsLabel => 'Destinatários';
+ @override
+ String get txDetailsChangeRecipientLabel => 'Troco';
+
+ @override
+ String get copiedToClipboard => 'Copiado para a área de transferência';
+
@override
String get lwsKeysTitle => 'Chaves do LWS';
diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb
index 834f921..d846eca 100644
--- a/lib/l10n/app_pt.arb
+++ b/lib/l10n/app_pt.arb
@@ -155,6 +155,8 @@
"txDetailsConfirmationsLabel": "Confirmações",
"txDetailsViewKeyLabel": "Chave de Visualização",
"txDetailsRecipientsLabel": "Destinatários",
+ "txDetailsChangeRecipientLabel": "Troco",
+ "copiedToClipboard": "Copiado para a área de transferência",
"lwsKeysTitle": "Chaves do LWS",
"lwsKeysPrimaryAddress": "Endereço Primário",
"lwsKeysRestoreHeight": "Bloco de Restauração",
diff --git a/lib/screens/wallet_home.dart b/lib/screens/wallet_home.dart
index c100607..1fb9050 100644
--- a/lib/screens/wallet_home.dart
+++ b/lib/screens/wallet_home.dart
@@ -16,7 +16,6 @@ import 'package:skylight_wallet/models/app_wallet.dart';
import 'package:skylight_wallet/wallet_core_glue.dart';
import 'package:skylight_wallet/consts.dart' as consts;
import 'package:skylight_wallet/widgets/wallet_navigation_bar.dart';
-import 'package:skylight_wallet/widgets/tx_details.dart';
enum LwsConnectionStatus { disconnected, connecting, connected }
@@ -185,7 +184,7 @@ class _WalletHomeScreenState extends State<WalletHomeScreen> {
}
void _showTxDetails(TxDetails txDetails) {
- TxDetailsDialog.show(context, txDetails);
+ showTxDetailsDialog(context, txDetails);
}
void _showTxSuccessToast() {
diff --git a/lib/wallet_core_glue.dart b/lib/wallet_core_glue.dart
index 6cb568d..7c79d5c 100644
--- a/lib/wallet_core_glue.dart
+++ b/lib/wallet_core_glue.dart
@@ -7,6 +7,8 @@ import 'package:provider/provider.dart';
import 'package:skylight_wallet/models/app_wallet.dart';
import 'package:skylight_wallet/models/fiat_rate_model.dart';
import 'package:skylight_wallet/models/monero_wallet_adapter.dart';
+import 'package:skylight_wallet/models/wallet_types.dart' show TxDetails;
+import 'package:skylight_wallet/widgets/tx_details.dart' show TxDetailsDialog;
import 'package:skylight_wallet/periodic_tasks.dart' show backgroundDispatcher;
import 'package:skylight_wallet/services/foreground_sync_service.dart' show foregroundSyncCallback;
import 'package:skylight_wallet/services/notifications_service.dart';
@@ -143,6 +145,17 @@ AppWallet appWalletOf(BuildContext context, {bool listen = false}) {
);
}
+/// Shows the shared tx-details dialog (`wallet_ui`, D24) for a neutral [tx] from
+/// the tx list. Bridges to the engine wallet + its wallet_domain TxDetails, which
+/// carry the exact BigInt amounts the neutral display type rounds to double.
+void showTxDetailsDialog(BuildContext context, TxDetails tx) {
+ final wallet =
+ Provider.of<WalletManager>(context, listen: false).getWallet('XMR') as MoneroWallet;
+ final matches = wallet.txHistory.where((t) => t.hash == tx.hash);
+ if (matches.isEmpty) return;
+ TxDetailsDialog.show(context, wallet, matches.first);
+}
+
/// Sets the wallet-encryption password (desktop-entered). Mobile mints a random
/// one at restore/create time instead.
void setWalletPassword(BuildContext context, String password) {
diff --git a/lib/widgets/tx_details.dart b/lib/widgets/tx_details.dart
index 6617ac6..f38ac04 100644
--- a/lib/widgets/tx_details.dart
+++ b/lib/widgets/tx_details.dart
@@ -1,226 +1,33 @@
import 'package:flutter/material.dart';
-import 'package:intl/intl.dart';
+
import 'package:skylight_wallet/l10n/app_localizations.dart';
-import 'package:skylight_wallet/models/wallet_types.dart';
-import 'package:skylight_wallet/util/secure_clipboard.dart';
+import 'package:wallet_domain/wallet_domain.dart' show CryptoWallet, TxDetails;
+import 'package:wallet_ui/wallet_ui.dart' as ui;
+/// The tx-details popup lives in wallet-core (`wallet_ui`, D24), localization-
+/// agnostic. This adapter keeps the app's call site unchanged and supplies
+/// skylight's translated strings.
class TxDetailsDialog {
- static void show(BuildContext context, TxDetails txDetails) {
- showDialog(
- context: context,
- builder: (context) => _TxDetailsDialog(txDetails: txDetails),
- );
- }
-}
-
-/// Splits [s] into [lines] newline-separated segments of near-equal length
-/// (sizes differ by at most one char).
-String _chunkIntoLines(String s, int lines) {
- if (lines <= 1 || s.length <= lines) return s;
- final baseSize = s.length ~/ lines;
- final remainder = s.length % lines;
- final buffer = StringBuffer();
- var start = 0;
- for (var i = 0; i < lines; i++) {
- final size = baseSize + (i < remainder ? 1 : 0);
- final end = start + size;
- if (i > 0) buffer.write('\n');
- buffer.write(s.substring(start, end));
- start = end;
- }
- return buffer.toString();
-}
-
-class _TxDetailsDialog extends StatelessWidget {
- final TxDetails txDetails;
-
- const _TxDetailsDialog({required this.txDetails});
-
- @override
- Widget build(BuildContext context) {
+ static void show(BuildContext context, CryptoWallet wallet, TxDetails txDetails) {
final i18n = AppLocalizations.of(context)!;
- final locale = Localizations.localeOf(context);
- final amountSent = txDetails.amount.toString();
- final fee = txDetails.fee.toString();
-
- final dateTime = DateTime.fromMillisecondsSinceEpoch(txDetails.timestamp * 1000);
-
- final dateFormatted = DateFormat.yMMMMd(locale.toString()).format(dateTime);
- final timeFormatted = DateFormat.jm(locale.toString()).format(dateTime);
-
- final screenWidth = MediaQuery.of(context).size.width;
- final dialogWidth = screenWidth.clamp(0.0, 500.0);
-
- return AlertDialog(
- constraints: BoxConstraints.tightFor(width: dialogWidth),
- insetPadding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
- title: Text(i18n.txDetailsTitle),
- content: SingleChildScrollView(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- spacing: 10,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.start,
- spacing: 20,
- children: [
- Text(i18n.txDetailsHashLabel, style: TextStyle(fontWeight: FontWeight.bold)),
- Flexible(
- child: Container(
- constraints: BoxConstraints(maxWidth: 280),
- child: GestureDetector(
- child: Text(
- _chunkIntoLines(txDetails.hash, 3),
- textAlign: TextAlign.end,
- style: TextStyle(fontFamily: 'monospace'),
- softWrap: true,
- ),
- onTap: () => SecureClipboard.copy(txDetails.hash),
- ),
- ),
- ),
- ],
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.start,
- spacing: 20,
- children: [
- Text(i18n.amount, style: TextStyle(fontWeight: FontWeight.bold)),
- Flexible(child: Text('$amountSent XMR', textAlign: TextAlign.end, softWrap: true)),
- ],
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.start,
- spacing: 20,
- children: [
- Text(i18n.networkFee, style: TextStyle(fontWeight: FontWeight.bold)),
- Flexible(child: Text('$fee XMR', textAlign: TextAlign.end, softWrap: true)),
- ],
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.start,
- spacing: 20,
- children: [
- Text(i18n.txDetailsTimeAndDateLabel, style: TextStyle(fontWeight: FontWeight.bold)),
- Flexible(
- child: Text(
- '$dateFormatted $timeFormatted',
- textAlign: TextAlign.end,
- softWrap: true,
- ),
- ),
- ],
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.start,
- spacing: 20,
- children: [
- Text(
- i18n.txDetailsConfirmationHeightLabel,
- style: TextStyle(fontWeight: FontWeight.bold),
- ),
- Flexible(
- child: Text(
- txDetails.height == -1 ? '0' : txDetails.height.toString(),
- textAlign: TextAlign.end,
- softWrap: true,
- ),
- ),
- ],
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.start,
- spacing: 20,
- children: [
- Text(
- i18n.txDetailsConfirmationsLabel,
- style: TextStyle(fontWeight: FontWeight.bold),
- ),
- Flexible(
- child: Text(
- txDetails.confirmations.toString(),
- textAlign: TextAlign.end,
- softWrap: true,
- ),
- ),
- ],
- ),
- if (txDetails.key.isNotEmpty)
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.start,
- spacing: 20,
- children: [
- Text(i18n.txDetailsViewKeyLabel, style: TextStyle(fontWeight: FontWeight.bold)),
- Flexible(
- child: Container(
- constraints: BoxConstraints(maxWidth: 280),
- child: GestureDetector(
- child: Text(
- txDetails.key,
- textAlign: TextAlign.end,
- style: TextStyle(fontFamily: 'monospace'),
- softWrap: true,
- ),
- onTap: () => SecureClipboard.copy(txDetails.key),
- ),
- ),
- ),
- ],
- ),
- if (txDetails.recipients.isNotEmpty)
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.start,
- spacing: 20,
- children: [
- Text(
- i18n.txDetailsRecipientsLabel,
- style: TextStyle(fontWeight: FontWeight.bold),
- ),
- Expanded(
- child: ListView.separated(
- shrinkWrap: true,
- itemCount: txDetails.recipients.length,
- separatorBuilder: (context, index) => SizedBox(height: 10),
- itemBuilder: (context, index) {
- final recipient = txDetails.recipients[index];
- final amountStr = recipient.amount.toString();
-
- return Column(
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- GestureDetector(
- child: Container(
- constraints: BoxConstraints(maxWidth: 280),
- child: Text(
- _chunkIntoLines(recipient.address, 3),
- style: TextStyle(fontFamily: 'monospace'),
- softWrap: true,
- textAlign: TextAlign.end,
- ),
- ),
- onTap: () => SecureClipboard.copy(recipient.address),
- ),
- Text('$amountStr XMR', softWrap: true),
- ],
- );
- },
- ),
- ),
- ],
- ),
- ],
- ),
+ ui.TxDetailsDialog.show(
+ context,
+ wallet,
+ txDetails,
+ ui.TxDetailsLabels(
+ title: i18n.txDetailsTitle,
+ hash: i18n.txDetailsHashLabel,
+ amount: i18n.amount,
+ networkFee: i18n.networkFee,
+ timeAndDate: i18n.txDetailsTimeAndDateLabel,
+ confirmationHeight: i18n.txDetailsConfirmationHeightLabel,
+ confirmations: i18n.txDetailsConfirmationsLabel,
+ viewKey: i18n.txDetailsViewKeyLabel,
+ recipients: i18n.txDetailsRecipientsLabel,
+ changeRecipient: i18n.txDetailsChangeRecipientLabel,
+ close: i18n.close,
+ copied: i18n.copiedToClipboard,
),
- actions: [TextButton(onPressed: () => Navigator.of(context).pop(), child: Text(i18n.close))],
);
}
}
diff --git a/pubspec.lock b/pubspec.lock
index 3977f95..9e83217 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1271,6 +1271,13 @@ packages:
relative: true
source: path
version: "0.1.0"
+ wallet_ui:
+ dependency: "direct main"
+ description:
+ path: "../wallet-core/packages/wallet_ui"
+ relative: true
+ source: path
+ version: "0.1.0"
web:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 99fa181..fc41b1f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -92,6 +92,11 @@ dependencies:
url: https://github.com/MAGICGrants/wallet-core
ref: f674cb907ab971126a88dcc78c33b549b630f58f
path: packages/wallet_fiat
+ wallet_ui:
+ git:
+ url: https://github.com/MAGICGrants/wallet-core
+ ref: f674cb907ab971126a88dcc78c33b549b630f58f
+ path: packages/wallet_ui
cupertino_icons: 1.0.8
path: 1.9.1
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.