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; String? _selectedMethodId; PaymentFlowProvider({ required PaymentType initialType, PaymentType? preferredType, }) : _selectedType = initialType, _preferredType = preferredType ?? initialType; PaymentType get selectedType => _selectedType; PaymentMethodData? get manualPaymentData => _manualPaymentData; Recipient? get recipient => _recipient; PaymentMethod? get selectedMethod { if (!hasRecipient) return null; if (_selectedMethodId != null) { final byId = _recipientMethods.firstWhereOrNull((method) => method.id == _selectedMethodId); if (byId != null) return byId; } return _preferredMethodForType(_selectedType, _recipientMethods); } bool get hasRecipient => _recipient != null; MethodMap get availableTypes => hasRecipient ? _buildAvailableTypes(_recipientMethods) : {for (final type in PaymentType.values) type: null}; PaymentMethodData? get selectedPaymentData => hasRecipient ? selectedMethod?.data : _manualPaymentData; List get methodsForRecipient => hasRecipient ? List.unmodifiable(_recipientMethods) : const []; List get methodsForSelectedType => hasRecipient ? List.unmodifiable( _recipientMethods.where((method) => method.type == _selectedType).toList(), ) : const []; 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; } _selectedType = type; if (hasRecipient) { _selectedMethodId = _preferredMethodForType(type, _recipientMethods)?.id; } if (resetManualData) { _manualPaymentData = null; } notifyListeners(); } void selectMethod(PaymentMethod method) { if (!hasRecipient) return; if (_selectedMethodId == method.id && _selectedType == method.type) return; _selectedMethodId = method.id; if (_selectedType != method.type) { _selectedType = method.type; } notifyListeners(); } void setManualPaymentData(PaymentMethodData? data) { _manualPaymentData = data; 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, PaymentType? preferredType, }) { if (recipient == null) { return preferredType ?? _selectedType; } if (availableTypes.isEmpty) { return preferredType ?? PaymentType.bankAccount; } if (availableTypes.keys.contains(_selectedType)) { return _selectedType; } if (preferredType != null && availableTypes.keys.contains(preferredType)) { return preferredType; } 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, ); final resolvedMethod = _resolveSelectedMethod( recipient: recipient, methods: methods, selectedType: resolvedType, selectedMethodId: _selectedMethodId, ); 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 ((resolvedMethod?.id ?? _selectedMethodId) != _selectedMethodId) { _selectedMethodId = resolvedMethod?.id; 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, }; PaymentMethod? _preferredMethodForType(PaymentType type, List methods) { final forType = methods.where((method) => method.type == type).toList(); if (forType.isEmpty) return null; return forType.firstWhereOrNull((method) => method.isMain) ?? forType.first; } PaymentMethod? _resolveSelectedMethod({ required Recipient? recipient, required List methods, required PaymentType selectedType, required String? selectedMethodId, }) { if (recipient == null) return null; final forType = methods.where((method) => method.type == selectedType).toList(); if (forType.isEmpty) return null; if (selectedMethodId != null) { final byId = forType.firstWhereOrNull((method) => method.id == selectedMethodId); if (byId != null) return byId; } return _preferredMethodForType(selectedType, methods); } 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; } }