fixes
This commit is contained in:
96
frontend/pweb/lib/utils/money_display.dart
Normal file
96
frontend/pweb/lib/utils/money_display.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
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/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
String unavailableMoneyValue(BuildContext context) {
|
||||
return AppLocalizations.of(context)!.valueUnavailable;
|
||||
}
|
||||
|
||||
String unavailableMoneyValueFromL10n(AppLocalizations l10n) {
|
||||
return l10n.valueUnavailable;
|
||||
}
|
||||
|
||||
String formatMoneyUi(
|
||||
BuildContext context,
|
||||
Money? money, {
|
||||
String separator = ' ',
|
||||
}) {
|
||||
return formatMoneyUiWithL10n(
|
||||
AppLocalizations.of(context)!,
|
||||
money,
|
||||
separator: separator,
|
||||
);
|
||||
}
|
||||
|
||||
String formatMoneyUiWithL10n(
|
||||
AppLocalizations l10n,
|
||||
Money? money, {
|
||||
String separator = ' ',
|
||||
}) {
|
||||
final unavailableValue = unavailableMoneyValueFromL10n(l10n);
|
||||
return formatMoneyDisplay(
|
||||
money,
|
||||
fallback: unavailableValue,
|
||||
invalidAmountFallback: unavailableValue,
|
||||
separator: separator,
|
||||
);
|
||||
}
|
||||
|
||||
String formatAmountUi(
|
||||
BuildContext context, {
|
||||
required double amount,
|
||||
required String currency,
|
||||
String separator = ' ',
|
||||
}) {
|
||||
return formatAmountUiWithL10n(
|
||||
AppLocalizations.of(context)!,
|
||||
amount: amount,
|
||||
currency: currency,
|
||||
separator: separator,
|
||||
);
|
||||
}
|
||||
|
||||
String formatAmountUiWithL10n(
|
||||
AppLocalizations l10n, {
|
||||
required double amount,
|
||||
required String currency,
|
||||
String separator = ' ',
|
||||
}) {
|
||||
return formatMoneyUiWithL10n(
|
||||
l10n,
|
||||
Money(amount: amountToString(amount), currency: currency),
|
||||
separator: separator,
|
||||
);
|
||||
}
|
||||
|
||||
String formatAssetUi(
|
||||
BuildContext context,
|
||||
Asset? asset, {
|
||||
String separator = ' ',
|
||||
}) {
|
||||
return formatAssetUiWithL10n(
|
||||
AppLocalizations.of(context)!,
|
||||
asset,
|
||||
separator: separator,
|
||||
);
|
||||
}
|
||||
|
||||
String formatAssetUiWithL10n(
|
||||
AppLocalizations l10n,
|
||||
Asset? asset, {
|
||||
String separator = ' ',
|
||||
}) {
|
||||
if (asset == null) return unavailableMoneyValueFromL10n(l10n);
|
||||
return formatAmountUiWithL10n(
|
||||
l10n,
|
||||
amount: asset.amount,
|
||||
currency: currencyCodeToString(asset.currency),
|
||||
separator: separator,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/widgets/dialogs/confirmation_dialog.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
Future<void> confirmPaymentMethodDelete(
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -50,31 +50,35 @@ StatusView operationStatusViewFromToken(
|
||||
case 'settled':
|
||||
return StatusView(
|
||||
label: l10n.operationStatusSuccessful,
|
||||
backgroundColor: scheme.tertiaryContainer,
|
||||
foregroundColor: scheme.onTertiaryContainer,
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
);
|
||||
|
||||
case 'skipped':
|
||||
return StatusView(
|
||||
label: l10n.operationStepStateSkipped,
|
||||
backgroundColor: scheme.secondaryContainer,
|
||||
foregroundColor: scheme.onSecondaryContainer,
|
||||
backgroundColor: Colors.grey,
|
||||
foregroundColor: Colors.white,
|
||||
);
|
||||
|
||||
case 'error':
|
||||
case 'failed':
|
||||
case 'rejected':
|
||||
case 'aborted':
|
||||
return StatusView(
|
||||
label: l10n.operationStatusUnsuccessful,
|
||||
backgroundColor: scheme.errorContainer,
|
||||
foregroundColor: scheme.onErrorContainer,
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
);
|
||||
|
||||
case 'cancelled':
|
||||
case 'canceled':
|
||||
return StatusView(
|
||||
label: l10n.paymentStatusCancelled,
|
||||
backgroundColor: scheme.surfaceContainerHighest,
|
||||
foregroundColor: scheme.onSurfaceVariant,
|
||||
backgroundColor: Colors.blueGrey,
|
||||
foregroundColor: Colors.white,
|
||||
);
|
||||
|
||||
case 'processing':
|
||||
case 'running':
|
||||
case 'executing':
|
||||
@@ -82,9 +86,10 @@ StatusView operationStatusViewFromToken(
|
||||
case 'started':
|
||||
return StatusView(
|
||||
label: l10n.paymentStatusProcessing,
|
||||
backgroundColor: scheme.primaryContainer,
|
||||
foregroundColor: scheme.onPrimaryContainer,
|
||||
backgroundColor: Colors.orange,
|
||||
foregroundColor: Colors.white,
|
||||
);
|
||||
|
||||
case 'pending':
|
||||
case 'queued':
|
||||
case 'waiting':
|
||||
@@ -92,26 +97,29 @@ StatusView operationStatusViewFromToken(
|
||||
case 'scheduled':
|
||||
return StatusView(
|
||||
label: l10n.operationStatusPending,
|
||||
backgroundColor: scheme.secondary,
|
||||
foregroundColor: scheme.onSecondary,
|
||||
backgroundColor: Colors.amber,
|
||||
foregroundColor: Colors.black,
|
||||
);
|
||||
|
||||
case 'needs_attention':
|
||||
return StatusView(
|
||||
label: l10n.operationStepStateNeedsAttention,
|
||||
backgroundColor: scheme.tertiary,
|
||||
foregroundColor: scheme.onTertiary,
|
||||
backgroundColor: Colors.grey.shade800,
|
||||
foregroundColor: Colors.white,
|
||||
);
|
||||
|
||||
case 'retrying':
|
||||
return StatusView(
|
||||
label: l10n.operationStepStateRetrying,
|
||||
backgroundColor: scheme.primary,
|
||||
foregroundColor: scheme.onPrimary,
|
||||
backgroundColor: Colors.purple,
|
||||
foregroundColor: Colors.white,
|
||||
);
|
||||
|
||||
default:
|
||||
return StatusView(
|
||||
label: fallbackLabel ?? humanizeOperationStatusToken(token),
|
||||
backgroundColor: scheme.surfaceContainerHighest,
|
||||
foregroundColor: scheme.onSurfaceVariant,
|
||||
backgroundColor: Colors.grey.shade400,
|
||||
foregroundColor: Colors.black,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,34 +3,35 @@ import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/localization.dart';
|
||||
import 'package:pweb/utils/money_display.dart';
|
||||
|
||||
|
||||
String formatMoney(Money? money, {String fallback = '-'}) {
|
||||
if (money == null) return fallback;
|
||||
final amount = money.amount.trim();
|
||||
if (amount.isEmpty) return fallback;
|
||||
final symbol = currencySymbolFromCode(money.currency);
|
||||
final suffix = symbol ?? money.currency;
|
||||
if (suffix.trim().isEmpty) return amount;
|
||||
return '$amount $suffix';
|
||||
String formatMoney(BuildContext context, Money? money) {
|
||||
if (money == null || money.amount.trim().isEmpty) {
|
||||
return unavailableMoneyValue(context);
|
||||
}
|
||||
return formatMoneyUi(context, money);
|
||||
}
|
||||
|
||||
String formatAmount(double amount, String currency, {String fallback = '-'}) {
|
||||
final trimmed = currency.trim();
|
||||
if (trimmed.isEmpty) return amountToString(amount);
|
||||
final symbol = currencySymbolFromCode(trimmed);
|
||||
final suffix = symbol ?? trimmed;
|
||||
return '${amountToString(amount)} $suffix';
|
||||
String formatAmount(BuildContext context, double amount, String currency) {
|
||||
return formatAmountUi(context, amount: amount, currency: currency);
|
||||
}
|
||||
|
||||
String formatDateLabel(BuildContext context, DateTime? date, {String fallback = '-'}) {
|
||||
String formatDateLabel(
|
||||
BuildContext context,
|
||||
DateTime? date, {
|
||||
String fallback = '-',
|
||||
}) {
|
||||
if (date == null || date.millisecondsSinceEpoch == 0) return fallback;
|
||||
return dateTimeToLocalFormat(context, date.toLocal());
|
||||
}
|
||||
|
||||
String formatLongDate(BuildContext context, DateTime? date, {String fallback = '-'}) {
|
||||
String formatLongDate(
|
||||
BuildContext context,
|
||||
DateTime? date, {
|
||||
String fallback = '-',
|
||||
}) {
|
||||
if (date == null || date.millisecondsSinceEpoch == 0) return fallback;
|
||||
final locale = Localizations.localeOf(context).toString();
|
||||
final formatter = DateFormat('d MMMM y', locale);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:pshared/models/payment/intent.dart';
|
||||
import 'package:pshared/models/payment/endpoint.dart';
|
||||
import 'package:pshared/models/payment/methods/data.dart';
|
||||
import 'package:pshared/models/payment/methods/ledger.dart';
|
||||
import 'package:pshared/models/payment/methods/managed_wallet.dart';
|
||||
@@ -7,6 +7,13 @@ import 'package:pshared/models/payment/payment.dart';
|
||||
import 'package:pshared/models/payment/source_type.dart';
|
||||
|
||||
|
||||
typedef _SourceRefExtractor = String? Function(PaymentMethodData source);
|
||||
|
||||
final Map<PaymentSourceType, _SourceRefExtractor> _sourceExtractors = {
|
||||
PaymentSourceType.wallet: _walletSourceRef,
|
||||
PaymentSourceType.ledger: _ledgerSourceRef,
|
||||
};
|
||||
|
||||
bool paymentMatchesSource(
|
||||
Payment payment, {
|
||||
required PaymentSourceType sourceType,
|
||||
@@ -15,95 +22,54 @@ bool paymentMatchesSource(
|
||||
final normalizedSourceRef = _normalize(sourceRef);
|
||||
if (normalizedSourceRef == null) return false;
|
||||
|
||||
final paymentSourceRef = _paymentSourceRef(payment, sourceType);
|
||||
return paymentSourceRef != null && paymentSourceRef == normalizedSourceRef;
|
||||
final paymentSourceRefs = _paymentSourceRefs(payment, sourceType);
|
||||
if (paymentSourceRefs.isEmpty) return false;
|
||||
|
||||
return paymentSourceRefs.contains(normalizedSourceRef);
|
||||
}
|
||||
|
||||
String? _paymentSourceRef(Payment payment, PaymentSourceType sourceType) {
|
||||
final fromIntent = _sourceRefFromIntent(payment.intent, sourceType);
|
||||
if (fromIntent != null) return fromIntent;
|
||||
return _sourceRefFromMetadata(payment.metadata, sourceType);
|
||||
Set<String> _paymentSourceRefs(Payment payment, PaymentSourceType sourceType) {
|
||||
final fromSource = _sourceRefsFromEndpoint(payment.source, sourceType);
|
||||
if (fromSource.isEmpty) return const <String>{};
|
||||
return fromSource;
|
||||
}
|
||||
|
||||
String? _sourceRefFromIntent(
|
||||
PaymentIntent? intent,
|
||||
Set<String> _sourceRefsFromEndpoint(
|
||||
PaymentEndpoint? endpoint,
|
||||
PaymentSourceType sourceType,
|
||||
) {
|
||||
final source = intent?.source;
|
||||
if (source == null) return null;
|
||||
if (endpoint == null) return const <String>{};
|
||||
|
||||
final fromIntentAttributes = _sourceRefFromMetadata(
|
||||
intent?.attributes,
|
||||
sourceType,
|
||||
);
|
||||
if (fromIntentAttributes != null) return fromIntentAttributes;
|
||||
|
||||
switch (sourceType) {
|
||||
case PaymentSourceType.wallet:
|
||||
return _walletSourceRef(source);
|
||||
case PaymentSourceType.ledger:
|
||||
return _ledgerSourceRef(source);
|
||||
final refs = <String>{};
|
||||
void collect(String? value) {
|
||||
final normalized = _normalize(value);
|
||||
if (normalized == null) return;
|
||||
refs.add(normalized);
|
||||
}
|
||||
|
||||
final source = endpoint.method;
|
||||
if (source != null) {
|
||||
final fromMethod = _sourceExtractors[sourceType]?.call(source);
|
||||
collect(fromMethod);
|
||||
}
|
||||
|
||||
collect(endpoint.paymentMethodRef);
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
String? _walletSourceRef(PaymentMethodData source) {
|
||||
if (source is ManagedWalletPaymentMethod) {
|
||||
return _normalize(source.managedWalletRef) ??
|
||||
_sourceRefFromMetadata(source.metadata, PaymentSourceType.wallet);
|
||||
}
|
||||
if (source is WalletPaymentMethod) {
|
||||
return _normalize(source.walletId) ??
|
||||
_sourceRefFromMetadata(source.metadata, PaymentSourceType.wallet);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
String? _walletSourceRef(PaymentMethodData source) => switch (source) {
|
||||
ManagedWalletPaymentMethod(:final managedWalletRef) => _normalize(
|
||||
managedWalletRef,
|
||||
),
|
||||
WalletPaymentMethod(:final walletId) => _normalize(walletId),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
String? _ledgerSourceRef(PaymentMethodData source) {
|
||||
if (source is LedgerPaymentMethod) {
|
||||
return _normalize(source.ledgerAccountRef) ??
|
||||
_sourceRefFromMetadata(source.metadata, PaymentSourceType.ledger);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _sourceRefFromMetadata(
|
||||
Map<String, String>? metadata,
|
||||
PaymentSourceType sourceType,
|
||||
) {
|
||||
if (metadata == null || metadata.isEmpty) return null;
|
||||
|
||||
final keys = switch (sourceType) {
|
||||
PaymentSourceType.wallet => const <String>[
|
||||
'source_wallet_ref',
|
||||
'managed_wallet_ref',
|
||||
'wallet_ref',
|
||||
'wallet_id',
|
||||
'source_wallet_id',
|
||||
'source_wallet_user_id',
|
||||
'wallet_user_id',
|
||||
'wallet_user_ref',
|
||||
'wallet_number',
|
||||
'source_wallet_number',
|
||||
'source_managed_wallet_ref',
|
||||
'source_ref',
|
||||
],
|
||||
PaymentSourceType.ledger => const <String>[
|
||||
'source_ledger_account_ref',
|
||||
'ledger_account_ref',
|
||||
'source_account_code',
|
||||
'ledger_account_code',
|
||||
'account_code',
|
||||
'source_ref',
|
||||
],
|
||||
};
|
||||
|
||||
for (final key in keys) {
|
||||
final value = _normalize(metadata[key]);
|
||||
if (value != null) return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
String? _ledgerSourceRef(PaymentMethodData source) => switch (source) {
|
||||
LedgerPaymentMethod(:final ledgerAccountRef) => _normalize(ledgerAccountRef),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
String? _normalize(String? value) {
|
||||
final normalized = value?.trim();
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/localization.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
|
||||
String formatMoney(Money? money, {String fallback = '-'}) {
|
||||
final amount = money?.amount.trim();
|
||||
if (amount == null || amount.isEmpty) return fallback;
|
||||
return '$amount ${money!.currency}';
|
||||
}
|
||||
|
||||
String formatAmount(double amount, String currency, {String fallback = '-'}) {
|
||||
final trimmed = currency.trim();
|
||||
if (trimmed.isEmpty) return amountToString(amount);
|
||||
final symbol = currencySymbolFromCode(trimmed);
|
||||
final suffix = symbol ?? trimmed;
|
||||
return '${amountToString(amount)} $suffix';
|
||||
}
|
||||
|
||||
String formatDateLabel(BuildContext context, DateTime? date, {String fallback = '-'}) {
|
||||
if (date == null || date.millisecondsSinceEpoch == 0) return fallback;
|
||||
return dateTimeToLocalFormat(context, date.toLocal());
|
||||
}
|
||||
|
||||
String formatLongDate(BuildContext context, DateTime? date, {String fallback = '-'}) {
|
||||
if (date == null || date.millisecondsSinceEpoch == 0) return fallback;
|
||||
final locale = Localizations.localeOf(context).toString();
|
||||
final formatter = DateFormat('d MMMM y', locale);
|
||||
return formatter.format(date.toLocal());
|
||||
}
|
||||
|
||||
String collapseWhitespace(String value) {
|
||||
return value.replaceAll(RegExp(r'\s+'), ' ').trim();
|
||||
}
|
||||
Reference in New Issue
Block a user