From 00045c1e6557b50c970f4ad28a71ebc8ef234366 Mon Sep 17 00:00:00 2001 From: Stephan D Date: Fri, 12 Dec 2025 13:45:58 +0100 Subject: [PATCH] quotation rate display --- .gitignore | 3 +- frontend/pshared/lib/models/asset.dart | 18 ++++ .../lib/models/currency.dart | 0 .../pshared/lib/provider/payment/amount.dart | 20 ++++ .../lib/provider/payment/quotation.dart | 56 +++++++++-- .../lib/service/payment/quotation.dart | 8 +- frontend/pshared/lib/utils/currency.dart | 72 ++++++++++++--- frontend/pweb/lib/data/mappers/wallet_ui.dart | 3 +- frontend/pweb/lib/l10n/en.arb | 8 +- frontend/pweb/lib/l10n/ru.arb | 8 +- frontend/pweb/lib/main.dart | 5 - frontend/pweb/lib/models/wallet.dart | 2 +- .../pweb/lib/models/wallet_transaction.dart | 2 +- .../dashboard/buttons/balance/amount.dart | 6 +- .../lib/pages/dashboard/payouts/amount.dart | 74 +++++++++++++++ .../pages/dashboard/payouts/fee_payer.dart | 29 ++++++ .../lib/pages/dashboard/payouts/form.dart | 43 +++++++++ .../pages/dashboard/payouts/payment_form.dart | 92 ------------------- .../pages/dashboard/payouts/summary/fee.dart | 23 +++++ .../payouts/summary/recipient_receives.dart | 23 +++++ .../pages/dashboard/payouts/summary/row.dart | 24 +++++ .../payouts/summary/sent_amount.dart | 26 ++++++ .../dashboard/payouts/summary/total.dart | 23 +++++ .../dashboard/payouts/summary/widget.dart | 33 +++++++ .../lib/pages/dashboard/payouts/widget.dart | 28 ++++++ .../payment_methods/payment_page/content.dart | 2 +- .../payment_page/method_selector.dart | 12 +-- .../payment_methods/payment_page/page.dart | 4 +- .../lib/pages/payout_page/wallet/card.dart | 4 +- .../payout_page/wallet/history/table.dart | 7 +- frontend/pweb/lib/pages/report/table/row.dart | 9 +- .../pweb/lib/pages/wallet_top_up/content.dart | 3 +- .../lib/services/wallet_transactions.dart | 2 +- frontend/pweb/lib/utils/currency.dart | 38 -------- frontend/pweb/lib/utils/payment/dropdown.dart | 19 ++-- 35 files changed, 530 insertions(+), 199 deletions(-) create mode 100644 frontend/pshared/lib/models/asset.dart rename frontend/{pweb => pshared}/lib/models/currency.dart (100%) create mode 100644 frontend/pshared/lib/provider/payment/amount.dart create mode 100644 frontend/pweb/lib/pages/dashboard/payouts/amount.dart create mode 100644 frontend/pweb/lib/pages/dashboard/payouts/fee_payer.dart create mode 100644 frontend/pweb/lib/pages/dashboard/payouts/form.dart delete mode 100644 frontend/pweb/lib/pages/dashboard/payouts/payment_form.dart create mode 100644 frontend/pweb/lib/pages/dashboard/payouts/summary/fee.dart create mode 100644 frontend/pweb/lib/pages/dashboard/payouts/summary/recipient_receives.dart create mode 100644 frontend/pweb/lib/pages/dashboard/payouts/summary/row.dart create mode 100644 frontend/pweb/lib/pages/dashboard/payouts/summary/sent_amount.dart create mode 100644 frontend/pweb/lib/pages/dashboard/payouts/summary/total.dart create mode 100644 frontend/pweb/lib/pages/dashboard/payouts/summary/widget.dart create mode 100644 frontend/pweb/lib/pages/dashboard/payouts/widget.dart delete mode 100644 frontend/pweb/lib/utils/currency.dart diff --git a/.gitignore b/.gitignore index 0822f75..df1e1b4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ untranslated.txt generate_protos.sh update_dep.sh .vscode/ -.gocache/ \ No newline at end of file +.gocache/ +.cache/ \ No newline at end of file diff --git a/frontend/pshared/lib/models/asset.dart b/frontend/pshared/lib/models/asset.dart new file mode 100644 index 0000000..19db849 --- /dev/null +++ b/frontend/pshared/lib/models/asset.dart @@ -0,0 +1,18 @@ +import 'package:pshared/models/currency.dart'; +import 'package:pshared/utils/currency.dart'; + + +class Asset { + final Currency currency; + final double amount; + + const Asset({ + required this.currency, + required this.amount, + }); +} + +Asset createAsset(String currencyCode, String amount) => Asset( + currency: currencyStringToCode(currencyCode), + amount: double.parse(amount), +); \ No newline at end of file diff --git a/frontend/pweb/lib/models/currency.dart b/frontend/pshared/lib/models/currency.dart similarity index 100% rename from frontend/pweb/lib/models/currency.dart rename to frontend/pshared/lib/models/currency.dart diff --git a/frontend/pshared/lib/provider/payment/amount.dart b/frontend/pshared/lib/provider/payment/amount.dart new file mode 100644 index 0000000..f2cde54 --- /dev/null +++ b/frontend/pshared/lib/provider/payment/amount.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + + +class PaymentAmountProvider with ChangeNotifier { + double _amount = 10.0; + bool _payerCoversFee = true; + + double get amount => _amount; + bool get payerCoversFee => _payerCoversFee; + + void setAmount(double value) { + _amount = value; + notifyListeners(); + } + + void setPayerCoversFee(bool value) { + _payerCoversFee = value; + notifyListeners(); + } +} diff --git a/frontend/pshared/lib/provider/payment/quotation.dart b/frontend/pshared/lib/provider/payment/quotation.dart index 6ef3702..d8c6fbf 100644 --- a/frontend/pshared/lib/provider/payment/quotation.dart +++ b/frontend/pshared/lib/provider/payment/quotation.dart @@ -1,5 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:pshared/models/asset.dart'; +import 'package:uuid/uuid.dart'; + +import 'package:pshared/models/payment/currency_pair.dart'; +import 'package:pshared/models/payment/fx/intent.dart'; +import 'package:pshared/models/payment/fx/side.dart'; +import 'package:pshared/models/payment/kind.dart'; +import 'package:pshared/models/payment/methods/card.dart'; +import 'package:pshared/models/payment/methods/managed_wallet.dart'; +import 'package:pshared/models/payment/money.dart'; +import 'package:pshared/models/payment/settlement_mode.dart'; +import 'package:pshared/provider/payment/amount.dart'; import 'package:pshared/api/requests/payment/quote.dart'; import 'package:pshared/data/mapper/payment/intent/payment.dart'; import 'package:pshared/models/payment/intent.dart'; @@ -7,7 +19,6 @@ import 'package:pshared/models/payment/quote.dart'; import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/resource.dart'; import 'package:pshared/service/payment/quotation.dart'; -import 'package:uuid/uuid.dart'; class QuotationProvider extends ChangeNotifier { @@ -15,14 +26,46 @@ class QuotationProvider extends ChangeNotifier { late OrganizationsProvider _organizations; bool _isLoaded = false; - void update(OrganizationsProvider venue) { + void update(OrganizationsProvider venue, PaymentAmountProvider payment) { _organizations = venue; + getQuotation(PaymentIntent( + kind: PaymentKind.payout, + amount: Money( + amount: payment.amount.toString(), + currency: 'USDT', + ), + destination: CardPaymentMethod( + pan: '4000000000000077', + firstName: 'John', + lastName: 'Doe', + ), + source: ManagedWalletPaymentMethod( + managedWalletRef: '', + ), + fx: FxIntent( + pair: CurrencyPair( + base: 'USDT', + quote: 'RUB', + ), + side: FxSide.sellBaseBuyQuote, + ), + settlementMode: payment.payerCoversFee ? SettlementMode.fixReceived : SettlementMode.fixSource, + )); } PaymentQuote? get quotation => _quotation.data; bool get isReady => _isLoaded && !_quotation.isLoading && _quotation.error == null; + Asset? get fee => quotation == null ? null : createAsset(quotation!.expectedFeeTotal!.currency, quotation!.expectedFeeTotal!.amount); + Asset? get total => quotation == null ? null : createAsset(quotation!.debitAmount!.currency, quotation!.debitAmount!.amount); + Asset? get recipientGets => quotation == null ? null : createAsset(quotation!.expectedSettlementAmount!.currency, quotation!.expectedSettlementAmount!.amount); + + void _setResource(Resource quotation) { + _quotation = quotation; + notifyListeners(); + } + Future getQuotation(PaymentIntent intent) async { if (!_organizations.isOrganizationSet) throw StateError('Organization is not set'); try { @@ -35,19 +78,20 @@ class QuotationProvider extends ChangeNotifier { ), ); _isLoaded = true; - _quotation = _quotation.copyWith(data: response, isLoading: false); + _setResource(_quotation.copyWith(data: response, isLoading: false, error: null)); } catch (e) { - _quotation = _quotation.copyWith( + _setResource(_quotation.copyWith( + data: null, error: e is Exception ? e : Exception(e.toString()), isLoading: false, - ); + )); } notifyListeners(); return _quotation.data; } void reset() { - _quotation = Resource(data: null, isLoading: false, error: null); + _setResource(Resource(data: null, isLoading: false, error: null)); _isLoaded = false; notifyListeners(); } diff --git a/frontend/pshared/lib/service/payment/quotation.dart b/frontend/pshared/lib/service/payment/quotation.dart index 90fe900..d3382cc 100644 --- a/frontend/pshared/lib/service/payment/quotation.dart +++ b/frontend/pshared/lib/service/payment/quotation.dart @@ -4,8 +4,8 @@ import 'package:pshared/api/requests/payment/quote.dart'; import 'package:pshared/api/responses/payment/quotation.dart'; import 'package:pshared/data/mapper/payment/payment_quote.dart'; import 'package:pshared/models/payment/quote.dart'; +import 'package:pshared/service/authorization/service.dart'; import 'package:pshared/service/services.dart'; -import 'package:pshared/utils/http/requests.dart'; class QuotationService { @@ -14,7 +14,11 @@ class QuotationService { static Future getQuotation(String organizationRef, QuotePaymentRequest request) async { _logger.fine('Quoting payment for organization $organizationRef'); - final response = await getPOSTResponse(_objectType, '/quote/$organizationRef', request.toJson()); + final response = await AuthorizationService.getPOSTResponse( + _objectType, + '/quote/$organizationRef', + request.toJson(), + ); return PaymentQuoteResponse.fromJson(response).quote.toDomain(); } } diff --git a/frontend/pshared/lib/utils/currency.dart b/frontend/pshared/lib/utils/currency.dart index af50a31..a739817 100644 --- a/frontend/pshared/lib/utils/currency.dart +++ b/frontend/pshared/lib/utils/currency.dart @@ -1,22 +1,64 @@ -String currencyCodeToSymbol(String currencyCode) { +import 'package:flutter/material.dart'; + +import 'package:pshared/models/asset.dart'; +import 'package:pshared/models/currency.dart'; + + +String currencyCodeToSymbol(Currency currencyCode) { switch (currencyCode) { - case 'USD': + case Currency.usd: return '\$'; - case 'PLN': - return 'zł'; - case 'EUR': - return '€'; - case 'GBP': - return '£'; - case 'HUF': - return 'Ft'; - case 'RUB': + case Currency.usdt: + return '₮'; + case Currency.usdc: + return '\$'; + case Currency.rub: return '₽'; - default: - return currencyCode; + case Currency.eur: + return '€'; } } -String currencyToString(String currencyCode, double amount) { - return '${currencyCodeToSymbol(currencyCode)}\u00A0${amount.toStringAsFixed(2)}'; +String amountToString(double amount) { + return amount.toStringAsFixed(2); +} + +String currencyToString(Currency currencyCode, double amount) { + return '${currencyCodeToSymbol(currencyCode)}\u00A0${amountToString(amount)}'; +} + +String assetToString(Asset asset) { + return currencyToString(asset.currency, asset.amount); +} + +Currency currencyStringToCode(String currencyCode) { + switch (currencyCode) { + case 'USD': + return Currency.usd; + case 'USDT': + return Currency.usdt; + case 'USDC': + return Currency.usdc; + case 'RUB': + return Currency.rub; + case 'EUR': + return Currency.eur; + default: + throw ArgumentError('Unknown currency code: $currencyCode'); + } +} + +IconData iconForCurrencyType(Currency currencyCode) { + switch (currencyCode) { + case Currency.usd: + return Icons.currency_exchange; + case Currency.eur: + return Icons.currency_exchange; + case Currency.rub: + return Icons.currency_ruble; + case Currency.usdt: + return Icons.currency_exchange; + case Currency.usdc: + return Icons.money; + } } \ No newline at end of file diff --git a/frontend/pweb/lib/data/mappers/wallet_ui.dart b/frontend/pweb/lib/data/mappers/wallet_ui.dart index 396ee31..a13f3b3 100644 --- a/frontend/pweb/lib/data/mappers/wallet_ui.dart +++ b/frontend/pweb/lib/data/mappers/wallet_ui.dart @@ -1,6 +1,7 @@ import 'package:pshared/models/wallet/wallet.dart' as domain; -import 'package:pweb/models/currency.dart'; +import 'package:pshared/models/currency.dart'; + import 'package:pweb/models/wallet.dart'; diff --git a/frontend/pweb/lib/l10n/en.arb b/frontend/pweb/lib/l10n/en.arb index 1987f47..22fd2f5 100644 --- a/frontend/pweb/lib/l10n/en.arb +++ b/frontend/pweb/lib/l10n/en.arb @@ -378,7 +378,7 @@ "send": "Send Payout", "recipientPaysFee": "Recipient pays the fee", - "sentAmount": "Sent amount: ${amount}", + "sentAmount": "Sent amount: {amount}", "@sentAmount": { "description": "Label showing the amount sent", "placeholders": { @@ -388,7 +388,7 @@ } }, - "fee": "Fee: ${fee}", + "fee": "Fee: {fee}", "@fee": { "description": "Label showing the transaction fee", "placeholders": { @@ -398,7 +398,7 @@ } }, - "recipientWillReceive": "Recipient will receive: ${amount}", + "recipientWillReceive": "Recipient will receive: {amount}", "@recipientWillReceive": { "description": "Label showing how much the recipient will receive", "placeholders": { @@ -408,7 +408,7 @@ } }, - "total": "Total: ${total}", + "total": "Total: {total}", "@total": { "description": "Label showing the total amount of the transaction", "placeholders": { diff --git a/frontend/pweb/lib/l10n/ru.arb b/frontend/pweb/lib/l10n/ru.arb index 8743ca8..e47f9dc 100644 --- a/frontend/pweb/lib/l10n/ru.arb +++ b/frontend/pweb/lib/l10n/ru.arb @@ -378,7 +378,7 @@ "send": "Отправить выплату", "recipientPaysFee": "Получатель оплачивает комиссию", - "sentAmount": "Отправленная сумма: ${amount}", + "sentAmount": "Отправленная сумма: {amount}", "@sentAmount": { "description": "Метка, показывающая отправленную сумму", "placeholders": { @@ -388,7 +388,7 @@ } }, - "fee": "Комиссия: ${fee}", + "fee": "Комиссия: {fee}", "@fee": { "description": "Метка, показывающая комиссию за транзакцию", "placeholders": { @@ -398,7 +398,7 @@ } }, - "recipientWillReceive": "Получатель получит: ${amount}", + "recipientWillReceive": "Получатель получит: {amount}", "@recipientWillReceive": { "description": "Метка, показывающая, сколько получит получатель", "placeholders": { @@ -408,7 +408,7 @@ } }, - "total": "Итого: ${total}", + "total": "Итого: {total}", "@total": { "description": "Метка, показывающая общую сумму транзакции", "placeholders": { diff --git a/frontend/pweb/lib/main.dart b/frontend/pweb/lib/main.dart index 696d989..6ac5990 100644 --- a/frontend/pweb/lib/main.dart +++ b/frontend/pweb/lib/main.dart @@ -12,7 +12,6 @@ import 'package:pshared/provider/locale.dart'; import 'package:pshared/provider/permissions.dart'; import 'package:pshared/provider/account.dart'; import 'package:pshared/provider/organizations.dart'; -import 'package:pshared/provider/payment/quotation.dart'; import 'package:pshared/provider/recipient/provider.dart'; import 'package:pshared/provider/recipient/pmethods.dart'; @@ -94,10 +93,6 @@ void main() async { ChangeNotifierProvider( create: (_) => OperationProvider(OperationService())..loadOperations(), ), - ChangeNotifierProxyProvider( - create: (_) => QuotationProvider(), - update: (context, orgnization, provider) => provider!..update(orgnization), - ), ], child: const PayApp(), ), diff --git a/frontend/pweb/lib/models/wallet.dart b/frontend/pweb/lib/models/wallet.dart index c902ac3..ece57cf 100644 --- a/frontend/pweb/lib/models/wallet.dart +++ b/frontend/pweb/lib/models/wallet.dart @@ -1,4 +1,4 @@ -import 'package:pweb/models/currency.dart'; +import 'package:pshared/models/currency.dart'; class Wallet { diff --git a/frontend/pweb/lib/models/wallet_transaction.dart b/frontend/pweb/lib/models/wallet_transaction.dart index b8a9f79..95a7296 100644 --- a/frontend/pweb/lib/models/wallet_transaction.dart +++ b/frontend/pweb/lib/models/wallet_transaction.dart @@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart'; import 'package:pshared/models/payment/status.dart'; -import 'package:pweb/models/currency.dart'; +import 'package:pshared/models/currency.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart index 409a59b..49026ad 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; + +import 'package:pshared/utils/currency.dart'; + import 'package:pweb/models/wallet.dart'; -import 'package:pweb/utils/currency.dart'; class BalanceAmount extends StatelessWidget { @@ -25,7 +27,7 @@ class BalanceAmount extends StatelessWidget { return Row( children: [ Text( - wallet.isHidden ? '•••• $currencyBalance' : '${wallet.balance.toStringAsFixed(2)} $currencyBalance', + wallet.isHidden ? '•••• $currencyBalance' : '${amountToString(wallet.balance)} $currencyBalance', style: textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, color: colorScheme.onSurface, diff --git a/frontend/pweb/lib/pages/dashboard/payouts/amount.dart b/frontend/pweb/lib/pages/dashboard/payouts/amount.dart new file mode 100644 index 0000000..3e2b2b2 --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/payouts/amount.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:pshared/provider/payment/amount.dart'; +import 'package:pshared/utils/currency.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class PaymentAmountWidget extends StatefulWidget { + const PaymentAmountWidget({super.key}); + + @override + State createState() => _PaymentAmountWidgetState(); +} + +class _PaymentAmountWidgetState extends State { + late final TextEditingController _controller; + bool _isSyncingText = false; + + @override + void initState() { + super.initState(); + final initialAmount = context.read().amount; + _controller = TextEditingController(text: amountToString(initialAmount)); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + double? _parseAmount(String value) => double.tryParse(value.replaceAll(',', '.')); + + void _syncTextWithAmount(double amount) { + final parsedText = _parseAmount(_controller.text); + if (parsedText != null && parsedText == amount) return; + + final nextText = amountToString(amount); + _isSyncingText = true; + _controller.value = TextEditingValue( + text: nextText, + selection: TextSelection.collapsed(offset: nextText.length), + ); + _isSyncingText = false; + } + + void _onChanged(String value) { + if (_isSyncingText) return; + + final parsed = _parseAmount(value); + if (parsed != null) { + context.read().setAmount(parsed); + } + } + + @override + Widget build(BuildContext context) { + final amount = context.select((provider) => provider.amount); + _syncTextWithAmount(amount); + + return TextField( + controller: _controller, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + decoration: InputDecoration( + labelText: AppLocalizations.of(context)!.amount, + border: const OutlineInputBorder(), + ), + onChanged: _onChanged, + ); + } +} diff --git a/frontend/pweb/lib/pages/dashboard/payouts/fee_payer.dart b/frontend/pweb/lib/pages/dashboard/payouts/fee_payer.dart new file mode 100644 index 0000000..9270152 --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/payouts/fee_payer.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:pshared/provider/payment/amount.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class FeePayerSwitch extends StatelessWidget { + final double spacing; + final TextStyle? style; + + const FeePayerSwitch({super.key, required this.spacing, TextStyle? this.style}); + + @override + Widget build(BuildContext context) => Consumer( + builder: (context, provider, _) => Row( + spacing: spacing, + children: [ + Text(AppLocalizations.of(context)!.recipientPaysFee, style: style), + Switch( + value: !provider.payerCoversFee, + onChanged: (val) => provider.setPayerCoversFee(!val), + ), + ], + ), + ); +} diff --git a/frontend/pweb/lib/pages/dashboard/payouts/form.dart b/frontend/pweb/lib/pages/dashboard/payouts/form.dart new file mode 100644 index 0000000..e076d52 --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/payouts/form.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +import 'package:pweb/pages/dashboard/payouts/amount.dart'; +import 'package:pweb/pages/dashboard/payouts/fee_payer.dart'; +import 'package:pweb/pages/dashboard/payouts/summary/widget.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class PaymentFormWidget extends StatelessWidget { + const PaymentFormWidget({super.key}); + + static const double _smallSpacing = 5; + static const double _mediumSpacing = 10; + static const double _largeSpacing = 16; + static const double _extraSpacing = 15; + + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final loc = AppLocalizations.of(context)!; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(loc.details, style: theme.textTheme.titleMedium), + const SizedBox(height: _smallSpacing), + + const PaymentAmountWidget(), + + const SizedBox(height: _mediumSpacing), + + FeePayerSwitch(spacing: _mediumSpacing, style: theme.textTheme.titleMedium), + + const SizedBox(height: _largeSpacing), + + const PaymentSummary(spacing: _extraSpacing), + ], + ); + } +} + diff --git a/frontend/pweb/lib/pages/dashboard/payouts/payment_form.dart b/frontend/pweb/lib/pages/dashboard/payouts/payment_form.dart deleted file mode 100644 index 4285416..0000000 --- a/frontend/pweb/lib/pages/dashboard/payouts/payment_form.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:provider/provider.dart'; - -import 'package:pweb/providers/mock_payment.dart'; - -import 'package:pweb/generated/i18n/app_localizations.dart'; - - -class PaymentFormWidget extends StatelessWidget { - const PaymentFormWidget({super.key}); - - static const double _smallSpacing = 5; - static const double _mediumSpacing = 10; - static const double _largeSpacing = 16; - static const double _extraSpacing = 15; - - String _formatAmount(double amount) => amount.toStringAsFixed(2); - - @override - Widget build(BuildContext context) { - final provider = Provider.of(context); - final theme = Theme.of(context); - final loc = AppLocalizations.of(context)!; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(loc.details, style: theme.textTheme.titleMedium), - const SizedBox(height: _smallSpacing), - - TextField( - keyboardType: const TextInputType.numberWithOptions(decimal: true), - decoration: InputDecoration( - labelText: loc.amount, - border: const OutlineInputBorder(), - ), - onChanged: (val) { - final parsed = double.tryParse(val.replaceAll(',', '.')) ?? 0.0; - provider.setAmount(parsed); - }, - ), - - const SizedBox(height: _mediumSpacing), - - Row( - spacing: _mediumSpacing, - children: [ - Text(loc.recipientPaysFee, style: theme.textTheme.titleMedium), - Switch( - value: !provider.payerCoversFee, - onChanged: (val) => provider.setPayerCoversFee(!val), - ), - ], - ), - - const SizedBox(height: _largeSpacing), - - Align( - alignment: Alignment.center, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _SummaryRow(label: loc.sentAmount(_formatAmount(provider.amount)), style: theme.textTheme.titleMedium), - _SummaryRow(label: loc.fee(_formatAmount(provider.fee)), style: theme.textTheme.titleMedium), - _SummaryRow(label: loc.recipientWillReceive(_formatAmount(provider.recipientGets)), style: theme.textTheme.titleMedium), - - const SizedBox(height: _extraSpacing), - - _SummaryRow( - label: loc.total(_formatAmount(provider.total)), - style: theme.textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600), - ), - ], - ), - ), - ], - ); - } -} - -class _SummaryRow extends StatelessWidget { - final String label; - final TextStyle? style; - - const _SummaryRow({required this.label, this.style}); - - @override - Widget build(BuildContext context) { - return Text(label, style: style); - } -} diff --git a/frontend/pweb/lib/pages/dashboard/payouts/summary/fee.dart b/frontend/pweb/lib/pages/dashboard/payouts/summary/fee.dart new file mode 100644 index 0000000..95f8aa5 --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/payouts/summary/fee.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:pshared/provider/payment/quotation.dart'; + +import 'package:pweb/pages/dashboard/payouts/summary/row.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class PaymentFeeRow extends StatelessWidget { + const PaymentFeeRow({super.key}); + + @override + Widget build(BuildContext context) => Consumer( + builder: (context, provider, _) => PaymentSummaryRow( + labelFactory: AppLocalizations.of(context)!.fee, + asset: provider.fee, + style: Theme.of(context).textTheme.titleMedium, + ), + ); +} diff --git a/frontend/pweb/lib/pages/dashboard/payouts/summary/recipient_receives.dart b/frontend/pweb/lib/pages/dashboard/payouts/summary/recipient_receives.dart new file mode 100644 index 0000000..e2f23a7 --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/payouts/summary/recipient_receives.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:pshared/provider/payment/quotation.dart'; + +import 'package:pweb/pages/dashboard/payouts/summary/row.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class PaymentRecipientReceivesRow extends StatelessWidget { + const PaymentRecipientReceivesRow({super.key}); + + @override + Widget build(BuildContext context) => Consumer( + builder: (context, provider, _) => PaymentSummaryRow( + labelFactory: AppLocalizations.of(context)!.recipientWillReceive, + asset: provider.recipientGets, + style: Theme.of(context).textTheme.titleMedium, + ), + ); +} diff --git a/frontend/pweb/lib/pages/dashboard/payouts/summary/row.dart b/frontend/pweb/lib/pages/dashboard/payouts/summary/row.dart new file mode 100644 index 0000000..0bbbe67 --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/payouts/summary/row.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +import 'package:pshared/models/asset.dart'; +import 'package:pshared/utils/currency.dart'; + + +class PaymentSummaryRow extends StatelessWidget { + final String Function(String) labelFactory; + final Asset? asset; + final TextStyle? style; + + const PaymentSummaryRow({ + super.key, + required this.labelFactory, + required this.asset, + this.style, + }); + + @override + Widget build(BuildContext context) => Text( + labelFactory(asset == null ? 'N/A' : assetToString(asset!)), + style: style, + ); +} diff --git a/frontend/pweb/lib/pages/dashboard/payouts/summary/sent_amount.dart b/frontend/pweb/lib/pages/dashboard/payouts/summary/sent_amount.dart new file mode 100644 index 0000000..540845e --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/payouts/summary/sent_amount.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:pshared/models/asset.dart'; +import 'package:pshared/models/currency.dart'; +import 'package:pshared/provider/payment/amount.dart'; + +import 'package:pweb/pages/dashboard/payouts/summary/row.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class PaymentSentAmountRow extends StatelessWidget { + final Currency currency; + const PaymentSentAmountRow({super.key, required this.currency}); + + @override + Widget build(BuildContext context) => Consumer( + builder: (context, provider, _) => PaymentSummaryRow( + labelFactory: AppLocalizations.of(context)!.sentAmount, + asset: Asset(currency: currency, amount: provider.amount), + style: Theme.of(context).textTheme.titleMedium, + ), + ); +} diff --git a/frontend/pweb/lib/pages/dashboard/payouts/summary/total.dart b/frontend/pweb/lib/pages/dashboard/payouts/summary/total.dart new file mode 100644 index 0000000..47f1b64 --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/payouts/summary/total.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:pshared/provider/payment/quotation.dart'; + +import 'package:pweb/pages/dashboard/payouts/summary/row.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class PaymentTotalRow extends StatelessWidget { + const PaymentTotalRow({super.key}); + + @override + Widget build(BuildContext context) => Consumer( + builder: (context, provider, _) => PaymentSummaryRow( + labelFactory: AppLocalizations.of(context)!.total, + asset: provider.total, + style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600), + ), + ); +} diff --git a/frontend/pweb/lib/pages/dashboard/payouts/summary/widget.dart b/frontend/pweb/lib/pages/dashboard/payouts/summary/widget.dart new file mode 100644 index 0000000..f07fbe6 --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/payouts/summary/widget.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:pshared/utils/currency.dart'; + +import 'package:pweb/pages/dashboard/payouts/summary/fee.dart'; +import 'package:pweb/pages/dashboard/payouts/summary/recipient_receives.dart'; +import 'package:pweb/pages/dashboard/payouts/summary/sent_amount.dart'; +import 'package:pweb/pages/dashboard/payouts/summary/total.dart'; +import 'package:pweb/providers/wallets.dart'; + + +class PaymentSummary extends StatelessWidget { + final double spacing; + + const PaymentSummary({super.key, required this.spacing}); + + @override + Widget build(BuildContext context) => Align( + alignment: Alignment.center, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + PaymentSentAmountRow(currency: currencyStringToCode(context.read().selectedWallet?.tokenSymbol ?? 'USDT')), + const PaymentFeeRow(), + const PaymentRecipientReceivesRow(), + SizedBox(height: spacing), + const PaymentTotalRow(), + ], + ), + ); +} diff --git a/frontend/pweb/lib/pages/dashboard/payouts/widget.dart b/frontend/pweb/lib/pages/dashboard/payouts/widget.dart new file mode 100644 index 0000000..27cda2e --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/payouts/widget.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:pshared/provider/organizations.dart'; +import 'package:pshared/provider/payment/amount.dart'; +import 'package:pshared/provider/payment/quotation.dart'; + +import 'package:pweb/pages/dashboard/payouts/form.dart'; + + +class PaymentFromWrappingWidget extends StatelessWidget { + const PaymentFromWrappingWidget({super.key}); + + @override + Widget build(BuildContext context) => MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => PaymentAmountProvider(), + ), + ChangeNotifierProxyProvider2( + create: (_) => QuotationProvider(), + update: (context, orgnization, payment, provider) => provider!..update(orgnization, payment), + ), + ], + child: const PaymentFormWidget(), + ); +} \ No newline at end of file diff --git a/frontend/pweb/lib/pages/payment_methods/payment_page/content.dart b/frontend/pweb/lib/pages/payment_methods/payment_page/content.dart index 20b478d..8a53814 100644 --- a/frontend/pweb/lib/pages/payment_methods/payment_page/content.dart +++ b/frontend/pweb/lib/pages/payment_methods/payment_page/content.dart @@ -11,7 +11,7 @@ import 'package:pweb/pages/payment_methods/payment_page/back_button.dart'; import 'package:pweb/pages/payment_methods/payment_page/header.dart'; import 'package:pweb/pages/payment_methods/payment_page/method_selector.dart'; import 'package:pweb/pages/payment_methods/payment_page/send_button.dart'; -import 'package:pweb/pages/dashboard/payouts/payment_form.dart'; +import 'package:pweb/pages/dashboard/payouts/form.dart'; import 'package:pweb/pages/payment_methods/widgets/payment_info_section.dart'; import 'package:pweb/pages/payment_methods/widgets/recipient_section.dart'; import 'package:pweb/pages/payment_methods/widgets/section_title.dart'; diff --git a/frontend/pweb/lib/pages/payment_methods/payment_page/method_selector.dart b/frontend/pweb/lib/pages/payment_methods/payment_page/method_selector.dart index 8d2bae8..512ca3e 100644 --- a/frontend/pweb/lib/pages/payment_methods/payment_page/method_selector.dart +++ b/frontend/pweb/lib/pages/payment_methods/payment_page/method_selector.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:pshared/models/payment/methods/type.dart'; -import 'package:pshared/provider/recipient/pmethods.dart'; +import 'package:pweb/models/wallet.dart'; +import 'package:pweb/providers/wallets.dart'; import 'package:pweb/utils/payment/dropdown.dart'; class PaymentMethodSelector extends StatelessWidget { - final ValueChanged onMethodChanged; + final ValueChanged onMethodChanged; const PaymentMethodSelector({ super.key, @@ -17,9 +17,9 @@ class PaymentMethodSelector extends StatelessWidget { }); @override - Widget build(BuildContext context) => Consumer(builder:(context, provider, _) => PaymentMethodDropdown( - methods: provider.methods, - initialValue: provider.currentObject, + Widget build(BuildContext context) => Consumer(builder:(context, provider, _) => PaymentMethodDropdown( + methods: provider.wallets, + initialValue: provider.selectedWallet, onChanged: onMethodChanged, )); } diff --git a/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart b/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart index 9d6a8cc..ee2ece1 100644 --- a/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart +++ b/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart @@ -7,7 +7,7 @@ import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/provider.dart'; -import 'package:pweb/pages/dashboard/payouts/payment_form.dart'; +import 'package:pweb/pages/dashboard/payouts/widget.dart'; import 'package:pweb/pages/payment_methods/payment_page/back_button.dart'; import 'package:pweb/pages/payment_methods/payment_page/header.dart'; import 'package:pweb/pages/payment_methods/payment_page/method_selector.dart'; @@ -105,7 +105,7 @@ class PaymentPageContent extends StatelessWidget { availableTypes: availablePaymentTypes, ), SizedBox(height: dimensions.paddingLarge), - const PaymentFormWidget(), + const PaymentFromWrappingWidget(), SizedBox(height: dimensions.paddingXXXLarge), SendButton(onPressed: onSend), SizedBox(height: dimensions.paddingLarge), diff --git a/frontend/pweb/lib/pages/payout_page/wallet/card.dart b/frontend/pweb/lib/pages/payout_page/wallet/card.dart index 6341477..ea0babf 100644 --- a/frontend/pweb/lib/pages/payout_page/wallet/card.dart +++ b/frontend/pweb/lib/pages/payout_page/wallet/card.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; + import 'package:provider/provider.dart'; +import 'package:pshared/utils/currency.dart'; + import 'package:pweb/models/wallet.dart'; import 'package:pweb/pages/dashboard/buttons/balance/amount.dart'; import 'package:pweb/providers/wallets.dart'; -import 'package:pweb/utils/currency.dart'; class WalletCard extends StatelessWidget { diff --git a/frontend/pweb/lib/pages/payout_page/wallet/history/table.dart b/frontend/pweb/lib/pages/payout_page/wallet/history/table.dart index 43312fb..9ae3d25 100644 --- a/frontend/pweb/lib/pages/payout_page/wallet/history/table.dart +++ b/frontend/pweb/lib/pages/payout_page/wallet/history/table.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:pshared/utils/currency.dart'; + import 'package:pweb/models/wallet_transaction.dart'; import 'package:pweb/pages/payout_page/wallet/history/chip.dart'; import 'package:pweb/pages/report/table/badge.dart'; -import 'package:pweb/utils/currency.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; @@ -66,11 +67,11 @@ class WalletTransactionsTable extends StatelessWidget { DataCell(OperationStatusBadge(status: tx.status)), DataCell(TypeChip(type: tx.type)), DataCell(Text( - '${tx.type.sign}${tx.amount.toStringAsFixed(2)} ${currencyCodeToSymbol(tx.currency)}')), + '${tx.type.sign}${amountToString(tx.amount)} ${currencyCodeToSymbol(tx.currency)}')), DataCell(Text( tx.balanceAfter == null ? '-' - : '${tx.balanceAfter!.toStringAsFixed(2)} ${currencyCodeToSymbol(tx.currency)}', + : '${amountToString(tx.balanceAfter!)} ${currencyCodeToSymbol(tx.currency)}', )), DataCell(Text(tx.counterparty ?? '-')), DataCell(Text( diff --git a/frontend/pweb/lib/pages/report/table/row.dart b/frontend/pweb/lib/pages/report/table/row.dart index 7398610..900c043 100644 --- a/frontend/pweb/lib/pages/report/table/row.dart +++ b/frontend/pweb/lib/pages/report/table/row.dart @@ -1,15 +1,18 @@ -// operation_row.dart import 'package:flutter/material.dart'; + import 'package:pshared/models/payment/operation.dart'; +import 'package:pshared/utils/currency.dart'; + import 'package:pweb/pages/report/table/badge.dart'; + class OperationRow { static DataRow build(OperationItem op, BuildContext context) { return DataRow(cells: [ DataCell(OperationStatusBadge(status: op.status)), DataCell(Text(op.fileName ?? '')), - DataCell(Text('${op.amount.toStringAsFixed(2)} ${op.currency}')), - DataCell(Text('${op.toAmount.toStringAsFixed(2)} ${op.toCurrency}')), + DataCell(Text('${amountToString(op.amount)} ${op.currency}')), + DataCell(Text('${amountToString(op.toAmount)} ${op.toCurrency}')), DataCell(Text(op.payId)), DataCell(Text(op.cardNumber ?? '-')), DataCell(Text(op.name)), diff --git a/frontend/pweb/lib/pages/wallet_top_up/content.dart b/frontend/pweb/lib/pages/wallet_top_up/content.dart index 7f66779..e472fda 100644 --- a/frontend/pweb/lib/pages/wallet_top_up/content.dart +++ b/frontend/pweb/lib/pages/wallet_top_up/content.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:pshared/utils/currency.dart'; + import 'package:pweb/models/wallet.dart'; import 'package:pweb/pages/wallet_top_up/details.dart'; import 'package:pweb/pages/wallet_top_up/header.dart'; import 'package:pweb/pages/wallet_top_up/meta.dart'; -import 'package:pweb/utils/currency.dart'; import 'package:pweb/utils/dimensions.dart'; diff --git a/frontend/pweb/lib/services/wallet_transactions.dart b/frontend/pweb/lib/services/wallet_transactions.dart index aa34059..086bab1 100644 --- a/frontend/pweb/lib/services/wallet_transactions.dart +++ b/frontend/pweb/lib/services/wallet_transactions.dart @@ -1,6 +1,6 @@ import 'package:pshared/models/payment/status.dart'; +import 'package:pshared/models/currency.dart'; -import 'package:pweb/models/currency.dart'; import 'package:pweb/models/wallet_transaction.dart'; diff --git a/frontend/pweb/lib/utils/currency.dart b/frontend/pweb/lib/utils/currency.dart deleted file mode 100644 index 7f9a33d..0000000 --- a/frontend/pweb/lib/utils/currency.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:pweb/models/currency.dart'; - - -String currencyCodeToSymbol(Currency currencyCode) { - switch (currencyCode) { - case Currency.usd: - return '\$'; - case Currency.eur: - return '€'; - case Currency.rub: - return '₽'; - case Currency.usdt: - return 'USDT'; - case Currency.usdc: - return 'USDC'; - } -} - -String currencyToString(Currency currencyCode, double amount) { - return '${amount.toStringAsFixed(2)} ${currencyCodeToSymbol(currencyCode)}'; -} - -IconData iconForCurrencyType(Currency currencyCode) { - switch (currencyCode) { - case Currency.usd: - return Icons.currency_exchange; - case Currency.eur: - return Icons.currency_exchange; - case Currency.rub: - return Icons.currency_ruble; - case Currency.usdt: - return Icons.currency_exchange; - case Currency.usdc: - return Icons.money; - } -} \ No newline at end of file diff --git a/frontend/pweb/lib/utils/payment/dropdown.dart b/frontend/pweb/lib/utils/payment/dropdown.dart index 7b9b292..77c5e02 100644 --- a/frontend/pweb/lib/utils/payment/dropdown.dart +++ b/frontend/pweb/lib/utils/payment/dropdown.dart @@ -1,16 +1,17 @@ import 'package:flutter/material.dart'; -import 'package:pshared/models/payment/methods/type.dart'; +import 'package:pshared/models/payment/type.dart'; +import 'package:pweb/models/wallet.dart'; import 'package:pweb/pages/payment_methods/icon.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; class PaymentMethodDropdown extends StatefulWidget { - final List methods; - final ValueChanged onChanged; - final PaymentMethod? initialValue; + final List methods; + final ValueChanged onChanged; + final Wallet? initialValue; const PaymentMethodDropdown({ super.key, @@ -24,7 +25,7 @@ class PaymentMethodDropdown extends StatefulWidget { } class _PaymentMethodDropdownState extends State { - late PaymentMethod _selectedMethod; + late Wallet _selectedMethod; @override void initState() { @@ -34,7 +35,7 @@ class _PaymentMethodDropdownState extends State { @override Widget build(BuildContext context) { - return DropdownButtonFormField( + return DropdownButtonFormField( dropdownColor: Theme.of(context).colorScheme.onSecondary, initialValue: _selectedMethod, decoration: InputDecoration( @@ -42,13 +43,13 @@ class _PaymentMethodDropdownState extends State { border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), items: widget.methods.map((method) { - return DropdownMenuItem( + return DropdownMenuItem( value: method, child: Row( children: [ - Icon(iconForPaymentType(method.type), size: 20), + Icon(iconForPaymentType(PaymentType.managedWallet), size: 20), const SizedBox(width: 8), - Text('${method.name}' + (method.description == null ? '' : ' (${method.description!})')), + Text(method.name), ], ), );