239 lines
7.4 KiB
Dart
239 lines
7.4 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:pshared/controllers/payment/source.dart';
|
|
import 'package:pshared/models/money.dart';
|
|
import 'package:pshared/models/payment/asset.dart';
|
|
import 'package:pshared/models/payment/chain_network.dart';
|
|
import 'package:pshared/models/payment/methods/data.dart';
|
|
import 'package:pshared/models/payment/methods/ledger.dart';
|
|
import 'package:pshared/models/payment/methods/managed_wallet.dart';
|
|
import 'package:pshared/models/payment/payment.dart';
|
|
import 'package:pshared/models/payment/quote/status_type.dart';
|
|
|
|
import 'package:pweb/models/payment/multiple_payouts/csv_row.dart';
|
|
import 'package:pweb/models/payment/multiple_payouts/state.dart';
|
|
import 'package:pweb/providers/multiple_payouts.dart';
|
|
import 'package:pweb/services/payments/csv_input.dart';
|
|
|
|
class MultiplePayoutsController extends ChangeNotifier {
|
|
final CsvInputService _csvInput;
|
|
MultiplePayoutsProvider? _provider;
|
|
PaymentSourceController? _sourceController;
|
|
_PickState _pickState = _PickState.idle;
|
|
Exception? _uiError;
|
|
String? _lastSourceKey;
|
|
|
|
MultiplePayoutsController({required CsvInputService csvInput})
|
|
: _csvInput = csvInput;
|
|
|
|
void update(
|
|
MultiplePayoutsProvider provider,
|
|
PaymentSourceController sourceController,
|
|
) {
|
|
var shouldNotify = false;
|
|
if (!identical(_provider, provider)) {
|
|
_provider?.removeListener(_onProviderChanged);
|
|
_provider = provider;
|
|
_provider?.addListener(_onProviderChanged);
|
|
shouldNotify = true;
|
|
}
|
|
if (!identical(_sourceController, sourceController)) {
|
|
_sourceController?.removeListener(_onSourceChanged);
|
|
_sourceController = sourceController;
|
|
_sourceController?.addListener(_onSourceChanged);
|
|
_lastSourceKey = _currentSourceKey;
|
|
shouldNotify = true;
|
|
}
|
|
if (shouldNotify) {
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
MultiplePayoutsState get state =>
|
|
_provider?.state ?? MultiplePayoutsState.idle;
|
|
String? get selectedFileName => _provider?.selectedFileName;
|
|
List<CsvPayoutRow> get rows => _provider?.rows ?? const <CsvPayoutRow>[];
|
|
int get sentCount => _provider?.sentCount ?? 0;
|
|
Exception? get error => _uiError ?? _provider?.error;
|
|
|
|
bool get isQuoting => _provider?.isQuoting ?? false;
|
|
bool get isSending => _provider?.isSending ?? false;
|
|
bool get isBusy => _provider?.isBusy ?? false;
|
|
|
|
bool get quoteIsLoading => _provider?.quoteIsLoading ?? false;
|
|
QuoteStatusType get quoteStatusType =>
|
|
_provider?.quoteStatusType ?? QuoteStatusType.missing;
|
|
Duration? get quoteTimeLeft => _provider?.quoteTimeLeft;
|
|
|
|
bool get canSend => (_provider?.canSend ?? false) && _selectedSource != null;
|
|
Money? get aggregateDebitAmount =>
|
|
_provider?.aggregateDebitAmountForCurrency(_selectedSourceCurrencyCode);
|
|
Money? get requestedSentAmount => _provider?.requestedSentAmount;
|
|
Money? get aggregateSettlementAmount => _provider
|
|
?.aggregateSettlementAmountForCurrency(_selectedSourceCurrencyCode);
|
|
Money? get aggregateFeeAmount =>
|
|
_provider?.aggregateFeeAmountForCurrency(_selectedSourceCurrencyCode);
|
|
double? get aggregateFeePercent =>
|
|
_provider?.aggregateFeePercentForCurrency(_selectedSourceCurrencyCode);
|
|
|
|
Future<void> pickAndQuote() async {
|
|
if (_pickState == _PickState.picking) return;
|
|
final provider = _provider;
|
|
if (provider == null) {
|
|
_setUiError(StateError('Multiple payouts provider is not ready'));
|
|
return;
|
|
}
|
|
|
|
_clearUiError();
|
|
_pickState = _PickState.picking;
|
|
try {
|
|
final picked = await _csvInput.pickCsv();
|
|
if (picked == null) return;
|
|
final source = _selectedSource;
|
|
if (source == null) {
|
|
_setUiError(StateError('Select source of funds first'));
|
|
return;
|
|
}
|
|
await provider.quoteFromCsv(
|
|
fileName: picked.name,
|
|
content: picked.content,
|
|
sourceMethod: source.method,
|
|
sourceCurrencyCode: source.currencyCode,
|
|
);
|
|
} catch (e) {
|
|
_setUiError(e);
|
|
} finally {
|
|
_pickState = _PickState.idle;
|
|
}
|
|
}
|
|
|
|
Future<List<Payment>> send() async {
|
|
return _provider?.send() ?? const <Payment>[];
|
|
}
|
|
|
|
Future<MultiplePayoutSendOutcome> sendAndGetOutcome() async {
|
|
_clearUiError();
|
|
final provider = _provider;
|
|
if (provider == null) {
|
|
_setUiError(StateError('Multiple payouts provider is not ready'));
|
|
return MultiplePayoutSendOutcome.failure;
|
|
}
|
|
|
|
final payments = await provider.send();
|
|
final hasError = provider.error != null;
|
|
if (hasError || payments.isEmpty) {
|
|
return MultiplePayoutSendOutcome.failure;
|
|
}
|
|
return MultiplePayoutSendOutcome.success;
|
|
}
|
|
|
|
void removeUploadedFile() {
|
|
_provider?.removeUploadedFile();
|
|
_clearUiError(notify: false);
|
|
}
|
|
|
|
void _onProviderChanged() {
|
|
notifyListeners();
|
|
}
|
|
|
|
void _onSourceChanged() {
|
|
final currentSourceKey = _currentSourceKey;
|
|
final sourceChanged = currentSourceKey != _lastSourceKey;
|
|
_lastSourceKey = currentSourceKey;
|
|
if (sourceChanged) {
|
|
unawaited(_requoteWithUploadedRows());
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
String? get _selectedSourceCurrencyCode =>
|
|
_sourceController?.selectedCurrencyCode;
|
|
String? get _currentSourceKey {
|
|
final source = _sourceController;
|
|
if (source == null ||
|
|
source.selectedType == null ||
|
|
source.selectedRef == null) {
|
|
return null;
|
|
}
|
|
return '${source.selectedType!.name}:${source.selectedRef!}';
|
|
}
|
|
|
|
({PaymentMethodData method, String currencyCode})? get _selectedSource {
|
|
final source = _sourceController;
|
|
if (source == null) return null;
|
|
|
|
final currencyCode = source.selectedCurrencyCode;
|
|
if (currencyCode == null || currencyCode.isEmpty) return null;
|
|
|
|
final wallet = source.selectedWallet;
|
|
if (wallet != null) {
|
|
final hasAsset = (wallet.tokenSymbol ?? '').isNotEmpty;
|
|
final asset = hasAsset
|
|
? PaymentAsset(
|
|
chain: wallet.network ?? ChainNetwork.unspecified,
|
|
tokenSymbol: wallet.tokenSymbol!,
|
|
contractAddress: wallet.contractAddress,
|
|
)
|
|
: null;
|
|
return (
|
|
method: ManagedWalletPaymentMethod(
|
|
managedWalletRef: wallet.id,
|
|
asset: asset,
|
|
),
|
|
currencyCode: currencyCode,
|
|
);
|
|
}
|
|
|
|
final ledger = source.selectedLedgerAccount;
|
|
if (ledger != null) {
|
|
return (
|
|
method: LedgerPaymentMethod(ledgerAccountRef: ledger.ledgerAccountRef),
|
|
currencyCode: currencyCode,
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
Future<void> _requoteWithUploadedRows() async {
|
|
final provider = _provider;
|
|
if (provider == null) return;
|
|
if (provider.selectedFileName == null || provider.rows.isEmpty) return;
|
|
|
|
final source = _selectedSource;
|
|
if (source == null) return;
|
|
|
|
_clearUiError(notify: false);
|
|
await provider.requoteUploadedRows(
|
|
sourceMethod: source.method,
|
|
sourceCurrencyCode: source.currencyCode,
|
|
);
|
|
}
|
|
|
|
void _setUiError(Object error) {
|
|
_uiError = error is Exception ? error : Exception(error.toString());
|
|
notifyListeners();
|
|
}
|
|
|
|
void _clearUiError({bool notify = true}) {
|
|
if (_uiError == null) return;
|
|
_uiError = null;
|
|
if (notify) {
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_provider?.removeListener(_onProviderChanged);
|
|
_sourceController?.removeListener(_onSourceChanged);
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
enum _PickState { idle, picking }
|
|
|
|
enum MultiplePayoutSendOutcome { success, failure }
|