Merge branch 'exchange_refactor' into paynyms

# Conflicts:
#	test/pages/send_view/send_view_test.mocks.dart
#	test/widget_tests/managed_favorite_test.mocks.dart
#	test/widget_tests/table_view/table_view_row_test.mocks.dart
#	test/widget_tests/wallet_card_test.mocks.dart
#	test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart
#	test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart
This commit is contained in:
julian 2023-02-07 11:17:16 -06:00
commit d4db845494
56 changed files with 9190 additions and 6229 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

View file

@ -1,4 +1,3 @@
import 'package:flutter/foundation.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/exceptions/main_db/main_db_exception.dart';
import 'package:stackwallet/exceptions/sw_exception.dart';
@ -30,7 +29,8 @@ class MainDB {
AddressLabelSchema,
],
directory: (await StackFileSystem.applicationIsarDirectory()).path,
inspector: kDebugMode,
// inspector: kDebugMode,
inspector: false,
name: "wallet_data",
);
return true;

View file

@ -0,0 +1,5 @@
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
class PairUnavailableException extends ExchangeException {
PairUnavailableException(super.message, super.type);
}

View file

@ -43,7 +43,6 @@ import 'package:stackwallet/providers/ui/color_theme_provider.dart';
import 'package:stackwallet/route_generator.dart';
// import 'package:stackwallet/services/buy/buy_data_loading_service.dart';
import 'package:stackwallet/services/debug_service.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart';
import 'package:stackwallet/services/locale_service.dart';
import 'package:stackwallet/services/node_service.dart';
@ -291,11 +290,16 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
// TODO: this should probably run unawaited. Keep commented out for now as proper community nodes ui hasn't been implemented yet
// unawaited(_nodeService.updateCommunityNodes());
print("================================================");
print("${ref.read(prefsChangeNotifierProvider).externalCalls}");
print("${await ref.read(prefsChangeNotifierProvider).isExternalCallsSet()}");
print("================================================");
// run without awaiting
if (ref.read(prefsChangeNotifierProvider).externalCalls &&
await ref.read(prefsChangeNotifierProvider).isExternalCallsSet()) {
if (Constants.enableExchange) {
unawaited(ExchangeDataLoadingService().loadAll(ref));
await ExchangeDataLoadingService.instance.init();
unawaited(ExchangeDataLoadingService.instance.loadAll());
}
// if (Constants.enableBuy) {
// unawaited(BuyDataLoadingService().loadAll(ref));
@ -328,7 +332,6 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
@override
void initState() {
ref.read(exchangeFormStateProvider).exchange = ChangeNowExchange();
final colorScheme = DB.instance
.get<dynamic>(boxName: DB.boxNameTheme, key: "colorScheme") as String?;

View file

@ -1,24 +0,0 @@
import 'package:stackwallet/models/exchange/response_objects/currency.dart';
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart';
import 'package:stackwallet/models/exchange/response_objects/pair.dart';
class CNAvailableCurrencies {
final List<Currency> currencies = [];
final List<Pair> pairs = [];
final List<FixedRateMarket> markets = [];
void updateCurrencies(List<Currency> newCurrencies) {
currencies.clear();
currencies.addAll(newCurrencies);
}
void updateFloatingPairs(List<Pair> newPairs) {
pairs.clear();
pairs.addAll(newPairs);
}
void updateMarkets(List<FixedRateMarket> newMarkets) {
markets.clear();
markets.addAll(newMarkets);
}
}

View file

@ -1,113 +1,143 @@
import 'package:decimal/decimal.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:stackwallet/models/exchange/response_objects/currency.dart';
import 'package:flutter/foundation.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange.dart';
// import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
import 'package:stackwallet/utilities/logger.dart';
class ExchangeFormState extends ChangeNotifier {
Exchange? _exchange;
Exchange? get exchange => _exchange;
set exchange(Exchange? value) {
_exchange = value;
_onExchangeTypeChanged();
Exchange get exchange => _exchange ??= Exchange.defaultExchange;
ExchangeRateType _exchangeRateType = ExchangeRateType.estimated;
ExchangeRateType get exchangeRateType => _exchangeRateType;
set exchangeRateType(ExchangeRateType exchangeRateType) {
_exchangeRateType = exchangeRateType;
//
}
ExchangeRateType _exchangeType = ExchangeRateType.estimated;
ExchangeRateType get exchangeType => _exchangeType;
set exchangeType(ExchangeRateType value) {
_exchangeType = value;
_onExchangeRateTypeChanged();
Estimate? _estimate;
Estimate? get estimate => _estimate;
bool _reversed = false;
bool get reversed => _reversed;
set reversed(bool reversed) {
_reversed = reversed;
//
}
bool reversed = false;
Decimal? _rate;
Decimal? get rate => _rate;
// set rate(Decimal? rate) {
// _rate = rate;
// //
// }
Decimal? fromAmount;
Decimal? toAmount;
Decimal? _sendAmount;
Decimal? get sendAmount => _sendAmount;
// set sendAmount(Decimal? sendAmount) {
// _sendAmount = sendAmount;
// //
// }
Decimal? minAmount;
Decimal? maxAmount;
Decimal? rate;
Estimate? estimate;
FixedRateMarket? _market;
FixedRateMarket? get market => _market;
Currency? _from;
Currency? _to;
@override
String toString() {
return 'ExchangeFormState: {_exchange: $_exchange, _exchangeType: $_exchangeType, reversed: $reversed, fromAmount: $fromAmount, toAmount: $toAmount, minAmount: $minAmount, maxAmount: $maxAmount, rate: $rate, estimate: $estimate, _market: $_market, _from: $_from, _to: $_to, _onError: $_onError}';
Decimal? _receiveAmount;
Decimal? get receiveAmount => _receiveAmount;
set receiveAmount(Decimal? receiveAmount) {
_receiveAmount = receiveAmount;
//
}
String? get fromTicker {
switch (exchangeType) {
case ExchangeRateType.estimated:
return _from?.ticker;
case ExchangeRateType.fixed:
switch (exchange?.name) {
// case SimpleSwapExchange.exchangeName:
// return _from?.ticker;
case ChangeNowExchange.exchangeName:
return market?.from;
default:
return null;
}
}
}
Currency? _sendCurrency;
Currency? get sendCurrency => _sendCurrency;
// set sendCurrency(Currency? sendCurrency) {
// _sendCurrency = sendCurrency;
// //
// }
String? get toTicker {
switch (exchangeType) {
case ExchangeRateType.estimated:
return _to?.ticker;
case ExchangeRateType.fixed:
switch (exchange?.name) {
// case SimpleSwapExchange.exchangeName:
// return _to?.ticker;
case ChangeNowExchange.exchangeName:
return market?.to;
default:
return null;
}
}
}
Currency? _receiveCurrency;
Currency? get receiveCurrency => _receiveCurrency;
// set receiveCurrency(Currency? receiveCurrency) {
// _receiveCurrency = receiveCurrency;
// //
// }
void Function(String)? _onError;
Decimal? _minSendAmount;
Decimal? get minSendAmount => _minSendAmount;
// set minSendAmount(Decimal? minSendAmount) {
// _minSendAmount = minSendAmount;
// //
// }
Currency? get from => _from;
Currency? get to => _to;
Decimal? _minReceiveAmount;
Decimal? get minReceiveAmount => _minReceiveAmount;
// set minReceiveAmount(Decimal? minReceiveAmount) {
// _minReceiveAmount = minReceiveAmount;
// //
// }
void setCurrencies(Currency from, Currency to) {
_from = from;
_to = to;
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 &&
exchange.name == sendCurrency!.exchangeName &&
exchange.name == receiveCurrency!.exchangeName &&
warning.isEmpty;
}
String get warning {
if (reversed) {
if (toTicker != null && toAmount != null) {
if (minAmount != null &&
toAmount! < minAmount! &&
toAmount! > Decimal.zero) {
return "Minimum amount ${minAmount!.toString()} ${toTicker!.toUpperCase()}";
} else if (maxAmount != null && toAmount! > maxAmount!) {
return "Maximum amount ${maxAmount!.toString()} ${toTicker!.toUpperCase()}";
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 (fromTicker != null && fromAmount != null) {
if (minAmount != null &&
fromAmount! < minAmount! &&
fromAmount! > Decimal.zero) {
return "Minimum amount ${minAmount!.toString()} ${fromTicker!.toUpperCase()}";
} else if (maxAmount != null && fromAmount! > maxAmount!) {
return "Maximum amount ${maxAmount!.toString()} ${fromTicker!.toUpperCase()}";
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()}";
}
}
}
@ -115,254 +145,374 @@ class ExchangeFormState extends ChangeNotifier {
return "";
}
String get fromAmountString => fromAmount?.toStringAsFixed(8) ?? "";
String get toAmountString => toAmount?.toStringAsFixed(8) ?? "";
//============================================================================
// public state updaters
//============================================================================
bool get canExchange {
if (exchange?.name == ChangeNowExchange.exchangeName &&
exchangeType == ExchangeRateType.fixed) {
return _market != null &&
fromAmount != null &&
toAmount != null &&
warning.isEmpty;
} else {
return fromAmount != null &&
fromAmount != Decimal.zero &&
toAmount != null &&
rate != null &&
warning.isEmpty;
}
}
void clearAmounts(bool shouldNotifyListeners) {
fromAmount = null;
toAmount = null;
minAmount = null;
maxAmount = null;
rate = null;
if (shouldNotifyListeners) {
notifyListeners();
}
}
Future<void> setFromAmountAndCalculateToAmount(
Decimal newFromAmount,
bool shouldNotifyListeners,
) async {
if (newFromAmount == Decimal.zero) {
toAmount = Decimal.zero;
}
fromAmount = newFromAmount;
reversed = false;
await updateRanges(shouldNotifyListeners: false);
await updateEstimate(
shouldNotifyListeners: false,
reversed: reversed,
);
if (shouldNotifyListeners) {
notifyListeners();
}
}
Future<void> setToAmountAndCalculateFromAmount(
Decimal newToAmount,
bool shouldNotifyListeners,
) async {
if (newToAmount == Decimal.zero) {
fromAmount = Decimal.zero;
}
toAmount = newToAmount;
reversed = true;
await updateRanges(shouldNotifyListeners: false);
await updateEstimate(
shouldNotifyListeners: false,
reversed: reversed,
);
if (shouldNotifyListeners) {
notifyListeners();
}
}
Future<void> updateTo(Currency to, bool shouldNotifyListeners) async {
try {
_to = to;
if (_from == null) {
rate = null;
notifyListeners();
return;
Future<void> updateExchange({
required Exchange exchange,
required bool shouldUpdateData,
required bool shouldNotifyListeners,
}) async {
_exchange = exchange;
if (shouldUpdateData) {
if (_sendCurrency != null) {
_sendCurrency = await ExchangeDataLoadingService
.instance.isar.currencies
.where()
.exchangeNameEqualTo(exchange.name)
.filter()
.tickerEqualTo(_sendCurrency!.ticker)
.and()
.group((q) => exchangeRateType == ExchangeRateType.fixed
? q
.rateTypeEqualTo(SupportedRateType.both)
.or()
.rateTypeEqualTo(SupportedRateType.fixed)
: q
.rateTypeEqualTo(SupportedRateType.both)
.or()
.rateTypeEqualTo(SupportedRateType.estimated))
.findFirst();
}
await updateRanges(shouldNotifyListeners: false);
await updateEstimate(
shouldNotifyListeners: false,
reversed: reversed,
);
//todo: check if print needed
// debugPrint(
// "_updated TO: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$fromAmount _toAmount=$toAmount rate:$rate for: $exchange");
if (shouldNotifyListeners) {
notifyListeners();
}
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Error);
}
}
Future<void> updateFrom(Currency from, bool shouldNotifyListeners) async {
try {
_from = from;
if (_to == null) {
rate = null;
notifyListeners();
return;
}
await updateRanges(shouldNotifyListeners: false);
await updateEstimate(
shouldNotifyListeners: false,
reversed: reversed,
);
//todo: check if print needed
// debugPrint(
// "_updated FROM: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$fromAmount _toAmount=$toAmount rate:$rate for: $exchange");
if (shouldNotifyListeners) {
notifyListeners();
}
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Error);
}
}
Future<void> updateMarket(
FixedRateMarket? market,
bool shouldNotifyListeners,
) async {
_market = market;
if (_market == null) {
fromAmount = null;
toAmount = null;
} else {
if (fromAmount != null) {
if (fromAmount! <= Decimal.zero) {
toAmount = Decimal.zero;
} else {
await updateRanges(shouldNotifyListeners: false);
await updateEstimate(
shouldNotifyListeners: false,
reversed: reversed,
);
if (_sendCurrency == null) {
switch (exchange.name) {
case ChangeNowExchange.exchangeName:
_sendCurrency = _cachedSendCN;
break;
case MajesticBankExchange.exchangeName:
_sendCurrency = _cachedSendMB;
break;
}
}
if (_receiveCurrency != null) {
_receiveCurrency = await ExchangeDataLoadingService
.instance.isar.currencies
.where()
.exchangeNameEqualTo(exchange.name)
.filter()
.tickerEqualTo(_receiveCurrency!.ticker)
.and()
.group((q) => exchangeRateType == ExchangeRateType.fixed
? q
.rateTypeEqualTo(SupportedRateType.both)
.or()
.rateTypeEqualTo(SupportedRateType.fixed)
: q
.rateTypeEqualTo(SupportedRateType.both)
.or()
.rateTypeEqualTo(SupportedRateType.estimated))
.findFirst();
}
if (_receiveCurrency == null) {
switch (exchange.name) {
case ChangeNowExchange.exchangeName:
_receiveCurrency = _cachedReceivingCN;
break;
case MajesticBankExchange.exchangeName:
_receiveCurrency = _cachedReceivingMB;
break;
}
}
_updateCachedCurrencies(
exchangeName: exchange.name,
send: _sendCurrency,
receiving: _receiveCurrency,
);
await _updateRangesAndEstimate(
shouldNotifyListeners: false,
);
}
if (shouldNotifyListeners) {
notifyListeners();
_notify();
}
}
void _onExchangeRateTypeChanged() {
print("_onExchangeRateTypeChanged");
updateRanges(shouldNotifyListeners: true).then(
(_) => updateEstimate(
shouldNotifyListeners: true,
reversed: reversed,
),
void setCurrencies(Currency from, Currency to) {
_sendCurrency = from;
_receiveCurrency = to;
_updateCachedCurrencies(
exchangeName: exchange.name,
send: _sendCurrency,
receiving: _receiveCurrency,
);
}
void _onExchangeTypeChanged() {
updateRanges(shouldNotifyListeners: true).then(
(_) => updateEstimate(
shouldNotifyListeners: true,
reversed: reversed,
),
);
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<void> updateRanges({required bool shouldNotifyListeners}) async {
Future<void> 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<void> 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<void> updateSendCurrency(
Currency sendCurrency,
bool shouldNotifyListeners,
) async {
try {
_sendCurrency = sendCurrency;
_minSendAmount = null;
_maxSendAmount = null;
_updateCachedCurrencies(
exchangeName: exchange.name,
send: _sendCurrency,
receiving: _receiveCurrency,
);
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<void> updateReceivingCurrency(
Currency receiveCurrency,
bool shouldNotifyListeners,
) async {
try {
_receiveCurrency = receiveCurrency;
_minReceiveAmount = null;
_maxReceiveAmount = null;
_updateCachedCurrencies(
exchangeName: exchange.name,
send: _sendCurrency,
receiving: _receiveCurrency,
);
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<void> swap({
required bool shouldNotifyListeners,
}) async {
final Decimal? temp = sendAmount;
_sendAmount = receiveAmount;
_receiveAmount = temp;
_minSendAmount = null;
_maxSendAmount = null;
_minReceiveAmount = null;
_maxReceiveAmount = null;
final Currency? tmp = sendCurrency;
_sendCurrency = receiveCurrency;
_receiveCurrency = tmp;
_updateCachedCurrencies(
exchangeName: exchange.name,
send: _sendCurrency,
receiving: _receiveCurrency,
);
await _updateRangesAndEstimate(
shouldNotifyListeners: false,
);
if (shouldNotifyListeners) {
_notify();
}
}
Future<void> refresh() => _updateRangesAndEstimate(
shouldNotifyListeners: true,
);
//============================================================================
// private state updaters
//============================================================================
Future<void> _updateRangesAndEstimate({
required bool shouldNotifyListeners,
}) async {
try {
await _updateRanges(shouldNotifyListeners: false);
await _updateEstimate(shouldNotifyListeners: false);
if (shouldNotifyListeners) {
_notify();
}
} catch (_) {
//
}
}
Future<void> _updateRanges({
required bool shouldNotifyListeners,
}) async {
// if (exchange?.name == SimpleSwapExchange.exchangeName) {
// reversed = false;
// }
final _fromTicker = reversed ? toTicker : fromTicker;
final _toTicker = reversed ? fromTicker : toTicker;
if (_fromTicker == null || _toTicker == null) {
final _send = sendCurrency;
final _receive = receiveCurrency;
if (_send == null || _receive == null) {
Logging.instance.log(
"Tried to $runtimeType.updateRanges where (from: $_fromTicker || to: $_toTicker) for: $exchange",
"Tried to $runtimeType.updateRanges where ( $_send || $_receive) for: $exchange",
level: LogLevel.Info,
);
return;
}
final response = await exchange?.getRange(
_fromTicker,
_toTicker,
exchangeType == ExchangeRateType.fixed,
final response = await exchange.getRange(
_send.ticker,
_receive.ticker,
exchangeRateType == ExchangeRateType.fixed,
);
if (response?.value == null) {
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,
);
final range = response!.value!;
if (responseReversed.value == null) {
Logging.instance.log(
"Tried to $runtimeType.updateRanges for: $exchange where response: $responseReversed",
level: LogLevel.Info,
);
return;
}
minAmount = range.min;
maxAmount = range.max;
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) {
notifyListeners();
_notify();
}
}
Future<void> updateEstimate({
Future<void> _updateEstimate({
required bool shouldNotifyListeners,
required bool reversed,
}) async {
// if (exchange?.name == SimpleSwapExchange.exchangeName) {
// reversed = false;
// }
final amount = reversed ? toAmount : fromAmount;
if (fromTicker == null ||
toTicker == null ||
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: $fromTicker || to: $toTicker || amount: $amount)",
"Tried to $runtimeType.updateEstimate for: $exchange where (from: $sendCurrency || to: $receiveCurrency || amount: $amount)",
level: LogLevel.Info,
);
return;
}
final response = await exchange?.getEstimate(
fromTicker!,
toTicker!,
final response = await exchange.getEstimate(
sendCurrency!.ticker,
receiveCurrency!.ticker,
amount,
exchangeType == ExchangeRateType.fixed,
exchangeRateType == ExchangeRateType.fixed,
reversed,
);
if (response?.value == null) {
if (response.value == null) {
Logging.instance.log(
"Tried to $runtimeType.updateEstimate for: $exchange where response: $response",
level: LogLevel.Info,
@ -370,63 +520,73 @@ class ExchangeFormState extends ChangeNotifier {
return;
}
estimate = response!.value!;
_estimate = response.value!;
if (reversed) {
fromAmount = estimate!.estimatedAmount;
_sendAmount = _estimate!.estimatedAmount;
} else {
toAmount = estimate!.estimatedAmount;
_receiveAmount = _estimate!.estimatedAmount;
}
rate = (toAmount! / fromAmount!).toDecimal(scaleOnInfinitePrecision: 12);
_rate =
(receiveAmount! / sendAmount!).toDecimal(scaleOnInfinitePrecision: 12);
//todo: check if print needed
// debugPrint(
// "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate");
if (shouldNotifyListeners) {
notifyListeners();
_notify();
}
}
void setOnError({
required void Function(String)? onError,
bool shouldNotifyListeners = false,
//============================================================================
Currency? _cachedReceivingMB;
Currency? _cachedSendMB;
Currency? _cachedReceivingCN;
Currency? _cachedSendCN;
void _updateCachedCurrencies({
required String exchangeName,
required Currency? send,
required Currency? receiving,
}) {
_onError = onError;
if (shouldNotifyListeners) {
notifyListeners();
switch (exchangeName) {
case ChangeNowExchange.exchangeName:
_cachedSendCN = send ?? _cachedSendCN;
_cachedReceivingCN = receiving ?? _cachedReceivingCN;
break;
case MajesticBankExchange.exchangeName:
_cachedSendMB = send ?? _cachedSendMB;
_cachedReceivingMB = receiving ?? _cachedReceivingMB;
break;
}
}
Future<void> swap({FixedRateMarket? market}) async {
final Decimal? newToAmount = fromAmount;
final Decimal? newFromAmount = toAmount;
fromAmount = newFromAmount;
toAmount = newToAmount;
minAmount = null;
maxAmount = null;
if (exchangeType == ExchangeRateType.fixed &&
exchange?.name == ChangeNowExchange.exchangeName) {
await updateMarket(market, false);
} else {
final Currency? newTo = from;
final Currency? newFrom = to;
_to = newTo;
_from = newFrom;
await updateRanges(shouldNotifyListeners: false);
await updateEstimate(
shouldNotifyListeners: false,
reversed: reversed,
);
}
void _notify() {
debugPrint("ExFState NOTIFY: ${toString()}");
notifyListeners();
}
@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}";
}
}

View file

@ -1,73 +0,0 @@
import 'dart:ui';
import 'package:stackwallet/utilities/logger.dart';
class Pair {
final String from;
final String fromNetwork;
final String to;
final String toNetwork;
final bool fixedRate;
final bool floatingRate;
Pair({
required this.from,
required this.fromNetwork,
required this.to,
required this.toNetwork,
required this.fixedRate,
required this.floatingRate,
});
factory Pair.fromMap(Map<String, dynamic> map) {
try {
return Pair(
from: map["from"] as String,
fromNetwork: map["fromNetwork"] as String,
to: map["to"] as String,
toNetwork: map["toNetwork"] as String,
fixedRate: map["fixedRate"] as bool,
floatingRate: map["floatingRate"] as bool,
);
} catch (e, s) {
Logging.instance.log("Pair.fromMap(): $e\n$s", level: LogLevel.Error);
rethrow;
}
}
Map<String, dynamic> toMap() {
return {
"from": from,
"fromNetwork": fromNetwork,
"to": to,
"toNetwork": toNetwork,
"fixedRate": fixedRate,
"floatingRate": floatingRate,
};
}
@override
bool operator ==(other) =>
other is Pair &&
from == other.from &&
fromNetwork == other.fromNetwork &&
to == other.to &&
toNetwork == other.toNetwork &&
fixedRate == other.fixedRate &&
floatingRate == other.floatingRate;
@override
int get hashCode => hashValues(
from,
fromNetwork,
to,
toNetwork,
fixedRate,
floatingRate,
);
@override
String toString() => "Pair: ${toMap()}";
}

View file

@ -1,30 +0,0 @@
import 'package:stackwallet/models/exchange/response_objects/currency.dart';
import 'package:stackwallet/models/exchange/response_objects/pair.dart';
class SPAvailableCurrencies {
final List<Currency> floatingRateCurrencies = [];
final List<Currency> fixedRateCurrencies = [];
final List<Pair> floatingRatePairs = [];
final List<Pair> fixedRatePairs = [];
void updateFloatingCurrencies(List<Currency> newCurrencies) {
floatingRateCurrencies.clear();
floatingRateCurrencies.addAll(newCurrencies);
}
void updateFixedCurrencies(List<Currency> newCurrencies) {
fixedRateCurrencies.clear();
fixedRateCurrencies.addAll(newCurrencies);
}
void updateFloatingPairs(List<Pair> newPairs) {
floatingRatePairs.clear();
floatingRatePairs.addAll(newPairs);
}
void updateFixedPairs(List<Pair> newPairs) {
fixedRatePairs.clear();
fixedRatePairs.addAll(newPairs);
}
}

View file

@ -1,5 +1,21 @@
import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
part 'currency.g.dart';
@Collection(accessor: "currencies")
class Currency {
Id? id;
@Index()
final String exchangeName;
/// Currency ticker
@Index(composite: [
CompositeIndex("exchangeName"),
CompositeIndex("name"),
])
final String ticker;
/// Currency name
@ -11,57 +27,57 @@ class Currency {
/// Currency logo url
final String image;
/// Indicates if a currency has an Extra ID
final bool hasExternalId;
/// external id if it exists
final String? externalId;
/// Indicates if a currency is a fiat currency (EUR, USD)
final bool isFiat;
/// Indicates if a currency is popular
final bool featured;
/// Indicates if a currency is stable
final bool isStable;
/// Indicates if a currency is available on a fixed-rate flow
final bool supportsFixedRate;
@enumerated
final SupportedRateType rateType;
/// (Optional - based on api call) Indicates whether the pair is
/// currently supported by change now
final bool? isAvailable;
@Index()
final bool isStackCoin;
Currency({
required this.exchangeName,
required this.ticker,
required this.name,
required this.network,
required this.image,
required this.hasExternalId,
this.externalId,
required this.isFiat,
required this.featured,
required this.isStable,
required this.supportsFixedRate,
required this.rateType,
this.isAvailable,
required this.isStackCoin,
});
factory Currency.fromJson(Map<String, dynamic> json) {
factory Currency.fromJson(
Map<String, dynamic> json, {
required String exchangeName,
required SupportedRateType rateType,
}) {
try {
final ticker = (json["ticker"] as String).toUpperCase();
return Currency(
ticker: json["ticker"] as String,
exchangeName: exchangeName,
ticker: ticker,
name: json["name"] as String,
network: json["network"] as String? ?? "",
image: json["image"] as String,
hasExternalId: json["hasExternalId"] as bool,
externalId: json["externalId"] as String?,
isFiat: json["isFiat"] as bool,
featured: json["featured"] as bool,
isStable: json["isStable"] as bool,
supportsFixedRate: json["supportsFixedRate"] as bool,
rateType: rateType,
isAvailable: json["isAvailable"] as bool?,
);
isStackCoin:
json["isStackCoin"] as bool? ?? Currency.checkIsStackCoin(ticker),
)..id = json["id"] as int?;
} catch (e) {
rethrow;
}
@ -69,55 +85,60 @@ class Currency {
Map<String, dynamic> toJson() {
final map = {
"id": id,
"exchangeName": exchangeName,
"ticker": ticker,
"name": name,
"network": network,
"image": image,
"hasExternalId": hasExternalId,
"externalId": externalId,
"isFiat": isFiat,
"featured": featured,
"isStable": isStable,
"supportsFixedRate": supportsFixedRate,
"rateType": rateType,
"isAvailable": isAvailable,
"isStackCoin": isStackCoin,
};
if (isAvailable != null) {
map["isAvailable"] = isAvailable!;
}
return map;
}
Currency copyWith({
Id? id,
String? exchangeName,
String? ticker,
String? name,
String? network,
String? image,
bool? hasExternalId,
String? externalId,
bool? isFiat,
bool? featured,
bool? isStable,
bool? supportsFixedRate,
SupportedRateType? rateType,
bool? isAvailable,
bool? isStackCoin,
}) {
return Currency(
exchangeName: exchangeName ?? this.exchangeName,
ticker: ticker ?? this.ticker,
name: name ?? this.name,
network: network ?? this.network,
image: image ?? this.image,
hasExternalId: hasExternalId ?? this.hasExternalId,
externalId: externalId ?? this.externalId,
isFiat: isFiat ?? this.isFiat,
featured: featured ?? this.featured,
isStable: isStable ?? this.isStable,
supportsFixedRate: supportsFixedRate ?? this.supportsFixedRate,
rateType: rateType ?? this.rateType,
isAvailable: isAvailable ?? this.isAvailable,
);
isStackCoin: isStackCoin ?? this.isStackCoin,
)..id = id ?? this.id;
}
@override
String toString() {
return "Currency: ${toJson()}";
}
static bool checkIsStackCoin(String ticker) {
try {
coinFromTickerCaseInsensitive(ticker);
return true;
} catch (_) {
return false;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,61 @@
import 'package:isar/isar.dart';
part 'pair.g.dart';
// embedded enum // no not modify
enum SupportedRateType { fixed, estimated, both }
@collection
class Pair {
Pair({
required this.exchangeName,
required this.from,
required this.to,
required this.rateType,
});
Id? id;
@Index()
final String exchangeName;
@Index(composite: [
CompositeIndex("exchangeName"),
CompositeIndex("to"),
])
final String from;
final String to;
@enumerated
final SupportedRateType rateType;
Map<String, dynamic> toMap() {
return {
"id": id,
"exchangeName": exchangeName,
"from": from,
"to": to,
"rateType": rateType,
};
}
@override
bool operator ==(other) =>
other is Pair &&
exchangeName == other.exchangeName &&
from == other.from &&
to == other.to &&
rateType == other.rateType;
@override
int get hashCode => Object.hash(
exchangeName,
from,
to,
rateType,
);
@override
String toString() => "Pair: ${toMap()}";
}

File diff suppressed because it is too large Load diff

View file

@ -252,12 +252,15 @@ bool isStackCoin(String? ticker) {
}
}
Widget? getIconForTicker(String ticker) {
Widget? getIconForTicker(
String ticker, {
double size = 20,
}) {
String? iconAsset = /*isStackCoin(ticker)
?*/
Assets.svg.iconFor(coin: coinFromTickerCaseInsensitive(ticker));
// : Assets.svg.buyIconFor(ticker);
return (iconAsset != null)
? SvgPicture.asset(iconAsset, height: 20, width: 20)
? SvgPicture.asset(iconAsset, height: size, width: size)
: null;
}

View file

@ -0,0 +1,502 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/pages/buy_view/sub_widgets/crypto_selection_view.dart';
import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
class ExchangeCurrencySelectionView extends StatefulWidget {
const ExchangeCurrencySelectionView({
Key? key,
required this.exchangeName,
required this.willChange,
required this.paired,
required this.isFixedRate,
required this.willChangeIsSend,
}) : super(key: key);
final String exchangeName;
final Currency? willChange;
final Currency? paired;
final bool isFixedRate;
final bool willChangeIsSend;
@override
State<ExchangeCurrencySelectionView> createState() =>
_ExchangeCurrencySelectionViewState();
}
class _ExchangeCurrencySelectionViewState
extends State<ExchangeCurrencySelectionView> {
late TextEditingController _searchController;
final _searchFocusNode = FocusNode();
final isDesktop = Util.isDesktop;
List<Currency> _currencies = [];
List<Pair> pairs = [];
bool _loaded = false;
String _searchString = "";
Future<T> _showUpdatingCurrencies<T>({
required Future<T> whileFuture,
}) async {
unawaited(
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => WillPopScope(
onWillPop: () async => false,
child: Container(
color: Theme.of(context)
.extension<StackColors>()!
.overlay
.withOpacity(0.6),
child: const CustomLoadingOverlay(
message: "Loading currencies",
eventBus: null,
),
),
),
),
);
final result = await whileFuture;
if (mounted) {
Navigator.of(context, rootNavigator: isDesktop).pop();
}
return result;
}
Future<List<Currency>> _loadCurrencies() async {
if (widget.paired == null) {
return await _getCurrencies();
}
final pairs = await _loadAvailablePairs();
List<Currency> currencies = [];
for (final pair in pairs) {
final currency =
await _getCurrency(widget.willChangeIsSend ? pair.from : pair.to);
if (currency != null) {
currencies.add(currency);
}
}
return currencies;
}
Future<Currency?> _getCurrency(String ticker) {
return ExchangeDataLoadingService.instance.isar.currencies
.where()
.exchangeNameEqualTo(widget.exchangeName)
.filter()
.tickerEqualTo(ticker, caseSensitive: false)
.group((q) => widget.isFixedRate
? q
.rateTypeEqualTo(SupportedRateType.both)
.or()
.rateTypeEqualTo(SupportedRateType.fixed)
: q
.rateTypeEqualTo(SupportedRateType.both)
.or()
.rateTypeEqualTo(SupportedRateType.estimated))
.findFirst();
}
Future<List<Pair>> _loadAvailablePairs() {
final query = ExchangeDataLoadingService.instance.isar.pairs
.where()
.exchangeNameEqualTo(widget.exchangeName)
.filter()
.group((q) => widget.isFixedRate
? q
.rateTypeEqualTo(SupportedRateType.both)
.or()
.rateTypeEqualTo(SupportedRateType.fixed)
: q
.rateTypeEqualTo(SupportedRateType.both)
.or()
.rateTypeEqualTo(SupportedRateType.estimated))
.and()
.group((q) => widget.willChangeIsSend
? q.toEqualTo(widget.paired!.ticker, caseSensitive: false)
: q.fromEqualTo(widget.paired!.ticker, caseSensitive: false));
if (widget.willChangeIsSend) {
return query.sortByFrom().findAll();
} else {
return query.sortByTo().findAll();
}
}
Future<List<Currency>> _getCurrencies() async {
return ExchangeDataLoadingService.instance.isar.currencies
.where()
.exchangeNameEqualTo(widget.exchangeName)
.filter()
.group((q) => widget.isFixedRate
? q
.rateTypeEqualTo(SupportedRateType.both)
.or()
.rateTypeEqualTo(SupportedRateType.fixed)
: q
.rateTypeEqualTo(SupportedRateType.both)
.or()
.rateTypeEqualTo(SupportedRateType.estimated))
.sortByIsStackCoin()
.thenByName()
.findAll();
}
List<Currency> filter(String text) {
if (text.isEmpty) {
return _currencies;
}
if (widget.paired == null) {
return _currencies
.where((e) =>
e.name.toLowerCase().contains(text.toLowerCase()) ||
e.ticker.toLowerCase().contains(text.toLowerCase()))
.toList(growable: false);
} else {
return _currencies
.where((e) =>
e.ticker.toLowerCase() != widget.paired!.ticker.toLowerCase() &&
(e.name.toLowerCase().contains(text.toLowerCase()) ||
e.ticker.toLowerCase().contains(text.toLowerCase())))
.toList(growable: false);
}
}
@override
void initState() {
_searchController = TextEditingController();
super.initState();
}
@override
void dispose() {
_searchController.dispose();
_searchFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (!_loaded) {
_loaded = true;
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
_currencies =
await _showUpdatingCurrencies(whileFuture: _loadCurrencies());
setState(() {});
});
}
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 50));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Choose a coin to exchange",
style: STextStyles.pageTitleH2(context),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: child,
),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max,
children: [
if (!isDesktop)
const SizedBox(
height: 16,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autofocus: isDesktop,
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: (value) => setState(() => _searchString = value),
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
desktopMed: isDesktop,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
_searchString = "";
});
},
),
],
),
),
)
: null,
),
),
),
const SizedBox(
height: 10,
),
Text(
"Popular coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: Builder(builder: (context) {
final coins = Coin.values.where((e) =>
e.ticker.toLowerCase() !=
widget.paired?.ticker.toLowerCase());
final items = filter(_searchString)
.where((e) => coins
.where((coin) =>
coin.ticker.toLowerCase() == e.ticker.toLowerCase())
.isNotEmpty)
.toList(growable: false);
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: items.length,
itemBuilder: (builderContext, index) {
final bool hasImageUrl =
items[index].image.startsWith("http");
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(items[index]);
},
child: RoundedWhiteContainer(
child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: isStackCoin(items[index].ticker)
? getIconForTicker(
items[index].ticker,
size: 24,
)
: hasImageUrl
? SvgPicture.network(
items[index].image,
width: 24,
height: 24,
placeholderBuilder: (_) =>
const LoadingIndicator(),
)
: const SizedBox(
width: 24,
height: 24,
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
items[index].name,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
items[index].ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
),
),
);
},
),
);
}),
),
const SizedBox(
height: 20,
),
Text(
"All coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: Builder(builder: (context) {
final filtered = filter(_searchString);
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: filtered.length,
itemBuilder: (builderContext, index) {
final bool hasImageUrl =
filtered[index].image.startsWith("http");
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(filtered[index]);
},
child: RoundedWhiteContainer(
child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: isStackCoin(filtered[index].ticker)
? getIconForTicker(
filtered[index].ticker,
size: 24,
)
: hasImageUrl
? SvgPicture.network(
filtered[index].image,
width: 24,
height: 24,
placeholderBuilder: (_) =>
const LoadingIndicator(),
)
: const SizedBox(
width: 24,
height: 24,
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
filtered[index].name,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
filtered[index].ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
),
),
);
},
),
);
}),
),
],
),
);
}
}

