Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions lib/shared/widgets/asset_amount_with_fiat.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
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.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: make it clear that the fiat value isn't passable and is automatically calculated from the SDK.

///
/// 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 {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: prefer a name that better aligns with Flutter's naming conventions, making it clear that it is a widget. I assumed AssetAmountWithFiat was a data class when I first read it.

e.g. AssetAmountWithFiatDisplay, AssetAmountWithFiatText, or otherwise if there's an existing widget with a backwards-compatible signature, refactor to add an additional parameter to specify if the fiat value should be shown (ignore if such a widget doesn't exist)

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
final formattedFiat = price != null
? ' (${formatUsdValue((price * amount).toDouble())})'
: '';

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);
}
}
195 changes: 151 additions & 44 deletions lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -302,59 +304,164 @@ 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});
const WithdrawPreviewDetails({
required this.preview,
super.key,
this.widthThreshold = 400,
this.minPadding = 2,
this.maxPadding = 16,
});

double _calculatePadding(double width) {
if (width >= widthThreshold) {
return maxPadding;
}

// 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);
final useRowLayout = constraints.maxWidth >= widthThreshold;

return Card(
child: Padding(
padding: EdgeInsets.all(padding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (useRowLayout)
_buildRow(
LocaleKeys.amount.tr(),
AssetAmountWithFiat(
assetId: assets.id,
// 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,
),
)
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),
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),
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.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),
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.left,
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),
),
);
},
);
}

Widget _buildRow(String label, Widget value) {
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),
),
],
Expand Down
Loading