From e4847cd1374890c959d767b519b0798f39453ab9 Mon Sep 17 00:00:00 2001 From: Arseni Date: Fri, 26 Dec 2025 20:37:59 +0300 Subject: [PATCH] Finally Fixed search field in payment page and cleaned up payment flow --- .../pshared/lib/provider/payment/flow.dart | 150 ++++++++++++------ .../lib/provider/payment/quotation.dart | 91 ++++++----- .../pshared/lib/provider/payment/wallets.dart | 25 ++- .../lib/provider/recipient/pmethods.dart | 20 +++ .../lib/provider/recipient/provider.dart | 23 +++ .../pshared/lib/service/payment/service.dart | 7 +- frontend/pweb/lib/main.dart | 25 ++- .../pweb/lib/pages/payment_methods/page.dart | 148 ++++------------- .../payment_methods/payment_page/body.dart | 9 +- .../payment_methods/payment_page/content.dart | 22 +-- .../payment_page/method_selector.dart | 12 +- .../payment_methods/payment_page/page.dart | 24 +-- .../widgets/payment_info_section.dart | 19 +-- .../widgets/recipient_section.dart | 59 ++++--- frontend/pweb/lib/utils/payment/dropdown.dart | 34 ++-- 15 files changed, 358 insertions(+), 310 deletions(-) diff --git a/frontend/pshared/lib/provider/payment/flow.dart b/frontend/pshared/lib/provider/payment/flow.dart index e4f3f73..1f80552 100644 --- a/frontend/pshared/lib/provider/payment/flow.dart +++ b/frontend/pshared/lib/provider/payment/flow.dart @@ -1,73 +1,63 @@ +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:pshared/models/payment/methods/data.dart'; +import 'package:pshared/models/payment/methods/type.dart'; import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/recipient/recipient.dart'; +import 'package:pshared/provider/recipient/provider.dart'; +import 'package:pshared/provider/recipient/pmethods.dart'; class PaymentFlowProvider extends ChangeNotifier { PaymentType _selectedType; + PaymentType? _preferredType; PaymentMethodData? _manualPaymentData; + List _recipientMethods = []; + Recipient? _recipient; PaymentFlowProvider({ required PaymentType initialType, - }) : _selectedType = initialType; + PaymentType? preferredType, + }) : _selectedType = initialType, + _preferredType = preferredType ?? initialType; PaymentType get selectedType => _selectedType; PaymentMethodData? get manualPaymentData => _manualPaymentData; + Recipient? get recipient => _recipient; + PaymentMethod? get selectedMethod => hasRecipient + ? _recipientMethods.firstWhereOrNull((method) => method.type == _selectedType) + : null; - void sync({ - required Recipient? recipient, - required MethodMap availableTypes, - PaymentType? preferredType, - }) { - final resolvedType = _resolveSelectedType( - recipient: recipient, - availableTypes: availableTypes, - preferredType: preferredType, - ); + bool get hasRecipient => _recipient != null; - var hasChanges = false; - if (resolvedType != _selectedType) { - _selectedType = resolvedType; - hasChanges = true; - } + MethodMap get availableTypes => hasRecipient + ? _buildAvailableTypes(_recipientMethods) + : {for (final type in PaymentType.values) type: null}; - if (recipient != null && _manualPaymentData != null) { - _manualPaymentData = null; - hasChanges = true; - } + PaymentMethodData? get selectedPaymentData => + hasRecipient ? selectedMethod?.data : _manualPaymentData; - if (hasChanges) notifyListeners(); - } + List get methodsForRecipient => hasRecipient + ? List.unmodifiable(_recipientMethods) + : const []; - void reset({ - required Recipient? recipient, - required MethodMap availableTypes, - PaymentType? preferredType, - }) { - final resolvedType = _resolveSelectedType( - recipient: recipient, - availableTypes: availableTypes, - preferredType: preferredType, - ); - - var hasChanges = false; - - if (resolvedType != _selectedType) { - _selectedType = resolvedType; - hasChanges = true; - } - - if (_manualPaymentData != null) { - _manualPaymentData = null; - hasChanges = true; - } - - if (hasChanges) notifyListeners(); - } + void update( + RecipientsProvider recipientsProvider, + PaymentMethodsProvider methodsProvider, + ) => + _applyState( + recipient: recipientsProvider.currentObject, + methods: methodsProvider.methodsForRecipient(recipientsProvider.currentObject), + preferredType: _preferredType, + forceResetManualData: false, + ); void selectType(PaymentType type, {bool resetManualData = false}) { + if (hasRecipient && !availableTypes.containsKey(type)) { + return; + } + if (_selectedType == type && (!resetManualData || _manualPaymentData == null)) { return; } @@ -84,6 +74,20 @@ class PaymentFlowProvider extends ChangeNotifier { notifyListeners(); } + void setPreferredType(PaymentType? preferredType) { + if (_preferredType == preferredType) { + return; + } + + _preferredType = preferredType; + _applyState( + recipient: _recipient, + methods: _recipientMethods, + preferredType: _preferredType, + forceResetManualData: false, + ); + } + PaymentType _resolveSelectedType({ required Recipient? recipient, required MethodMap availableTypes, @@ -107,4 +111,56 @@ class PaymentFlowProvider extends ChangeNotifier { return availableTypes.keys.first; } + + void _applyState({ + required Recipient? recipient, + required List methods, + required PaymentType? preferredType, + required bool forceResetManualData, + }) { + final availableTypes = _buildAvailableTypes(methods); + final resolvedType = _resolveSelectedType( + recipient: recipient, + availableTypes: availableTypes, + preferredType: preferredType, + ); + + var hasChanges = false; + + if (_recipient != recipient) { + _recipient = recipient; + hasChanges = true; + } + + if (!_hasSameMethods(methods)) { + _recipientMethods = methods; + hasChanges = true; + } + + if (resolvedType != _selectedType) { + _selectedType = resolvedType; + hasChanges = true; + } + + if ((recipient != null || forceResetManualData) && _manualPaymentData != null) { + _manualPaymentData = null; + hasChanges = true; + } + + if (hasChanges) notifyListeners(); + } + + MethodMap _buildAvailableTypes(List methods) => { + for (final method in methods) method.type: method.data, + }; + + bool _hasSameMethods(List methods) { + if (_recipientMethods.length != methods.length) return false; + for (var i = 0; i < methods.length; i++) { + final current = _recipientMethods[i]; + final next = methods[i]; + if (current.id != next.id || current.updatedAt != next.updatedAt) return false; + } + return true; + } } diff --git a/frontend/pshared/lib/provider/payment/quotation.dart b/frontend/pshared/lib/provider/payment/quotation.dart index 076cbe8..7141de9 100644 --- a/frontend/pshared/lib/provider/payment/quotation.dart +++ b/frontend/pshared/lib/provider/payment/quotation.dart @@ -83,6 +83,58 @@ class QuotationProvider extends ChangeNotifier { 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); + Customer _buildCustomer({ + required Recipient? recipient, + required PaymentMethod method, + }) { + final name = _resolveCustomerName(method, recipient); + String? firstName; + String? middleName; + String? lastName; + + if (name != null && name.isNotEmpty) { + final parts = name.split(RegExp(r'\s+')); + if (parts.length == 1) { + firstName = parts.first; + } else if (parts.length == 2) { + firstName = parts.first; + lastName = parts.last; + } else { + firstName = parts.first; + lastName = parts.last; + middleName = parts.sublist(1, parts.length - 1).join(' '); + } + } + + return Customer( + id: recipient?.id ?? method.recipientRef, + firstName: firstName, + middleName: middleName, + lastName: lastName, + country: method.cardData?.country, + ); + } + + String? _resolveCustomerName(PaymentMethod method, Recipient? recipient) { + final card = method.cardData; + if (card != null) { + return '${card.firstName} ${card.lastName}'.trim(); + } + + final iban = method.ibanData; + if (iban != null && iban.accountHolder.trim().isNotEmpty) { + return iban.accountHolder.trim(); + } + + final bank = method.bankAccountData; + if (bank != null && bank.recipientName.trim().isNotEmpty) { + return bank.recipientName.trim(); + } + + final recipientName = recipient?.name.trim(); + return recipientName?.isNotEmpty == true ? recipientName : null; + } + void _setResource(Resource quotation) { _quotation = quotation; notifyListeners(); @@ -118,42 +170,3 @@ class QuotationProvider extends ChangeNotifier { notifyListeners(); } } - -Customer? _buildCustomer({ - required Recipient? recipient, - required PaymentMethod method, -}) { - final recipientId = (recipient?.id ?? method.recipientRef).trim(); - if (recipientId.isEmpty) { - return null; - } - - var firstName = ''; - var lastName = ''; - final cardData = method.cardData; - if (cardData != null) { - firstName = cardData.firstName.trim(); - lastName = cardData.lastName.trim(); - } - - if ((firstName.isEmpty || lastName.isEmpty) && recipient != null) { - final parts = recipient.name.trim().split(RegExp(r'\s+')); - if (parts.isNotEmpty && firstName.isEmpty) { - firstName = parts.first; - } - if (parts.length > 1 && lastName.isEmpty) { - lastName = parts.sublist(1).join(' '); - } - } - - if (lastName.isEmpty) { - lastName = firstName; - } - - return Customer( - id: recipientId, - firstName: firstName.isEmpty ? null : firstName, - lastName: lastName.isEmpty ? null : lastName, - country: cardData?.country, - ); -} diff --git a/frontend/pshared/lib/provider/payment/wallets.dart b/frontend/pshared/lib/provider/payment/wallets.dart index 4629cec..2e655ef 100644 --- a/frontend/pshared/lib/provider/payment/wallets.dart +++ b/frontend/pshared/lib/provider/payment/wallets.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:pshared/models/payment/wallet.dart'; @@ -38,10 +39,7 @@ class WalletsProvider with ChangeNotifier { throw Exception('update wallet is not implemented'); } - void selectWallet(Wallet wallet) { - _selectedWallet = wallet; - notifyListeners(); - } + void selectWallet(Wallet wallet) => _setSelectedWallet(wallet); Future loadWalletsWithBalances() async { _setResource(_resource.copyWith(isLoading: true, error: null)); @@ -98,6 +96,25 @@ class WalletsProvider with ChangeNotifier { void _setResource(Resource> newResource) { _resource = newResource; + _selectedWallet = _resolveSelectedWallet(_selectedWallet, wallets); + notifyListeners(); + } + + Wallet? _resolveSelectedWallet(Wallet? current, List available) { + if (available.isEmpty) return null; + final currentId = current?.id; + if (currentId != null) { + final existing = available.firstWhereOrNull((wallet) => wallet.id == currentId); + if (existing != null) return existing; + } + return available.firstWhereOrNull((wallet) => !wallet.isHidden) ?? available.first; + } + + void _setSelectedWallet(Wallet wallet) { + if (_selectedWallet?.id == wallet.id && _selectedWallet?.isHidden == wallet.isHidden) { + return; + } + _selectedWallet = wallet; notifyListeners(); } } diff --git a/frontend/pshared/lib/provider/recipient/pmethods.dart b/frontend/pshared/lib/provider/recipient/pmethods.dart index a4ecc87..9af60e4 100644 --- a/frontend/pshared/lib/provider/recipient/pmethods.dart +++ b/frontend/pshared/lib/provider/recipient/pmethods.dart @@ -5,6 +5,8 @@ import 'package:pshared/models/describable.dart'; import 'package:pshared/models/organization/bound.dart'; import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/payment/methods/type.dart'; +import 'package:pshared/models/payment/type.dart'; +import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/models/permissions/bound.dart'; import 'package:pshared/models/storable.dart'; import 'package:pshared/provider/organizations.dart'; @@ -20,6 +22,24 @@ class PaymentMethodsProvider extends GenericProvider { List get methods => List.unmodifiable(items.toList()..sort((a, b) => a.storable.createdAt.compareTo(b.storable.createdAt))); + List methodsForRecipient(Recipient? recipient) { + if (recipient == null || !isReady) return []; + + return methods + .where((method) => !method.isArchived && method.recipientRef == recipient.id) + .toList(); + } + + MethodMap availableTypesForRecipient(Recipient? recipient) => { + for (final method in methodsForRecipient(recipient)) method.type: method.data, + }; + + PaymentMethod? findMethodByType({ + required PaymentType type, + required Recipient? recipient, + }) => + methodsForRecipient(recipient).firstWhereOrNull((method) => method.type == type); + void updateProviders(OrganizationsProvider organizations, RecipientsProvider recipients) { if (recipients.currentObject != null) loadMethods(organizations, recipients.currentObject?.id); } diff --git a/frontend/pshared/lib/provider/recipient/provider.dart b/frontend/pshared/lib/provider/recipient/provider.dart index 756e348..f4f35e6 100644 --- a/frontend/pshared/lib/provider/recipient/provider.dart +++ b/frontend/pshared/lib/provider/recipient/provider.dart @@ -14,6 +14,7 @@ class RecipientsProvider extends GenericProvider { RecipientFilter _selectedFilter = RecipientFilter.all; String _query = ''; + String? _previousRecipientRef; RecipientFilter get selectedFilter => _selectedFilter; String get query => _query; @@ -22,6 +23,10 @@ class RecipientsProvider extends GenericProvider { RecipientsProvider() : super(service: RecipientService.basicService); + Recipient? get previousRecipient => _previousRecipientRef == null + ? null + : getItemByRef(_previousRecipientRef!); + List get filteredRecipients { List filtered = recipients.where((r) { switch (_selectedFilter) { @@ -53,6 +58,24 @@ class RecipientsProvider extends GenericProvider { notifyListeners(); } + @override + bool setCurrentObject(String? objectRef) { + final currentRef = currentObject?.id; + final didUpdate = super.setCurrentObject(objectRef); + + if (didUpdate && currentRef != null && currentRef != objectRef) { + _previousRecipientRef = currentRef; + } + + return didUpdate; + } + + void restorePreviousRecipient() { + if (_previousRecipientRef != null) { + setCurrentObject(_previousRecipientRef); + } + } + Future create({ required String name, required String email, diff --git a/frontend/pshared/lib/service/payment/service.dart b/frontend/pshared/lib/service/payment/service.dart index b8e0a27..a72b6f8 100644 --- a/frontend/pshared/lib/service/payment/service.dart +++ b/frontend/pshared/lib/service/payment/service.dart @@ -1,5 +1,4 @@ import 'package:logging/logging.dart'; - import 'package:uuid/uuid.dart'; import 'package:pshared/api/requests/payment/initiate.dart'; @@ -27,10 +26,10 @@ class PaymentService { metadata: metadata, ); final response = await AuthorizationService.getPOSTResponse( - _objectType, - '/by-quote/$organizationRef', + _objectType, + '/by-quote/$organizationRef', request.toJson(), ); return PaymentResponse.fromJson(response).payment.toDomain(); - } + } } diff --git a/frontend/pweb/lib/main.dart b/frontend/pweb/lib/main.dart index e959c34..c313c4d 100644 --- a/frontend/pweb/lib/main.dart +++ b/frontend/pweb/lib/main.dart @@ -13,15 +13,18 @@ import 'package:pshared/provider/permissions.dart'; import 'package:pshared/provider/account.dart'; import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/payment/amount.dart'; +import 'package:pshared/provider/payment/flow.dart'; +import 'package:pshared/provider/payment/provider.dart'; +import 'package:pshared/provider/payment/quotation.dart'; import 'package:pshared/provider/recipient/provider.dart'; import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/payment/wallets.dart'; +import 'package:pshared/models/payment/type.dart'; import 'package:pshared/service/payment/wallets.dart'; import 'package:pweb/app/app.dart'; import 'package:pweb/app/timeago.dart'; import 'package:pweb/providers/carousel.dart'; -import 'package:pweb/providers/mock_payment.dart'; import 'package:pweb/providers/operatioins.dart'; import 'package:pweb/providers/two_factor.dart'; import 'package:pweb/providers/upload_history.dart'; @@ -89,8 +92,12 @@ void main() async { ChangeNotifierProvider( create: (_) => WalletTransactionsProvider(MockWalletTransactionsService())..load(), ), - ChangeNotifierProvider( - create: (_) => MockPaymentProvider(), + ChangeNotifierProxyProvider2( + create: (_) => PaymentFlowProvider(initialType: PaymentType.bankAccount), + update: (context, recipients, methods, provider) => provider!..update( + recipients, + methods, + ), ), ChangeNotifierProvider( @@ -99,6 +106,18 @@ void main() async { ChangeNotifierProvider( create: (_) => PaymentAmountProvider(), ), + ChangeNotifierProxyProvider6( + create: (_) => QuotationProvider(), + update: (_, organization, payment, wallet, flow, recipients, methods, provider) => + provider!..update(organization, payment, wallet, flow, recipients, methods), + ), + ChangeNotifierProxyProvider2( + create: (_) => PaymentProvider(), + update: (context, organization, quotation, provider) => provider!..update( + organization, + quotation, + ), + ), ], child: const PayApp(), ), diff --git a/frontend/pweb/lib/pages/payment_methods/page.dart b/frontend/pweb/lib/pages/payment_methods/page.dart index d464088..5598647 100644 --- a/frontend/pweb/lib/pages/payment_methods/page.dart +++ b/frontend/pweb/lib/pages/payment_methods/page.dart @@ -1,24 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:collection/collection.dart'; - import 'package:provider/provider.dart'; -import 'package:pshared/models/payment/methods/data.dart'; -import 'package:pshared/models/payment/methods/type.dart'; import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/recipient/recipient.dart'; -import 'package:pshared/provider/organizations.dart'; -import 'package:pshared/provider/payment/amount.dart'; import 'package:pshared/provider/payment/flow.dart'; import 'package:pshared/provider/payment/provider.dart'; -import 'package:pshared/provider/payment/quotation.dart'; import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/provider.dart'; - -import 'package:pshared/models/payment/wallet.dart'; -import 'package:pweb/pages/payment_methods/payment_page/body.dart'; import 'package:pshared/provider/payment/wallets.dart'; + +import 'package:pweb/pages/payment_methods/payment_page/body.dart'; import 'package:pweb/widgets/sidebar/destinations.dart'; import 'package:pweb/services/posthog.dart'; @@ -60,30 +52,23 @@ class _PaymentPageState extends State { } void _initializePaymentPage() { - final methodsProvider = context.read(); - _handleWalletAutoSelection(methodsProvider); + final flowProvider = context.read(); + flowProvider.setPreferredType(widget.initialPaymentType); } void _handleSearchChanged(String query) { context.read().setQuery(query); } - void _handleRecipientSelected(BuildContext context, Recipient recipient) { + void _handleRecipientSelected(Recipient recipient) { final recipientProvider = context.read(); recipientProvider.setCurrentObject(recipient.id); _clearSearchField(); } - void _handleRecipientCleared(BuildContext context) { + void _handleRecipientCleared() { final recipientProvider = context.read(); - final methodsProvider = context.read(); - recipientProvider.setCurrentObject(null); - context.read().reset( - recipient: null, - availableTypes: _availablePaymentTypes(null, methodsProvider), - preferredType: widget.initialPaymentType, - ); _clearSearchField(); } @@ -93,107 +78,42 @@ class _PaymentPageState extends State { context.read().setQuery(''); } - void _handleSendPayment(BuildContext context) { - if (context.read().isReady) { - context.read().pay(); - PosthogService.paymentInitiated( - method: context.read().selectedType, + void _handleSendPayment() { + final flowProvider = context.read(); + final paymentProvider = context.read(); + if (paymentProvider.isLoading) return; + + paymentProvider.pay().then((_) { + PosthogService.paymentInitiated(method: flowProvider.selectedType); + }).catchError((error) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(error.toString())), ); - } + }); } @override Widget build(BuildContext context) { final methodsProvider = context.watch(); - final recipientProvider = context.watch(); - final recipient = recipientProvider.currentObject; - final availableTypes = _availablePaymentTypes(recipient, methodsProvider); - - return MultiProvider( - providers: [ - ChangeNotifierProxyProvider2( - create: (_) => PaymentFlowProvider( - initialType: widget.initialPaymentType ?? PaymentType.bankAccount, - ), - update: (_, recipients, methods, flow) { - final currentRecipient = recipients.currentObject; - flow!.sync( - recipient: currentRecipient, - availableTypes: _availablePaymentTypes(currentRecipient, methods), - preferredType: currentRecipient != null ? widget.initialPaymentType : null, - ); - return flow; - }, - ), - ChangeNotifierProvider( - create: (_) => PaymentAmountProvider(), - ), - ChangeNotifierProxyProvider6( - create: (_) => QuotationProvider(), - update: (_, organization, payment, wallet, flow, recipients, methods, provider) => - provider!..update(organization, payment, wallet, flow, recipients, methods), - ), - ChangeNotifierProxyProvider2( - create: (_) => PaymentProvider(), - update: (_, organization, quotation, provider) => provider!..update(organization, quotation), - ), - ], - child: Builder( - builder: (innerContext) => PaymentPageBody( - onBack: widget.onBack, - fallbackDestination: widget.fallbackDestination, - recipient: recipient, - recipientProvider: recipientProvider, - methodsProvider: methodsProvider, - availablePaymentTypes: availableTypes, - searchController: _searchController, - searchFocusNode: _searchFocusNode, - onSearchChanged: _handleSearchChanged, - onRecipientSelected: (selected) => _handleRecipientSelected(innerContext, selected), - onRecipientCleared: () => _handleRecipientCleared(innerContext), - onSend: () => _handleSendPayment(innerContext), - ), - ), - ); - } - - void _handleWalletAutoSelection(PaymentMethodsProvider methodsProvider) { - final wallet = context.read().selectedWallet; - if (wallet == null) return; - - final matchingMethod = _getPaymentMethodForWallet(wallet, methodsProvider); - if (matchingMethod != null) { - methodsProvider.setCurrentObject(matchingMethod.id); - } - } - - MethodMap _availablePaymentTypes( - Recipient? recipient, - PaymentMethodsProvider methodsProvider, - ) { - if (recipient == null || !methodsProvider.isReady) return {}; - - final methodsForRecipient = methodsProvider.methods.where( - (method) => !method.isArchived && method.recipientRef == recipient.id, + final recipientProvider = context.read(); + final recipient = context.select( + (provider) => provider.currentObject, ); - return { - for (final method in methodsForRecipient) method.type: method.data, - }; - } - - PaymentMethod? _getPaymentMethodForWallet( - Wallet wallet, - PaymentMethodsProvider methodsProvider, - ) { - if (methodsProvider.methods.isEmpty) { - return null; - } - - return methodsProvider.methods.firstWhereOrNull( - (method) => - method.type == PaymentType.wallet && - (method.description?.contains(wallet.walletUserID) ?? false), + return PaymentPageBody( + onBack: widget.onBack, + fallbackDestination: widget.fallbackDestination, + recipient: recipient, + recipientProvider: recipientProvider, + methodsProvider: methodsProvider, + onWalletSelected: context.read().selectWallet, + searchController: _searchController, + searchFocusNode: _searchFocusNode, + onSearchChanged: _handleSearchChanged, + onRecipientSelected: _handleRecipientSelected, + onRecipientCleared: _handleRecipientCleared, + onSend: _handleSendPayment, ); } } diff --git a/frontend/pweb/lib/pages/payment_methods/payment_page/body.dart b/frontend/pweb/lib/pages/payment_methods/payment_page/body.dart index 2caf9da..9741606 100644 --- a/frontend/pweb/lib/pages/payment_methods/payment_page/body.dart +++ b/frontend/pweb/lib/pages/payment_methods/payment_page/body.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/recipient/recipient.dart'; +import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/provider.dart'; @@ -17,7 +17,7 @@ class PaymentPageBody extends StatelessWidget { final Recipient? recipient; final RecipientsProvider recipientProvider; final PaymentMethodsProvider methodsProvider; - final MethodMap availablePaymentTypes; + final ValueChanged onWalletSelected; final PayoutDestination fallbackDestination; final TextEditingController searchController; final FocusNode searchFocusNode; @@ -32,7 +32,7 @@ class PaymentPageBody extends StatelessWidget { required this.recipient, required this.recipientProvider, required this.methodsProvider, - required this.availablePaymentTypes, + required this.onWalletSelected, required this.fallbackDestination, required this.searchController, required this.searchFocusNode, @@ -60,8 +60,7 @@ class PaymentPageBody extends StatelessWidget { onBack: onBack, recipient: recipient, recipientProvider: recipientProvider, - methodsProvider: methodsProvider, - availablePaymentTypes: availablePaymentTypes, + onWalletSelected: onWalletSelected, fallbackDestination: fallbackDestination, searchController: searchController, searchFocusNode: searchFocusNode, 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 2638330..aa274cf 100644 --- a/frontend/pweb/lib/pages/payment_methods/payment_page/content.dart +++ b/frontend/pweb/lib/pages/payment_methods/payment_page/content.dart @@ -1,12 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:pshared/models/payment/methods/data.dart'; +import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/models/recipient/recipient.dart'; -import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/provider.dart'; -import 'package:pshared/provider/payment/flow.dart'; import 'package:pweb/pages/payment_methods/payment_page/back_button.dart'; import 'package:pweb/pages/payment_methods/payment_page/header.dart'; @@ -26,8 +22,7 @@ class PaymentPageContent extends StatelessWidget { final ValueChanged? onBack; final Recipient? recipient; final RecipientsProvider recipientProvider; - final PaymentMethodsProvider methodsProvider; - final MethodMap availablePaymentTypes; + final ValueChanged onWalletSelected; final PayoutDestination fallbackDestination; final TextEditingController searchController; final FocusNode searchFocusNode; @@ -41,8 +36,7 @@ class PaymentPageContent extends StatelessWidget { required this.onBack, required this.recipient, required this.recipientProvider, - required this.methodsProvider, - required this.availablePaymentTypes, + required this.onWalletSelected, required this.fallbackDestination, required this.searchController, required this.searchFocusNode, @@ -55,7 +49,6 @@ class PaymentPageContent extends StatelessWidget { @override Widget build(BuildContext context) { final dimensions = AppDimensions(); - final flowProvider = context.watch(); final loc = AppLocalizations.of(context)!; return Align( @@ -84,7 +77,7 @@ class PaymentPageContent extends StatelessWidget { SectionTitle(loc.sourceOfFunds), SizedBox(height: dimensions.paddingSmall), PaymentMethodSelector( - onMethodChanged: (m) => methodsProvider.setCurrentObject(m.id), + onMethodChanged: onWalletSelected, ), SizedBox(height: dimensions.paddingXLarge), RecipientSection( @@ -98,12 +91,7 @@ class PaymentPageContent extends StatelessWidget { onRecipientCleared: onRecipientCleared, ), SizedBox(height: dimensions.paddingXLarge), - PaymentInfoSection( - dimensions: dimensions, - flowProvider: flowProvider, - recipient: recipient, - availableTypes: availablePaymentTypes, - ), + PaymentInfoSection(dimensions: dimensions), SizedBox(height: dimensions.paddingLarge), const PaymentFormWidget(), SizedBox(height: dimensions.paddingXXXLarge), 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 42404c0..ba4c3fc 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 @@ -17,9 +17,11 @@ class PaymentMethodSelector extends StatelessWidget { }); @override - Widget build(BuildContext context) => Consumer(builder:(context, provider, _) => PaymentMethodDropdown( - methods: provider.wallets, - initialValue: provider.selectedWallet, - onChanged: onMethodChanged, - )); + Widget build(BuildContext context) => Consumer( + builder: (context, provider, _) => PaymentMethodDropdown( + methods: provider.wallets, + selectedMethod: 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 2352d0d..3096cb8 100644 --- a/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart +++ b/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart @@ -1,14 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:pshared/models/payment/methods/data.dart'; +import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/models/recipient/recipient.dart'; -import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/provider.dart'; -import 'package:pshared/provider/payment/flow.dart'; - import 'package:pweb/pages/dashboard/payouts/form.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'; @@ -26,8 +22,7 @@ class PaymentPageContent extends StatelessWidget { final ValueChanged? onBack; final Recipient? recipient; final RecipientsProvider recipientProvider; - final PaymentMethodsProvider methodsProvider; - final MethodMap availablePaymentTypes; + final ValueChanged onWalletSelected; final PayoutDestination fallbackDestination; final TextEditingController searchController; final FocusNode searchFocusNode; @@ -41,8 +36,7 @@ class PaymentPageContent extends StatelessWidget { required this.onBack, required this.recipient, required this.recipientProvider, - required this.methodsProvider, - required this.availablePaymentTypes, + required this.onWalletSelected, required this.fallbackDestination, required this.searchController, required this.searchFocusNode, @@ -55,7 +49,6 @@ class PaymentPageContent extends StatelessWidget { @override Widget build(BuildContext context) { final dimensions = AppDimensions(); - final flowProvider = context.watch(); final loc = AppLocalizations.of(context)!; return Align( @@ -84,7 +77,7 @@ class PaymentPageContent extends StatelessWidget { SectionTitle(loc.sourceOfFunds), SizedBox(height: dimensions.paddingSmall), PaymentMethodSelector( - onMethodChanged: (m) => methodsProvider.setCurrentObject(m.id), + onMethodChanged: onWalletSelected, ), SizedBox(height: dimensions.paddingXLarge), RecipientSection( @@ -98,12 +91,7 @@ class PaymentPageContent extends StatelessWidget { onRecipientCleared: onRecipientCleared, ), SizedBox(height: dimensions.paddingXLarge), - PaymentInfoSection( - dimensions: dimensions, - flowProvider: flowProvider, - recipient: recipient, - availableTypes: availablePaymentTypes, - ), + PaymentInfoSection(dimensions: dimensions), SizedBox(height: dimensions.paddingLarge), const PaymentFormWidget(), SizedBox(height: dimensions.paddingXXXLarge), diff --git a/frontend/pweb/lib/pages/payment_methods/widgets/payment_info_section.dart b/frontend/pweb/lib/pages/payment_methods/widgets/payment_info_section.dart index 600184e..bb1228e 100644 --- a/frontend/pweb/lib/pages/payment_methods/widgets/payment_info_section.dart +++ b/frontend/pweb/lib/pages/payment_methods/widgets/payment_info_section.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + import 'package:pshared/models/payment/methods/data.dart'; -import 'package:pshared/models/payment/type.dart'; -import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/provider/payment/flow.dart'; import 'package:pweb/pages/payment_methods/form.dart'; @@ -15,25 +15,18 @@ import 'package:pweb/generated/i18n/app_localizations.dart'; class PaymentInfoSection extends StatelessWidget { final AppDimensions dimensions; - final MethodMap availableTypes; - final PaymentFlowProvider flowProvider; - final Recipient? recipient; const PaymentInfoSection({ super.key, required this.dimensions, - required this.availableTypes, - required this.flowProvider, - required this.recipient, }); @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context)!; - final hasRecipient = recipient != null; - final MethodMap resolvedAvailableTypes = hasRecipient - ? availableTypes - : {for (final type in PaymentType.values) type: null}; + final flowProvider = context.watch(); + final hasRecipient = flowProvider.hasRecipient; + final MethodMap resolvedAvailableTypes = flowProvider.availableTypes; if (hasRecipient && resolvedAvailableTypes.isEmpty) { return Text(loc.recipientNoPaymentDetails); @@ -62,7 +55,7 @@ class PaymentInfoSection extends StatelessWidget { flowProvider.setManualPaymentData(data); } }, - initialData: hasRecipient ? resolvedAvailableTypes[selectedType] : flowProvider.manualPaymentData, + initialData: flowProvider.selectedPaymentData, isEditable: !hasRecipient, ), ], diff --git a/frontend/pweb/lib/pages/payment_methods/widgets/recipient_section.dart b/frontend/pweb/lib/pages/payment_methods/widgets/recipient_section.dart index 8d6e718..c2ad789 100644 --- a/frontend/pweb/lib/pages/payment_methods/widgets/recipient_section.dart +++ b/frontend/pweb/lib/pages/payment_methods/widgets/recipient_section.dart @@ -45,25 +45,44 @@ class RecipientSection extends StatelessWidget { ); } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SectionTitle(loc.recipient), - SizedBox(height: dimensions.paddingSmall), - RecipientSearchField( - controller: searchController, - onChanged: onSearchChanged, - focusNode: searchFocusNode, - ), - if (recipientProvider.query.isNotEmpty) ...[ - SizedBox(height: dimensions.paddingMedium), - RecipientSearchResults( - dimensions: dimensions, - recipientProvider: recipientProvider, - onRecipientSelected: onRecipientSelected, - ), - ], - ], + return AnimatedBuilder( + animation: recipientProvider, + builder: (context, _) { + final previousRecipient = recipientProvider.previousRecipient; + final hasQuery = recipientProvider.query.isNotEmpty; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SectionTitle(loc.recipient), + SizedBox(height: dimensions.paddingSmall), + RecipientSearchField( + controller: searchController, + onChanged: onSearchChanged, + focusNode: searchFocusNode, + ), + if (previousRecipient != null) ...[ + SizedBox(height: dimensions.paddingSmall), + ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + leading: const Icon(Icons.undo), + title: Text(loc.back), + subtitle: Text(previousRecipient.name), + onTap: () => onRecipientSelected(previousRecipient), + ), + ], + if (hasQuery) ...[ + SizedBox(height: dimensions.paddingMedium), + RecipientSearchResults( + dimensions: dimensions, + recipientProvider: recipientProvider, + onRecipientSelected: onRecipientSelected, + ), + ], + ], + ); + }, ); } -} \ 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 25a132b..eb237d0 100644 --- a/frontend/pweb/lib/utils/payment/dropdown.dart +++ b/frontend/pweb/lib/utils/payment/dropdown.dart @@ -8,40 +8,27 @@ import 'package:pweb/pages/payment_methods/icon.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; -class PaymentMethodDropdown extends StatefulWidget { +class PaymentMethodDropdown extends StatelessWidget { final List methods; final ValueChanged onChanged; - final Wallet? initialValue; + final Wallet? selectedMethod; const PaymentMethodDropdown({ super.key, required this.methods, required this.onChanged, - this.initialValue, + this.selectedMethod, }); - @override - State createState() => _PaymentMethodDropdownState(); -} - -class _PaymentMethodDropdownState extends State { - late Wallet _selectedMethod; - - @override - void initState() { - super.initState(); - _selectedMethod = widget.initialValue ?? widget.methods.first; - } - @override Widget build(BuildContext context) => DropdownButtonFormField( dropdownColor: Theme.of(context).colorScheme.onSecondary, - initialValue: _selectedMethod, + value: _getSelectedMethod(), decoration: InputDecoration( labelText: AppLocalizations.of(context)!.whereGetMoney, border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), ), - items: widget.methods.map((method) => DropdownMenuItem( + items: methods.map((method) => DropdownMenuItem( value: method, child: Row( children: [ @@ -53,9 +40,14 @@ class _PaymentMethodDropdownState extends State { )).toList(), onChanged: (value) { if (value != null) { - setState(() => _selectedMethod = value); - widget.onChanged(value); + onChanged(value); } }, ); -} \ No newline at end of file + + Wallet? _getSelectedMethod() { + if (selectedMethod != null) return selectedMethod; + if (methods.isEmpty) return null; + return methods.first; + } +} -- 2.49.1