View file

@ -1,384 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/exchange/response_objects/currency.dart';
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:tuple/tuple.dart';
class FixedRateMarketPairCoinSelectionView extends ConsumerStatefulWidget {
const FixedRateMarketPairCoinSelectionView({
Key? key,
required this.markets,
required this.currencies,
required this.isFrom,
}) : super(key: key);
final List<FixedRateMarket> markets;
final List<Currency> currencies;
final bool isFrom;
@override
ConsumerState<FixedRateMarketPairCoinSelectionView> createState() =>
_FixedRateMarketPairCoinSelectionViewState();
}
class _FixedRateMarketPairCoinSelectionViewState
extends ConsumerState<FixedRateMarketPairCoinSelectionView> {
late TextEditingController _searchController;
final _searchFocusNode = FocusNode();
late final List<FixedRateMarket> markets;
late List<FixedRateMarket> _markets;
late final bool isFrom;
Tuple2<String, String> _imageUrlAndNameFor(String ticker) {
final matches = widget.currencies.where(
(element) => element.ticker.toLowerCase() == ticker.toLowerCase());
if (matches.isNotEmpty) {
return Tuple2(matches.first.image, matches.first.name);
}
return Tuple2("", ticker);
}
void filter(String text) {
setState(() {
_markets = [
...markets.where((e) {
final String ticker = isFrom ? e.from : e.to;
final __currencies = widget.currencies
.where((e) => e.ticker.toLowerCase() == ticker.toLowerCase());
if (__currencies.isNotEmpty) {
return __currencies.first.name
.toLowerCase()
.contains(text.toLowerCase()) ||
ticker.toLowerCase().contains(text.toLowerCase());
}
return ticker.toLowerCase().contains(text.toLowerCase());
})
];
});
}
@override
void initState() {
_searchController = TextEditingController();
isFrom = widget.isFrom;
markets = [...widget.markets];
if (isFrom) {
markets.sort(
(a, b) => a.from.toLowerCase().compareTo(b.from.toLowerCase()),
);
for (Coin coin in Coin.values.reversed) {
int index = markets.indexWhere((element) =>
element.from.toLowerCase() == coin.ticker.toLowerCase());
if (index > 0) {
final market = markets.removeAt(index);
markets.insert(0, market);
}
}
} else {
markets.sort(
(a, b) => a.to.toLowerCase().compareTo(b.to.toLowerCase()),
);
for (Coin coin in Coin.values.reversed) {
int index = markets.indexWhere(
(element) => element.to.toLowerCase() == coin.ticker.toLowerCase());
if (index > 0) {
final market = markets.removeAt(index);
markets.insert(0, market);
}
}
}
_markets = [...markets];
super.initState();
}
@override
void dispose() {
_searchController.dispose();
_searchFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 50));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Choose a coin to exchange",
style: STextStyles.pageTitleH2(context),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: child,
),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isDesktop)
const SizedBox(
height: 16,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autofocus: isDesktop,
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: filter,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
},
),
],
),
),
)
: null,
),
),
),
const SizedBox(
height: 10,
),
Text(
"Popular coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: Builder(builder: (context) {
final items = _markets
.where((e) => Coin.values
.where((coin) =>
coin.ticker.toLowerCase() ==
(isFrom ? e.from.toLowerCase() : e.to.toLowerCase()))
.isNotEmpty)
.toList(growable: false);
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: items.length,
itemBuilder: (builderContext, index) {
final String ticker =
isFrom ? items[index].from : items[index].to;
final tuple = _imageUrlAndNameFor(ticker);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(ticker);
},
child: RoundedWhiteContainer(
child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: SvgPicture.network(
tuple.item1,
width: 24,
height: 24,
placeholderBuilder: (_) =>
const LoadingIndicator(),
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
tuple.item2,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
),
),
);
},
),
);
}),
),
const SizedBox(
height: 20,
),
Text(
"All coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: _markets.length,
itemBuilder: (builderContext, index) {
final String ticker =
isFrom ? _markets[index].from : _markets[index].to;
final tuple = _imageUrlAndNameFor(ticker);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(ticker);
},
child: RoundedWhiteContainer(
child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: SvgPicture.network(
tuple.item1,
width: 24,
height: 24,
placeholderBuilder: (_) =>
const LoadingIndicator(),
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
tuple.item2,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
),
),
);
},
),
),
),
],
),
);
}
}

