small fixes

This commit is contained in:
Arseni
2026-02-17 11:17:19 +03:00
parent 0eea39fb97
commit e2e2257167
13 changed files with 120 additions and 58 deletions

View File

@@ -1,13 +1,16 @@
import 'package:pshared/models/wallet/wallet.dart' as domain; import 'package:pshared/models/wallet/wallet.dart' as domain;
import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/utils/currency.dart'; import 'package:pshared/utils/currency.dart';
import 'package:pshared/utils/money.dart';
extension WalletUiMapper on domain.WalletModel { extension WalletUiMapper on domain.WalletModel {
Wallet toUi() => Wallet( Wallet toUi() => Wallet(
id: walletRef, id: walletRef,
walletUserID: walletRef, walletUserID: walletRef,
balance: double.tryParse(availableMoney?.amount ?? balance?.available?.amount ?? '0') ?? 0, balance: parseMoneyAmount(
availableMoney?.amount ?? balance?.available?.amount,
),
currency: currencyStringToCode(asset.tokenSymbol), currency: currencyStringToCode(asset.tokenSymbol),
calculatedAt: balance?.calculatedAt ?? DateTime.now(), calculatedAt: balance?.calculatedAt ?? DateTime.now(),
depositAddress: depositAddress, depositAddress: depositAddress,

View File

@@ -3,6 +3,7 @@ import 'package:pshared/models/describable.dart';
import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/models/wallet/chain_asset.dart'; import 'package:pshared/models/wallet/chain_asset.dart';
import 'package:pshared/service/wallet.dart' as shared_wallet_service; import 'package:pshared/service/wallet.dart' as shared_wallet_service;
import 'package:pshared/utils/money.dart';
abstract class WalletsService { abstract class WalletsService {
@@ -29,8 +30,7 @@ class ApiWalletsService implements WalletsService {
organizationRef: organizationRef, organizationRef: organizationRef,
walletRef: walletRef, walletRef: walletRef,
); );
final amount = balance.available?.amount; return parseMoneyAmount(balance.available?.amount);
return amount == null ? 0 : double.tryParse(amount) ?? 0;
} }
@override @override

View File

@@ -0,0 +1,12 @@
import 'package:pshared/models/money.dart';
double parseMoneyAmount(String? raw, {double fallback = 0}) {
final trimmed = raw?.trim();
if (trimmed == null || trimmed.isEmpty) return fallback;
return double.tryParse(trimmed) ?? fallback;
}
extension MoneyAmountX on Money {
double get amountValue => parseMoneyAmount(amount);
}

View File

@@ -17,17 +17,11 @@ class RecentPaymentsController extends ChangeNotifier {
void update(PaymentsProvider provider) { void update(PaymentsProvider provider) {
if (!identical(_payments, provider)) { if (!identical(_payments, provider)) {
_payments?.removeListener(_onPaymentsChanged);
_payments = provider; _payments = provider;
_payments?.addListener(_onPaymentsChanged);
} }
_rebuild(); _rebuild();
} }
void _onPaymentsChanged() {
_rebuild();
}
void _rebuild() { void _rebuild() {
final operations = (_payments?.payments ?? const []) final operations = (_payments?.payments ?? const [])
.map(mapPaymentToOperation) .map(mapPaymentToOperation)
@@ -36,9 +30,4 @@ class RecentPaymentsController extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
@override
void dispose() {
_payments?.removeListener(_onPaymentsChanged);
super.dispose();
}
} }

View File

@@ -28,9 +28,7 @@ class ReportOperationsController extends ChangeNotifier {
void update(PaymentsProvider provider) { void update(PaymentsProvider provider) {
if (!identical(_payments, provider)) { if (!identical(_payments, provider)) {
_payments?.removeListener(_onPaymentsChanged);
_payments = provider; _payments = provider;
_payments?.addListener(_onPaymentsChanged);
} }
_rebuildOperations(); _rebuildOperations();
} }
@@ -61,10 +59,6 @@ class ReportOperationsController extends ChangeNotifier {
await _payments?.refresh(); await _payments?.refresh();
} }
void _onPaymentsChanged() {
_rebuildOperations();
}
void _rebuildOperations() { void _rebuildOperations() {
final items = _payments?.payments ?? const []; final items = _payments?.payments ?? const [];
_operations = items.map(mapPaymentToOperation).toList(); _operations = items.map(mapPaymentToOperation).toList();
@@ -107,9 +101,4 @@ class ReportOperationsController extends ChangeNotifier {
left.end.isAtSameMomentAs(right.end); left.end.isAtSameMomentAs(right.end);
} }
@override
void dispose() {
_payments?.removeListener(_onPaymentsChanged);
super.dispose();
}
} }

View File

@@ -0,0 +1,27 @@
enum PaymentState {
success,
failed,
cancelled,
processing,
unknown,
}
PaymentState paymentStateFromRaw(String? raw) {
final trimmed = (raw ?? '').trim().toUpperCase();
final normalized = trimmed.startsWith('PAYMENT_STATE_')
? trimmed.substring('PAYMENT_STATE_'.length)
: trimmed;
switch (normalized) {
case 'SUCCESS':
return PaymentState.success;
case 'FAILED':
return PaymentState.failed;
case 'CANCELLED':
return PaymentState.cancelled;
case 'PROCESSING':
return PaymentState.processing;
default:
return PaymentState.unknown;
}
}

View File

@@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
import 'package:pshared/controllers/balance_mask/ledger_accounts.dart'; import 'package:pshared/controllers/balance_mask/ledger_accounts.dart';
import 'package:pshared/models/ledger/account.dart'; import 'package:pshared/models/ledger/account.dart';
import 'package:pshared/utils/currency.dart'; import 'package:pshared/utils/currency.dart';
import 'package:pshared/utils/money.dart';
import 'package:pweb/pages/dashboard/buttons/balance/config.dart'; import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
import 'package:pweb/pages/dashboard/buttons/balance/header.dart'; import 'package:pweb/pages/dashboard/buttons/balance/header.dart';
@@ -25,8 +26,8 @@ class LedgerAccountCard extends StatelessWidget {
final money = account.balance?.balance; final money = account.balance?.balance;
if (money == null) return '--'; if (money == null) return '--';
final amount = double.tryParse(money.amount); final amount = parseMoneyAmount(money.amount, fallback: double.nan);
if (amount == null) { if (amount.isNaN) {
return '${money.amount} ${money.currency}'; return '${money.amount} ${money.currency}';
} }

View File

@@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
import 'package:pshared/provider/payment/amount.dart'; import 'package:pshared/provider/payment/amount.dart';
import 'package:pshared/utils/currency.dart'; import 'package:pshared/utils/currency.dart';
import 'package:pshared/utils/money.dart';
import 'package:pweb/generated/i18n/app_localizations.dart'; import 'package:pweb/generated/i18n/app_localizations.dart';
@@ -32,7 +33,13 @@ class _PaymentAmountWidgetState extends State<PaymentAmountWidget> {
super.dispose(); super.dispose();
} }
double? _parseAmount(String value) => double.tryParse(value.replaceAll(',', '.')); double? _parseAmount(String value) {
final parsed = parseMoneyAmount(
value.replaceAll(',', '.'),
fallback: double.nan,
);
return parsed.isNaN ? null : parsed;
}
void _syncTextWithAmount(double amount) { void _syncTextWithAmount(double amount) {
final parsedText = _parseAmount(_controller.text); final parsedText = _parseAmount(_controller.text);

View File

@@ -1,14 +1,15 @@
import 'package:pshared/models/asset.dart'; import 'package:pshared/models/asset.dart';
import 'package:pshared/models/money.dart'; import 'package:pshared/models/money.dart';
import 'package:pshared/utils/currency.dart'; import 'package:pshared/utils/currency.dart';
import 'package:pshared/utils/money.dart';
import 'package:pweb/controllers/multiple_payouts.dart'; import 'package:pweb/controllers/multiple_payouts.dart';
String moneyLabel(Money? money) { String moneyLabel(Money? money) {
if (money == null) return 'N/A'; if (money == null) return 'N/A';
final amount = double.tryParse(money.amount); final amount = parseMoneyAmount(money.amount, fallback: double.nan);
if (amount == null) return '${money.amount} ${money.currency}'; if (amount.isNaN) return '${money.amount} ${money.currency}';
try { try {
return assetToString( return assetToString(
Asset( Asset(

View File

@@ -8,6 +8,7 @@ import 'package:pshared/provider/payment/multiple/provider.dart';
import 'package:pshared/provider/payment/multiple/quotation.dart'; import 'package:pshared/provider/payment/multiple/quotation.dart';
import 'package:pshared/provider/payment/payments.dart'; import 'package:pshared/provider/payment/payments.dart';
import 'package:pshared/utils/currency.dart'; import 'package:pshared/utils/currency.dart';
import 'package:pshared/utils/money.dart';
import 'package:pweb/models/multiple_payouts/csv_row.dart'; import 'package:pweb/models/multiple_payouts/csv_row.dart';
import 'package:pweb/models/multiple_payouts/state.dart'; import 'package:pweb/models/multiple_payouts/state.dart';
@@ -93,8 +94,8 @@ class MultiplePayoutsProvider extends ChangeNotifier {
double total = 0; double total = 0;
for (final row in _rows) { for (final row in _rows) {
final value = double.tryParse(row.amount); final value = parseMoneyAmount(row.amount, fallback: double.nan);
if (value == null) return null; if (value.isNaN) return null;
total += value; total += value;
} }
return Money(amount: amountToString(total), currency: currency); return Money(amount: amountToString(total), currency: currency);
@@ -121,10 +122,10 @@ class MultiplePayoutsProvider extends ChangeNotifier {
final fee = aggregateFeeAmountFor(sourceWallet); final fee = aggregateFeeAmountFor(sourceWallet);
if (debit == null || fee == null) return null; if (debit == null || fee == null) return null;
final debitValue = double.tryParse(debit.amount); final debitValue = parseMoneyAmount(debit.amount, fallback: double.nan);
final feeValue = double.tryParse(fee.amount); final feeValue = parseMoneyAmount(fee.amount, fallback: double.nan);
if (debit.currency.toUpperCase() != fee.currency.toUpperCase()) return null; if (debit.currency.toUpperCase() != fee.currency.toUpperCase()) return null;
if (debitValue == null || feeValue == null || debitValue <= 0) return null; if (debitValue.isNaN || feeValue.isNaN || debitValue <= 0) return null;
return (feeValue / debitValue) * 100; return (feeValue / debitValue) * 100;
} }

View File

@@ -1,3 +1,5 @@
import 'package:pshared/utils/money.dart';
import 'package:pweb/models/multiple_payouts/csv_row.dart'; import 'package:pweb/models/multiple_payouts/csv_row.dart';
@@ -79,8 +81,8 @@ class MultipleCsvParser {
throw FormatException('CSV row ${i + 1}: amount is required'); throw FormatException('CSV row ${i + 1}: amount is required');
} }
final parsedAmount = double.tryParse(amount); final parsedAmount = parseMoneyAmount(amount, fallback: double.nan);
if (parsedAmount == null || parsedAmount <= 0) { if (parsedAmount.isNaN || parsedAmount <= 0) {
throw FormatException( throw FormatException(
'CSV row ${i + 1}: amount must be greater than 0', 'CSV row ${i + 1}: amount must be greater than 0',
); );

View File

@@ -1,6 +1,9 @@
import 'package:pshared/models/payment/operation.dart'; import 'package:pshared/models/payment/operation.dart';
import 'package:pshared/models/payment/payment.dart'; import 'package:pshared/models/payment/payment.dart';
import 'package:pshared/models/payment/status.dart'; import 'package:pshared/models/payment/status.dart';
import 'package:pshared/utils/money.dart';
import 'package:pweb/models/payment_state.dart';
OperationItem mapPaymentToOperation(Payment payment) { OperationItem mapPaymentToOperation(Payment payment) {
@@ -8,9 +11,11 @@ OperationItem mapPaymentToOperation(Payment payment) {
final settlement = payment.lastQuote?.expectedSettlementAmount; final settlement = payment.lastQuote?.expectedSettlementAmount;
final amountMoney = debit ?? settlement; final amountMoney = debit ?? settlement;
final amount = _parseAmount(amountMoney?.amount); final amount = parseMoneyAmount(amountMoney?.amount);
final currency = amountMoney?.currency ?? ''; final currency = amountMoney?.currency ?? '';
final toAmount = settlement == null ? amount : _parseAmount(settlement.amount); final toAmount = settlement == null
? amount
: parseMoneyAmount(settlement.amount);
final toCurrency = settlement?.currency ?? currency; final toCurrency = settlement?.currency ?? currency;
final payId = _firstNonEmpty([ final payId = _firstNonEmpty([
@@ -48,14 +53,15 @@ OperationItem mapPaymentToOperation(Payment payment) {
} }
OperationStatus statusFromPayment(Payment payment) { OperationStatus statusFromPayment(Payment payment) {
final normalized = _normalizePaymentState(payment.state); final state = paymentStateFromRaw(payment.state);
switch (normalized) { switch (state) {
case 'SUCCESS': case PaymentState.success:
return OperationStatus.success; return OperationStatus.success;
case 'FAILED': case PaymentState.failed:
case 'CANCELLED': case PaymentState.cancelled:
return OperationStatus.error; return OperationStatus.error;
default: case PaymentState.processing:
case PaymentState.unknown:
return OperationStatus.processing; return OperationStatus.processing;
} }
} }
@@ -102,16 +108,3 @@ String? _firstNonEmpty(List<String?> values) {
} }
return null; return null;
} }
String _normalizePaymentState(String? raw) {
final trimmed = (raw ?? '').trim().toUpperCase();
if (trimmed.startsWith('PAYMENT_STATE_')) {
return trimmed.substring('PAYMENT_STATE_'.length);
}
return trimmed;
}
double _parseAmount(String? amount) {
if (amount == null || amount.trim().isEmpty) return 0;
return double.tryParse(amount) ?? 0;
}

View File

@@ -0,0 +1,37 @@
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();
}