From 19537e07d3069d3f817dc48274165287d6b9778b Mon Sep 17 00:00:00 2001 From: Francois Date: Mon, 27 Oct 2025 20:26:36 +0200 Subject: [PATCH 1/4] feat(withdraw): append fiat value to amount and fee also decrease perimeter padding and increase vertical padding --- .../widgets/asset_amount_with_fiat.dart | 74 ++++++++++ .../withdraw_form/withdraw_form.dart | 135 ++++++++++++------ 2 files changed, 165 insertions(+), 44 deletions(-) create mode 100644 lib/shared/widgets/asset_amount_with_fiat.dart diff --git a/lib/shared/widgets/asset_amount_with_fiat.dart b/lib/shared/widgets/asset_amount_with_fiat.dart new file mode 100644 index 0000000000..4d5f7f2490 --- /dev/null +++ b/lib/shared/widgets/asset_amount_with_fiat.dart @@ -0,0 +1,74 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:komodo_defi_types/komodo_defi_types.dart'; +import 'package:komodo_ui_kit/komodo_ui_kit.dart'; +import 'package:web_dex/shared/utils/formatters.dart'; +import 'package:web_dex/shared/utils/utils.dart'; + +/// A widget that displays an asset amount with its fiat value in parentheses. +/// +/// Example output: "0.5 BTC ($15,234.50)" +/// +/// The fiat value is only shown if pricing data is available from the SDK. +class AssetAmountWithFiat extends StatelessWidget { + const AssetAmountWithFiat({ + required this.assetId, + required this.amount, + super.key, + this.style, + this.isSelectable = true, + this.isAutoScrollEnabled = true, + this.showCoinSymbol = true, + }); + + /// The asset ID to fetch pricing for + final AssetId assetId; + + /// The crypto amount to display + final Decimal amount; + + /// Text style for the main amount + final TextStyle? style; + + /// Whether the text should be selectable + final bool isSelectable; + + /// Whether to enable auto-scrolling for long text + final bool isAutoScrollEnabled; + + /// Whether to append the coin symbol to the amount + final bool showCoinSymbol; + + @override + Widget build(BuildContext context) { + final sdk = context.sdk; + final price = sdk.marketData.priceIfKnown(assetId); + + String displayText = amount.toString(); + if (showCoinSymbol) { + displayText = '$displayText ${assetId.id}'; + } + + // If no price available, just show the amount + var formattedFiat = ''; + if (price != null) { + final fiatValue = (price * amount).toDouble(); + formattedFiat = ' (${formatUsdValue(fiatValue)})'; + } + + final fullText = '$displayText$formattedFiat'; + + if (isAutoScrollEnabled) { + return AutoScrollText( + text: fullText, + style: style, + isSelectable: isSelectable, + textAlign: TextAlign.right, + ); + } + + return isSelectable + ? SelectableText(fullText, style: style, textAlign: TextAlign.right) + : Text(fullText, style: style, textAlign: TextAlign.right); + } +} diff --git a/lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart b/lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart index 0f05b5dd57..8dec2be687 100644 --- a/lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart +++ b/lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart @@ -2,26 +2,28 @@ import 'dart:async' show Timer; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:komodo_ui_kit/komodo_ui_kit.dart'; -import 'package:web_dex/generated/codegen_loader.g.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:komodo_defi_sdk/komodo_defi_sdk.dart'; import 'package:komodo_defi_types/komodo_defi_types.dart'; import 'package:komodo_ui/komodo_ui.dart'; +import 'package:komodo_ui_kit/komodo_ui_kit.dart'; +import 'package:web_dex/analytics/events/transaction_events.dart'; import 'package:web_dex/bloc/analytics/analytics_bloc.dart'; import 'package:web_dex/bloc/auth_bloc/auth_bloc.dart'; -import 'package:web_dex/analytics/events/transaction_events.dart'; +import 'package:web_dex/bloc/coins_bloc/asset_coin_extension.dart'; import 'package:web_dex/bloc/withdraw_form/withdraw_form_bloc.dart'; +import 'package:web_dex/generated/codegen_loader.g.dart'; import 'package:web_dex/mm2/mm2_api/rpc/base.dart'; import 'package:web_dex/model/text_error.dart'; import 'package:web_dex/model/wallet.dart'; +import 'package:web_dex/shared/utils/extensions/kdf_user_extensions.dart'; import 'package:web_dex/shared/utils/utils.dart'; +import 'package:web_dex/shared/widgets/asset_amount_with_fiat.dart'; import 'package:web_dex/shared/widgets/copied_text.dart' show CopiedTextV2; -import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/fill_form/fields/fill_form_memo.dart'; import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/fill_form/fields/fields.dart'; -import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/withdraw_form_header.dart'; +import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/fill_form/fields/fill_form_memo.dart'; import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/trezor_withdraw_progress_dialog.dart'; -import 'package:web_dex/shared/utils/extensions/kdf_user_extensions.dart'; +import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/withdraw_form_header.dart'; bool _isMemoSupportedProtocol(Asset asset) { final protocol = asset.protocol; @@ -302,49 +304,93 @@ class ZhtlcPreviewDelayNote extends StatelessWidget { class WithdrawPreviewDetails extends StatelessWidget { final WithdrawalPreview preview; + final double widthThreshold; + final double minPadding; + final double maxPadding; + + const WithdrawPreviewDetails({ + required this.preview, + super.key, + this.widthThreshold = 350, + this.minPadding = 2, + this.maxPadding = 16, + }); + + double _calculatePadding(double width) { + if (width >= widthThreshold) { + return maxPadding; + } - const WithdrawPreviewDetails({required this.preview, super.key}); + // Scale padding linearly based on width below threshold + final ratio = width / widthThreshold; + final scaledPadding = minPadding + (maxPadding - minPadding) * ratio; + + return scaledPadding.clamp(minPadding, maxPadding); + } @override Widget build(BuildContext context) { - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildTextRow( - LocaleKeys.amount.tr(), - preview.balanceChanges.netChange.toString(), - ), - const SizedBox(height: 8), - _buildTextRow(LocaleKeys.fee.tr(), preview.fee.formatTotal()), - const SizedBox(height: 8), - _buildRow( - LocaleKeys.recipientAddress.tr(), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - for (final recipient in preview.to) - CopiedTextV2(copiedValue: recipient, fontSize: 14), + final sdk = context.sdk; + + final assets = sdk.getSdkAsset(preview.coin); + final feeAssets = sdk.getSdkAsset(preview.fee.coin); + + return LayoutBuilder( + builder: (context, constraints) { + final padding = _calculatePadding(constraints.maxWidth); + + return Card( + child: Padding( + padding: EdgeInsets.all(padding), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildRow( + LocaleKeys.amount.tr(), + AssetAmountWithFiat( + assetId: assets.id, + amount: preview.balanceChanges.netChange, + isAutoScrollEnabled: true, + ), + ), + const SizedBox(height: 16), + _buildRow( + LocaleKeys.fee.tr(), + AssetAmountWithFiat( + assetId: feeAssets.id, + amount: preview.fee.totalFee, + isAutoScrollEnabled: true, + ), + ), + const SizedBox(height: 16), + _buildRow( + LocaleKeys.recipientAddress.tr(), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + for (final recipient in preview.to) + CopiedTextV2(copiedValue: recipient, fontSize: 14), + ], + ), + ), + if (preview.memo?.isNotEmpty ?? false) ...[ + const SizedBox(height: 16), + _buildRow( + LocaleKeys.memo.tr(), + Text( + preview.memo!, + textAlign: TextAlign.right, + softWrap: true, + overflow: TextOverflow.visible, + ), + ), ], - ), + ], ), - if (preview.memo != null) ...[ - const SizedBox(height: 8), - _buildTextRow(LocaleKeys.memo.tr(), preview.memo!), - ], - ], - ), - ), - ); - } - - Widget _buildTextRow(String label, String value) { - return _buildRow( - label, - AutoScrollText(text: value, textAlign: TextAlign.right), + ), + ); + }, ); } @@ -352,9 +398,10 @@ class WithdrawPreviewDetails extends StatelessWidget { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label), + Expanded(flex: 2, child: Text(label)), const SizedBox(width: 12), Expanded( + flex: 3, child: Align(alignment: Alignment.centerRight, child: value), ), ], From 89e62fc88b92beee30618259ef9d78dddbfdf1a2 Mon Sep 17 00:00:00 2001 From: Francois Date: Tue, 28 Oct 2025 10:36:17 +0200 Subject: [PATCH 2/4] style(withdraw): add vertical layout for devices with limited width --- .../withdraw_form/withdraw_form.dart | 85 ++++++++++++++++--- 1 file changed, 71 insertions(+), 14 deletions(-) diff --git a/lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart b/lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart index 8dec2be687..0cde3d7530 100644 --- a/lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart +++ b/lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart @@ -338,6 +338,7 @@ class WithdrawPreviewDetails extends StatelessWidget { return LayoutBuilder( builder: (context, constraints) { final padding = _calculatePadding(constraints.maxWidth); + final useRowLayout = constraints.maxWidth >= widthThreshold; return Card( child: Padding( @@ -345,46 +346,102 @@ class WithdrawPreviewDetails extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildRow( - LocaleKeys.amount.tr(), + if (useRowLayout) + _buildRow( + LocaleKeys.amount.tr(), + AssetAmountWithFiat( + assetId: assets.id, + amount: preview.balanceChanges.netChange, + isAutoScrollEnabled: true, + ), + ) + else ...[ + Text( + LocaleKeys.amount.tr(), + style: Theme.of(context).textTheme.labelLarge, + ), + const SizedBox(height: 4), AssetAmountWithFiat( assetId: assets.id, amount: preview.balanceChanges.netChange, isAutoScrollEnabled: true, ), - ), + ], const SizedBox(height: 16), - _buildRow( - LocaleKeys.fee.tr(), + if (useRowLayout) + _buildRow( + LocaleKeys.fee.tr(), + AssetAmountWithFiat( + assetId: feeAssets.id, + amount: preview.fee.totalFee, + isAutoScrollEnabled: true, + ), + ) + else ...[ + Text( + LocaleKeys.fee.tr(), + style: Theme.of(context).textTheme.labelLarge, + ), + const SizedBox(height: 4), AssetAmountWithFiat( assetId: feeAssets.id, amount: preview.fee.totalFee, isAutoScrollEnabled: true, ), - ), + ], const SizedBox(height: 16), - _buildRow( - LocaleKeys.recipientAddress.tr(), + if (useRowLayout) + _buildRow( + LocaleKeys.recipientAddress.tr(), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + for (final recipient in preview.to) + CopiedTextV2(copiedValue: recipient, fontSize: 14), + ], + ), + ) + else ...[ + Text( + LocaleKeys.recipientAddress.tr(), + style: Theme.of(context).textTheme.labelLarge, + ), + const SizedBox(height: 4), Column( - crossAxisAlignment: CrossAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ for (final recipient in preview.to) CopiedTextV2(copiedValue: recipient, fontSize: 14), ], ), - ), + ], if (preview.memo?.isNotEmpty ?? false) ...[ const SizedBox(height: 16), - _buildRow( - LocaleKeys.memo.tr(), + if (useRowLayout) + _buildRow( + LocaleKeys.memo.tr(), + Text( + preview.memo!, + textAlign: TextAlign.right, + softWrap: true, + overflow: TextOverflow.visible, + ), + ) + else ...[ + Text( + LocaleKeys.memo.tr(), + style: Theme.of(context).textTheme.labelLarge, + ), + const SizedBox(height: 4), Text( preview.memo!, - textAlign: TextAlign.right, + textAlign: TextAlign.left, softWrap: true, overflow: TextOverflow.visible, ), - ), + ], ], ], ), From fc2f90700ddd8b07e4be2ac6bea86f59cce48083 Mon Sep 17 00:00:00 2001 From: Nitride <77973576+CharlVS@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:55:38 +0100 Subject: [PATCH 3/4] Update lib/shared/widgets/asset_amount_with_fiat.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/shared/widgets/asset_amount_with_fiat.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/shared/widgets/asset_amount_with_fiat.dart b/lib/shared/widgets/asset_amount_with_fiat.dart index 4d5f7f2490..736ec67c76 100644 --- a/lib/shared/widgets/asset_amount_with_fiat.dart +++ b/lib/shared/widgets/asset_amount_with_fiat.dart @@ -50,11 +50,9 @@ class AssetAmountWithFiat extends StatelessWidget { } // If no price available, just show the amount - var formattedFiat = ''; - if (price != null) { - final fiatValue = (price * amount).toDouble(); - formattedFiat = ' (${formatUsdValue(fiatValue)})'; - } + final formattedFiat = price != null + ? ' (${formatUsdValue((price * amount).toDouble())})' + : ''; final fullText = '$displayText$formattedFiat'; From 0f4bc9632f2a2eea6b2eb78ac92171baac1e8d60 Mon Sep 17 00:00:00 2001 From: Francois Date: Tue, 28 Oct 2025 17:45:35 +0200 Subject: [PATCH 4/4] refactor(withdraw): use absolute value of netchange in preview details --- .../wallet/coin_details/withdraw_form/withdraw_form.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart b/lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart index 0cde3d7530..2999029a39 100644 --- a/lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart +++ b/lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart @@ -311,7 +311,7 @@ class WithdrawPreviewDetails extends StatelessWidget { const WithdrawPreviewDetails({ required this.preview, super.key, - this.widthThreshold = 350, + this.widthThreshold = 400, this.minPadding = 2, this.maxPadding = 16, }); @@ -351,7 +351,10 @@ class WithdrawPreviewDetails extends StatelessWidget { LocaleKeys.amount.tr(), AssetAmountWithFiat( assetId: assets.id, - amount: preview.balanceChanges.netChange, + // netchange for withdrawals is expected to be negative + // so we display the absolute value here to avoid + // confusion with the negative sign + amount: preview.balanceChanges.netChange.abs(), isAutoScrollEnabled: true, ), )