This commit is contained in:
Arseni
2026-03-11 18:26:21 +03:00
parent fdd8dd8845
commit 0172176978
46 changed files with 678 additions and 643 deletions

View 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,
);
}

View File

@@ -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();
}
}

View File

@@ -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,
);
}
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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();
}