View file

@ -1,334 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/exchange/response_objects/currency.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
class FloatingRateCurrencySelectionView extends StatefulWidget {
const FloatingRateCurrencySelectionView({
Key? key,
required this.currencies,
}) : super(key: key);
final List<Currency> currencies;
@override
State<FloatingRateCurrencySelectionView> createState() =>
_FloatingRateCurrencySelectionViewState();
}
class _FloatingRateCurrencySelectionViewState
extends State<FloatingRateCurrencySelectionView> {
late TextEditingController _searchController;
final _searchFocusNode = FocusNode();
late final List<Currency> currencies;
late List<Currency> _currencies;
void filter(String text) {
setState(() {
_currencies = [
...currencies.where((e) =>
e.name.toLowerCase().contains(text.toLowerCase()) ||
e.ticker.toLowerCase().contains(text.toLowerCase()))
];
});
}
@override
void initState() {
_searchController = TextEditingController();
currencies = [...widget.currencies];
currencies.sort(
(a, b) => a.ticker.toLowerCase().compareTo(b.ticker.toLowerCase()));
for (Coin coin in Coin.values.reversed) {
int index = currencies.indexWhere((element) =>
element.ticker.toLowerCase() == coin.ticker.toLowerCase());
if (index > 0) {
final currency = currencies.removeAt(index);
currencies.insert(0, currency);
}
}
_currencies = [...currencies];
super.initState();
}
@override
void dispose() {
_searchController.dispose();
_searchFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 50));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Choose a coin to exchange",
style: STextStyles.pageTitleH2(context),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: child,
),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max,
children: [
if (!isDesktop)
const SizedBox(
height: 16,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autofocus: isDesktop,
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: filter,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
desktopMed: isDesktop,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
filter("");
},
),
],
),
),
)
: null,
),
),
),
const SizedBox(
height: 10,
),
Text(
"Popular coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: Builder(builder: (context) {
final items = _currencies
.where((e) => Coin.values
.where((coin) =>
coin.ticker.toLowerCase() == e.ticker.toLowerCase())
.isNotEmpty)
.toList(growable: false);
return RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: items.length,
itemBuilder: (builderContext, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(items[index]);
},
child: RoundedWhiteContainer(
child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: SvgPicture.network(
items[index].image,
width: 24,
height: 24,
placeholderBuilder: (_) =>
const LoadingIndicator(),
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
items[index].name,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
items[index].ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
),
),
);
},
),
);
}),
),
const SizedBox(
height: 20,
),
Text(
"All coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: _currencies.length,
itemBuilder: (builderContext, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(_currencies[index]);
},
child: RoundedWhiteContainer(
child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: SvgPicture.network(
_currencies[index].image,
width: 24,
height: 24,
placeholderBuilder: (_) =>
const LoadingIndicator(),
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_currencies[index].name,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
_currencies[index].ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
),
),
);
},
),
),
),
],
),
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@ import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_3_view.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
@ -123,6 +124,10 @@ class _Step2ViewState extends ConsumerState<Step2View> {
@override
Widget build(BuildContext context) {
final supportsRefund =
ref.watch(currentExchangeNameStateProvider.state).state !=
MajesticBankExchange.exchangeName;
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
@ -217,8 +222,9 @@ class _Step2ViewState extends ConsumerState<Step2View> {
setState(() {
enableNext =
_toController.text.isNotEmpty &&
_refundController
.text.isNotEmpty;
(_refundController
.text.isNotEmpty ||
!supportsRefund);
});
}
});
@ -291,8 +297,9 @@ class _Step2ViewState extends ConsumerState<Step2View> {
setState(() {
enableNext = _toController
.text.isNotEmpty &&
_refundController
.text.isNotEmpty;
(_refundController.text
.isNotEmpty ||
!supportsRefund);
});
},
child: const XIcon(),
@ -318,8 +325,10 @@ class _Step2ViewState extends ConsumerState<Step2View> {
enableNext = _toController
.text
.isNotEmpty &&
_refundController
.text.isNotEmpty;
(_refundController
.text
.isNotEmpty ||
!supportsRefund);
});
}
},
@ -367,8 +376,9 @@ class _Step2ViewState extends ConsumerState<Step2View> {
setState(() {
enableNext = _toController
.text.isNotEmpty &&
_refundController
.text.isNotEmpty;
(_refundController.text
.isNotEmpty ||
!supportsRefund);
});
});
},
@ -396,8 +406,9 @@ class _Step2ViewState extends ConsumerState<Step2View> {
setState(() {
enableNext = _toController
.text.isNotEmpty &&
_refundController
.text.isNotEmpty;
(_refundController.text
.isNotEmpty ||
!supportsRefund);
});
} else {
_toController.text =
@ -408,8 +419,9 @@ class _Step2ViewState extends ConsumerState<Step2View> {
setState(() {
enableNext = _toController
.text.isNotEmpty &&
_refundController
.text.isNotEmpty;
(_refundController.text
.isNotEmpty ||
!supportsRefund);
});
}
} on PlatformException catch (e, s) {
@ -440,133 +452,230 @@ class _Step2ViewState extends ConsumerState<Step2View> {
const SizedBox(
height: 24,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Refund Wallet (required)",
style: STextStyles.smallMed12(context),
),
if (isStackCoin(model.sendTicker))
CustomTextButton(
text: "Choose from Stack",
onTap: () {
try {
final coin =
coinFromTickerCaseInsensitive(
model.sendTicker,
);
Navigator.of(context)
.pushNamed(
ChooseFromStackView.routeName,
arguments: coin,
)
.then((value) async {
if (value is String) {
final manager = ref
.read(
walletsChangeNotifierProvider)
.getManager(value);
if (supportsRefund)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Refund Wallet (required)",
style: STextStyles.smallMed12(context),
),
if (isStackCoin(model.sendTicker))
CustomTextButton(
text: "Choose from Stack",
onTap: () {
try {
final coin =
coinFromTickerCaseInsensitive(
model.sendTicker,
);
Navigator.of(context)
.pushNamed(
ChooseFromStackView.routeName,
arguments: coin,
)
.then((value) async {
if (value is String) {
final manager = ref
.read(
walletsChangeNotifierProvider)
.getManager(value);
_refundController.text =
manager.walletName;
model.refundAddress = await manager
.currentReceivingAddress;
}
setState(() {
enableNext = _toController
.text.isNotEmpty &&
_refundController.text.isNotEmpty;
_refundController.text =
manager.walletName;
model.refundAddress = await manager
.currentReceivingAddress;
}
setState(() {
enableNext =
_toController.text.isNotEmpty &&
_refundController
.text.isNotEmpty;
});
});
});
} catch (e, s) {
Logging.instance
.log("$e\n$s", level: LogLevel.Info);
}
},
),
],
),
const SizedBox(
height: 4,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
} catch (e, s) {
Logging.instance.log("$e\n$s",
level: LogLevel.Info);
}
},
),
],
),
child: TextField(
key: const Key(
"refundExchangeStep2ViewAddressFieldKey"),
controller: _refundController,
readOnly: false,
autocorrect: false,
enableSuggestions: false,
// inputFormatters: <TextInputFormatter>[
// FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")),
// ],
toolbarOptions: const ToolbarOptions(
copy: false,
cut: false,
paste: true,
selectAll: false,
if (supportsRefund)
const SizedBox(
height: 4,
),
if (supportsRefund)
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
focusNode: _refundFocusNode,
style: STextStyles.field(context),
onChanged: (value) {
setState(() {});
},
decoration: standardInputDecoration(
"Enter ${model.sendTicker.toUpperCase()} refund address",
_refundFocusNode,
context,
).copyWith(
contentPadding: const EdgeInsets.only(
left: 16,
top: 6,
bottom: 8,
right: 5,
child: TextField(
key: const Key(
"refundExchangeStep2ViewAddressFieldKey"),
controller: _refundController,
readOnly: false,
autocorrect: false,
enableSuggestions: false,
// inputFormatters: <TextInputFormatter>[
// FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")),
// ],
toolbarOptions: const ToolbarOptions(
copy: false,
cut: false,
paste: true,
selectAll: false,
),
suffixIcon: Padding(
padding: _refundController.text.isEmpty
? const EdgeInsets.only(right: 16)
: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
_refundController.text.isNotEmpty
? TextFieldIconButton(
key: const Key(
"sendViewClearAddressFieldButtonKey"),
onTap: () {
_refundController.text = "";
model.refundAddress =
_refundController.text;
focusNode: _refundFocusNode,
style: STextStyles.field(context),
onChanged: (value) {
setState(() {});
},
decoration: standardInputDecoration(
"Enter ${model.sendTicker.toUpperCase()} refund address",
_refundFocusNode,
context,
).copyWith(
contentPadding: const EdgeInsets.only(
left: 16,
top: 6,
bottom: 8,
right: 5,
),
suffixIcon: Padding(
padding: _refundController.text.isEmpty
? const EdgeInsets.only(right: 16)
: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
_refundController.text.isNotEmpty
? TextFieldIconButton(
key: const Key(
"sendViewClearAddressFieldButtonKey"),
onTap: () {
_refundController.text = "";
model.refundAddress =
_refundController.text;
setState(() {
enableNext = _toController
.text
.isNotEmpty &&
_refundController
.text.isNotEmpty;
});
},
child: const XIcon(),
)
: TextFieldIconButton(
key: const Key(
"sendViewPasteAddressFieldButtonKey"),
onTap: () async {
final ClipboardData? data =
await clipboard.getData(
Clipboard
.kTextPlain);
if (data?.text != null &&
data!
.text!.isNotEmpty) {
final content =
data.text!.trim();
_refundController.text =
content;
model.refundAddress =
_refundController
.text;
setState(() {
enableNext = _toController
.text
.isNotEmpty &&
_refundController
.text
.isNotEmpty;
});
}
},
child: _refundController
.text.isEmpty
? const ClipboardIcon()
: const XIcon(),
),
if (_refundController.text.isEmpty)
TextFieldIconButton(
key: const Key(
"sendViewAddressBookButtonKey"),
onTap: () {
ref
.read(
exchangeFlowIsActiveStateProvider
.state)
.state = true;
Navigator.of(context)
.pushNamed(
AddressBookView.routeName,
)
.then((_) {
ref
.read(
exchangeFlowIsActiveStateProvider
.state)
.state = false;
final address = ref
.read(
exchangeFromAddressBookAddressStateProvider
.state)
.state;
if (address.isNotEmpty) {
_refundController.text =
address;
model.refundAddress =
_refundController.text;
}
setState(() {
enableNext = _toController
.text.isNotEmpty &&
_refundController
.text.isNotEmpty;
});
},
child: const XIcon(),
)
: TextFieldIconButton(
key: const Key(
"sendViewPasteAddressFieldButtonKey"),
onTap: () async {
final ClipboardData? data =
await clipboard.getData(
Clipboard.kTextPlain);
if (data?.text != null &&
data!.text!.isNotEmpty) {
final content =
data.text!.trim();
});
},
child: const AddressBookIcon(),
),
if (_refundController.text.isEmpty)
TextFieldIconButton(
key: const Key(
"sendViewScanQrButtonKey"),
onTap: () async {
try {
final qrResult =
await scanner.scan();
final results =
AddressUtils.parseUri(
qrResult.rawContent);
if (results.isNotEmpty) {
// auto fill address
_refundController.text =
content;
results["address"] ??
"";
model.refundAddress =
_refundController.text;
setState(() {
enableNext = _toController
.text
.isNotEmpty &&
_refundController
.text.isNotEmpty;
});
} else {
_refundController.text =
qrResult.rawContent;
model.refundAddress =
_refundController.text;
@ -578,116 +687,33 @@ class _Step2ViewState extends ConsumerState<Step2View> {
.text.isNotEmpty;
});
}
},
child: _refundController
.text.isEmpty
? const ClipboardIcon()
: const XIcon(),
),
if (_refundController.text.isEmpty)
TextFieldIconButton(
key: const Key(
"sendViewAddressBookButtonKey"),
onTap: () {
ref
.read(
exchangeFlowIsActiveStateProvider
.state)
.state = true;
Navigator.of(context)
.pushNamed(
AddressBookView.routeName,
)
.then((_) {
ref
.read(
exchangeFlowIsActiveStateProvider
.state)
.state = false;
final address = ref
.read(
exchangeFromAddressBookAddressStateProvider
.state)
.state;
if (address.isNotEmpty) {
_refundController.text =
address;
model.refundAddress =
_refundController.text;
} on PlatformException catch (e, s) {
Logging.instance.log(
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
level: LogLevel.Warning,
);
}
setState(() {
enableNext = _toController
.text.isNotEmpty &&
_refundController
.text.isNotEmpty;
});
});
},
child: const AddressBookIcon(),
),
if (_refundController.text.isEmpty)
TextFieldIconButton(
key: const Key(
"sendViewScanQrButtonKey"),
onTap: () async {
try {
final qrResult =
await scanner.scan();
final results =
AddressUtils.parseUri(
qrResult.rawContent);
if (results.isNotEmpty) {
// auto fill address
_refundController.text =
results["address"] ?? "";
model.refundAddress =
_refundController.text;
setState(() {
enableNext = _toController
.text.isNotEmpty &&
_refundController
.text.isNotEmpty;
});
} else {
_refundController.text =
qrResult.rawContent;
model.refundAddress =
_refundController.text;
setState(() {
enableNext = _toController
.text.isNotEmpty &&
_refundController
.text.isNotEmpty;
});
}
} on PlatformException catch (e, s) {
Logging.instance.log(
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
level: LogLevel.Warning,
);
}
},
child: const QrCodeIcon(),
),
],
},
child: const QrCodeIcon(),
),
],
),
),
),
),
),
),
),
const SizedBox(
height: 6,
),
RoundedWhiteContainer(
child: Text(
"In case something goes wrong during the exchange, we might need a refund address so we can return your coins back to you.",
style: STextStyles.label(context),
if (supportsRefund)
const SizedBox(
height: 6,
),
if (supportsRefund)
RoundedWhiteContainer(
child: Text(
"In case something goes wrong during the exchange, we might need a refund address so we can return your coins back to you.",
style: STextStyles.label(context),
),
),
),
const Spacer(),
Row(
children: [

View file

@ -7,9 +7,11 @@ import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_4_view.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart';
import 'package:stackwallet/providers/exchange/current_exchange_name_state_provider.dart';
import 'package:stackwallet/providers/exchange/exchange_provider.dart';
import 'package:stackwallet/providers/global/trades_service_provider.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
@ -51,6 +53,10 @@ class _Step3ViewState extends ConsumerState<Step3View> {
@override
Widget build(BuildContext context) {
final supportsRefund =
ref.watch(currentExchangeNameStateProvider.state).state !=
MajesticBankExchange.exchangeName;
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
@ -174,27 +180,29 @@ class _Step3ViewState extends ConsumerState<Step3View> {
],
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Refund ${model.sendTicker.toUpperCase()} address",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 4,
),
Text(
model.refundAddress!,
style: STextStyles.itemSubtitle12(context),
)
],
if (supportsRefund)
const SizedBox(
height: 8,
),
if (supportsRefund)
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Refund ${model.sendTicker.toUpperCase()} address",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 4,
),
Text(
model.refundAddress!,
style: STextStyles.itemSubtitle12(context),
)
],
),
),
),
const SizedBox(
height: 8,
),
@ -259,8 +267,9 @@ class _Step3ViewState extends ConsumerState<Step3View> {
addressTo:
model.recipientAddress!,
extraId: null,
addressRefund:
model.refundAddress!,
addressRefund: supportsRefund
? model.refundAddress!
: "",
refundExtraId: "",
rateId: model.rateId,
reversed: model.reversed,

View file

@ -2,11 +2,12 @@ import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -59,10 +60,11 @@ class ExchangeProviderOptions extends ConsumerWidget {
ChangeNowExchange.exchangeName) {
ref.read(currentExchangeNameStateProvider.state).state =
ChangeNowExchange.exchangeName;
ref.read(exchangeFormStateProvider).exchange =
Exchange.fromName(ref
.read(currentExchangeNameStateProvider.state)
.state);
ref.read(exchangeFormStateProvider).updateExchange(
exchange: ref.read(exchangeProvider),
shouldUpdateData: true,
shouldNotifyListeners: true,
);
}
},
child: Container(
@ -88,18 +90,22 @@ class ExchangeProviderOptions extends ConsumerWidget {
groupValue: ref
.watch(currentExchangeNameStateProvider.state)
.state,
onChanged: (value) {
if (value is String) {
ref
.read(
currentExchangeNameStateProvider.state)
.state = value;
ref.read(exchangeFormStateProvider).exchange =
Exchange.fromName(ref
.read(currentExchangeNameStateProvider
.state)
.state);
}
onChanged: (_) {
// if (value is String) {
// ref
// .read(
// currentExchangeNameStateProvider.state)
// .state = value;
// ref
// .read(exchangeFormStateProvider(ref
// .read(prefsChangeNotifierProvider)
// .exchangeRateType))
// .exchange =
// Exchange.fromName(ref
// .read(currentExchangeNameStateProvider
// .state)
// .state);
// }
},
),
),
@ -109,10 +115,14 @@ class ExchangeProviderOptions extends ConsumerWidget {
),
Padding(
padding: const EdgeInsets.only(top: 5.0),
child: SvgPicture.asset(
Assets.exchange.changeNow,
child: SizedBox(
width: isDesktop ? 32 : 24,
height: isDesktop ? 32 : 24,
child: SvgPicture.asset(
Assets.exchange.changeNow,
width: isDesktop ? 32 : 24,
height: isDesktop ? 32 : 24,
),
),
),
const SizedBox(
@ -139,7 +149,7 @@ class ExchangeProviderOptions extends ConsumerWidget {
fromAmount != null &&
fromAmount! > Decimal.zero)
FutureBuilder(
future: ChangeNowExchange().getEstimate(
future: ChangeNowExchange.instance.getEstimate(
from!,
to!,
reversed ? toAmount! : fromAmount!,
@ -194,6 +204,254 @@ class ExchangeProviderOptions extends ConsumerWidget {
.textSubtitle1,
),
);
} else if (snapshot.data?.exception
is PairUnavailableException) {
return Text(
"Unsupported pair",
style:
STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
} else {
Logging.instance.log(
"$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}",
level: LogLevel.Warning,
);
return Text(
"Failed to fetch rate",
style:
STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
}
} else {
return AnimatedText(
stringsToLoopThrough: const [
"Loading",
"Loading.",
"Loading..",
"Loading...",
],
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
}
},
),
if (!(from != null &&
to != null &&
toAmount != null &&
toAmount! > Decimal.zero &&
fromAmount != null &&
fromAmount! > Decimal.zero))
Text(
"n/a",
style: STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
),
),
),
),
if (isDesktop)
Container(
height: 1,
color: Theme.of(context).extension<StackColors>()!.background,
),
if (!isDesktop)
const SizedBox(
height: 16,
),
ConditionalParent(
condition: isDesktop,
builder: (child) => MouseRegion(
cursor: SystemMouseCursors.click,
child: child,
),
child: GestureDetector(
onTap: () {
if (ref.read(currentExchangeNameStateProvider.state).state !=
MajesticBankExchange.exchangeName) {
ref.read(currentExchangeNameStateProvider.state).state =
MajesticBankExchange.exchangeName;
ref.read(exchangeFormStateProvider).updateExchange(
exchange: ref.read(exchangeProvider),
shouldUpdateData: true,
shouldNotifyListeners: true,
);
}
},
child: Container(
color: Colors.transparent,
child: Padding(
padding: isDesktop
? const EdgeInsets.all(16)
: const EdgeInsets.all(0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 20,
height: 20,
child: Padding(
padding:
EdgeInsets.only(top: isDesktop ? 20.0 : 15.0),
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: MajesticBankExchange.exchangeName,
groupValue: ref
.watch(currentExchangeNameStateProvider.state)
.state,
onChanged: (_) {
// if (value is String) {
// ref
// .read(
// currentExchangeNameStateProvider.state)
// .state = value;
// ref
// .read(exchangeFormStateProvider(ref
// .read(prefsChangeNotifierProvider)
// .exchangeRateType))
// .exchange =
// Exchange.fromName(ref
// .read(currentExchangeNameStateProvider
// .state)
// .state);
// }
},
),
),
),
const SizedBox(
width: 14,
),
Padding(
padding: const EdgeInsets.only(top: 5.0),
child: SizedBox(
width: isDesktop ? 32 : 24,
height: isDesktop ? 32 : 24,
child: SvgPicture.asset(
Assets.exchange.majesticBankBlue,
width: isDesktop ? 32 : 24,
height: isDesktop ? 32 : 24,
),
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
MajesticBankExchange.exchangeName,
style: STextStyles.titleBold12(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark2,
),
),
if (from != null &&
to != null &&
toAmount != null &&
toAmount! > Decimal.zero &&
fromAmount != null &&
fromAmount! > Decimal.zero)
FutureBuilder(
future:
MajesticBankExchange.instance.getEstimate(
from!,
to!,
reversed ? toAmount! : fromAmount!,
fixedRate,
reversed,
),
builder: (context,
AsyncSnapshot<ExchangeResponse<Estimate>>
snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.hasData) {
final estimate = snapshot.data?.value;
if (estimate != null) {
Decimal rate;
if (estimate.reversed) {
rate = (toAmount! /
estimate.estimatedAmount)
.toDecimal(
scaleOnInfinitePrecision: 12);
} else {
rate = (estimate.estimatedAmount /
fromAmount!)
.toDecimal(
scaleOnInfinitePrecision: 12);
}
Coin coin;
try {
coin =
coinFromTickerCaseInsensitive(to!);
} catch (_) {
coin = Coin.bitcoin;
}
return Text(
"1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed(
value: rate,
locale: ref.watch(
localeServiceChangeNotifierProvider
.select(
(value) => value.locale),
),
decimalPlaces:
Constants.decimalPlacesForCoin(
coin),
)} ${to!.toUpperCase()}",
style:
STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
} else if (snapshot.data?.exception
is PairUnavailableException) {
return Text(
"Unsupported pair",
style:
STextStyles.itemSubtitle12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
);
} else {
Logging.instance.log(
"$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}",

View file

@ -21,21 +21,21 @@ class RateTypeToggle extends ConsumerWidget {
debugPrint("BUILD: $runtimeType");
final isDesktop = Util.isDesktop;
final estimated = ref.watch(prefsChangeNotifierProvider
.select((value) => value.exchangeRateType)) ==
ExchangeRateType.estimated;
return Toggle(
onValueChanged: (value) {
if (!estimated) {
if (value) {
ref.read(prefsChangeNotifierProvider).exchangeRateType =
ExchangeRateType.fixed;
onChanged?.call(ExchangeRateType.fixed);
} else {
ref.read(prefsChangeNotifierProvider).exchangeRateType =
ExchangeRateType.estimated;
onChanged?.call(ExchangeRateType.estimated);
} else {
onChanged?.call(ExchangeRateType.fixed);
}
},
isOn: !estimated,
isOn: ref.watch(prefsChangeNotifierProvider
.select((value) => value.exchangeRateType)) ==
ExchangeRateType.fixed,
onColor: isDesktop
? Theme.of(context)
.extension<StackColors>()!
@ -56,178 +56,5 @@ class RateTypeToggle extends ConsumerWidget {
offIcon: Assets.svg.lock,
offText: "Fixed rate",
);
//
// return RoundedContainer(
// padding: const EdgeInsets.all(0),
// color: isDesktop
// ? Theme.of(context).extension<StackColors>()!.buttonBackSecondary
// : Theme.of(context).extension<StackColors>()!.popupBG,
// child: Row(
// children: [
// Expanded(
// child: ConditionalParent(
// condition: isDesktop,
// builder: (child) => MouseRegion(
// cursor: estimated
// ? SystemMouseCursors.basic
// : SystemMouseCursors.click,
// child: child,
// ),
// child: GestureDetector(
// onTap: () {
// if (!estimated) {
// ref.read(prefsChangeNotifierProvider).exchangeRateType =
// ExchangeRateType.estimated;
// onChanged?.call(ExchangeRateType.estimated);
// }
// },
// child: RoundedContainer(
// padding: isDesktop
// ? const EdgeInsets.all(17)
// : const EdgeInsets.all(12),
// color: estimated
// ? Theme.of(context)
// .extension<StackColors>()!
// .textFieldDefaultBG
// : Colors.transparent,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// SvgPicture.asset(
// Assets.svg.lockOpen,
// width: 12,
// height: 14,
// color: isDesktop
// ? estimated
// ? Theme.of(context)
// .extension<StackColors>()!
// .accentColorBlue
// : Theme.of(context)
// .extension<StackColors>()!
// .buttonTextSecondary
// : estimated
// ? Theme.of(context)
// .extension<StackColors>()!
// .textDark
// : Theme.of(context)
// .extension<StackColors>()!
// .textSubtitle1,
// ),
// const SizedBox(
// width: 5,
// ),
// Text(
// "Estimate rate",
// style: isDesktop
// ? STextStyles.desktopTextExtraExtraSmall(context)
// .copyWith(
// color: estimated
// ? Theme.of(context)
// .extension<StackColors>()!
// .accentColorBlue
// : Theme.of(context)
// .extension<StackColors>()!
// .buttonTextSecondary,
// )
// : STextStyles.smallMed12(context).copyWith(
// color: estimated
// ? Theme.of(context)
// .extension<StackColors>()!
// .textDark
// : Theme.of(context)
// .extension<StackColors>()!
// .textSubtitle1,
// ),
// ),
// ],
// ),
// ),
// ),
// ),
// ),
// Expanded(
// child: ConditionalParent(
// condition: isDesktop,
// builder: (child) => MouseRegion(
// cursor: !estimated
// ? SystemMouseCursors.basic
// : SystemMouseCursors.click,
// child: child,
// ),
// child: GestureDetector(
// onTap: () {
// if (estimated) {
// ref.read(prefsChangeNotifierProvider).exchangeRateType =
// ExchangeRateType.fixed;
// onChanged?.call(ExchangeRateType.fixed);
// }
// },
// child: RoundedContainer(
// padding: isDesktop
// ? const EdgeInsets.all(17)
// : const EdgeInsets.all(12),
// color: !estimated
// ? Theme.of(context)
// .extension<StackColors>()!
// .textFieldDefaultBG
// : Colors.transparent,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// SvgPicture.asset(
// Assets.svg.lock,
// width: 12,
// height: 14,
// color: isDesktop
// ? !estimated
// ? Theme.of(context)
// .extension<StackColors>()!
// .accentColorBlue
// : Theme.of(context)
// .extension<StackColors>()!
// .buttonTextSecondary
// : !estimated
// ? Theme.of(context)
// .extension<StackColors>()!
// .textDark
// : Theme.of(context)
// .extension<StackColors>()!
// .textSubtitle1,
// ),
// const SizedBox(
// width: 5,
// ),
// Text(
// "Fixed rate",
// style: isDesktop
// ? STextStyles.desktopTextExtraExtraSmall(context)
// .copyWith(
// color: !estimated
// ? Theme.of(context)
// .extension<StackColors>()!
// .accentColorBlue
// : Theme.of(context)
// .extension<StackColors>()!
// .buttonTextSecondary,
// )
// : STextStyles.smallMed12(context).copyWith(
// color: !estimated
// ? Theme.of(context)
// .extension<StackColors>()!
// .textDark
// : Theme.of(context)
// .extension<StackColors>()!
// .textSubtitle1,
// ),
// ),
// ],
// ),
// ),
// ),
// ),
// ),
// ],
// ),
// );
}
}

View file

@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/buy_view/buy_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_loading_overlay.dart';
import 'package:stackwallet/pages/exchange_view/exchange_view.dart';
import 'package:stackwallet/pages/home_view/sub_widgets/home_view_button_bar.dart';
import 'package:stackwallet/pages/notification_views/notifications_view.dart';
@ -12,13 +11,10 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/global_set
import 'package:stackwallet/pages/settings_views/global_settings_view/hidden_settings.dart';
import 'package:stackwallet/pages/wallets_view/wallets_view.dart';
import 'package:stackwallet/providers/global/notifications_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/providers/ui/home_view_index_provider.dart';
import 'package:stackwallet/providers/ui/unread_notifications_provider.dart';
import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/background.dart';
@ -45,7 +41,6 @@ class _HomeViewState extends ConsumerState<HomeView> {
bool _exitEnabled = false;
final _exchangeDataLoadingService = ExchangeDataLoadingService();
// final _buyDataLoadingService = BuyDataLoadingService();
Future<bool> _onWillPop() async {
@ -84,16 +79,6 @@ class _HomeViewState extends ConsumerState<HomeView> {
return _exitEnabled;
}
void _loadCNData() {
// unawaited future
if (ref.read(prefsChangeNotifierProvider).externalCalls) {
_exchangeDataLoadingService.loadAll(ref);
} else {
Logging.instance.log("User does not want to use external calls",
level: LogLevel.Info);
}
}
// void _loadSimplexData() {
// // unawaited future
// if (ref.read(prefsChangeNotifierProvider).externalCalls) {
@ -123,9 +108,9 @@ class _HomeViewState extends ConsumerState<HomeView> {
Stack(
children: [
const ExchangeView(),
ExchangeLoadingOverlayView(
unawaitedLoad: _loadCNData,
),
// ExchangeLoadingOverlayView(
// unawaitedLoad: _loadCNData,
// ),
],
),
if (Constants.enableBuy)
@ -331,9 +316,9 @@ class _HomeViewState extends ConsumerState<HomeView> {
_ref.listen(homeViewPageIndexStateProvider,
(previous, next) {
if (next is int && next >= 0 && next <= 2) {
if (next == 1) {
_exchangeDataLoadingService.loadAll(ref);
}
// if (next == 1) {
// _exchangeDataLoadingService.loadAll(ref);
// }
// if (next == 2) {
// _buyDataLoadingService.loadAll(ref);
// }

View file

@ -1,10 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
class HomeViewButtonBar extends ConsumerStatefulWidget {
const HomeViewButtonBar({Key? key}) : super(key: key);
@ -19,16 +17,16 @@ class _HomeViewButtonBarState extends ConsumerState<HomeViewButtonBar> {
@override
void initState() {
ref.read(exchangeFormStateProvider).setOnError(
onError: (String message) => showDialog<dynamic>(
context: context,
barrierDismissible: true,
builder: (_) => StackDialog(
title: "Exchange API Call Failed",
message: message,
),
),
);
// ref.read(exchangeFormStateProvider).setOnError(
// onError: (String message) => showDialog<dynamic>(
// context: context,
// barrierDismissible: true,
// builder: (_) => StackDialog(
// title: "Exchange API Call Failed",
// message: message,
// ),
// ),
// );
super.initState();
}
@ -106,7 +104,7 @@ class _HomeViewButtonBarState extends ConsumerState<HomeViewButtonBar> {
// DateTime now = DateTime.now();
// if (ref.read(prefsChangeNotifierProvider).externalCalls) {
// print("loading?");
await ExchangeDataLoadingService().loadAll(ref);
// await ExchangeDataLoadingService().loadAll(ref);
// }
// if (now.difference(_lastRefreshed) > _refreshInterval) {
// await ExchangeDataLoadingService().loadAll(ref);

View file

@ -5,14 +5,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/providers/global/debug_service_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_api.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import '../../../services/exchange/majestic_bank/majestic_bank_api.dart';
class HiddenSettings extends StatelessWidget {
const HiddenSettings({Key? key}) : super(key: key);
@ -157,8 +156,8 @@ class HiddenSettings extends StatelessWidget {
Consumer(builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
final x =
await MajesticBankAPI.instance.getLimits();
final x = await MajesticBankAPI.instance
.getLimit(fromCurrency: 'btc');
print(x);
},
child: RoundedWhiteContainer(

View file

@ -230,8 +230,13 @@ class _StackPrivacyCalls extends ConsumerState<StackPrivacyCalls> {
value: isEasy)
.then((_) {
if (isEasy) {
unawaited(ExchangeDataLoadingService()
.loadAll(ref));
unawaited(
ExchangeDataLoadingService.instance
.init()
.then((_) => ExchangeDataLoadingService
.instance
.loadAll()),
);
// unawaited(
// BuyDataLoadingService().loadAll(ref));
ref

View file

@ -7,7 +7,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages/notification_views/notifications_view.dart';
@ -30,14 +29,12 @@ import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/background.dart';
@ -80,8 +77,6 @@ class _WalletViewState extends ConsumerState<WalletView> {
late StreamSubscription<dynamic> _syncStatusSubscription;
late StreamSubscription<dynamic> _nodeStatusSubscription;
final _cnLoadingService = ExchangeDataLoadingService();
@override
void initState() {
walletId = widget.walletId;
@ -231,7 +226,8 @@ class _WalletViewState extends ConsumerState<WalletView> {
}
void _onExchangePressed(BuildContext context) async {
unawaited(_cnLoadingService.loadAll(ref));
// too expensive
// unawaited(ExchangeDataLoadingService.instance.loadAll(ref));
final coin = ref.read(managerProvider).coin;
@ -250,35 +246,33 @@ class _WalletViewState extends ConsumerState<WalletView> {
),
);
} else {
ref.read(currentExchangeNameStateProvider.state).state =
ChangeNowExchange.exchangeName;
final walletId = ref.read(managerProvider).walletId;
ref.read(prefsChangeNotifierProvider).exchangeRateType =
ExchangeRateType.estimated;
ref.read(exchangeFormStateProvider).exchange = ref.read(exchangeProvider);
ref.read(exchangeFormStateProvider).exchangeType =
ExchangeRateType.estimated;
final currencies = ref
.read(availableChangeNowCurrenciesProvider)
.currencies
.where((element) =>
element.ticker.toLowerCase() == coin.ticker.toLowerCase());
if (currencies.isNotEmpty) {
ref.read(exchangeFormStateProvider).setCurrencies(
currencies.first,
ref
.read(availableChangeNowCurrenciesProvider)
.currencies
.firstWhere(
(element) =>
element.ticker.toLowerCase() !=
coin.ticker.toLowerCase(),
),
);
}
// ref.read(currentExchangeNameStateProvider.state).state =
// ChangeNowExchange.exchangeName;
// final walletId = ref.read(managerProvider).walletId;
// ref.read(prefsChangeNotifierProvider).exchangeRateType =
// ExchangeRateType.estimated;
//
// final currencies = ref
// .read(availableChangeNowCurrenciesProvider)
// .currencies
// .where((element) =>
// element.ticker.toLowerCase() == coin.ticker.toLowerCase());
//
// if (currencies.isNotEmpty) {
// ref
// .read(exchangeFormStateProvider(ExchangeRateType.estimated))
// .setCurrencies(
// currencies.first,
// ref
// .read(availableChangeNowCurrenciesProvider)
// .currencies
// .firstWhere(
// (element) =>
// element.ticker.toLowerCase() !=
// coin.ticker.toLowerCase(),
// ),
// );
// }
if (mounted) {
unawaited(
@ -311,7 +305,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
);
final firoWallet = ref.read(managerProvider).wallet as FiroWallet;
final publicBalance = await firoWallet.availablePublicBalance();
final publicBalance = firoWallet.availablePublicBalance();
if (publicBalance <= Decimal.zero) {
shouldPop = true;
if (mounted) {
@ -363,12 +357,13 @@ class _WalletViewState extends ConsumerState<WalletView> {
void _loadCNData() {
// unawaited future
if (ref.read(prefsChangeNotifierProvider).externalCalls) {
_cnLoadingService.loadAll(ref, coin: ref.read(managerProvider).coin);
} else {
Logging.instance.log("User does not want to use external calls",
level: LogLevel.Info);
}
// if (ref.read(prefsChangeNotifierProvider).externalCalls) {
ExchangeDataLoadingService.instance.loadAll();
// .loadAll(ref, coin: ref.read(managerProvider).coin);
// } else {
// Logging.instance.log("User does not want to use external calls",
// level: LogLevel.Info);
// }
}
@override

View file

@ -169,7 +169,9 @@ class _StackPrivacyDialog extends ConsumerState<StackPrivacyDialog> {
value: isEasy)
.then((_) {
if (isEasy) {
unawaited(ExchangeDataLoadingService().loadAll(ref));
unawaited(
ExchangeDataLoadingService.instance.loadAll(),
);
ref
.read(priceAnd24hChangeNotifierProvider)
.start(true);

View file

@ -1,6 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/exchange/change_now/cn_available_currencies.dart';
final availableChangeNowCurrenciesProvider = Provider<CNAvailableCurrencies>(
(ref) => CNAvailableCurrencies(),
);

View file

@ -1,6 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/exchange/simpleswap/sp_available_currencies.dart';
final availableSimpleswapCurrenciesProvider = Provider<SPAvailableCurrencies>(
(ref) => SPAvailableCurrencies(),
);

View file

@ -1,6 +1,6 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange.dart';
final currentExchangeNameStateProvider = StateProvider<String>(
(ref) => ChangeNowExchange.exchangeName,
(ref) => Exchange.defaultExchange.name,
);

View file

@ -1,6 +1,5 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/exchange/exchange_form_state.dart';
final exchangeFormStateProvider = ChangeNotifierProvider<ExchangeFormState>(
(ref) => ExchangeFormState(),
);
final exchangeFormStateProvider =
ChangeNotifierProvider<ExchangeFormState>((ref) => ExchangeFormState());

View file

@ -1,8 +1,6 @@
export './buy/buy_form_state_provider.dart';
export './buy/simplex_initial_load_status.dart';
export './buy/simplex_provider.dart';
export './exchange/available_changenow_currencies_provider.dart';
export './exchange/available_simpleswap_currencies_provider.dart';
export './exchange/changenow_initial_load_status.dart';
export './exchange/current_exchange_name_state_provider.dart';
export './exchange/exchange_flow_is_active_state_provider.dart';

View file

@ -29,7 +29,6 @@ import 'package:stackwallet/pages/buy_view/buy_quote_preview.dart';
import 'package:stackwallet/pages/buy_view/buy_view.dart';
import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart';
import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_loading_overlay.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_1_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_3_view.dart';
@ -907,7 +906,7 @@ class RouteGenerator {
return _routeError("${settings.name} invalid args: ${args.toString()}");
case WalletInitiatedExchangeView.routeName:
if (args is Tuple3<String, Coin, VoidCallback>) {
if (args is Tuple2<String, Coin>) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => Stack(
@ -916,9 +915,9 @@ class RouteGenerator {
walletId: args.item1,
coin: args.item2,
),
ExchangeLoadingOverlayView(
unawaitedLoad: args.item3,
),
// ExchangeLoadingOverlayView(
// unawaitedLoad: args.item3,
// ),
],
),
settings: RouteSettings(

View file

@ -0,0 +1,389 @@
// import 'package:decimal/decimal.dart';
// import 'package:flutter/foundation.dart';
// import 'package:stackwallet/models/exchange/response_objects/currency.dart';
// import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
// import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
// import 'package:stackwallet/services/exchange/exchange.dart';
// import 'package:stackwallet/utilities/logger.dart';
//
// class ExchangeFormState extends ChangeNotifier {
// ExchangeFormState(this.exchangeRateType);
// final ExchangeRateType exchangeRateType;
//
// Exchange? _exchange;
// Exchange get exchange =>
// _exchange ??= ChangeNowExchange(); // default to change now
// set exchange(Exchange value) {
// _exchange = value;
// _updateRangesAndEstimate(
// shouldNotifyListeners: true,
// );
// }
//
// 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;
// // //
// // }
//
// Currency? _sendCurrency;
// Currency? get sendCurrency => _sendCurrency;
// // set sendCurrency(Currency? sendCurrency) {
// // _sendCurrency = sendCurrency;
// // //
// // }
//
// Currency? _receiveCurrency;
// Currency? 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 warning {
// if (reversed) {
// if (_receiveCurrency != null && _receiveAmount != null) {
// if (_minReceiveAmount != null &&
// _receiveAmount! < _minReceiveAmount! &&
// _receiveAmount! > Decimal.zero) {
// return "Minimum amount ${_minReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}";
// } else if (_maxReceiveAmount != null &&
// _receiveAmount! > _maxReceiveAmount!) {
// return "Maximum amount ${_maxReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}";
// }
// }
// } else {
// if (_sendCurrency != null && _sendAmount != null) {
// if (_minSendAmount != null &&
// _sendAmount! < _minSendAmount! &&
// _sendAmount! > Decimal.zero) {
// return "Minimum amount ${_minSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}";
// } else if (_maxSendAmount != null && _sendAmount! > _maxSendAmount!) {
// return "Maximum amount ${_maxSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}";
// }
// }
// }
//
// return "";
// }
//
// //============================================================================
// // public state updaters
// //============================================================================
//
// void reset(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) {
// notifyListeners();
// }
// }
//
// Future<void> setFromAmountAndCalculateToAmount(
// 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) {
// notifyListeners();
// }
// }
//
// Future<void> setToAmountAndCalculateFromAmount(
// 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) {
// notifyListeners();
// }
// }
//
// Future<void> updateFrom(
// Currency sendCurrency,
// bool shouldNotifyListeners,
// ) async {
// try {
// _sendCurrency = sendCurrency;
// if (_receiveCurrency == null) {
// _rate = null;
// } else {
// await _updateRangesAndEstimate(
// shouldNotifyListeners: false,
// );
// }
// } catch (e, s) {
// Logging.instance.log("$e\n$s", level: LogLevel.Error);
// }
// if (shouldNotifyListeners) {
// notifyListeners();
// }
// }
//
// Future<void> updateTo(
// Currency receiveCurrency,
// bool shouldNotifyListeners,
// ) async {
// try {
// _receiveCurrency = receiveCurrency;
//
// if (_sendCurrency == null) {
// _rate = null;
// } else {
// await _updateRangesAndEstimate(
// shouldNotifyListeners: false,
// );
// }
// } catch (e, s) {
// Logging.instance.log("$e\n$s", level: LogLevel.Error);
// }
// if (shouldNotifyListeners) {
// notifyListeners();
// }
// }
//
// Future<void> swap(
// {required bool shouldNotifyListeners,}) async {
// final Decimal? temp = sendAmount;
// _sendAmount = receiveAmount;
// _receiveAmount = temp;
//
// _minSendAmount = null;
// _maxSendAmount = null;
// _minReceiveAmount = null;
// _maxReceiveAmount = null;
//
// final Currency? tmp = sendCurrency;
// _sendCurrency = receiveCurrency;
// _receiveCurrency = tmp;
//
// await _updateRangesAndEstimate(
// shouldNotifyListeners: false,
// );
// }
//
// //============================================================================
// // private state updaters
// //============================================================================
//
// Future<void> _updateRangesAndEstimate(
// {required bool shouldNotifyListeners,}) async {
// await _updateRanges(shouldNotifyListeners: false);
// await _updateEstimate(shouldNotifyListeners: false);
// if (shouldNotifyListeners) {
// notifyListeners();
// }
// }
//
// Future<void> _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) {
// notifyListeners();
// }
// }
//
// Future<void> _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;
// }
//
// final 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) {
// notifyListeners();
// }
// }
//
//
// }

View file

@ -4,18 +4,21 @@ import 'package:decimal/decimal.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart';
import 'package:stackwallet/external_api_keys.dart';
import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart';
import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart';
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart';
import 'package:stackwallet/models/exchange/response_objects/currency.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart';
import 'package:stackwallet/models/exchange/response_objects/pair.dart';
import 'package:stackwallet/models/exchange/response_objects/range.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:tuple/tuple.dart';
class ChangeNowAPI {
static const String scheme = "https";
@ -127,7 +130,9 @@ class ChangeNowAPI {
try {
final result = await compute(
_parseAvailableCurrenciesJson, jsonArray as List<dynamic>);
_parseAvailableCurrenciesJson,
Tuple2(jsonArray as List<dynamic>, fixedRate == true),
);
return result;
} catch (e, s) {
Logging.instance.log("getAvailableCurrencies exception: $e\n$s",
@ -152,14 +157,23 @@ class ChangeNowAPI {
}
ExchangeResponse<List<Currency>> _parseAvailableCurrenciesJson(
List<dynamic> jsonArray) {
Tuple2<List<dynamic>, bool> args,
) {
try {
List<Currency> currencies = [];
for (final json in jsonArray) {
for (final json in args.item1) {
try {
currencies
.add(Currency.fromJson(Map<String, dynamic>.from(json as Map)));
final map = Map<String, dynamic>.from(json as Map);
currencies.add(
Currency.fromJson(
map,
rateType: (map["supportsFixedRate"] as bool)
? SupportedRateType.both
: SupportedRateType.estimated,
exchangeName: ChangeNowExchange.exchangeName,
),
);
} catch (_) {
return ExchangeResponse(
exception: ExchangeException("Failed to serialize $json",
@ -199,8 +213,16 @@ class ChangeNowAPI {
try {
for (final json in jsonArray) {
try {
currencies
.add(Currency.fromJson(Map<String, dynamic>.from(json as Map)));
final map = Map<String, dynamic>.from(json as Map);
currencies.add(
Currency.fromJson(
map,
rateType: (map["supportsFixedRate"] as bool)
? SupportedRateType.both
: SupportedRateType.estimated,
exchangeName: ChangeNowExchange.exchangeName,
),
);
} catch (_) {
return ExchangeResponse(
exception: ExchangeException(
@ -329,8 +351,27 @@ class ChangeNowAPI {
final json = await _makeGetRequest(uri);
try {
final value = EstimatedExchangeAmount.fromJson(
Map<String, dynamic>.from(json as Map));
final map = Map<String, dynamic>.from(json as Map);
if (map["error"] != null) {
if (map["error"] == "pair_is_inactive") {
return ExchangeResponse(
exception: PairUnavailableException(
map["message"] as String,
ExchangeExceptionType.generic,
),
);
} else {
return ExchangeResponse(
exception: ExchangeException(
map["message"] as String,
ExchangeExceptionType.generic,
),
);
}
}
final value = EstimatedExchangeAmount.fromJson(map);
return ExchangeResponse(
value: Estimate(
estimatedAmount: value.estimatedAmount,
@ -823,12 +864,10 @@ class ChangeNowAPI {
final List<String> stringPair = (json as String).split("_");
pairs.add(
Pair(
exchangeName: ChangeNowExchange.exchangeName,
from: stringPair[0],
to: stringPair[1],
fromNetwork: "",
toNetwork: "",
fixedRate: false,
floatingRate: true,
rateType: SupportedRateType.estimated,
),
);
} catch (_) {

View file

@ -1,16 +1,21 @@
import 'package:decimal/decimal.dart';
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
import 'package:stackwallet/models/exchange/response_objects/currency.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/pair.dart';
import 'package:stackwallet/models/exchange/response_objects/range.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_api.dart';
import 'package:stackwallet/services/exchange/exchange.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:uuid/uuid.dart';
class ChangeNowExchange extends Exchange {
ChangeNowExchange._();
static ChangeNowExchange? _instance;
static ChangeNowExchange get instance => _instance ??= ChangeNowExchange._();
static const exchangeName = "ChangeNOW";
@override
@ -85,8 +90,29 @@ class ChangeNowExchange extends Exchange {
@override
Future<ExchangeResponse<List<Pair>>> getAllPairs(bool fixedRate) async {
// TODO: implement getAllPairs
throw UnimplementedError();
if (fixedRate) {
final markets =
await ChangeNowAPI.instance.getAvailableFixedRateMarkets();
if (markets.value == null) {
return ExchangeResponse(exception: markets.exception);
}
final List<Pair> pairs = [];
for (final market in markets.value!) {
pairs.add(
Pair(
exchangeName: ChangeNowExchange.exchangeName,
from: market.from,
to: market.to,
rateType: SupportedRateType.fixed,
),
);
}
return ExchangeResponse(value: pairs);
} else {
return await ChangeNowAPI.instance.getAvailableFloatingRatePairs();
}
}
@override

View file

@ -1,20 +1,25 @@
import 'package:decimal/decimal.dart';
import 'package:stackwallet/models/exchange/response_objects/currency.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/pair.dart';
import 'package:stackwallet/models/exchange/response_objects/range.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
abstract class Exchange {
static Exchange get defaultExchange => ChangeNowExchange.instance;
static Exchange fromName(String name) {
switch (name) {
case ChangeNowExchange.exchangeName:
return ChangeNowExchange();
return ChangeNowExchange.instance;
case SimpleSwapExchange.exchangeName:
return SimpleSwapExchange();
return SimpleSwapExchange.instance;
case MajesticBankExchange.exchangeName:
return MajesticBankExchange.instance;
default:
throw ArgumentError("Unknown exchange name");
}

View file

@ -1,200 +1,225 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_api.dart';
import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:flutter/foundation.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/stack_file_system.dart';
class ExchangeDataLoadingService {
Future<void> loadAll(WidgetRef ref, {Coin? coin}) async {
try {
await Future.wait([
_loadFixedRateMarkets(ref, coin: coin),
_loadChangeNowStandardCurrencies(ref, coin: coin),
loadSimpleswapFixedRateCurrencies(ref),
loadSimpleswapFloatingRateCurrencies(ref),
]);
} catch (e, s) {
Logging.instance.log("ExchangeDataLoadingService.loadAll failed: $e\n$s",
level: LogLevel.Error);
}
ExchangeDataLoadingService._();
static final ExchangeDataLoadingService _instance =
ExchangeDataLoadingService._();
static ExchangeDataLoadingService get instance => _instance;
Isar? _isar;
Isar get isar => _isar!;
Future<void> init() async {
if (_isar != null && isar.isOpen) return;
_isar = await Isar.open(
[
CurrencySchema,
PairSchema,
],
directory: (await StackFileSystem.applicationIsarDirectory()).path,
inspector: kDebugMode,
name: "exchange_cache",
);
}
Future<void> _loadFixedRateMarkets(WidgetRef ref, {Coin? coin}) async {
if (ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state ==
ChangeNowLoadStatus.loading) {
// already in progress so just
return;
}
bool _locked = false;
ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state =
ChangeNowLoadStatus.loading;
Future<void> loadAll() async {
print("LOADINGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG: LOCKED=$_locked");
if (!_locked) {
_locked = true;
print("LOADINGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG");
final time = DateTime.now();
try {
await Future.wait([
_loadChangeNowCurrencies(),
_loadChangeNowFixedRatePairs(),
_loadChangeNowEstimatedRatePairs(),
// loadSimpleswapFixedRateCurrencies(ref),
// loadSimpleswapFloatingRateCurrencies(ref),
loadMajesticBankCurrencies(),
]);
final response3 =
await ChangeNowAPI.instance.getAvailableFixedRateMarkets();
if (response3.value != null) {
ref
.read(availableChangeNowCurrenciesProvider)
.updateMarkets(response3.value!);
if (ref.read(exchangeFormStateProvider).market == null) {
String fromTicker = "btc";
String toTicker = "xmr";
if (coin != null) {
fromTicker = coin.ticker.toLowerCase();
}
final matchingMarkets = response3.value!
.where((e) => e.to == toTicker && e.from == fromTicker);
if (matchingMarkets.isNotEmpty) {
await ref
.read(exchangeFormStateProvider)
.updateMarket(matchingMarkets.first, true);
}
}
} else {
Logging.instance.log(
"Failed to load changeNOW fixed rate markets: ${response3.exception?.message}",
level: LogLevel.Error);
ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state =
ChangeNowLoadStatus.failed;
return;
}
ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state =
ChangeNowLoadStatus.success;
}
Future<void> _loadChangeNowStandardCurrencies(
WidgetRef ref, {
Coin? coin,
}) async {
if (ref
.read(changeNowEstimatedInitialLoadStatusStateProvider.state)
.state ==
ChangeNowLoadStatus.loading) {
// already in progress so just
return;
}
ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state =
ChangeNowLoadStatus.loading;
final response = await ChangeNowAPI.instance.getAvailableCurrencies();
final response2 =
await ChangeNowAPI.instance.getAvailableFloatingRatePairs();
if (response.value != null) {
ref
.read(availableChangeNowCurrenciesProvider)
.updateCurrencies(response.value!);
if (response2.value != null) {
ref
.read(availableChangeNowCurrenciesProvider)
.updateFloatingPairs(response2.value!);
String fromTicker = "btc";
String toTicker = "xmr";
if (coin != null) {
fromTicker = coin.ticker.toLowerCase();
}
if (response.value!.length > 1) {
if (ref.read(exchangeFormStateProvider).from == null) {
if (response.value!
.where((e) => e.ticker == fromTicker)
.isNotEmpty) {
await ref.read(exchangeFormStateProvider).updateFrom(
response.value!.firstWhere((e) => e.ticker == fromTicker),
false);
}
}
if (ref.read(exchangeFormStateProvider).to == null) {
if (response.value!.where((e) => e.ticker == toTicker).isNotEmpty) {
await ref.read(exchangeFormStateProvider).updateTo(
response.value!.firstWhere((e) => e.ticker == toTicker),
false);
}
}
}
} else {
print(
"LOADINGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG done in ${DateTime.now().difference(time).inSeconds} seconds");
} catch (e, s) {
Logging.instance.log(
"Failed to load changeNOW available floating rate pairs: ${response2.exception?.message}",
"ExchangeDataLoadingService.loadAll failed: $e\n$s",
level: LogLevel.Error);
ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state =
ChangeNowLoadStatus.failed;
return;
}
} else {
Logging.instance.log(
"Failed to load changeNOW currencies: ${response.exception?.message}",
level: LogLevel.Error);
await Future<void>.delayed(const Duration(seconds: 3));
ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state =
ChangeNowLoadStatus.failed;
return;
_locked = false;
}
ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state =
ChangeNowLoadStatus.success;
}
Future<void> loadSimpleswapFloatingRateCurrencies(WidgetRef ref) async {
final exchange = SimpleSwapExchange();
Future<void> _loadChangeNowCurrencies() async {
final exchange = ChangeNowExchange.instance;
final responseCurrencies = await exchange.getAllCurrencies(false);
if (responseCurrencies.value != null) {
await isar.writeTxn(() async {
final idsToDelete = await isar.currencies
.where()
.exchangeNameEqualTo(ChangeNowExchange.exchangeName)
.idProperty()
.findAll();
await isar.currencies.deleteAll(idsToDelete);
await isar.currencies.putAll(responseCurrencies.value!);
});
} else {
Logging.instance.log(
"Failed to load changeNOW currencies: ${responseCurrencies.exception?.message}",
level: LogLevel.Error);
return;
}
}
Future<void> _loadChangeNowFixedRatePairs() async {
final exchange = ChangeNowExchange.instance;
final responsePairs = await exchange.getAllPairs(true);
if (responsePairs.value != null) {
await isar.writeTxn(() async {
final idsToDelete2 = await isar.pairs
.where()
.exchangeNameEqualTo(ChangeNowExchange.exchangeName)
.filter()
.rateTypeEqualTo(SupportedRateType.fixed)
.idProperty()
.findAll();
await isar.pairs.deleteAll(idsToDelete2);
await isar.pairs.putAll(responsePairs.value!);
});
} else {
Logging.instance.log(
"Failed to load changeNOW available fixed rate pairs: ${responsePairs.exception?.message}",
level: LogLevel.Error);
return;
}
}
Future<void> _loadChangeNowEstimatedRatePairs() async {
final exchange = ChangeNowExchange.instance;
final responsePairs = await exchange.getAllPairs(false);
if (responsePairs.value != null) {
await isar.writeTxn(() async {
final idsToDelete = await isar.pairs
.where()
.exchangeNameEqualTo(ChangeNowExchange.exchangeName)
.filter()
.rateTypeEqualTo(SupportedRateType.estimated)
.idProperty()
.findAll();
await isar.pairs.deleteAll(idsToDelete);
await isar.pairs.putAll(responsePairs.value!);
});
} else {
Logging.instance.log(
"Failed to load changeNOW available floating rate pairs: ${responsePairs.exception?.message}",
level: LogLevel.Error);
return;
}
}
//
// Future<void> loadSimpleswapFloatingRateCurrencies(WidgetRef ref) async {
// final exchange = SimpleSwapExchange();
// final responseCurrencies = await exchange.getAllCurrencies(false);
//
// if (responseCurrencies.value != null) {
// ref
// .read(availableSimpleswapCurrenciesProvider)
// .updateFloatingCurrencies(responseCurrencies.value!);
//
// final responsePairs = await exchange.getAllPairs(false);
//
// if (responsePairs.value != null) {
// ref
// .read(availableSimpleswapCurrenciesProvider)
// .updateFloatingPairs(responsePairs.value!);
// } else {
// Logging.instance.log(
// "loadSimpleswapFloatingRateCurrencies: $responsePairs",
// level: LogLevel.Warning,
// );
// }
// } else {
// Logging.instance.log(
// "loadSimpleswapFloatingRateCurrencies: $responseCurrencies",
// level: LogLevel.Warning,
// );
// }
// }
//
// Future<void> loadSimpleswapFixedRateCurrencies(WidgetRef ref) async {
// final exchange = SimpleSwapExchange();
// final responseCurrencies = await exchange.getAllCurrencies(true);
//
// if (responseCurrencies.value != null) {
// ref
// .read(availableSimpleswapCurrenciesProvider)
// .updateFixedCurrencies(responseCurrencies.value!);
//
// final responsePairs = await exchange.getAllPairs(true);
//
// if (responsePairs.value != null) {
// ref
// .read(availableSimpleswapCurrenciesProvider)
// .updateFixedPairs(responsePairs.value!);
// } else {
// Logging.instance.log(
// "loadSimpleswapFixedRateCurrencies: $responsePairs",
// level: LogLevel.Warning,
// );
// }
// } else {
// Logging.instance.log(
// "loadSimpleswapFixedRateCurrencies: $responseCurrencies",
// level: LogLevel.Warning,
// );
// }
// }
Future<void> loadMajesticBankCurrencies() async {
final exchange = MajesticBankExchange.instance;
final responseCurrencies = await exchange.getAllCurrencies(false);
if (responseCurrencies.value != null) {
ref
.read(availableSimpleswapCurrenciesProvider)
.updateFloatingCurrencies(responseCurrencies.value!);
final responsePairs = await exchange.getAllPairs(false);
if (responsePairs.value != null) {
ref
.read(availableSimpleswapCurrenciesProvider)
.updateFloatingPairs(responsePairs.value!);
await isar.writeTxn(() async {
final idsToDelete = await isar.currencies
.where()
.exchangeNameEqualTo(MajesticBankExchange.exchangeName)
.idProperty()
.findAll();
await isar.currencies.deleteAll(idsToDelete);
await isar.currencies.putAll(responseCurrencies.value!);
final idsToDelete2 = await isar.pairs
.where()
.exchangeNameEqualTo(MajesticBankExchange.exchangeName)
.idProperty()
.findAll();
await isar.pairs.deleteAll(idsToDelete2);
await isar.pairs.putAll(responsePairs.value!);
});
} else {
Logging.instance.log(
"loadSimpleswapFloatingRateCurrencies: $responsePairs",
"loadMajesticBankCurrencies: $responsePairs",
level: LogLevel.Warning,
);
}
} else {
Logging.instance.log(
"loadSimpleswapFloatingRateCurrencies: $responseCurrencies",
level: LogLevel.Warning,
);
}
}
Future<void> loadSimpleswapFixedRateCurrencies(WidgetRef ref) async {
final exchange = SimpleSwapExchange();
final responseCurrencies = await exchange.getAllCurrencies(true);
if (responseCurrencies.value != null) {
ref
.read(availableSimpleswapCurrenciesProvider)
.updateFixedCurrencies(responseCurrencies.value!);
final responsePairs = await exchange.getAllPairs(true);
if (responsePairs.value != null) {
ref
.read(availableSimpleswapCurrenciesProvider)
.updateFixedPairs(responsePairs.value!);
} else {
Logging.instance.log(
"loadSimpleswapFixedRateCurrencies: $responsePairs",
level: LogLevel.Warning,
);
}
} else {
Logging.instance.log(
"loadSimpleswapFixedRateCurrencies: $responseCurrencies",
"loadMajesticBankCurrencies: $responseCurrencies",
level: LogLevel.Warning,
);
}

View file

@ -0,0 +1 @@
class ExchangeService {}

View file

@ -3,6 +3,8 @@ import 'dart:convert';
import 'package:decimal/decimal.dart';
import 'package:http/http.dart' as http;
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart';
import 'package:stackwallet/external_api_keys.dart';
import 'package:stackwallet/models/exchange/majestic_bank/mb_limit.dart';
import 'package:stackwallet/models/exchange/majestic_bank/mb_order.dart';
import 'package:stackwallet/models/exchange/majestic_bank/mb_order_calculation.dart';
@ -15,7 +17,6 @@ class MajesticBankAPI {
static const String scheme = "https";
static const String authority = "majesticbank.sc";
static const String version = "v1";
static const String refCode = "fixme";
MajesticBankAPI._();
@ -39,7 +40,6 @@ class MajesticBankAPI {
);
code = response.statusCode;
print(response.body);
final parsed = jsonDecode(response.body);
@ -180,6 +180,28 @@ class MajesticBankAPI {
try {
final jsonObject = await _makeGetRequest(uri);
final map = Map<String, dynamic>.from(jsonObject as Map);
if (map["error"] != null) {
final errorMessage = map["extra"] as String?;
if (errorMessage != null &&
errorMessage.startsWith("Bad") &&
errorMessage.endsWith("currency symbol")) {
return ExchangeResponse(
exception: PairUnavailableException(
errorMessage,
ExchangeExceptionType.generic,
),
);
} else {
return ExchangeResponse(
exception: ExchangeException(
errorMessage ?? "Error: ${map["error"]}",
ExchangeExceptionType.generic,
),
);
}
}
final result = MBOrderCalculation(
fromCurrency: map["from_currency"] as String,
fromAmount: Decimal.parse(map["from_amount"].toString()),
@ -189,8 +211,9 @@ class MajesticBankAPI {
return ExchangeResponse(value: result);
} catch (e, s) {
Logging.instance
.log("calculateOrder exception: $e\n$s", level: LogLevel.Error);
Logging.instance.log(
"calculateOrder $fromCurrency-$receiveCurrency exception: $e\n$s",
level: LogLevel.Error);
return ExchangeResponse(
exception: ExchangeException(
e.toString(),
@ -211,7 +234,7 @@ class MajesticBankAPI {
"from_currency": fromCurrency,
"receive_currency": receiveCurrency,
"receive_address": receiveAddress,
"referral_code": refCode,
"referral_code": kMajesticBankRefCode,
};
final uri = _buildUri(endpoint: "exchange", params: params);
@ -260,7 +283,7 @@ class MajesticBankAPI {
"from_currency": fromCurrency,
"receive_currency": receiveCurrency,
"receive_address": receiveAddress,
"referral_code": refCode,
"referral_code": kMajesticBankRefCode,
};
if (reversed) {

View file

@ -2,18 +2,24 @@ import 'package:decimal/decimal.dart';
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
import 'package:stackwallet/exceptions/exchange/majestic_bank/mb_exception.dart';
import 'package:stackwallet/models/exchange/majestic_bank/mb_order.dart';
import 'package:stackwallet/models/exchange/response_objects/currency.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/pair.dart';
import 'package:stackwallet/models/exchange/response_objects/range.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/services/exchange/exchange.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_api.dart';
import 'package:uuid/uuid.dart';
class MajesticBankExchange extends Exchange {
static const exchangeName = "MajesticBank";
MajesticBankExchange._();
static MajesticBankExchange? _instance;
static MajesticBankExchange get instance =>
_instance ??= MajesticBankExchange._();
static const exchangeName = "Majestic Bank";
@override
Future<ExchangeResponse<Trade>> createTrade({
@ -102,15 +108,15 @@ class MajesticBankExchange extends Exchange {
for (final limit in limits) {
final currency = Currency(
exchangeName: MajesticBankExchange.exchangeName,
ticker: limit.currency,
name: limit.currency,
name: limit.currency, // todo: get full coin name
network: "",
image: "",
hasExternalId: false,
isFiat: false,
featured: false,
isStable: false,
supportsFixedRate: true,
rateType: SupportedRateType.both,
isAvailable: true,
isStackCoin: Currency.checkIsStackCoin(limit.currency),
);
currencies.add(currency);
}
@ -130,12 +136,10 @@ class MajesticBankExchange extends Exchange {
for (final rate in rates) {
final pair = Pair(
exchangeName: MajesticBankExchange.exchangeName,
from: rate.fromCurrency,
fromNetwork: "",
to: rate.toCurrency,
toNetwork: "",
fixedRate: true,
floatingRate: true,
rateType: SupportedRateType.both,
);
pairs.add(pair);
}

View file

@ -6,10 +6,10 @@ import 'package:http/http.dart' as http;
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
import 'package:stackwallet/external_api_keys.dart';
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart';
import 'package:stackwallet/models/exchange/response_objects/pair.dart';
import 'package:stackwallet/models/exchange/response_objects/range.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/models/exchange/simpleswap/sp_currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
import 'package:stackwallet/utilities/logger.dart';
@ -272,12 +272,10 @@ class SimpleSwapAPI {
for (final to in entry.value as List) {
pairs.add(
Pair(
exchangeName: SimpleSwapExchange.exchangeName,
from: from,
fromNetwork: "",
to: to as String,
toNetwork: "",
fixedRate: args.item2,
floatingRate: !args.item2,
rateType: SupportedRateType.estimated,
),
);
}

View file

@ -1,14 +1,20 @@
import 'package:decimal/decimal.dart';
import 'package:stackwallet/models/exchange/response_objects/currency.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/pair.dart';
import 'package:stackwallet/models/exchange/response_objects/range.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/services/exchange/exchange.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/services/exchange/simpleswap/simpleswap_api.dart';
class SimpleSwapExchange extends Exchange {
SimpleSwapExchange._();
static SimpleSwapExchange? _instance;
static SimpleSwapExchange get instance =>
_instance ??= SimpleSwapExchange._();
static const exchangeName = "SimpleSwap";
@override
@ -47,18 +53,22 @@ class SimpleSwapExchange extends Exchange {
await SimpleSwapAPI.instance.getAllCurrencies(fixedRate: fixedRate);
if (response.value != null) {
final List<Currency> currencies = response.value!
.map((e) => Currency(
ticker: e.symbol,
name: e.name,
network: e.network,
image: e.image,
hasExternalId: e.hasExtraId,
externalId: e.extraId,
isFiat: false,
featured: false,
isStable: false,
supportsFixedRate: fixedRate,
))
.map(
(e) => Currency(
exchangeName: exchangeName,
ticker: e.symbol,
name: e.name,
network: e.network,
image: e.image,
externalId: e.extraId,
isFiat: false,
rateType: fixedRate
? SupportedRateType.both
: SupportedRateType.estimated,
isAvailable: true,
isStackCoin: Currency.checkIsStackCoin(e.symbol),
),
)
.toList();
return ExchangeResponse<List<Currency>>(
value: currencies,

View file

@ -6,9 +6,7 @@ import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
import 'package:stackwallet/hive/db.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/models/notification_model.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/notifications_api.dart';
import 'package:stackwallet/services/trade_service.dart';
@ -16,6 +14,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'exchange/exchange.dart';
class NotificationsService extends ChangeNotifier {
late NodeService nodeService;
late TradesService tradesService;
@ -196,15 +196,12 @@ class NotificationsService extends ChangeNotifier {
}
final oldTrade = trades.first;
late final ExchangeResponse<Trade> response;
switch (oldTrade.exchangeName) {
case SimpleSwapExchange.exchangeName:
response = await SimpleSwapExchange().updateTrade(oldTrade);
break;
case ChangeNowExchange.exchangeName:
response = await ChangeNowExchange().updateTrade(oldTrade);
break;
default:
return;
try {
final exchange = Exchange.fromName(oldTrade.exchangeName);
response = await exchange.updateTrade(oldTrade);
} catch (_) {
return;
}
if (response.value == null) {

View file

@ -26,6 +26,8 @@ class _EXCHANGE {
String get changeNow => "assets/svg/exchange_icons/change_now_logo_1.svg";
String get simpleSwap => "assets/svg/exchange_icons/simpleswap-icon.svg";
String get majesticBankBlue => "assets/svg/exchange_icons/mb_blue.svg";
String get majesticBankGreen => "assets/svg/exchange_icons/mb_green.svg";
}
class _BUY {

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/buy_view/sub_widgets/crypto_selection_view.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
@ -156,12 +157,18 @@ class _ExchangeTextFieldState extends State<ExchangeTextField> {
),
child: Builder(
builder: (context) {
final image = widget.image;
if (image != null && image.isNotEmpty) {
if (isStackCoin(widget.ticker)) {
return Center(
child: getIconForTicker(
widget.ticker!,
size: 18,
),
);
} else if (widget.image != null &&
widget.image!.isNotEmpty) {
return Center(
child: SvgPicture.network(
image,
widget.image!,
height: 18,
placeholderBuilder: (_) => Container(
width: 18,
@ -240,150 +247,3 @@ class _ExchangeTextFieldState extends State<ExchangeTextField> {
);
}
}
// experimental UNUSED
// class ExchangeTextField extends StatefulWidget {
// const ExchangeTextField({
// Key? key,
// this.borderRadius = 0,
// this.background,
// required this.controller,
// this.buttonColor,
// required this.focusNode,
// this.buttonContent,
// required this.textStyle,
// this.onButtonTap,
// this.onChanged,
// this.onSubmitted,
// }) : super(key: key);
//
// final double borderRadius;
// final Color? background;
// final Color? buttonColor;
// final Widget? buttonContent;
// final TextEditingController controller;
// final FocusNode focusNode;
// final TextStyle textStyle;
// final VoidCallback? onButtonTap;
// final void Function(String)? onChanged;
// final void Function(String)? onSubmitted;
//
// @override
// State<ExchangeTextField> createState() => _ExchangeTextFieldState();
// }
//
// class _ExchangeTextFieldState extends State<ExchangeTextField> {
// late final TextEditingController controller;
// late final FocusNode focusNode;
// late final TextStyle textStyle;
//
// late final double borderRadius;
//
// late final Color? background;
// late final Color? buttonColor;
// late final Widget? buttonContent;
// late final VoidCallback? onButtonTap;
// late final void Function(String)? onChanged;
// late final void Function(String)? onSubmitted;
//
// @override
// void initState() {
// borderRadius = widget.borderRadius;
// background = widget.background;
// buttonColor = widget.buttonColor;
// controller = widget.controller;
// focusNode = widget.focusNode;
// buttonContent = widget.buttonContent;
// textStyle = widget.textStyle;
// onButtonTap = widget.onButtonTap;
// onChanged = widget.onChanged;
// onSubmitted = widget.onSubmitted;
//
// super.initState();
// }
//
// @override
// Widget build(BuildContext context) {
// return Container(
// decoration: BoxDecoration(
// color: background,
// borderRadius: BorderRadius.circular(borderRadius),
// ),
// child: IntrinsicHeight(
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.stretch,
// children: [
// Expanded(
// child: MouseRegion(
// cursor: SystemMouseCursors.text,
// child: GestureDetector(
// onTap: () {
// //
// },
// child: Padding(
// padding: const EdgeInsets.only(
// left: 16,
// top: 18,
// bottom: 17,
// ),
// child: IgnorePointer(
// ignoring: true,
// child: EditableText(
// controller: controller,
// focusNode: focusNode,
// style: textStyle,
// onChanged: onChanged,
// onSubmitted: onSubmitted,
// onEditingComplete: () => print("lol"),
// autocorrect: false,
// enableSuggestions: false,
// keyboardType: const TextInputType.numberWithOptions(
// signed: false,
// decimal: true,
// ),
// inputFormatters: [
// // regex to validate a crypto amount with 8 decimal places
// TextInputFormatter.withFunction((oldValue,
// newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
// ],
// cursorColor: textStyle.color ??
// Theme.of(context).backgroundColor,
// backgroundCursorColor: background ?? Colors.transparent,
// ),
// ),
// ),
// ),
// ),
// ),
// MouseRegion(
// cursor: SystemMouseCursors.click,
// child: GestureDetector(
// onTap: () => onButtonTap?.call(),
// child: Container(
// decoration: BoxDecoration(
// color: buttonColor,
// borderRadius: BorderRadius.horizontal(
// right: Radius.circular(
// borderRadius,
// ),
// ),
// ),
// child: Padding(
// padding: const EdgeInsets.symmetric(
// horizontal: 16,
// ),
// child: buttonContent,
// ),
// ),
// ),
// ),
// ],
// ),
// ),
// );
// }
// }

View file

@ -344,6 +344,8 @@ flutter:
# exchange icons
- assets/svg/exchange_icons/change_now_logo_1.svg
- assets/svg/exchange_icons/simpleswap-icon.svg
- assets/svg/exchange_icons/mb_green.svg
- assets/svg/exchange_icons/mb_blue.svg
# theme selectors
- assets/svg/dark-theme.svg

File diff suppressed because it is too large Load diff

View file

@ -15,17 +15,16 @@ import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart
as _i20;
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'
as _i21;
import 'package:stackwallet/models/exchange/response_objects/currency.dart'
as _i14;
import 'package:stackwallet/models/exchange/response_objects/estimate.dart'
as _i17;
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'
as _i19;
import 'package:stackwallet/models/exchange/response_objects/pair.dart' as _i22;
import 'package:stackwallet/models/exchange/response_objects/range.dart'
as _i16;
import 'package:stackwallet/models/exchange/response_objects/trade.dart'
as _i10;
import 'package:stackwallet/models/isar/exchange_cache/currency.dart' as _i14;
import 'package:stackwallet/models/isar/exchange_cache/pair.dart' as _i22;
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'
as _i5;
import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'

View file

@ -5,13 +5,12 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart';
import 'package:stackwallet/exceptions/exchange/exchange_exception.dart';
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/pair.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_api.dart';
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'change_now_sample_data.dart';
import 'change_now_test.mocks.dart';

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff