import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:stackwallet/models/exchange/aggregate_currency.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; class ExchangeFormState extends ChangeNotifier { Exchange? _exchange; Exchange get exchange => _exchange ??= Exchange.defaultExchange; ExchangeRateType _exchangeRateType = ExchangeRateType.estimated; ExchangeRateType get exchangeRateType => _exchangeRateType; set exchangeRateType(ExchangeRateType exchangeRateType) { _exchangeRateType = exchangeRateType; // } Estimate? _estimate; Estimate? get estimate => _estimate; bool _reversed = false; bool get reversed => _reversed; set reversed(bool reversed) { _reversed = reversed; // } Decimal? _rate; Decimal? get rate => _rate; // set rate(Decimal? rate) { // _rate = rate; // // // } Decimal? _sendAmount; Decimal? get sendAmount => _sendAmount; // set sendAmount(Decimal? sendAmount) { // _sendAmount = sendAmount; // // // } Decimal? _receiveAmount; Decimal? get receiveAmount => _receiveAmount; set receiveAmount(Decimal? receiveAmount) { _receiveAmount = receiveAmount; // } AggregateCurrency? _sendCurrency; AggregateCurrency? get sendCurrency => _sendCurrency; // set sendCurrency(Currency? sendCurrency) { // _sendCurrency = sendCurrency; // // // } AggregateCurrency? _receiveCurrency; AggregateCurrency? get receiveCurrency => _receiveCurrency; // set receiveCurrency(Currency? receiveCurrency) { // _receiveCurrency = receiveCurrency; // // // } Decimal? _minSendAmount; Decimal? get minSendAmount => _minSendAmount; // set minSendAmount(Decimal? minSendAmount) { // _minSendAmount = minSendAmount; // // // } Decimal? _minReceiveAmount; Decimal? get minReceiveAmount => _minReceiveAmount; // set minReceiveAmount(Decimal? minReceiveAmount) { // _minReceiveAmount = minReceiveAmount; // // // } Decimal? _maxSendAmount; Decimal? get maxSendAmount => _maxSendAmount; // set maxSendAmount(Decimal? maxSendAmount) { // _maxSendAmount = maxSendAmount; // // // } Decimal? _maxReceiveAmount; Decimal? get maxReceiveAmount => _maxReceiveAmount; // set maxReceiveAmount(Decimal? maxReceiveAmount) { // _maxReceiveAmount = maxReceiveAmount; // // // } //============================================================================ // computed properties //============================================================================ String? get fromTicker => _sendCurrency?.ticker; String? get toTicker => _receiveCurrency?.ticker; String get fromAmountString => _sendAmount?.toStringAsFixed(8) ?? ""; String get toAmountString => _receiveAmount?.toStringAsFixed(8) ?? ""; bool get canExchange { return sendCurrency != null && receiveCurrency != null && sendAmount != null && sendAmount! >= Decimal.zero && receiveAmount != null && rate != null && rate! >= Decimal.zero && sendCurrency!.forExchange(exchange.name) != null && receiveCurrency!.forExchange(exchange.name) != null && warning.isEmpty; } String get warning { if (reversed) { if (_receiveCurrency != null && _receiveAmount != null) { if (_minReceiveAmount != null && _receiveAmount! < _minReceiveAmount! && _receiveAmount! > Decimal.zero) { return "Min receive amount ${_minReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}"; } else if (_maxReceiveAmount != null && _receiveAmount! > _maxReceiveAmount!) { return "Max receive amount ${_maxReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}"; } } } else { if (_sendCurrency != null && _sendAmount != null) { if (_minSendAmount != null && _sendAmount! < _minSendAmount! && _sendAmount! > Decimal.zero) { return "Min send amount ${_minSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}"; } else if (_maxSendAmount != null && _sendAmount! > _maxSendAmount!) { return "Max send amount ${_maxSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}"; } } } return ""; } //============================================================================ // public state updaters //============================================================================ Future updateExchange({ required Exchange exchange, required bool shouldUpdateData, required bool shouldNotifyListeners, }) async { _exchange = exchange; if (shouldUpdateData) { await _updateRangesAndEstimate( shouldNotifyListeners: false, ); } if (shouldNotifyListeners) { _notify(); } } void setCurrencies(AggregateCurrency from, AggregateCurrency to) { _sendCurrency = from; _receiveCurrency = to; } void reset({ required bool shouldNotifyListeners, }) { _exchange = null; _reversed = false; _rate = null; _sendAmount = null; _receiveAmount = null; _sendCurrency = null; _receiveCurrency = null; _minSendAmount = null; _minReceiveAmount = null; _maxSendAmount = null; _maxReceiveAmount = null; if (shouldNotifyListeners) { _notify(); } } Future setSendAmountAndCalculateReceiveAmount( Decimal? newSendAmount, bool shouldNotifyListeners, ) async { if (newSendAmount == null) { // todo: check if this breaks things and stuff _receiveAmount = null; _sendAmount = null; } else { if (newSendAmount <= Decimal.zero) { _receiveAmount = Decimal.zero; } _sendAmount = newSendAmount; _reversed = false; await _updateRangesAndEstimate( shouldNotifyListeners: false, ); } if (shouldNotifyListeners) { _notify(); } } Future setReceivingAmountAndCalculateSendAmount( Decimal? newReceiveAmount, bool shouldNotifyListeners, ) async { if (newReceiveAmount == null) { // todo: check if this breaks things and stuff _receiveAmount = null; _sendAmount = null; } else { if (newReceiveAmount <= Decimal.zero) { _sendAmount = Decimal.zero; } _receiveAmount = newReceiveAmount; _reversed = true; await _updateRangesAndEstimate( shouldNotifyListeners: false, ); } if (shouldNotifyListeners) { _notify(); } } Future updateSendCurrency( AggregateCurrency sendCurrency, bool shouldNotifyListeners, ) async { try { _sendCurrency = sendCurrency; _minSendAmount = null; _maxSendAmount = null; if (_receiveCurrency == null) { _rate = null; } else { await _updateRangesAndEstimate( shouldNotifyListeners: false, ); } if (shouldNotifyListeners) { _notify(); } } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Error); } } Future updateReceivingCurrency( AggregateCurrency receiveCurrency, bool shouldNotifyListeners, ) async { try { _receiveCurrency = receiveCurrency; _minReceiveAmount = null; _maxReceiveAmount = null; if (_sendCurrency == null) { _rate = null; } else { await _updateRangesAndEstimate( shouldNotifyListeners: false, ); } if (shouldNotifyListeners) { _notify(); } } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Error); } } Future swap({ required bool shouldNotifyListeners, }) async { final Decimal? temp = sendAmount; _sendAmount = receiveAmount; _receiveAmount = temp; _minSendAmount = null; _maxSendAmount = null; _minReceiveAmount = null; _maxReceiveAmount = null; final AggregateCurrency? tmp = sendCurrency; _sendCurrency = receiveCurrency; _receiveCurrency = tmp; await _updateRangesAndEstimate( shouldNotifyListeners: false, ); if (shouldNotifyListeners) { _notify(); } } Future refresh() => _updateRangesAndEstimate( shouldNotifyListeners: true, ); //============================================================================ // private state updaters //============================================================================ Future _updateRangesAndEstimate({ required bool shouldNotifyListeners, }) async { try { switch (exchange.name) { case ChangeNowExchange.exchangeName: if (!_exchangeSupported( exchangeName: exchange.name, sendCurrency: sendCurrency, receiveCurrency: receiveCurrency, exchangeRateType: exchangeRateType, )) { _exchange = MajesticBankExchange.instance; } break; case MajesticBankExchange.exchangeName: if (!_exchangeSupported( exchangeName: exchange.name, sendCurrency: sendCurrency, receiveCurrency: receiveCurrency, exchangeRateType: exchangeRateType, )) { _exchange = ChangeNowExchange.instance; } break; } await _updateRanges(shouldNotifyListeners: false); await _updateEstimate(shouldNotifyListeners: false); if (shouldNotifyListeners) { _notify(); } } catch (_) { // } } Future _updateRanges({ required bool shouldNotifyListeners, }) async { // if (exchange?.name == SimpleSwapExchange.exchangeName) { // reversed = false; // } final _send = sendCurrency; final _receive = receiveCurrency; if (_send == null || _receive == null) { Logging.instance.log( "Tried to $runtimeType.updateRanges where ( $_send || $_receive) for: $exchange", level: LogLevel.Info, ); return; } final response = await exchange.getRange( _send.ticker, _receive.ticker, exchangeRateType == ExchangeRateType.fixed, ); if (response.value == null) { Logging.instance.log( "Tried to $runtimeType.updateRanges for: $exchange where response: $response", level: LogLevel.Info, ); return; } final responseReversed = await exchange.getRange( _receive.ticker, _send.ticker, exchangeRateType == ExchangeRateType.fixed, ); if (responseReversed.value == null) { Logging.instance.log( "Tried to $runtimeType.updateRanges for: $exchange where response: $responseReversed", level: LogLevel.Info, ); return; } final range = response.value!; final rangeReversed = responseReversed.value!; _minSendAmount = range.min; _maxSendAmount = range.max; _minReceiveAmount = rangeReversed.min; _maxReceiveAmount = rangeReversed.max; //todo: check if print needed // debugPrint( // "updated range for: $exchange for $_fromTicker-$_toTicker: $range"); if (shouldNotifyListeners) { _notify(); } } Future _updateEstimate({ required bool shouldNotifyListeners, }) async { // if (exchange?.name == SimpleSwapExchange.exchangeName) { // reversed = false; // } final amount = reversed ? receiveAmount : sendAmount; if (sendCurrency == null || receiveCurrency == null || amount == null || amount <= Decimal.zero) { Logging.instance.log( "Tried to $runtimeType.updateEstimate for: $exchange where (from: $sendCurrency || to: $receiveCurrency || amount: $amount)", level: LogLevel.Info, ); return; } final response = await exchange.getEstimate( sendCurrency!.ticker, receiveCurrency!.ticker, amount, exchangeRateType == ExchangeRateType.fixed, reversed, ); if (response.value == null) { Logging.instance.log( "Tried to $runtimeType.updateEstimate for: $exchange where response: $response", level: LogLevel.Info, ); return; } _estimate = response.value!; if (reversed) { _sendAmount = _estimate!.estimatedAmount; } else { _receiveAmount = _estimate!.estimatedAmount; } _rate = (receiveAmount! / sendAmount!).toDecimal(scaleOnInfinitePrecision: 12); //todo: check if print needed // debugPrint( // "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate"); if (shouldNotifyListeners) { _notify(); } } //============================================================================ void _notify() { debugPrint("ExFState NOTIFY: ${toString()}"); notifyListeners(); } bool _exchangeSupported({ required String exchangeName, required AggregateCurrency? sendCurrency, required AggregateCurrency? receiveCurrency, required ExchangeRateType exchangeRateType, }) { final send = sendCurrency?.forExchange(exchangeName); if (send == null) return false; final rcv = receiveCurrency?.forExchange(exchangeName); if (rcv == null) return false; if (exchangeRateType == ExchangeRateType.fixed) { return send.supportsFixedRate && rcv.supportsFixedRate; } else { return send.supportsEstimatedRate && rcv.supportsEstimatedRate; } } @override String toString() { return "{" "\n\t exchange: $exchange," "\n\t exchangeRateType: $exchangeRateType," "\n\t sendCurrency: $sendCurrency," "\n\t receiveCurrency: $receiveCurrency," "\n\t rate: $rate," "\n\t reversed: $reversed," "\n\t sendAmount: $sendAmount," "\n\t receiveAmount: $receiveAmount," "\n\t estimate: $estimate," "\n\t minSendAmount: $minSendAmount," "\n\t maxSendAmount: $maxSendAmount," "\n\t minReceiveAmount: $minReceiveAmount," "\n\t maxReceiveAmount: $maxReceiveAmount," "\n\t canExchange: $canExchange," "\n\t warning: $warning," "\n}"; } }