fixes
This commit is contained in:
@@ -7,7 +7,7 @@ import 'package:pshared/models/recipient/payment_method_draft.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/feilds/email.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/header.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/feilds/name.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/payment_methods/panel.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/payment_methods/panel/panel.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/payment_methods/selector_row.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/save_button.dart';
|
||||
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/methods/data.dart';
|
||||
import 'package:pshared/models/payment/type.dart';
|
||||
import 'package:pshared/models/recipient/payment_method_draft.dart';
|
||||
|
||||
import 'package:pweb/pages/payment_methods/form.dart';
|
||||
import 'package:pweb/pages/payment_methods/icon.dart';
|
||||
import 'package:pweb/widgets/dialogs/confirmation_dialog.dart';
|
||||
import 'package:pweb/models/state/control_state.dart';
|
||||
import 'package:pweb/models/state/visibility.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentMethodPanel extends StatelessWidget {
|
||||
final PaymentType selectedType;
|
||||
final int selectedIndex;
|
||||
final List<RecipientMethodDraft> entries;
|
||||
final ValueChanged<int> onRemove;
|
||||
final void Function(int, PaymentMethodData) onChanged;
|
||||
final ControlState editState;
|
||||
final VisibilityState deleteVisibility;
|
||||
|
||||
final double padding;
|
||||
|
||||
const PaymentMethodPanel({
|
||||
super.key,
|
||||
required this.selectedType,
|
||||
required this.selectedIndex,
|
||||
required this.entries,
|
||||
required this.onRemove,
|
||||
required this.onChanged,
|
||||
this.editState = ControlState.enabled,
|
||||
this.deleteVisibility = VisibilityState.visible,
|
||||
this.padding = 16,
|
||||
});
|
||||
|
||||
Future<void> _confirmDelete(BuildContext context, VoidCallback onConfirmed) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final confirmed = await showConfirmationDialog(
|
||||
context: context,
|
||||
title: l10n.delete,
|
||||
message: l10n.deletePaymentConfirmation,
|
||||
confirmLabel: l10n.delete,
|
||||
);
|
||||
if (confirmed) {
|
||||
onConfirmed();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final label = l10n.paymentMethodDetails;
|
||||
final entry = selectedIndex >= 0 && selectedIndex < entries.length
|
||||
? entries[selectedIndex]
|
||||
: null;
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 3000),
|
||||
padding: EdgeInsets.all(padding),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onSecondary,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: theme.colorScheme.onSurface.withAlpha(30)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
iconForPaymentType(selectedType),
|
||||
size: 18,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (entry != null && deleteVisibility == VisibilityState.visible)
|
||||
TextButton.icon(
|
||||
onPressed: () => _confirmDelete(context, () => onRemove(selectedIndex)),
|
||||
icon: Icon(Icons.delete, color: theme.colorScheme.error),
|
||||
label: Text(
|
||||
l10n.delete,
|
||||
style: TextStyle(color: theme.colorScheme.error),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (entry != null)
|
||||
PaymentMethodForm(
|
||||
key: ValueKey('${selectedType.name}-${entry.existing?.id ?? selectedIndex}-form'),
|
||||
selectedType: selectedType,
|
||||
initialData: entry.data,
|
||||
isEditable: editState == ControlState.enabled,
|
||||
onChanged: (data) {
|
||||
if (data == null) return;
|
||||
onChanged(selectedIndex, data);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/methods/data.dart';
|
||||
import 'package:pshared/models/payment/type.dart';
|
||||
import 'package:pshared/models/recipient/payment_method_draft.dart';
|
||||
|
||||
import 'package:pweb/models/state/control_state.dart';
|
||||
import 'package:pweb/models/state/visibility.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/payment_methods/panel/panel_container.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/payment_methods/panel/panel_entry_form.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/payment_methods/panel/panel_header.dart';
|
||||
import 'package:pweb/utils/payment/method_delete_confirmation.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentMethodPanel extends StatelessWidget {
|
||||
final PaymentType selectedType;
|
||||
final int selectedIndex;
|
||||
final List<RecipientMethodDraft> entries;
|
||||
final ValueChanged<int>? onRemove;
|
||||
final void Function(int, PaymentMethodData)? onChanged;
|
||||
final ControlState editState;
|
||||
final VisibilityState deleteVisibility;
|
||||
final double padding;
|
||||
|
||||
const PaymentMethodPanel({
|
||||
super.key,
|
||||
required this.selectedType,
|
||||
required this.selectedIndex,
|
||||
required this.entries,
|
||||
this.onRemove,
|
||||
this.onChanged,
|
||||
this.editState = ControlState.enabled,
|
||||
this.deleteVisibility = VisibilityState.visible,
|
||||
this.padding = 16,
|
||||
}) : assert(editState == ControlState.disabled || onChanged != null),
|
||||
assert(deleteVisibility == VisibilityState.hidden || onRemove != null);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final entry = selectedIndex >= 0 && selectedIndex < entries.length
|
||||
? entries[selectedIndex]
|
||||
: null;
|
||||
final showDelete =
|
||||
entry != null &&
|
||||
deleteVisibility == VisibilityState.visible &&
|
||||
onRemove != null;
|
||||
|
||||
return PaymentMethodPanelContainer(
|
||||
padding: padding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
PaymentMethodPanelHeader(
|
||||
selectedType: selectedType,
|
||||
title: l10n.paymentMethodDetails,
|
||||
deleteLabel: l10n.delete,
|
||||
showDelete: showDelete,
|
||||
onDelete: showDelete
|
||||
? () => confirmPaymentMethodDelete(
|
||||
context,
|
||||
() => onRemove!(selectedIndex),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (entry != null)
|
||||
PaymentMethodPanelEntryForm(
|
||||
selectedType: selectedType,
|
||||
selectedIndex: selectedIndex,
|
||||
entry: entry,
|
||||
isEditable: editState == ControlState.enabled,
|
||||
onChanged: onChanged == null
|
||||
? null
|
||||
: (data) => onChanged!(selectedIndex, data),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class PaymentMethodPanelContainer extends StatelessWidget {
|
||||
final double padding;
|
||||
final Widget child;
|
||||
|
||||
const PaymentMethodPanelContainer({
|
||||
super.key,
|
||||
required this.padding,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 3000),
|
||||
padding: EdgeInsets.all(padding),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onSecondary,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: theme.colorScheme.onSurface.withAlpha(30)),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/methods/data.dart';
|
||||
import 'package:pshared/models/payment/type.dart';
|
||||
import 'package:pshared/models/recipient/payment_method_draft.dart';
|
||||
|
||||
import 'package:pweb/pages/payment_methods/form.dart';
|
||||
|
||||
|
||||
class PaymentMethodPanelEntryForm extends StatelessWidget {
|
||||
final PaymentType selectedType;
|
||||
final int selectedIndex;
|
||||
final RecipientMethodDraft entry;
|
||||
final bool isEditable;
|
||||
final ValueChanged<PaymentMethodData>? onChanged;
|
||||
|
||||
const PaymentMethodPanelEntryForm({
|
||||
super.key,
|
||||
required this.selectedType,
|
||||
required this.selectedIndex,
|
||||
required this.entry,
|
||||
required this.isEditable,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PaymentMethodForm(
|
||||
key: ValueKey(
|
||||
'${selectedType.name}-${entry.existing?.id ?? selectedIndex}-form',
|
||||
),
|
||||
selectedType: selectedType,
|
||||
initialData: entry.data,
|
||||
isEditable: isEditable,
|
||||
onChanged: (data) {
|
||||
if (data == null) return;
|
||||
onChanged?.call(data);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/type.dart';
|
||||
|
||||
import 'package:pweb/pages/payment_methods/icon.dart';
|
||||
|
||||
|
||||
class PaymentMethodPanelHeader extends StatelessWidget {
|
||||
final PaymentType selectedType;
|
||||
final String title;
|
||||
final String deleteLabel;
|
||||
final bool showDelete;
|
||||
final VoidCallback? onDelete;
|
||||
|
||||
const PaymentMethodPanelHeader({
|
||||
super.key,
|
||||
required this.selectedType,
|
||||
required this.title,
|
||||
required this.deleteLabel,
|
||||
required this.showDelete,
|
||||
this.onDelete,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
iconForPaymentType(selectedType),
|
||||
size: 18,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showDelete && onDelete != null)
|
||||
TextButton.icon(
|
||||
onPressed: onDelete,
|
||||
icon: Icon(Icons.delete, color: theme.colorScheme.error),
|
||||
label: Text(
|
||||
deleteLabel,
|
||||
style: TextStyle(color: theme.colorScheme.error),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/controllers/balance_mask/wallets.dart';
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/utils/money_display.dart';
|
||||
|
||||
|
||||
class BalanceAmount extends StatelessWidget {
|
||||
final Wallet wallet;
|
||||
@@ -25,6 +28,13 @@ class BalanceAmount extends StatelessWidget {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final currencyBalance = currencyCodeToSymbol(wallet.currency);
|
||||
final formattedBalance = formatMoneyUi(
|
||||
context,
|
||||
Money(
|
||||
amount: amountToString(wallet.balance),
|
||||
currency: currencyCodeToString(wallet.currency),
|
||||
),
|
||||
);
|
||||
final wallets = context.watch<WalletsController>();
|
||||
final isMasked = wallets.isBalanceMasked(wallet.id);
|
||||
|
||||
@@ -32,9 +42,7 @@ class BalanceAmount extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
isMasked
|
||||
? '•••• $currencyBalance'
|
||||
: '${amountToString(wallet.balance)} $currencyBalance',
|
||||
isMasked ? '•••• $currencyBalance' : formattedBalance,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: textTheme.headlineMedium?.copyWith(
|
||||
|
||||
@@ -6,8 +6,8 @@ import 'package:pshared/models/payment/wallet.dart';
|
||||
import 'package:pweb/models/dashboard/balance_item.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add/card.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/source/card.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/source/cards/ledger.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/source/cards/wallet.dart';
|
||||
|
||||
|
||||
class BalanceCarouselCardItem extends StatelessWidget {
|
||||
@@ -29,9 +29,9 @@ class BalanceCarouselCardItem extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final card = switch (item) {
|
||||
WalletBalanceItem(:final wallet) => WalletCard(
|
||||
WalletBalanceItem(:final wallet) => BalanceSourceCard.wallet(
|
||||
wallet: wallet,
|
||||
onTopUp: () => onTopUp(wallet),
|
||||
onAddFunds: () => onTopUp(wallet),
|
||||
onTap: () => onWalletTap(wallet),
|
||||
),
|
||||
LedgerBalanceItem(:final account) => LedgerAccountCard(
|
||||
|
||||
@@ -23,7 +23,7 @@ class LedgerBalanceAmount extends StatelessWidget {
|
||||
final isMasked = controller.isBalanceMasked(account.ledgerAccountRef);
|
||||
final balance = isMasked
|
||||
? LedgerBalanceFormatter.formatMasked(account)
|
||||
: LedgerBalanceFormatter.format(account);
|
||||
: LedgerBalanceFormatter.format(context, account);
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
@@ -61,7 +61,7 @@ class BalanceSourceCard extends StatelessWidget {
|
||||
? null
|
||||
: wallet.network!.localizedName(context);
|
||||
final symbol = wallet.tokenSymbol?.trim();
|
||||
final copyState = _copyController.wallet(wallet.depositAddress);
|
||||
final copyState = _copyController.wallet(context, wallet.depositAddress);
|
||||
|
||||
return BalanceSourceCardLayout(
|
||||
title: wallet.name,
|
||||
@@ -105,7 +105,7 @@ class BalanceSourceCard extends StatelessWidget {
|
||||
final badge = account.currency.trim().isEmpty
|
||||
? null
|
||||
: account.currency.toUpperCase();
|
||||
final copyState = _copyController.ledger(accountCode);
|
||||
final copyState = _copyController.ledger(context, accountCode);
|
||||
|
||||
return BalanceSourceCardLayout(
|
||||
title: title,
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/source/card.dart';
|
||||
|
||||
|
||||
class WalletCard extends StatelessWidget {
|
||||
final Wallet wallet;
|
||||
final VoidCallback onTopUp;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const WalletCard({
|
||||
super.key,
|
||||
required this.wallet,
|
||||
required this.onTopUp,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BalanceSourceCard.wallet(
|
||||
wallet: wallet,
|
||||
onTap: onTap,
|
||||
onAddFunds: onTopUp,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,9 @@ class PaymentAmountField extends StatelessWidget {
|
||||
decoration: InputDecoration(
|
||||
labelText: loc.amount,
|
||||
border: const OutlineInputBorder(),
|
||||
prefixText: symbol == null ? null : '$symbol\u00A0',
|
||||
prefixText: symbol == null
|
||||
? null
|
||||
: withTrailingNonBreakingSpace(symbol),
|
||||
),
|
||||
onChanged: ui.handleChanged,
|
||||
),
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import 'package:pshared/models/asset.dart';
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
import 'package:pweb/controllers/payouts/multiple_payouts.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
String moneyLabel(Money? money) {
|
||||
if (money == null) return 'N/A';
|
||||
final amount = parseMoneyAmount(money.amount, fallback: double.nan);
|
||||
if (amount.isNaN) return '${money.amount} ${money.currency}';
|
||||
try {
|
||||
return assetToString(
|
||||
Asset(currency: currencyStringToCode(money.currency), amount: amount),
|
||||
);
|
||||
} catch (_) {
|
||||
return '${money.amount} ${money.currency}';
|
||||
}
|
||||
}
|
||||
|
||||
String sentAmountLabel(MultiplePayoutsController controller) {
|
||||
final requested = controller.requestedSentAmount;
|
||||
final sourceDebit = controller.aggregateDebitAmount;
|
||||
|
||||
if (requested == null && sourceDebit == null) return 'N/A';
|
||||
if (sourceDebit != null) return moneyLabel(sourceDebit);
|
||||
return moneyLabel(requested);
|
||||
}
|
||||
|
||||
String feeLabel(MultiplePayoutsController controller, AppLocalizations l10n) {
|
||||
final fee = controller.aggregateFeeAmount;
|
||||
if (fee == null) return l10n.noFee;
|
||||
return moneyLabel(fee);
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/controllers/payouts/multiple_payouts.dart';
|
||||
import 'package:pweb/models/dashboard/summary_values.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/multiple/panels/source_quote/helpers.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/summary/widget.dart';
|
||||
import 'package:pweb/utils/money_display.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class SourceQuoteSummary extends StatelessWidget {
|
||||
const SourceQuoteSummary({
|
||||
super.key,
|
||||
@@ -18,12 +22,27 @@ class SourceQuoteSummary extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return PaymentSummary(
|
||||
spacing: spacing,
|
||||
values: PaymentSummaryValues(
|
||||
fee: feeLabel(controller, AppLocalizations.of(context)!),
|
||||
recipientReceives: moneyLabel(controller.aggregateSettlementAmount),
|
||||
total: moneyLabel(controller.aggregateDebitAmount),
|
||||
fee: controller.aggregateFeeAmount == null
|
||||
? l10n.noFee
|
||||
: formatMoneyUiWithL10n(
|
||||
l10n,
|
||||
controller.aggregateFeeAmount,
|
||||
separator: nonBreakingSpace,
|
||||
),
|
||||
recipientReceives: formatMoneyUiWithL10n(
|
||||
l10n,
|
||||
controller.aggregateSettlementAmount,
|
||||
separator: nonBreakingSpace,
|
||||
),
|
||||
total: formatMoneyUiWithL10n(
|
||||
l10n,
|
||||
controller.aggregateDebitAmount,
|
||||
separator: nonBreakingSpace,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/asset.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/utils/money_display.dart';
|
||||
|
||||
|
||||
class PaymentSummaryRow extends StatelessWidget {
|
||||
@@ -11,8 +12,8 @@ class PaymentSummaryRow extends StatelessWidget {
|
||||
final TextStyle? style;
|
||||
|
||||
const PaymentSummaryRow({
|
||||
super.key,
|
||||
required this.labelFactory,
|
||||
super.key,
|
||||
required this.labelFactory,
|
||||
required this.asset,
|
||||
this.value,
|
||||
this.style,
|
||||
@@ -20,8 +21,7 @@ class PaymentSummaryRow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final formatted = value ??
|
||||
(asset == null ? 'N/A' : assetToString(asset!));
|
||||
final formatted = value ?? formatAssetUi(context, asset);
|
||||
return Text(labelFactory(formatted), style: style);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/methods/data.dart';
|
||||
import 'package:pshared/models/recipient/payment_method_draft.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/payment_methods/panel/panel.dart';
|
||||
|
||||
import 'package:pweb/pages/address_book/form/widgets/payment_methods/panel.dart';
|
||||
import 'package:pweb/pages/payout_page/send/widgets/payment_info/header.dart';
|
||||
import 'package:pweb/models/state/control_state.dart';
|
||||
import 'package:pweb/models/state/visibility.dart';
|
||||
@@ -40,8 +40,6 @@ class PaymentInfoManualDetailsSection extends StatelessWidget {
|
||||
selectedType: data.type,
|
||||
selectedIndex: 0,
|
||||
entries: [entry],
|
||||
onRemove: (_) {},
|
||||
onChanged: (_, ignored) {},
|
||||
editState: ControlState.disabled,
|
||||
deleteVisibility: VisibilityState.hidden,
|
||||
),
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/type.dart';
|
||||
import 'package:pshared/models/recipient/payment_method_draft.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/payment_methods/panel/panel.dart';
|
||||
|
||||
import 'package:pweb/pages/address_book/form/widgets/payment_methods/panel.dart';
|
||||
import 'package:pweb/pages/address_book/form/widgets/payment_methods/selector_row.dart';
|
||||
import 'package:pweb/pages/payout_page/send/widgets/payment_info/details_builder.dart';
|
||||
import 'package:pweb/pages/payout_page/send/widgets/payment_info/header.dart';
|
||||
@@ -78,8 +78,6 @@ class PaymentInfoMethodsSection extends StatelessWidget {
|
||||
selectedType: state.selectedType,
|
||||
selectedIndex: state.selectedIndex!,
|
||||
entries: state.selectedEntries,
|
||||
onRemove: (_) {},
|
||||
onChanged: (_, _) {},
|
||||
editState: ControlState.disabled,
|
||||
deleteVisibility: VisibilityState.hidden,
|
||||
),
|
||||
|
||||
@@ -1,30 +1,16 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:pshared/models/ledger/account.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
import 'package:pweb/utils/money_display.dart';
|
||||
|
||||
|
||||
class LedgerBalanceFormatter {
|
||||
const LedgerBalanceFormatter._();
|
||||
|
||||
static String format(LedgerAccount account) {
|
||||
final money = account.balance?.balance;
|
||||
if (money == null) return '--';
|
||||
|
||||
final amount = parseMoneyAmount(money.amount, fallback: double.nan);
|
||||
if (amount.isNaN) {
|
||||
return '${money.amount} ${money.currency}';
|
||||
}
|
||||
|
||||
try {
|
||||
final currency = currencyStringToCode(money.currency);
|
||||
final symbol = currencyCodeToSymbol(currency);
|
||||
if (symbol.trim().isEmpty) {
|
||||
return '${amountToString(amount)} ${money.currency}';
|
||||
}
|
||||
return '${amountToString(amount)} $symbol';
|
||||
} catch (_) {
|
||||
return '${amountToString(amount)} ${money.currency}';
|
||||
}
|
||||
static String format(BuildContext context, LedgerAccount account) {
|
||||
return formatMoneyUi(context, account.balance?.balance);
|
||||
}
|
||||
|
||||
static String formatMasked(LedgerAccount account) {
|
||||
|
||||
@@ -26,7 +26,7 @@ class LedgerSection extends StatelessWidget {
|
||||
final hasAccountCode = accountCode.isNotEmpty;
|
||||
final balance = isMasked
|
||||
? LedgerBalanceFormatter.formatMasked(ledger)
|
||||
: LedgerBalanceFormatter.format(ledger);
|
||||
: LedgerBalanceFormatter.format(context, ledger);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/models/wallet/wallet_transaction.dart';
|
||||
import 'package:pweb/pages/payout_page/wallet/history/chip.dart';
|
||||
import 'package:pweb/pages/report/table/badge.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletTransactionsTable extends StatelessWidget {
|
||||
final List<WalletTransaction> transactions;
|
||||
|
||||
const WalletTransactionsTable({super.key, required this.transactions});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
if (transactions.isEmpty) {
|
||||
return Card(
|
||||
color: theme.colorScheme.onSecondary,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(loc.walletHistoryEmpty),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Card(
|
||||
color: theme.colorScheme.onSecondary,
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: DataTable(
|
||||
columnSpacing: 18,
|
||||
headingTextStyle: const TextStyle(fontWeight: FontWeight.w600),
|
||||
columns: [
|
||||
DataColumn(label: Text(loc.colStatus)),
|
||||
DataColumn(label: Text(loc.colType)),
|
||||
DataColumn(label: Text(loc.colAmount)),
|
||||
DataColumn(label: Text(loc.colBalance)),
|
||||
DataColumn(label: Text(loc.colCounterparty)),
|
||||
DataColumn(label: Text(loc.colDate)),
|
||||
DataColumn(label: Text(loc.colComment)),
|
||||
],
|
||||
rows: List.generate(
|
||||
transactions.length,
|
||||
(index) {
|
||||
final tx = transactions[index];
|
||||
final color = WidgetStateProperty.resolveWith<Color?>(
|
||||
(states) => index.isEven
|
||||
? theme.colorScheme.surfaceContainerHighest
|
||||
: null,
|
||||
);
|
||||
|
||||
return DataRow.byIndex(
|
||||
index: index,
|
||||
color: color,
|
||||
cells: [
|
||||
DataCell(OperationStatusBadge(status: tx.status)),
|
||||
DataCell(TypeChip(type: tx.type)),
|
||||
DataCell(Text(
|
||||
'${tx.type.sign}${amountToString(tx.amount)} ${currencyCodeToSymbol(tx.currency)}')),
|
||||
DataCell(Text(
|
||||
tx.balanceAfter == null
|
||||
? '-'
|
||||
: '${amountToString(tx.balanceAfter!)} ${currencyCodeToSymbol(tx.currency)}',
|
||||
)),
|
||||
DataCell(Text(tx.counterparty ?? '-')),
|
||||
DataCell(Text(
|
||||
'${TimeOfDay.fromDateTime(tx.date).format(context)}\n'
|
||||
'${tx.date.toLocal().toIso8601String().split("T").first}',
|
||||
)),
|
||||
DataCell(Text(tx.description)),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,19 +15,23 @@ class OperationCard extends StatelessWidget {
|
||||
final OperationItem operation;
|
||||
final ValueChanged<OperationItem>? onTap;
|
||||
|
||||
const OperationCard({
|
||||
super.key,
|
||||
required this.operation,
|
||||
this.onTap,
|
||||
});
|
||||
const OperationCard({super.key, required this.operation, this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final canOpen = onTap != null && paymentIdFromOperation(operation) != null;
|
||||
final amountLabel = formatAmount(operation.amount, operation.currency);
|
||||
final toAmountLabel = formatAmount(operation.toAmount, operation.toCurrency);
|
||||
final amountLabel = formatAmount(
|
||||
context,
|
||||
operation.amount,
|
||||
operation.currency,
|
||||
);
|
||||
final toAmountLabel = formatAmount(
|
||||
context,
|
||||
operation.toAmount,
|
||||
operation.toCurrency,
|
||||
);
|
||||
final showToAmount = shouldShowToAmount(operation);
|
||||
final timeLabel = formatOperationTime(context, operation.date);
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ class PayoutTotalsList extends StatelessWidget {
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
formatAmount(
|
||||
context,
|
||||
totals[index].amount,
|
||||
totals[index].currency,
|
||||
),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/models/payment/payment.dart';
|
||||
import 'package:pshared/models/payment/fx/quote.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
import 'package:pweb/pages/report/details/section.dart';
|
||||
import 'package:pweb/pages/report/details/sections/rows.dart';
|
||||
@@ -13,26 +15,17 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
class PaymentFxSection extends StatelessWidget {
|
||||
final Payment payment;
|
||||
|
||||
const PaymentFxSection({
|
||||
super.key,
|
||||
required this.payment,
|
||||
});
|
||||
const PaymentFxSection({super.key, required this.payment});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final fx = payment.lastQuote?.fxQuote;
|
||||
final rows = buildDetailRows([
|
||||
DetailValue(
|
||||
label: loc.fxRateLabel,
|
||||
value: _formatRate(fx),
|
||||
),
|
||||
DetailValue(label: loc.fxRateLabel, value: _formatRate(fx)),
|
||||
]);
|
||||
|
||||
return DetailsSection(
|
||||
title: loc.paymentDetailsFx,
|
||||
children: rows,
|
||||
);
|
||||
return DetailsSection(title: loc.paymentDetailsFx, children: rows);
|
||||
}
|
||||
|
||||
String? _formatRate(FxQuote? fx) {
|
||||
@@ -40,24 +33,33 @@ class PaymentFxSection extends StatelessWidget {
|
||||
final price = fx.price?.trim();
|
||||
if (price == null || price.isEmpty) return null;
|
||||
|
||||
final base = _firstNonEmpty([
|
||||
currencySymbolFromCode(fx.baseCurrency),
|
||||
currencySymbolFromCode(fx.baseAmount?.currency),
|
||||
final baseCurrency = _firstNonEmpty([
|
||||
fx.baseCurrency,
|
||||
fx.baseAmount?.currency,
|
||||
currencySymbolFromCode(fx.baseCurrency),
|
||||
currencySymbolFromCode(fx.baseAmount?.currency),
|
||||
]);
|
||||
final quote = _firstNonEmpty([
|
||||
currencySymbolFromCode(fx.quoteCurrency),
|
||||
currencySymbolFromCode(fx.quoteAmount?.currency),
|
||||
final quoteCurrency = _firstNonEmpty([
|
||||
fx.quoteCurrency,
|
||||
fx.quoteAmount?.currency,
|
||||
currencySymbolFromCode(fx.quoteCurrency),
|
||||
currencySymbolFromCode(fx.quoteAmount?.currency),
|
||||
]);
|
||||
|
||||
if (base == null || quote == null) {
|
||||
return price;
|
||||
}
|
||||
if (baseCurrency == null || quoteCurrency == null) return price;
|
||||
|
||||
return '1 $base = $price $quote';
|
||||
final baseDisplay = formatMoneyDisplay(
|
||||
Money(amount: '1', currency: baseCurrency),
|
||||
fallback: '1 $baseCurrency',
|
||||
invalidAmountFallback: '1',
|
||||
);
|
||||
final quoteDisplay = formatMoneyDisplay(
|
||||
Money(amount: _normalizeAmount(price), currency: quoteCurrency),
|
||||
fallback: '$price $quoteCurrency',
|
||||
invalidAmountFallback: price,
|
||||
);
|
||||
|
||||
return '$baseDisplay = $quoteDisplay';
|
||||
}
|
||||
|
||||
String? _firstNonEmpty(List<String?> values) {
|
||||
@@ -67,4 +69,8 @@ class PaymentFxSection extends StatelessWidget {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _normalizeAmount(String raw) {
|
||||
return raw.replaceAll(RegExp(r'\s+'), '').replaceAll(',', '.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/payment.dart';
|
||||
|
||||
import 'package:pweb/pages/report/details/row.dart';
|
||||
import 'package:pweb/pages/report/details/section.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentMetadataSection extends StatelessWidget {
|
||||
final Payment payment;
|
||||
|
||||
const PaymentMetadataSection({
|
||||
super.key,
|
||||
required this.payment,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final metadata = payment.metadata ?? const {};
|
||||
const allowedKeys = {'upload_filename', 'upload_rows'};
|
||||
final filtered = Map<String, String>.fromEntries(
|
||||
metadata.entries.where((entry) => allowedKeys.contains(entry.key)),
|
||||
);
|
||||
|
||||
if (filtered.isEmpty) {
|
||||
return DetailsSection(
|
||||
title: loc.paymentDetailsMetadata,
|
||||
children: [
|
||||
Text(
|
||||
loc.metadataEmpty,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final entries = filtered.entries.toList()
|
||||
..sort((a, b) => a.key.compareTo(b.key));
|
||||
|
||||
return DetailsSection(
|
||||
title: loc.paymentDetailsMetadata,
|
||||
children: entries
|
||||
.map(
|
||||
(entry) => DetailRow(
|
||||
label: _metadataLabel(loc, entry.key),
|
||||
value: entry.value,
|
||||
monospaced: true,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _metadataLabel(AppLocalizations loc, String key) {
|
||||
switch (key) {
|
||||
case 'upload_filename':
|
||||
return loc.metadataUploadFileName;
|
||||
case 'upload_rows':
|
||||
return loc.metadataTotalRecipients;
|
||||
default:
|
||||
return key;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ class OperationHistoryTile extends StatelessWidget {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final title = resolveOperationTitle(loc, operation.code);
|
||||
final operationLabel = operation.label?.trim();
|
||||
final stateView = resolveStepStateView(context, operation.state);
|
||||
final completedAt = formatCompletedAt(context, operation.completedAt);
|
||||
final canDownload = canDownloadDocument && onDownloadDocument != null;
|
||||
@@ -49,13 +50,24 @@ class OperationHistoryTile extends StatelessWidget {
|
||||
StepStateChip(view: stateView),
|
||||
],
|
||||
),
|
||||
if (operationLabel != null &&
|
||||
operationLabel.isNotEmpty &&
|
||||
operationLabel != title) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
operationLabel,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${loc.completedAtLabel}: $completedAt',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (canDownload) ...[
|
||||
const SizedBox(height: 8),
|
||||
TextButton.icon(
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:pweb/pages/report/details/summary_card/info_line.dart';
|
||||
import 'package:pweb/pages/report/table/badge.dart';
|
||||
import 'package:pweb/utils/report/amount_parts.dart';
|
||||
import 'package:pweb/utils/report/format.dart';
|
||||
import 'package:pweb/utils/money_display.dart';
|
||||
import 'package:pweb/utils/report/payment_mapper.dart';
|
||||
import 'package:pweb/utils/clipboard.dart';
|
||||
|
||||
@@ -24,6 +25,7 @@ class PaymentSummaryCard extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final unavailableValue = unavailableMoneyValue(context);
|
||||
final status = statusFromPayment(payment);
|
||||
final dateLabel = formatDateLabel(context, resolvePaymentDate(payment));
|
||||
|
||||
@@ -33,14 +35,16 @@ class PaymentSummaryCard extends StatelessWidget {
|
||||
final toAmount = payment.lastQuote?.amounts?.destinationSettlement;
|
||||
final fee = quoteFeeTotal(payment.lastQuote);
|
||||
|
||||
final amountLabel = formatMoney(primaryAmount);
|
||||
final toAmountLabel = formatMoney(toAmount);
|
||||
final feeLabel = formatMoney(fee);
|
||||
final amountLabel = formatMoney(context, primaryAmount);
|
||||
final toAmountLabel = formatMoney(context, toAmount);
|
||||
final feeLabel = formatMoney(context, fee);
|
||||
final paymentRef = (payment.paymentRef ?? '').trim();
|
||||
|
||||
final showToAmount = toAmountLabel != '-';
|
||||
final showToAmount = toAmountLabel != unavailableValue;
|
||||
final showFee = payment.lastQuote != null;
|
||||
final feeText = feeLabel != '-' ? loc.fee(feeLabel) : loc.fee(loc.noFee);
|
||||
final feeText = feeLabel != unavailableValue
|
||||
? loc.fee(feeLabel)
|
||||
: loc.fee(loc.noFee);
|
||||
final showPaymentId = paymentRef.isNotEmpty;
|
||||
final amountParts = splitAmount(amountLabel);
|
||||
|
||||
@@ -73,12 +77,12 @@ class PaymentSummaryCard extends StatelessWidget {
|
||||
currency: amountParts.currency,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
if (amountLabel != '-')
|
||||
if (amountLabel != unavailableValue)
|
||||
InfoLine(
|
||||
icon: Icons.send_outlined,
|
||||
text: loc.sentAmount(amountLabel),
|
||||
),
|
||||
if (showToAmount && toAmountLabel != '-')
|
||||
if (showToAmount && toAmountLabel != unavailableValue)
|
||||
InfoLine(
|
||||
icon: Icons.south_east,
|
||||
text: loc.recipientWillReceive(toAmountLabel),
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/models/payment/operation.dart';
|
||||
import 'package:pshared/models/payment/status.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/pages/report/table/badge.dart';
|
||||
import 'package:pweb/utils/money_display.dart';
|
||||
import 'package:pweb/utils/report/download_act.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class OperationRow {
|
||||
static DataRow build(OperationItem op, BuildContext context) {
|
||||
final isUnknownDate = op.date.millisecondsSinceEpoch == 0;
|
||||
@@ -35,13 +38,21 @@ class OperationRow {
|
||||
label: Text(loc.downloadAct),
|
||||
)
|
||||
: Text(op.fileName ?? '');
|
||||
final amountLabel = formatMoneyUiWithL10n(
|
||||
loc,
|
||||
Money(amount: amountToString(op.amount), currency: op.currency),
|
||||
);
|
||||
final toAmountLabel = formatMoneyUiWithL10n(
|
||||
loc,
|
||||
Money(amount: amountToString(op.toAmount), currency: op.toCurrency),
|
||||
);
|
||||
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(OperationStatusBadge(status: op.status)),
|
||||
DataCell(documentCell),
|
||||
DataCell(Text('${amountToString(op.amount)} ${op.currency}')),
|
||||
DataCell(Text('${amountToString(op.toAmount)} ${op.toCurrency}')),
|
||||
DataCell(Text(amountLabel)),
|
||||
DataCell(Text(toAmountLabel)),
|
||||
DataCell(Text(op.payId)),
|
||||
DataCell(Text(op.cardNumber ?? '-')),
|
||||
DataCell(Text(op.name)),
|
||||
|
||||
Reference in New Issue
Block a user