diff --git a/frontend/pshared/lib/api/responses/payment/payment.dart b/frontend/pshared/lib/api/responses/payment/payment.dart deleted file mode 100644 index b5b5d3e..0000000 --- a/frontend/pshared/lib/api/responses/payment/payment.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -import 'package:pshared/api/responses/base.dart'; -import 'package:pshared/api/responses/token.dart'; -import 'package:pshared/data/dto/payment/payment.dart'; - -part 'payment.g.dart'; - - -@JsonSerializable(explicitToJson: true) -class PaymentResponse extends BaseAuthorizedResponse { - - final PaymentDTO payment; - - const PaymentResponse({required super.accessToken, required this.payment}); - - factory PaymentResponse.fromJson(Map json) => _$PaymentResponseFromJson(json); - @override - Map toJson() => _$PaymentResponseToJson(this); -} diff --git a/frontend/pshared/lib/provider/payment/flow.dart b/frontend/pshared/lib/provider/payment/flow.dart index e4f3f73..12b909f 100644 --- a/frontend/pshared/lib/provider/payment/flow.dart +++ b/frontend/pshared/lib/provider/payment/flow.dart @@ -3,11 +3,14 @@ import 'package:flutter/foundation.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/recipient/pmethods.dart'; class PaymentFlowProvider extends ChangeNotifier { PaymentType _selectedType; PaymentMethodData? _manualPaymentData; + MethodMap _availableTypes = {}; + Recipient? _recipient; PaymentFlowProvider({ required PaymentType initialType, @@ -15,57 +18,40 @@ class PaymentFlowProvider extends ChangeNotifier { PaymentType get selectedType => _selectedType; PaymentMethodData? get manualPaymentData => _manualPaymentData; + Recipient? get recipient => _recipient; - void sync({ + bool get hasRecipient => _recipient != null; + + MethodMap get availableTypes => hasRecipient + ? _availableTypes + : {for (final type in PaymentType.values) type: null}; + + PaymentMethodData? get selectedPaymentData => + hasRecipient ? _availableTypes[_selectedType] : _manualPaymentData; + + void syncWith({ required Recipient? recipient, - required MethodMap availableTypes, + required PaymentMethodsProvider methodsProvider, PaymentType? preferredType, - }) { - final resolvedType = _resolveSelectedType( - recipient: recipient, - availableTypes: availableTypes, - preferredType: preferredType, - ); - - var hasChanges = false; - if (resolvedType != _selectedType) { - _selectedType = resolvedType; - hasChanges = true; - } - - if (recipient != null && _manualPaymentData != null) { - _manualPaymentData = null; - hasChanges = true; - } - - if (hasChanges) notifyListeners(); - } + }) => + _applyState( + recipient: recipient, + availableTypes: methodsProvider.availableTypesForRecipient(recipient), + preferredType: preferredType, + forceResetManualData: false, + ); void reset({ required Recipient? recipient, - required MethodMap availableTypes, + required PaymentMethodsProvider methodsProvider, 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(); - } + }) => + _applyState( + recipient: recipient, + availableTypes: methodsProvider.availableTypesForRecipient(recipient), + preferredType: preferredType, + forceResetManualData: true, + ); void selectType(PaymentType type, {bool resetManualData = false}) { if (_selectedType == type && (!resetManualData || _manualPaymentData == null)) { @@ -107,4 +93,41 @@ class PaymentFlowProvider extends ChangeNotifier { return availableTypes.keys.first; } + + void _applyState({ + required Recipient? recipient, + required MethodMap availableTypes, + required PaymentType? preferredType, + required bool forceResetManualData, + }) { + final resolvedType = _resolveSelectedType( + recipient: recipient, + availableTypes: availableTypes, + preferredType: preferredType, + ); + + var hasChanges = false; + + if (_recipient != recipient) { + _recipient = recipient; + hasChanges = true; + } + + if (!mapEquals(_availableTypes, availableTypes)) { + _availableTypes = availableTypes; + hasChanges = true; + } + + if (resolvedType != _selectedType) { + _selectedType = resolvedType; + hasChanges = true; + } + + if ((recipient != null || forceResetManualData) && _manualPaymentData != null) { + _manualPaymentData = null; + hasChanges = true; + } + + if (hasChanges) notifyListeners(); + } } diff --git a/frontend/pshared/lib/provider/payment/provider.dart b/frontend/pshared/lib/provider/payment/provider.dart deleted file mode 100644 index 6afd032..0000000 --- a/frontend/pshared/lib/provider/payment/provider.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:flutter/foundation.dart'; - -import 'package:pshared/models/payment/payment.dart'; -import 'package:pshared/provider/organizations.dart'; -import 'package:pshared/provider/payment/quotation.dart'; -import 'package:pshared/provider/resource.dart'; -import 'package:pshared/service/payment/service.dart'; - - -class PaymentProvider extends ChangeNotifier { - late OrganizationsProvider _organization; - late QuotationProvider _quotation; - - Resource _payment = Resource(data: null, isLoading: false, error: null); - bool _isLoaded = false; - - void update(OrganizationsProvider organization, QuotationProvider quotation) { - _quotation = quotation; - _organization = organization; - } - - Payment? get payment => _payment.data; - bool get isLoading => _payment.isLoading; - Exception? get error => _payment.error; - bool get isReady => _isLoaded && !_payment.isLoading && _payment.error == null; - - void _setResource(Resource payment) { - _payment = payment; - notifyListeners(); - } - - Future pay({String? idempotencyKey, Map? metadata}) async { - if (!_organization.isOrganizationSet) throw StateError('Organization is not set'); - if (!_quotation.isReady) throw StateError('Quotation is not ready'); - final quoteRef = _quotation.quotation?.quoteRef; - if (quoteRef == null || quoteRef.isEmpty) { - throw StateError('Quotation reference is not set'); - } - - _setResource(_payment.copyWith(isLoading: true, error: null)); - try { - final response = await PaymentService.pay( - _organization.current.id, - quoteRef, - idempotencyKey: idempotencyKey, - metadata: metadata, - ); - _isLoaded = true; - _setResource(_payment.copyWith(data: response, isLoading: false, error: null)); - } catch (e) { - _setResource(_payment.copyWith( - data: null, - error: e is Exception ? e : Exception(e.toString()), - isLoading: false, - )); - } - return _payment.data; - } - - void reset() { - _setResource(Resource(data: null, isLoading: false, error: null)); - _isLoaded = false; - } -} diff --git a/frontend/pshared/lib/provider/payment/quotation.dart b/frontend/pshared/lib/provider/payment/quotation.dart index a36bacc..d093f49 100644 --- a/frontend/pshared/lib/provider/payment/quotation.dart +++ b/frontend/pshared/lib/provider/payment/quotation.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:collection/collection.dart'; - import 'package:uuid/uuid.dart'; import 'package:pshared/api/requests/payment/quote.dart'; @@ -20,7 +18,6 @@ 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/wallets.dart'; -import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/resource.dart'; import 'package:pshared/service/payment/quotation.dart'; import 'package:pshared/utils/currency.dart'; @@ -36,12 +33,10 @@ class QuotationProvider extends ChangeNotifier { PaymentAmountProvider payment, WalletsProvider wallets, PaymentFlowProvider flow, - PaymentMethodsProvider methods, ) { _organizations = venue; - final t = flow.selectedType; - final method = methods.methods.firstWhereOrNull((m) => m.type == t); - if ((wallets.selectedWallet != null) && (method != null)) { + final destination = flow.selectedPaymentData; + if ((wallets.selectedWallet != null) && (destination != null)) { getQuotation(PaymentIntent( kind: PaymentKind.payout, amount: Money( @@ -49,7 +44,7 @@ class QuotationProvider extends ChangeNotifier { // TODO: adapt to possible other sources currency: currencyCodeToString(wallets.selectedWallet!.currency), ), - destination: method.data, + destination: destination, source: ManagedWalletPaymentMethod( managedWalletRef: wallets.selectedWallet!.id, ), 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 deleted file mode 100644 index b8e0a27..0000000 --- a/frontend/pshared/lib/service/payment/service.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:logging/logging.dart'; - -import 'package:uuid/uuid.dart'; - -import 'package:pshared/api/requests/payment/initiate.dart'; -import 'package:pshared/api/responses/payment/payment.dart'; -import 'package:pshared/data/mapper/payment/payment_response.dart'; -import 'package:pshared/models/payment/payment.dart'; -import 'package:pshared/service/authorization/service.dart'; -import 'package:pshared/service/services.dart'; - - -class PaymentService { - static final _logger = Logger('service.payment'); - static const String _objectType = Services.payments; - - static Future pay( - String organizationRef, - String quotationRef, { - String? idempotencyKey, - Map? metadata, - }) async { - _logger.fine('Executing payment for quotation $quotationRef in $organizationRef'); - final request = InitiatePaymentRequest( - idempotencyKey: idempotencyKey ?? Uuid().v4(), - quoteRef: quotationRef, - metadata: metadata, - ); - final response = await AuthorizationService.getPOSTResponse( - _objectType, - '/by-quote/$organizationRef', - request.toJson(), - ); - return PaymentResponse.fromJson(response).payment.toDomain(); - } -} 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..a88849e --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/payouts/widget.dart @@ -0,0 +1,30 @@ +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/flow.dart'; +import 'package:pshared/provider/payment/quotation.dart'; +import 'package:pshared/provider/payment/wallets.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(), + ), + ChangeNotifierProxyProvider4( + create: (_) => QuotationProvider(), + update: (context, orgnization, payment, wallet, flow, provider) => provider!..update(orgnization, payment, wallet, flow), + ), + ], + child: const PaymentFormWidget(), + ); +} diff --git a/frontend/pweb/lib/pages/payment_methods/page.dart b/frontend/pweb/lib/pages/payment_methods/page.dart index 2c10623..870c02d 100644 --- a/frontend/pweb/lib/pages/payment_methods/page.dart +++ b/frontend/pweb/lib/pages/payment_methods/page.dart @@ -4,21 +4,17 @@ 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'; @@ -42,12 +38,16 @@ class PaymentPage extends StatefulWidget { class _PaymentPageState extends State { late final TextEditingController _searchController; late final FocusNode _searchFocusNode; + late final PaymentFlowProvider _flowProvider; @override void initState() { super.initState(); _searchController = TextEditingController(); _searchFocusNode = FocusNode(); + _flowProvider = PaymentFlowProvider( + initialType: widget.initialPaymentType ?? PaymentType.bankAccount, + ); WidgetsBinding.instance.addPostFrameCallback((_) => _initializePaymentPage()); } @@ -56,32 +56,47 @@ class _PaymentPageState extends State { void dispose() { _searchController.dispose(); _searchFocusNode.dispose(); + _flowProvider.dispose(); super.dispose(); } void _initializePaymentPage() { final methodsProvider = context.read(); _handleWalletAutoSelection(methodsProvider); + + final recipient = context.read().currentObject; + _flowProvider.syncWith( + recipient: recipient, + methodsProvider: methodsProvider, + preferredType: widget.initialPaymentType, + ); } void _handleSearchChanged(String query) { context.read().setQuery(query); } - void _handleRecipientSelected(BuildContext context, Recipient recipient) { + void _handleRecipientSelected(Recipient recipient) { final recipientProvider = context.read(); + final methodsProvider = context.read(); + recipientProvider.setCurrentObject(recipient.id); + _flowProvider.reset( + recipient: recipient, + methodsProvider: methodsProvider, + preferredType: widget.initialPaymentType, + ); _clearSearchField(); } - void _handleRecipientCleared(BuildContext context) { + void _handleRecipientCleared() { final recipientProvider = context.read(); final methodsProvider = context.read(); recipientProvider.setCurrentObject(null); - context.read().reset( + _flowProvider.reset( recipient: null, - availableTypes: _availablePaymentTypes(null, methodsProvider), + methodsProvider: methodsProvider, preferredType: widget.initialPaymentType, ); _clearSearchField(); @@ -93,65 +108,39 @@ 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() { + // TODO: Handle Payment logic + PosthogService.paymentInitiated(method: _flowProvider.selectedType); } @override Widget build(BuildContext context) { final methodsProvider = context.watch(); - final recipientProvider = context.watch(); - final recipient = recipientProvider.currentObject; - final availableTypes = _availablePaymentTypes(recipient, methodsProvider); + final recipientProvider = context.read(); + final recipient = context.select( + (provider) => provider.currentObject, + ); - 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(), - ), - ChangeNotifierProxyProvider5( - create: (_) => QuotationProvider(), - update: (_, organization, payment, wallet, flow, methods, provider) => provider!..update(organization, payment, wallet, flow, 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), - ), + _flowProvider.syncWith( + recipient: recipient, + methodsProvider: methodsProvider, + preferredType: recipient != null ? widget.initialPaymentType : null, + ); + + return ChangeNotifierProvider.value( + value: _flowProvider, + child: PaymentPageBody( + onBack: widget.onBack, + fallbackDestination: widget.fallbackDestination, + recipient: recipient, + recipientProvider: recipientProvider, + methodsProvider: methodsProvider, + searchController: _searchController, + searchFocusNode: _searchFocusNode, + onSearchChanged: _handleSearchChanged, + onRecipientSelected: _handleRecipientSelected, + onRecipientCleared: _handleRecipientCleared, + onSend: _handleSendPayment, ), ); } @@ -166,21 +155,6 @@ class _PaymentPageState extends State { } } - MethodMap _availablePaymentTypes( - Recipient? recipient, - PaymentMethodsProvider methodsProvider, - ) { - if (recipient == null || !methodsProvider.isReady) return {}; - - final methodsForRecipient = methodsProvider.methods.where( - (method) => !method.isArchived && method.recipientRef == recipient.id, - ); - - return { - for (final method in methodsForRecipient) method.type: method.data, - }; - } - PaymentMethod? _getPaymentMethodForWallet( Wallet wallet, PaymentMethodsProvider methodsProvider, @@ -195,4 +169,4 @@ class _PaymentPageState extends State { (method.description?.contains(wallet.walletUserID) ?? false), ); } -} +} \ No newline at end of file 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..6d58a6a 100644 --- a/frontend/pweb/lib/pages/payment_methods/payment_page/body.dart +++ b/frontend/pweb/lib/pages/payment_methods/payment_page/body.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/provider.dart'; @@ -17,7 +16,6 @@ class PaymentPageBody extends StatelessWidget { final Recipient? recipient; final RecipientsProvider recipientProvider; final PaymentMethodsProvider methodsProvider; - final MethodMap availablePaymentTypes; final PayoutDestination fallbackDestination; final TextEditingController searchController; final FocusNode searchFocusNode; @@ -32,7 +30,6 @@ class PaymentPageBody extends StatelessWidget { required this.recipient, required this.recipientProvider, required this.methodsProvider, - required this.availablePaymentTypes, required this.fallbackDestination, required this.searchController, required this.searchFocusNode, @@ -61,7 +58,6 @@ class PaymentPageBody extends StatelessWidget { recipient: recipient, recipientProvider: recipientProvider, methodsProvider: methodsProvider, - availablePaymentTypes: availablePaymentTypes, 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..ebb9f3e 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/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'; @@ -27,7 +23,6 @@ class PaymentPageContent extends StatelessWidget { final Recipient? recipient; final RecipientsProvider recipientProvider; final PaymentMethodsProvider methodsProvider; - final MethodMap availablePaymentTypes; final PayoutDestination fallbackDestination; final TextEditingController searchController; final FocusNode searchFocusNode; @@ -42,7 +37,6 @@ class PaymentPageContent extends StatelessWidget { required this.recipient, required this.recipientProvider, required this.methodsProvider, - required this.availablePaymentTypes, 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( @@ -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/page.dart b/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart index 2352d0d..7d23770 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/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/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'; @@ -27,7 +23,6 @@ class PaymentPageContent extends StatelessWidget { final Recipient? recipient; final RecipientsProvider recipientProvider; final PaymentMethodsProvider methodsProvider; - final MethodMap availablePaymentTypes; final PayoutDestination fallbackDestination; final TextEditingController searchController; final FocusNode searchFocusNode; @@ -42,7 +37,6 @@ class PaymentPageContent extends StatelessWidget { required this.recipient, required this.recipientProvider, required this.methodsProvider, - required this.availablePaymentTypes, 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( @@ -98,14 +91,9 @@ 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(), + const PaymentFromWrappingWidget(), SizedBox(height: dimensions.paddingXXXLarge), SendButton(onPressed: onSend), SizedBox(height: dimensions.paddingLarge), 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 +}