Files
sendico/frontend/pweb/lib/controllers/payments/amount_field.dart
2026-03-03 21:03:30 +03:00

155 lines
4.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/models/payment/settlement_mode.dart';
import 'package:pshared/provider/payment/amount.dart';
import 'package:pshared/utils/currency.dart';
import 'package:pshared/utils/money.dart';
import 'package:pweb/models/payment/amount/mode.dart';
class PaymentAmountFieldController extends ChangeNotifier {
static const String _settlementCurrencyCode = 'RUB';
final TextEditingController textController;
final FocusNode focusNode = FocusNode();
PaymentAmountProvider? _provider;
PaymentSourceController? _source;
bool _isSyncingText = false;
PaymentAmountMode _mode = PaymentAmountMode.debit;
PaymentAmountFieldController({required double initialAmount})
: textController = TextEditingController(
text: amountToString(initialAmount),
);
PaymentAmountMode get mode => _mode;
bool get isReverseModeAvailable {
final sourceCurrencyCode = _sourceCurrencyCode;
return sourceCurrencyCode != null &&
sourceCurrencyCode != _settlementCurrencyCode;
}
String? get activeCurrencyCode => switch (_mode) {
PaymentAmountMode.debit => _sourceCurrencyCode,
PaymentAmountMode.settlement => _settlementCurrencyCode,
};
void update(PaymentAmountProvider provider, PaymentSourceController source) {
if (!identical(_provider, provider)) {
_provider?.removeListener(_handleProviderChanged);
_provider = provider;
_provider?.addListener(_handleProviderChanged);
_syncModeWithProvider(provider);
}
if (!identical(_source, source)) {
_source?.removeListener(_handleSourceChanged);
_source = source;
_source?.addListener(_handleSourceChanged);
_normalizeModeForSource();
}
_syncTextWithAmount(provider.amount);
}
void handleChanged(String value) {
if (_isSyncingText) return;
final parsed = _parseAmount(value);
if (parsed != null) {
_provider?.setAmount(parsed);
}
}
void handleModeChanged(PaymentAmountMode value) {
if (value == _mode) return;
if (!isReverseModeAvailable && value == PaymentAmountMode.settlement) {
return;
}
_mode = value;
_provider?.setSettlementMode(_settlementModeFromMode(value));
notifyListeners();
}
void _handleProviderChanged() {
final provider = _provider;
if (provider == null) return;
_syncTextWithAmount(provider.amount);
final changed = _syncModeWithProvider(provider);
if (changed) {
_normalizeModeForSource();
notifyListeners();
}
}
void _handleSourceChanged() {
_normalizeModeForSource();
notifyListeners();
}
bool _syncModeWithProvider(PaymentAmountProvider provider) {
final nextMode = _modeFromSettlementMode(provider.settlementMode);
if (nextMode == _mode) return false;
_mode = nextMode;
return true;
}
bool _normalizeModeForSource() {
if (isReverseModeAvailable || _mode != PaymentAmountMode.settlement) {
return false;
}
_mode = PaymentAmountMode.debit;
_provider?.setSettlementMode(SettlementMode.fixSource);
return true;
}
String? get _sourceCurrencyCode {
return _source?.selectedCurrencyCode;
}
PaymentAmountMode _modeFromSettlementMode(SettlementMode mode) =>
switch (mode) {
SettlementMode.fixReceived => PaymentAmountMode.settlement,
SettlementMode.fixSource ||
SettlementMode.unspecified => PaymentAmountMode.debit,
};
SettlementMode _settlementModeFromMode(PaymentAmountMode mode) =>
switch (mode) {
PaymentAmountMode.debit => SettlementMode.fixSource,
PaymentAmountMode.settlement => SettlementMode.fixReceived,
};
double? _parseAmount(String value) {
final parsed = parseMoneyAmount(
value.replaceAll(',', '.'),
fallback: double.nan,
);
return parsed.isNaN ? null : parsed;
}
void _syncTextWithAmount(double amount) {
final parsedText = _parseAmount(textController.text);
if (parsedText != null && parsedText == amount) return;
final nextText = amountToString(amount);
_isSyncingText = true;
textController.value = TextEditingValue(
text: nextText,
selection: TextSelection.collapsed(offset: nextText.length),
);
_isSyncingText = false;
}
@override
void dispose() {
_provider?.removeListener(_handleProviderChanged);
_source?.removeListener(_handleSourceChanged);
focusNode.dispose();
textController.dispose();
super.dispose();
}
}