diff --git a/frontend/pshared/lib/provider/payment/flow.dart b/frontend/pshared/lib/provider/payment/flow.dart index e045149..1f80552 100644 --- a/frontend/pshared/lib/provider/payment/flow.dart +++ b/frontend/pshared/lib/provider/payment/flow.dart @@ -1,6 +1,8 @@ +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'; @@ -11,7 +13,7 @@ class PaymentFlowProvider extends ChangeNotifier { PaymentType _selectedType; PaymentType? _preferredType; PaymentMethodData? _manualPaymentData; - MethodMap _availableTypes = {}; + List _recipientMethods = []; Recipient? _recipient; PaymentFlowProvider({ @@ -23,15 +25,22 @@ class PaymentFlowProvider extends ChangeNotifier { PaymentType get selectedType => _selectedType; PaymentMethodData? get manualPaymentData => _manualPaymentData; Recipient? get recipient => _recipient; + PaymentMethod? get selectedMethod => hasRecipient + ? _recipientMethods.firstWhereOrNull((method) => method.type == _selectedType) + : null; bool get hasRecipient => _recipient != null; MethodMap get availableTypes => hasRecipient - ? _availableTypes + ? _buildAvailableTypes(_recipientMethods) : {for (final type in PaymentType.values) type: null}; PaymentMethodData? get selectedPaymentData => - hasRecipient ? _availableTypes[_selectedType] : _manualPaymentData; + hasRecipient ? selectedMethod?.data : _manualPaymentData; + + List get methodsForRecipient => hasRecipient + ? List.unmodifiable(_recipientMethods) + : const []; void update( RecipientsProvider recipientsProvider, @@ -39,12 +48,16 @@ class PaymentFlowProvider extends ChangeNotifier { ) => _applyState( recipient: recipientsProvider.currentObject, - availableTypes: methodsProvider.availableTypesForRecipient(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; } @@ -69,7 +82,7 @@ class PaymentFlowProvider extends ChangeNotifier { _preferredType = preferredType; _applyState( recipient: _recipient, - availableTypes: _availableTypes, + methods: _recipientMethods, preferredType: _preferredType, forceResetManualData: false, ); @@ -101,10 +114,11 @@ class PaymentFlowProvider extends ChangeNotifier { void _applyState({ required Recipient? recipient, - required MethodMap availableTypes, + required List methods, required PaymentType? preferredType, required bool forceResetManualData, }) { + final availableTypes = _buildAvailableTypes(methods); final resolvedType = _resolveSelectedType( recipient: recipient, availableTypes: availableTypes, @@ -118,8 +132,8 @@ class PaymentFlowProvider extends ChangeNotifier { hasChanges = true; } - if (!mapEquals(_availableTypes, availableTypes)) { - _availableTypes = availableTypes; + if (!_hasSameMethods(methods)) { + _recipientMethods = methods; hasChanges = true; } @@ -135,4 +149,18 @@ class PaymentFlowProvider extends ChangeNotifier { 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/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/pweb/lib/main.dart b/frontend/pweb/lib/main.dart index a93a4f9..4ab6802 100644 --- a/frontend/pweb/lib/main.dart +++ b/frontend/pweb/lib/main.dart @@ -19,8 +19,8 @@ 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/service/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'; diff --git a/frontend/pweb/lib/pages/payment_methods/page.dart b/frontend/pweb/lib/pages/payment_methods/page.dart index 153c5b1..5598647 100644 --- a/frontend/pweb/lib/pages/payment_methods/page.dart +++ b/frontend/pweb/lib/pages/payment_methods/page.dart @@ -1,17 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:collection/collection.dart'; - import 'package:provider/provider.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/payment/flow.dart'; import 'package:pshared/provider/payment/provider.dart'; import 'package:pshared/provider/recipient/pmethods.dart'; import 'package:pshared/provider/recipient/provider.dart'; -import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/provider/payment/wallets.dart'; import 'package:pweb/pages/payment_methods/payment_page/body.dart'; @@ -57,9 +53,7 @@ class _PaymentPageState extends State { void _initializePaymentPage() { final flowProvider = context.read(); - final methodsProvider = context.read(); flowProvider.setPreferredType(widget.initialPaymentType); - _handleWalletAutoSelection(methodsProvider); } void _handleSearchChanged(String query) { @@ -113,6 +107,7 @@ class _PaymentPageState extends State { recipient: recipient, recipientProvider: recipientProvider, methodsProvider: methodsProvider, + onWalletSelected: context.read().selectWallet, searchController: _searchController, searchFocusNode: _searchFocusNode, onSearchChanged: _handleSearchChanged, @@ -121,29 +116,4 @@ class _PaymentPageState extends State { onSend: _handleSendPayment, ); } - - 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); - } - } - - 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), - ); - } } 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 6d58a6a..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,6 +1,7 @@ import 'package:flutter/material.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'; @@ -16,6 +17,7 @@ class PaymentPageBody extends StatelessWidget { final Recipient? recipient; final RecipientsProvider recipientProvider; final PaymentMethodsProvider methodsProvider; + final ValueChanged onWalletSelected; final PayoutDestination fallbackDestination; final TextEditingController searchController; final FocusNode searchFocusNode; @@ -30,6 +32,7 @@ class PaymentPageBody extends StatelessWidget { required this.recipient, required this.recipientProvider, required this.methodsProvider, + required this.onWalletSelected, required this.fallbackDestination, required this.searchController, required this.searchFocusNode, @@ -57,7 +60,7 @@ class PaymentPageBody extends StatelessWidget { onBack: onBack, recipient: recipient, recipientProvider: recipientProvider, - methodsProvider: methodsProvider, + 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 ebb9f3e..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,7 +1,7 @@ import 'package:flutter/material.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:pweb/pages/payment_methods/payment_page/back_button.dart'; @@ -22,7 +22,7 @@ class PaymentPageContent extends StatelessWidget { final ValueChanged? onBack; final Recipient? recipient; final RecipientsProvider recipientProvider; - final PaymentMethodsProvider methodsProvider; + final ValueChanged onWalletSelected; final PayoutDestination fallbackDestination; final TextEditingController searchController; final FocusNode searchFocusNode; @@ -36,7 +36,7 @@ class PaymentPageContent extends StatelessWidget { required this.onBack, required this.recipient, required this.recipientProvider, - required this.methodsProvider, + required this.onWalletSelected, required this.fallbackDestination, required this.searchController, required this.searchFocusNode, @@ -77,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( 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 7d23770..03d4f90 100644 --- a/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart +++ b/frontend/pweb/lib/pages/payment_methods/payment_page/page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.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:pweb/pages/dashboard/payouts/widget.dart'; @@ -22,7 +22,7 @@ class PaymentPageContent extends StatelessWidget { final ValueChanged? onBack; final Recipient? recipient; final RecipientsProvider recipientProvider; - final PaymentMethodsProvider methodsProvider; + final ValueChanged onWalletSelected; final PayoutDestination fallbackDestination; final TextEditingController searchController; final FocusNode searchFocusNode; @@ -36,7 +36,7 @@ class PaymentPageContent extends StatelessWidget { required this.onBack, required this.recipient, required this.recipientProvider, - required this.methodsProvider, + required this.onWalletSelected, required this.fallbackDestination, required this.searchController, required this.searchFocusNode, @@ -77,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( 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; + } +}