diff --git a/assets/svg/exchange_icons/change_now_logo_1.svg b/assets/svg/exchange_icons/change_now_logo_1.svg
new file mode 100644
index 000000000..323488e52
--- /dev/null
+++ b/assets/svg/exchange_icons/change_now_logo_1.svg
@@ -0,0 +1,25 @@
+
diff --git a/assets/svg/exchange_icons/simpleswap-icon.svg b/assets/svg/exchange_icons/simpleswap-icon.svg
new file mode 100644
index 000000000..b3986ef2f
--- /dev/null
+++ b/assets/svg/exchange_icons/simpleswap-icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/lib/hive/db.dart b/lib/hive/db.dart
index c8e148923..062d029a5 100644
--- a/lib/hive/db.dart
+++ b/lib/hive/db.dart
@@ -4,6 +4,7 @@ import 'package:cw_core/wallet_info.dart' as xmr;
import 'package:hive/hive.dart';
import 'package:mutex/mutex.dart';
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
+import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/notification_model.dart';
import 'package:stackwallet/models/trade_wallet_lookup.dart';
@@ -22,6 +23,7 @@ class DB {
"watchedTxNotificationModels";
static const String boxNameWatchedTrades = "watchedTradesNotificationModels";
static const String boxNameTrades = "exchangeTransactionsBox";
+ static const String boxNameTradesV2 = "exchangeTradesBox";
static const String boxNameTradeNotes = "tradeNotesBox";
static const String boxNameTradeLookup = "tradeToTxidLookUpBox";
static const String boxNameFavoriteWallets = "favoriteWallets";
@@ -48,6 +50,7 @@ class DB {
late final Box _boxWatchedTransactions;
late final Box _boxWatchedTrades;
late final Box _boxTrades;
+ late final Box _boxTradesV2;
late final Box _boxTradeNotes;
late final Box _boxFavoriteWallets;
late final Box _walletInfoSource;
@@ -125,6 +128,7 @@ class DB {
_boxWatchedTrades =
await Hive.openBox(boxNameWatchedTrades);
_boxTrades = await Hive.openBox(boxNameTrades);
+ _boxTradesV2 = await Hive.openBox(boxNameTradesV2);
_boxTradeNotes = await Hive.openBox(boxNameTradeNotes);
_boxTradeLookup =
await Hive.openBox(boxNameTradeLookup);
diff --git a/lib/main.dart b/lib/main.dart
index da08c6765..d01e0e2a7 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -18,12 +18,12 @@ import 'package:path_provider/path_provider.dart';
import 'package:stackwallet/hive/db.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/trade.dart';
import 'package:stackwallet/models/isar/models/log.dart';
import 'package:stackwallet/models/models.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/notification_model.dart';
import 'package:stackwallet/models/trade_wallet_lookup.dart';
-import 'package:stackwallet/pages/exchange_view/exchange_view.dart';
import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages/intro_view.dart';
import 'package:stackwallet/pages/loading_view.dart';
@@ -31,13 +31,6 @@ import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart';
import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart';
import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart';
-import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart';
-import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart';
-import 'package:stackwallet/providers/exchange/change_now_provider.dart';
-import 'package:stackwallet/providers/exchange/changenow_initial_load_status.dart';
-import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart';
-import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart';
-import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart';
import 'package:stackwallet/providers/global/auto_swb_service_provider.dart';
import 'package:stackwallet/providers/global/base_currencies_provider.dart';
// import 'package:stackwallet/providers/global/has_authenticated_start_state_provider.dart';
@@ -46,6 +39,8 @@ import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/ui/color_theme_provider.dart';
import 'package:stackwallet/route_generator.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';
import 'package:stackwallet/services/notifications_api.dart';
@@ -124,6 +119,8 @@ void main() async {
Hive.registerAdapter(ExchangeTransactionAdapter());
Hive.registerAdapter(ExchangeTransactionStatusAdapter());
+ Hive.registerAdapter(TradeAdapter());
+
// reference lookup data adapter
Hive.registerAdapter(TradeWalletLookupAdapter());
@@ -225,7 +222,6 @@ class _MaterialAppWithThemeState extends ConsumerState
nodeService: _nodeService,
tradesService: _tradesService,
prefs: _prefs,
- changeNow: ref.read(changeNowProvider),
);
await _prefs.init();
ref.read(priceAnd24hChangeNotifierProvider).start(true);
@@ -234,6 +230,11 @@ class _MaterialAppWithThemeState extends ConsumerState
// TODO: this should probably run unawaited. Keep commented out for now as proper community nodes ui hasn't been implemented yet
// unawaited(_nodeService.updateCommunityNodes());
+ // run without awaiting
+ if (Constants.enableExchange) {
+ unawaited(ExchangeDataLoadingService().loadAll(ref));
+ }
+
if (_prefs.isAutoBackupEnabled) {
switch (_prefs.backupFrequencyType) {
case BackupFrequencyType.everyTenMinutes:
@@ -251,112 +252,9 @@ class _MaterialAppWithThemeState extends ConsumerState
}
}
- Future _loadChangeNowStandardCurrencies() async {
- if (ref
- .read(availableChangeNowCurrenciesStateProvider.state)
- .state
- .isNotEmpty &&
- ref
- .read(availableFloatingRatePairsStateProvider.state)
- .state
- .isNotEmpty) {
- return;
- }
- final response = await ref.read(changeNowProvider).getAvailableCurrencies();
- final response2 =
- await ref.read(changeNowProvider).getAvailableFloatingRatePairs();
- if (response.value != null) {
- ref.read(availableChangeNowCurrenciesStateProvider.state).state =
- response.value!;
- if (response2.value != null) {
- ref.read(availableFloatingRatePairsStateProvider.state).state =
- response2.value!;
-
- if (response.value!.length > 1) {
- if (ref.read(estimatedRateExchangeFormProvider).from == null) {
- if (response.value!.where((e) => e.ticker == "btc").isNotEmpty) {
- await ref.read(estimatedRateExchangeFormProvider).updateFrom(
- response.value!.firstWhere((e) => e.ticker == "btc"), false);
- }
- }
- if (ref.read(estimatedRateExchangeFormProvider).to == null) {
- if (response.value!.where((e) => e.ticker == "doge").isNotEmpty) {
- await ref.read(estimatedRateExchangeFormProvider).updateTo(
- response.value!.firstWhere((e) => e.ticker == "doge"), false);
- }
- }
- }
- } else {
- Logging.instance.log(
- "Failed to load changeNOW available floating rate pairs: ${response2.exception?.errorMessage}",
- level: LogLevel.Error);
- ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state =
- ChangeNowLoadStatus.failed;
- return;
- }
- } else {
- Logging.instance.log(
- "Failed to load changeNOW currencies: ${response.exception?.errorMessage}",
- level: LogLevel.Error);
- await Future.delayed(const Duration(seconds: 1));
- ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state =
- ChangeNowLoadStatus.failed;
- return;
- }
-
- ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state =
- ChangeNowLoadStatus.success;
- }
-
- Future _loadFixedRateMarkets() async {
- Logging.instance.log("Starting initial fixed rate market data loading...",
- level: LogLevel.Info);
- if (ref.read(fixedRateMarketPairsStateProvider.state).state.isNotEmpty) {
- return;
- }
-
- final response3 =
- await ref.read(changeNowProvider).getAvailableFixedRateMarkets();
- if (response3.value != null) {
- ref.read(fixedRateMarketPairsStateProvider.state).state =
- response3.value!;
-
- if (ref.read(fixedRateExchangeFormProvider).market == null) {
- final matchingMarkets =
- response3.value!.where((e) => e.to == "doge" && e.from == "btc");
- if (matchingMarkets.isNotEmpty) {
- await ref
- .read(fixedRateExchangeFormProvider)
- .updateMarket(matchingMarkets.first, true);
- }
- }
-
- Logging.instance.log("Initial fixed rate market data loading complete.",
- level: LogLevel.Info);
- } else {
- Logging.instance.log(
- "Failed to load changeNOW fixed rate markets: ${response3.exception?.errorMessage}",
- level: LogLevel.Error);
-
- ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state =
- ChangeNowLoadStatus.failed;
- return;
- }
-
- ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state =
- ChangeNowLoadStatus.success;
- }
-
- Future _loadChangeNowData() async {
- List> concurrentFutures = [];
- concurrentFutures.add(_loadChangeNowStandardCurrencies());
- if (kFixedRateEnabled) {
- concurrentFutures.add(_loadFixedRateMarkets());
- }
- }
-
@override
void initState() {
+ ref.read(exchangeFormStateProvider).exchange = ChangeNowExchange();
final colorScheme = DB.instance
.get(boxName: DB.boxNameTheme, key: "colorScheme") as String?;
@@ -637,11 +535,6 @@ class _MaterialAppWithThemeState extends ConsumerState
if (_wallets.hasWallets || _prefs.hasPin) {
// return HomeView();
- // run without awaiting
- if (Constants.enableExchange) {
- _loadChangeNowData();
- }
-
String? startupWalletId;
if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) {
startupWalletId =
diff --git a/lib/models/exchange/change_now/available_floating_rate_pair.dart b/lib/models/exchange/change_now/available_floating_rate_pair.dart
deleted file mode 100644
index d30b9817e..000000000
--- a/lib/models/exchange/change_now/available_floating_rate_pair.dart
+++ /dev/null
@@ -1,26 +0,0 @@
-import 'package:flutter/material.dart';
-
-class AvailableFloatingRatePair {
- final String fromTicker;
- final String toTicker;
-
- AvailableFloatingRatePair({
- required this.fromTicker,
- required this.toTicker,
- });
-
- @override
- bool operator ==(other) {
- return other is AvailableFloatingRatePair &&
- fromTicker == other.fromTicker &&
- toTicker == other.toTicker;
- }
-
- @override
- int get hashCode => hashValues(fromTicker, toTicker);
-
- @override
- String toString() {
- return "${fromTicker}_$toTicker";
- }
-}
diff --git a/lib/models/exchange/change_now/change_now_response.dart b/lib/models/exchange/change_now/change_now_response.dart
deleted file mode 100644
index a45a8d665..000000000
--- a/lib/models/exchange/change_now/change_now_response.dart
+++ /dev/null
@@ -1,24 +0,0 @@
-enum ChangeNowExceptionType { generic, serializeResponseError }
-
-class ChangeNowException implements Exception {
- String errorMessage;
- ChangeNowExceptionType type;
- ChangeNowException(this.errorMessage, this.type);
-
- @override
- String toString() {
- return errorMessage;
- }
-}
-
-class ChangeNowResponse {
- late final T? value;
- late final ChangeNowException? exception;
-
- ChangeNowResponse({this.value, this.exception});
-
- @override
- String toString() {
- return "{ error: $exception, value: $value }";
- }
-}
diff --git a/lib/models/exchange/change_now/cn_available_currencies.dart b/lib/models/exchange/change_now/cn_available_currencies.dart
new file mode 100644
index 000000000..aaf6a5d05
--- /dev/null
+++ b/lib/models/exchange/change_now/cn_available_currencies.dart
@@ -0,0 +1,24 @@
+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 currencies = [];
+ final List pairs = [];
+ final List markets = [];
+
+ void updateCurrencies(List newCurrencies) {
+ currencies.clear();
+ currencies.addAll(newCurrencies);
+ }
+
+ void updateFloatingPairs(List newPairs) {
+ pairs.clear();
+ pairs.addAll(newPairs);
+ }
+
+ void updateMarkets(List newMarkets) {
+ markets.clear();
+ markets.addAll(newMarkets);
+ }
+}
diff --git a/lib/models/exchange/change_now/estimated_exchange_amount.dart b/lib/models/exchange/change_now/estimated_exchange_amount.dart
index cf70f812f..dccf6b3b3 100644
--- a/lib/models/exchange/change_now/estimated_exchange_amount.dart
+++ b/lib/models/exchange/change_now/estimated_exchange_amount.dart
@@ -35,8 +35,10 @@ class EstimatedExchangeAmount {
factory EstimatedExchangeAmount.fromJson(Map json) {
try {
return EstimatedExchangeAmount(
- estimatedAmount: Decimal.parse(json["estimatedAmount"].toString()),
- transactionSpeedForecast: json["transactionSpeedForecast"] as String,
+ estimatedAmount: Decimal.parse(json["estimatedAmount"]?.toString() ??
+ json["estimatedDeposit"].toString()),
+ transactionSpeedForecast:
+ json["transactionSpeedForecast"] as String? ?? "",
warningMessage: json["warningMessage"] as String?,
rateId: json["rateId"] as String?,
networkFee: Decimal.tryParse(json["networkFee"].toString()),
diff --git a/lib/models/exchange/change_now/exchange_transaction.dart b/lib/models/exchange/change_now/exchange_transaction.dart
index 0ea153876..233c242c9 100644
--- a/lib/models/exchange/change_now/exchange_transaction.dart
+++ b/lib/models/exchange/change_now/exchange_transaction.dart
@@ -5,6 +5,8 @@ import 'package:uuid/uuid.dart';
part '../../type_adaptors/exchange_transaction.g.dart';
+@Deprecated(
+ "Do not use. Migrated to Trade in db_version_migration to hive_data_version 2")
// @HiveType(typeId: 13)
class ExchangeTransaction {
/// You can use it to get transaction status at the Transaction status API endpoint
diff --git a/lib/models/exchange/estimated_rate_exchange_form_state.dart b/lib/models/exchange/estimated_rate_exchange_form_state.dart
deleted file mode 100644
index 3161d83c1..000000000
--- a/lib/models/exchange/estimated_rate_exchange_form_state.dart
+++ /dev/null
@@ -1,282 +0,0 @@
-import 'package:decimal/decimal.dart';
-import 'package:flutter/cupertino.dart';
-import 'package:flutter/material.dart';
-import 'package:stackwallet/models/exchange/change_now/currency.dart';
-import 'package:stackwallet/services/change_now/change_now.dart';
-import 'package:stackwallet/utilities/logger.dart';
-
-class EstimatedRateExchangeFormState extends ChangeNotifier {
- /// used in testing to inject mock
- ChangeNow? cnTesting;
-
- Decimal? _fromAmount;
- Decimal? _toAmount;
-
- Decimal? _minFromAmount;
- Decimal? _minToAmount;
-
- Decimal? rate;
-
- Currency? _from;
- Currency? _to;
-
- void Function(String)? _onError;
-
- Currency? get from => _from;
- Currency? get to => _to;
-
- String get fromAmountString =>
- _fromAmount == null ? "" : _fromAmount!.toStringAsFixed(8);
- String get toAmountString =>
- _toAmount == null ? "" : _toAmount!.toStringAsFixed(8);
-
- String get rateDisplayString {
- if (rate == null || from == null || to == null) {
- return "N/A";
- } else {
- return "1 ${from!.ticker.toUpperCase()} ~${rate!.toStringAsFixed(8)} ${to!.ticker.toUpperCase()}";
- }
- }
-
- bool get canExchange {
- return _fromAmount != null &&
- _fromAmount != Decimal.zero &&
- _toAmount != null &&
- rate != null &&
- minimumSendWarning.isEmpty;
- }
-
- String get minimumSendWarning {
- if (_from != null &&
- _fromAmount != null &&
- _minFromAmount != null &&
- _fromAmount! < _minFromAmount!) {
- return "Minimum amount ${_minFromAmount!.toString()} ${from!.ticker.toUpperCase()}";
- }
-
- return "";
- }
-
- Future init(Currency? from, Currency? to) async {
- _from = from;
- _to = to;
- }
-
- void clearAmounts(bool shouldNotifyListeners) {
- _fromAmount = null;
- _toAmount = null;
- _minFromAmount = null;
- _minToAmount = null;
- rate = null;
-
- if (shouldNotifyListeners) {
- notifyListeners();
- }
- }
-
- Future swap() async {
- final Decimal? newToAmount = _fromAmount;
- final Decimal? newFromAmount = _toAmount;
-
- final Decimal? newMinFromAmount = _minToAmount;
- final Decimal? newMinToAmount = _minFromAmount;
-
- final Currency? newTo = from;
- final Currency? newFrom = to;
-
- _fromAmount = newFromAmount;
- _toAmount = newToAmount;
-
- _minToAmount = newMinToAmount;
- _minFromAmount = newMinFromAmount;
-
- // rate = newRate;
-
- _to = newTo;
- _from = newFrom;
-
- await _updateMinFromAmount(shouldNotifyListeners: false);
-
- await updateRate();
-
- notifyListeners();
- }
-
- Future updateTo(Currency to, bool shouldNotifyListeners) async {
- try {
- _to = to;
- if (_from == null) {
- rate = null;
- notifyListeners();
- return;
- }
-
- await _updateMinFromAmount(shouldNotifyListeners: shouldNotifyListeners);
-
- await updateRate(shouldNotifyListeners: shouldNotifyListeners);
-
- debugPrint(
- "_updated TO: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$_fromAmount _toAmount=$_toAmount rate:$rate");
-
- if (shouldNotifyListeners) {
- notifyListeners();
- }
- } catch (e, s) {
- Logging.instance.log("$e\n$s", level: LogLevel.Error);
- }
- }
-
- Future updateFrom(Currency from, bool shouldNotifyListeners) async {
- try {
- _from = from;
-
- if (_to == null) {
- rate = null;
- notifyListeners();
- return;
- }
-
- await _updateMinFromAmount(shouldNotifyListeners: shouldNotifyListeners);
-
- await updateRate(shouldNotifyListeners: shouldNotifyListeners);
-
- debugPrint(
- "_updated FROM: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$_fromAmount _toAmount=$_toAmount rate:$rate");
- if (shouldNotifyListeners) {
- notifyListeners();
- }
- } catch (e, s) {
- Logging.instance.log("$e\n$s", level: LogLevel.Error);
- }
- }
-
- Future _updateMinFromAmount(
- {required bool shouldNotifyListeners}) async {
- _minFromAmount = await getStandardMinExchangeAmount(from: from!, to: to!);
- if (shouldNotifyListeners) {
- notifyListeners();
- }
- }
-
- // Future setToAmountAndCalculateFromAmount(
- // Decimal newToAmount,
- // bool shouldNotifyListeners,
- // ) async {
- // if (newToAmount == Decimal.zero) {
- // _fromAmount = Decimal.zero;
- // }
- //
- // _toAmount = newToAmount;
- // await updateRate();
- // if (shouldNotifyListeners) {
- // notifyListeners();
- // }
- // }
-
- Future setFromAmountAndCalculateToAmount(
- Decimal newFromAmount,
- bool shouldNotifyListeners,
- ) async {
- if (newFromAmount == Decimal.zero) {
- _toAmount = Decimal.zero;
- }
-
- _fromAmount = newFromAmount;
- await updateRate(shouldNotifyListeners: shouldNotifyListeners);
-
- if (shouldNotifyListeners) {
- notifyListeners();
- }
- }
-
- Future getStandardEstimatedToAmount({
- required Decimal fromAmount,
- required Currency from,
- required Currency to,
- }) async {
- final response =
- await (cnTesting ?? ChangeNow.instance).getEstimatedExchangeAmount(
- fromTicker: from.ticker,
- toTicker: to.ticker,
- fromAmount: fromAmount,
- );
-
- if (response.value != null) {
- return response.value!.estimatedAmount;
- } else {
- _onError?.call(
- "Failed to fetch estimated amount: ${response.exception?.toString()}");
- return null;
- }
- }
-
- // Future getStandardEstimatedFromAmount({
- // required Decimal toAmount,
- // required Currency from,
- // required Currency to,
- // }) async {
- // final response = await (cnTesting ?? ChangeNow.instance)
- // .getEstimatedExchangeAmount(
- // fromTicker: from.ticker,
- // toTicker: to.ticker,
- // fromAmount: toAmount, );
- //
- // if (response.value != null) {
- // return response.value!.fromAmount;
- // } else {
- // _onError?.call(
- // "Failed to fetch estimated amount: ${response.exception?.toString()}");
- // return null;
- // }
- // }
-
- Future getStandardMinExchangeAmount({
- required Currency from,
- required Currency to,
- }) async {
- final response = await (cnTesting ?? ChangeNow.instance)
- .getMinimalExchangeAmount(fromTicker: from.ticker, toTicker: to.ticker);
-
- if (response.value != null) {
- return response.value!;
- } else {
- _onError?.call(
- "Could not update minimal exchange amounts: ${response.exception?.toString()}");
- return null;
- }
- }
-
- void setOnError({
- required void Function(String)? onError,
- bool shouldNotifyListeners = false,
- }) {
- _onError = onError;
- if (shouldNotifyListeners) {
- notifyListeners();
- }
- }
-
- Future updateRate({bool shouldNotifyListeners = false}) async {
- rate = null;
- final amount = _fromAmount;
- final minAmount = _minFromAmount;
- if (amount != null && amount > Decimal.zero) {
- Decimal? amt;
- if (minAmount != null) {
- if (minAmount <= amount) {
- amt = await getStandardEstimatedToAmount(
- fromAmount: amount, from: _from!, to: _to!);
- if (amt != null) {
- rate = (amt / amount).toDecimal(scaleOnInfinitePrecision: 12);
- }
- }
- }
- if (rate != null && amt != null) {
- _toAmount = amt;
- }
- }
- if (shouldNotifyListeners) {
- notifyListeners();
- }
- }
-}
diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart
new file mode 100644
index 000000000..892149f3f
--- /dev/null
+++ b/lib/models/exchange/exchange_form_state.dart
@@ -0,0 +1,400 @@
+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:stackwallet/models/exchange/response_objects/estimate.dart';
+import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart';
+import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
+import 'package:stackwallet/services/exchange/exchange.dart';
+import 'package:stackwallet/services/exchange/simpleswap/simpleswap_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();
+ }
+
+ ExchangeRateType _exchangeType = ExchangeRateType.estimated;
+ ExchangeRateType get exchangeType => _exchangeType;
+ set exchangeType(ExchangeRateType value) {
+ _exchangeType = value;
+ _onExchangeRateTypeChanged();
+ }
+
+ bool reversed = false;
+
+ Decimal? fromAmount;
+ Decimal? toAmount;
+
+ Decimal? minAmount;
+ Decimal? maxAmount;
+
+ Decimal? rate;
+ Estimate? estimate;
+
+ FixedRateMarket? _market;
+ FixedRateMarket? get market => _market;
+
+ Currency? _from;
+ Currency? _to;
+
+ String? get fromTicker {
+ switch (exchangeType) {
+ case ExchangeRateType.estimated:
+ return _from?.ticker;
+ case ExchangeRateType.fixed:
+ return _market?.from;
+ }
+ }
+
+ String? get toTicker {
+ switch (exchangeType) {
+ case ExchangeRateType.estimated:
+ return _to?.ticker;
+ case ExchangeRateType.fixed:
+ return _market?.to;
+ }
+ }
+
+ void Function(String)? _onError;
+
+ Currency? get from => _from;
+ Currency? get to => _to;
+
+ void setCurrencies(Currency from, Currency to) {
+ _from = from;
+ _to = to;
+ }
+
+ String get warning {
+ if (reversed) {
+ if (toTicker != null && toAmount != null) {
+ if (minAmount != null && toAmount! < minAmount!) {
+ return "Minimum amount ${minAmount!.toString()} ${toTicker!.toUpperCase()}";
+ } else if (maxAmount != null && toAmount! > maxAmount!) {
+ return "Maximum amount ${maxAmount!.toString()} ${toTicker!.toUpperCase()}";
+ }
+ }
+ } else {
+ if (fromTicker != null && fromAmount != null) {
+ if (minAmount != null && fromAmount! < minAmount!) {
+ return "Minimum amount ${minAmount!.toString()} ${fromTicker!.toUpperCase()}";
+ } else if (maxAmount != null && fromAmount! > maxAmount!) {
+ return "Maximum amount ${maxAmount!.toString()} ${fromTicker!.toUpperCase()}";
+ }
+ }
+ }
+
+ return "";
+ }
+
+ String get fromAmountString => fromAmount?.toStringAsFixed(8) ?? "";
+ String get toAmountString => toAmount?.toStringAsFixed(8) ?? "";
+
+ bool get canExchange {
+ switch (exchangeType) {
+ case ExchangeRateType.estimated:
+ return fromAmount != null &&
+ fromAmount != Decimal.zero &&
+ toAmount != null &&
+ rate != null &&
+ warning.isEmpty;
+ case ExchangeRateType.fixed:
+ return _market != null &&
+ fromAmount != null &&
+ toAmount != null &&
+ warning.isEmpty;
+ }
+ }
+
+ void clearAmounts(bool shouldNotifyListeners) {
+ fromAmount = null;
+ toAmount = null;
+ minAmount = null;
+ maxAmount = null;
+ rate = null;
+
+ if (shouldNotifyListeners) {
+ notifyListeners();
+ }
+ }
+
+ Future 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 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 updateTo(Currency to, bool shouldNotifyListeners) async {
+ try {
+ _to = to;
+ if (_from == null) {
+ rate = null;
+ notifyListeners();
+ return;
+ }
+
+ await updateRanges(shouldNotifyListeners: false);
+
+ await updateEstimate(
+ shouldNotifyListeners: false,
+ reversed: reversed,
+ );
+
+ 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 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,
+ );
+
+ 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 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 (shouldNotifyListeners) {
+ notifyListeners();
+ }
+ }
+
+ void _onExchangeRateTypeChanged() {
+ print("_onExchangeRateTypeChanged");
+ }
+
+ void _onExchangeTypeChanged() {
+ updateRanges(shouldNotifyListeners: true).then(
+ (_) => updateEstimate(
+ shouldNotifyListeners: true,
+ reversed: reversed,
+ ),
+ );
+ }
+
+ Future 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) {
+ Logging.instance.log(
+ "Tried to $runtimeType.updateRanges where (from: $_fromTicker || to: $_toTicker) for: $exchange",
+ level: LogLevel.Info,
+ );
+ return;
+ }
+ final response = await exchange?.getRange(
+ _fromTicker,
+ _toTicker,
+ exchangeType == ExchangeRateType.fixed,
+ );
+
+ if (response?.value == null) {
+ Logging.instance.log(
+ "Tried to $runtimeType.updateRanges for: $exchange where response: $response",
+ level: LogLevel.Info,
+ );
+ return;
+ }
+
+ final range = response!.value!;
+
+ minAmount = range.min;
+ maxAmount = range.max;
+
+ debugPrint(
+ "updated range for: $exchange for $_fromTicker-$_toTicker: $range");
+
+ if (shouldNotifyListeners) {
+ notifyListeners();
+ }
+ }
+
+ Future 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 ||
+ amount == null ||
+ amount <= Decimal.zero) {
+ Logging.instance.log(
+ "Tried to $runtimeType.updateEstimate for: $exchange where (from: $fromTicker || to: $toTicker || amount: $amount)",
+ level: LogLevel.Info,
+ );
+ return;
+ }
+ final response = await exchange?.getEstimate(
+ fromTicker!,
+ toTicker!,
+ amount,
+ exchangeType == ExchangeRateType.fixed,
+ reversed,
+ );
+
+ if (response?.value == null) {
+ Logging.instance.log(
+ "Tried to $runtimeType.updateEstimate for: $exchange where response: $response",
+ level: LogLevel.Info,
+ );
+ return;
+ }
+
+ estimate = response!.value!;
+
+ if (reversed) {
+ fromAmount = estimate!.estimatedAmount;
+ } else {
+ toAmount = estimate!.estimatedAmount;
+ }
+
+ rate = (toAmount! / fromAmount!).toDecimal(scaleOnInfinitePrecision: 12);
+
+ debugPrint(
+ "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate");
+
+ if (shouldNotifyListeners) {
+ notifyListeners();
+ }
+ }
+
+ void setOnError({
+ required void Function(String)? onError,
+ bool shouldNotifyListeners = false,
+ }) {
+ _onError = onError;
+ if (shouldNotifyListeners) {
+ notifyListeners();
+ }
+ }
+
+ Future swap({FixedRateMarket? market}) async {
+ final Decimal? newToAmount = fromAmount;
+ final Decimal? newFromAmount = toAmount;
+
+ fromAmount = newFromAmount;
+ toAmount = newToAmount;
+
+ minAmount = null;
+ maxAmount = null;
+
+ switch (exchangeType) {
+ case ExchangeRateType.estimated:
+ final Currency? newTo = from;
+ final Currency? newFrom = to;
+
+ _to = newTo;
+ _from = newFrom;
+
+ await updateRanges(shouldNotifyListeners: false);
+
+ await updateEstimate(
+ shouldNotifyListeners: false,
+ reversed: reversed,
+ );
+ break;
+ case ExchangeRateType.fixed:
+ await updateMarket(market, false);
+ break;
+ }
+
+ notifyListeners();
+ }
+}
diff --git a/lib/models/exchange/fixed_rate_exchange_form_state.dart b/lib/models/exchange/fixed_rate_exchange_form_state.dart
deleted file mode 100644
index b75193158..000000000
--- a/lib/models/exchange/fixed_rate_exchange_form_state.dart
+++ /dev/null
@@ -1,178 +0,0 @@
-import 'package:decimal/decimal.dart';
-import 'package:flutter/cupertino.dart';
-import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart';
-import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart';
-import 'package:stackwallet/services/change_now/change_now.dart';
-import 'package:stackwallet/utilities/logger.dart';
-
-class FixedRateExchangeFormState extends ChangeNotifier {
- Decimal? _fromAmount;
- Decimal? _toAmount;
-
- FixedRateMarket? _market;
- FixedRateMarket? get market => _market;
-
- CNExchangeEstimate? _estimate;
- CNExchangeEstimate? get estimate => _estimate;
-
- Decimal? get rate {
- if (_estimate == null) {
- return null;
- } else {
- return (_estimate!.toAmount / _estimate!.fromAmount)
- .toDecimal(scaleOnInfinitePrecision: 12);
- }
- }
-
- Future swap(FixedRateMarket reverseFixedRateMarket) async {
- final Decimal? tmp = _fromAmount;
- _fromAmount = _toAmount;
- _toAmount = tmp;
-
- await updateMarket(reverseFixedRateMarket, false);
- await updateRateEstimate(CNEstimateType.direct);
- _toAmount = _estimate?.toAmount ?? Decimal.zero;
- notifyListeners();
- }
-
- String get fromAmountString =>
- _fromAmount == null ? "" : _fromAmount!.toStringAsFixed(8);
- String get toAmountString =>
- _toAmount == null ? "" : _toAmount!.toStringAsFixed(8);
-
- Future 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 updateRateEstimate(CNEstimateType.direct);
- }
- }
- }
-
- if (shouldNotifyListeners) {
- notifyListeners();
- }
- }
-
- String get rateDisplayString {
- if (_market == null || _estimate == null) {
- return "N/A";
- } else {
- return "1 ${_estimate!.fromCurrency.toUpperCase()} ~${rate!.toStringAsFixed(8)} ${_estimate!.toCurrency.toUpperCase()}";
- }
- }
-
- bool get canExchange {
- return _market != null &&
- _fromAmount != null &&
- _toAmount != null &&
- sendAmountWarning.isEmpty;
- }
-
- String get sendAmountWarning {
- if (_market != null && _fromAmount != null) {
- if (_fromAmount! < _market!.min) {
- return "Minimum amount ${_market!.min.toString()} ${_market!.from.toUpperCase()}";
- } else if (_fromAmount! > _market!.max) {
- return "Maximum amount ${_market!.max.toString()} ${_market!.from.toUpperCase()}";
- }
- }
-
- return "";
- }
-
- Future setToAmountAndCalculateFromAmount(
- Decimal newToAmount,
- bool shouldNotifyListeners,
- ) async {
- _toAmount = newToAmount;
-
- if (shouldNotifyListeners) {
- await updateRateEstimate(CNEstimateType.reverse);
- notifyListeners();
- }
- }
-
- Future setFromAmountAndCalculateToAmount(
- Decimal newFromAmount,
- bool shouldNotifyListeners,
- ) async {
- _fromAmount = newFromAmount;
-
- if (shouldNotifyListeners) {
- await updateRateEstimate(CNEstimateType.direct);
- notifyListeners();
- }
- }
-
- void Function(String)? _onError;
-
- void setOnError({
- required void Function(String)? onError,
- bool shouldNotifyListeners = false,
- }) {
- _onError = onError;
- if (shouldNotifyListeners) {
- notifyListeners();
- }
- }
-
- Future updateRateEstimate(CNEstimateType direction) async {
- if (market != null) {
- Decimal? amount;
- // set amount based on trade estimate direction
- switch (direction) {
- case CNEstimateType.direct:
- if (_fromAmount != null
- // &&
- // market!.min >= _fromAmount! &&
- // _fromAmount! <= market!.max
- ) {
- amount = _fromAmount!;
- }
- break;
- case CNEstimateType.reverse:
- if (_toAmount != null
- // &&
- // market!.min >= _toAmount! &&
- // _toAmount! <= market!.max
- ) {
- amount = _toAmount!;
- }
- break;
- }
-
- if (amount != null && market != null && amount > Decimal.zero) {
- final response = await ChangeNow.instance.getEstimatedExchangeAmountV2(
- fromTicker: market!.from,
- toTicker: market!.to,
- fromOrTo: direction,
- flow: CNFlowType.fixedRate,
- amount: amount,
- );
-
- if (response.value != null) {
- // update estimate if response succeeded
- _estimate = response.value;
-
- _toAmount = _estimate?.toAmount;
- _fromAmount = _estimate?.fromAmount;
- notifyListeners();
- } else if (response.exception != null) {
- Logging.instance.log("updateRateEstimate(): ${response.exception}",
- level: LogLevel.Warning);
- }
- }
- }
- }
-}
diff --git a/lib/models/exchange/incomplete_exchange.dart b/lib/models/exchange/incomplete_exchange.dart
index cd80e402f..58219dc5d 100644
--- a/lib/models/exchange/incomplete_exchange.dart
+++ b/lib/models/exchange/incomplete_exchange.dart
@@ -1,5 +1,5 @@
import 'package:decimal/decimal.dart';
-import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
+import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
class IncompleteExchangeModel {
@@ -13,12 +13,14 @@ class IncompleteExchangeModel {
final ExchangeRateType rateType;
+ final bool reversed;
+
String? recipientAddress;
String? refundAddress;
String? rateId;
- ExchangeTransaction? trade;
+ Trade? trade;
IncompleteExchangeModel({
required this.sendTicker,
@@ -27,6 +29,7 @@ class IncompleteExchangeModel {
required this.sendAmount,
required this.receiveAmount,
required this.rateType,
+ required this.reversed,
this.rateId,
});
}
diff --git a/lib/models/exchange/change_now/currency.dart b/lib/models/exchange/response_objects/currency.dart
similarity index 85%
rename from lib/models/exchange/change_now/currency.dart
rename to lib/models/exchange/response_objects/currency.dart
index add872879..0850f9a38 100644
--- a/lib/models/exchange/change_now/currency.dart
+++ b/lib/models/exchange/response_objects/currency.dart
@@ -5,12 +5,18 @@ class Currency {
/// Currency name
final String name;
+ /// Currency network
+ final String network;
+
/// 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;
@@ -30,8 +36,10 @@ class Currency {
Currency({
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,
@@ -44,8 +52,10 @@ class Currency {
return Currency(
ticker: json["ticker"] as String,
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,
@@ -61,8 +71,10 @@ class Currency {
final map = {
"ticker": ticker,
"name": name,
+ "network": network,
"image": image,
"hasExternalId": hasExternalId,
+ "externalId": externalId,
"isFiat": isFiat,
"featured": featured,
"isStable": isStable,
@@ -79,8 +91,10 @@ class Currency {
Currency copyWith({
String? ticker,
String? name,
+ String? network,
String? image,
bool? hasExternalId,
+ String? externalId,
bool? isFiat,
bool? featured,
bool? isStable,
@@ -90,8 +104,10 @@ class Currency {
return Currency(
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,
diff --git a/lib/models/exchange/response_objects/estimate.dart b/lib/models/exchange/response_objects/estimate.dart
new file mode 100644
index 000000000..7df490079
--- /dev/null
+++ b/lib/models/exchange/response_objects/estimate.dart
@@ -0,0 +1,46 @@
+import 'package:decimal/decimal.dart';
+import 'package:stackwallet/utilities/logger.dart';
+
+class Estimate {
+ final Decimal estimatedAmount;
+ final bool fixedRate;
+ final bool reversed;
+ final String? warningMessage;
+ final String? rateId;
+
+ Estimate({
+ required this.estimatedAmount,
+ required this.fixedRate,
+ required this.reversed,
+ this.warningMessage,
+ this.rateId,
+ });
+
+ factory Estimate.fromMap(Map map) {
+ try {
+ return Estimate(
+ estimatedAmount: Decimal.parse(map["estimatedAmount"] as String),
+ fixedRate: map["fixedRate"] as bool,
+ reversed: map["reversed"] as bool,
+ warningMessage: map["warningMessage"] as String?,
+ rateId: map["rateId"] as String?,
+ );
+ } catch (e, s) {
+ Logging.instance.log("Estimate.fromMap(): $e\n$s", level: LogLevel.Error);
+ rethrow;
+ }
+ }
+
+ Map toMap() {
+ return {
+ "estimatedAmount": estimatedAmount.toString(),
+ "fixedRate": fixedRate,
+ "reversed": reversed,
+ "warningMessage": warningMessage,
+ "rateId": rateId,
+ };
+ }
+
+ @override
+ String toString() => "Estimate: ${toMap()}";
+}
diff --git a/lib/models/exchange/change_now/fixed_rate_market.dart b/lib/models/exchange/response_objects/fixed_rate_market.dart
similarity index 77%
rename from lib/models/exchange/change_now/fixed_rate_market.dart
rename to lib/models/exchange/response_objects/fixed_rate_market.dart
index 9b8b2db80..7e2b363c9 100644
--- a/lib/models/exchange/change_now/fixed_rate_market.dart
+++ b/lib/models/exchange/response_objects/fixed_rate_market.dart
@@ -1,4 +1,5 @@
import 'package:decimal/decimal.dart';
+import 'package:stackwallet/utilities/logger.dart';
class FixedRateMarket {
/// Currency ticker
@@ -20,7 +21,7 @@ class FixedRateMarket {
/// Network fee for transferring funds between wallets, it should
/// be deducted from the result.
- final Decimal minerFee;
+ final Decimal? minerFee;
FixedRateMarket({
required this.from,
@@ -31,7 +32,7 @@ class FixedRateMarket {
required this.minerFee,
});
- factory FixedRateMarket.fromJson(Map json) {
+ factory FixedRateMarket.fromMap(Map json) {
try {
return FixedRateMarket(
from: json["from"] as String,
@@ -39,15 +40,19 @@ class FixedRateMarket {
min: Decimal.parse(json["min"].toString()),
max: Decimal.parse(json["max"].toString()),
rate: Decimal.parse(json["rate"].toString()),
- minerFee: Decimal.parse(json["minerFee"].toString()),
+ minerFee: Decimal.tryParse(json["minerFee"].toString()),
+ );
+ } catch (e, s) {
+ Logging.instance.log(
+ "FixedRateMarket.fromMap(): $e\n$s",
+ level: LogLevel.Error,
);
- } catch (e) {
rethrow;
}
}
- Map toJson() {
- final map = {
+ Map toMap() {
+ return {
"from": from,
"to": to,
"min": min,
@@ -55,8 +60,6 @@ class FixedRateMarket {
"rate": rate,
"minerFee": minerFee,
};
-
- return map;
}
FixedRateMarket copyWith({
@@ -78,7 +81,5 @@ class FixedRateMarket {
}
@override
- String toString() {
- return "FixedRateMarket: ${toJson()}";
- }
+ String toString() => "FixedRateMarket: ${toMap()}";
}
diff --git a/lib/models/exchange/response_objects/pair.dart b/lib/models/exchange/response_objects/pair.dart
new file mode 100644
index 000000000..1f12acf08
--- /dev/null
+++ b/lib/models/exchange/response_objects/pair.dart
@@ -0,0 +1,73 @@
+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 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 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()}";
+}
diff --git a/lib/models/exchange/response_objects/range.dart b/lib/models/exchange/response_objects/range.dart
new file mode 100644
index 000000000..715a3ecce
--- /dev/null
+++ b/lib/models/exchange/response_objects/range.dart
@@ -0,0 +1,32 @@
+import 'package:decimal/decimal.dart';
+
+class Range {
+ final Decimal? min;
+ final Decimal? max;
+
+ Range({this.min, this.max});
+
+ Range copyWith({
+ Decimal? min,
+ Decimal? max,
+ }) {
+ return Range(
+ min: min ?? this.min,
+ max: max ?? this.max,
+ );
+ }
+
+ Map toMap() {
+ final map = {
+ "min": min?.toString(),
+ "max": max?.toString(),
+ };
+
+ return map;
+ }
+
+ @override
+ String toString() {
+ return "Range: ${toMap()}";
+ }
+}
diff --git a/lib/models/exchange/response_objects/trade.dart b/lib/models/exchange/response_objects/trade.dart
new file mode 100644
index 000000000..7fc019aa9
--- /dev/null
+++ b/lib/models/exchange/response_objects/trade.dart
@@ -0,0 +1,239 @@
+import 'package:hive/hive.dart';
+import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
+import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
+
+part 'trade.g.dart';
+
+@HiveType(typeId: Trade.typeId)
+class Trade {
+ static const typeId = 22;
+
+ @HiveField(0)
+ final String uuid;
+
+ @HiveField(1)
+ final String tradeId;
+
+ @HiveField(2)
+ final String rateType;
+
+ @HiveField(3)
+ final String direction;
+
+ @HiveField(4)
+ final DateTime timestamp;
+
+ @HiveField(5)
+ final DateTime updatedAt;
+
+ @HiveField(6)
+ final String payInCurrency;
+
+ @HiveField(7)
+ final String payInAmount;
+
+ @HiveField(8)
+ final String payInAddress;
+
+ @HiveField(9)
+ final String payInNetwork;
+
+ @HiveField(10)
+ final String payInExtraId;
+
+ @HiveField(11)
+ final String payInTxid;
+
+ @HiveField(12)
+ final String payOutCurrency;
+
+ @HiveField(13)
+ final String payOutAmount;
+
+ @HiveField(14)
+ final String payOutAddress;
+
+ @HiveField(15)
+ final String payOutNetwork;
+
+ @HiveField(16)
+ final String payOutExtraId;
+
+ @HiveField(17)
+ final String payOutTxid;
+
+ @HiveField(18)
+ final String refundAddress;
+
+ @HiveField(19)
+ final String refundExtraId;
+
+ @HiveField(20)
+ final String status;
+
+ @HiveField(21)
+ final String exchangeName;
+
+ const Trade({
+ required this.uuid,
+ required this.tradeId,
+ required this.rateType,
+ required this.direction,
+ required this.timestamp,
+ required this.updatedAt,
+ required this.payInCurrency,
+ required this.payInAmount,
+ required this.payInAddress,
+ required this.payInNetwork,
+ required this.payInExtraId,
+ required this.payInTxid,
+ required this.payOutCurrency,
+ required this.payOutAmount,
+ required this.payOutAddress,
+ required this.payOutNetwork,
+ required this.payOutExtraId,
+ required this.payOutTxid,
+ required this.refundAddress,
+ required this.refundExtraId,
+ required this.status,
+ required this.exchangeName,
+ });
+
+ Trade copyWith({
+ String? tradeId,
+ String? rateType,
+ String? direction,
+ DateTime? timestamp,
+ DateTime? updatedAt,
+ String? payInCurrency,
+ String? payInAmount,
+ String? payInAddress,
+ String? payInNetwork,
+ String? payInExtraId,
+ String? payInTxid,
+ String? payOutCurrency,
+ String? payOutAmount,
+ String? payOutAddress,
+ String? payOutNetwork,
+ String? payOutExtraId,
+ String? payOutTxid,
+ String? refundAddress,
+ String? refundExtraId,
+ String? status,
+ String? exchangeName,
+ }) {
+ return Trade(
+ uuid: uuid,
+ tradeId: tradeId ?? this.tradeId,
+ rateType: rateType ?? this.rateType,
+ direction: direction ?? this.direction,
+ timestamp: timestamp ?? this.timestamp,
+ updatedAt: updatedAt ?? this.updatedAt,
+ payInCurrency: payInCurrency ?? this.payInCurrency,
+ payInAmount: payInAmount ?? this.payInAmount,
+ payInAddress: payInAddress ?? this.payInAddress,
+ payInNetwork: payInNetwork ?? this.payInNetwork,
+ payInExtraId: payInExtraId ?? this.payInExtraId,
+ payInTxid: payInTxid ?? this.payInTxid,
+ payOutCurrency: payOutCurrency ?? this.payOutCurrency,
+ payOutAmount: payOutAmount ?? this.payOutAmount,
+ payOutAddress: payOutAddress ?? this.payOutAddress,
+ payOutNetwork: payOutNetwork ?? this.payOutNetwork,
+ payOutExtraId: payOutExtraId ?? this.payOutExtraId,
+ payOutTxid: payOutTxid ?? this.payOutTxid,
+ refundAddress: refundAddress ?? this.refundAddress,
+ refundExtraId: refundExtraId ?? this.refundExtraId,
+ status: status ?? this.status,
+ exchangeName: exchangeName ?? this.exchangeName,
+ );
+ }
+
+ Map toMap() {
+ return {
+ "uuid": uuid,
+ "tradeId": tradeId,
+ "rateType": rateType,
+ "direction": direction,
+ "timestamp": timestamp.toIso8601String(),
+ "updatedAt": updatedAt.toIso8601String(),
+ "payInCurrency": payInCurrency,
+ "payInAmount": payInAmount,
+ "payInAddress": payInAddress,
+ "payInNetwork": payInNetwork,
+ "payInExtraId": payInExtraId,
+ "payInTxid": payInTxid,
+ "payOutCurrency": payOutCurrency,
+ "payOutAmount": payOutAmount,
+ "payOutAddress": payOutAddress,
+ "payOutNetwork": payOutNetwork,
+ "payOutExtraId": payOutExtraId,
+ "payOutTxid": payOutTxid,
+ "refundAddress": refundAddress,
+ "refundExtraId": refundExtraId,
+ "status": status,
+ "exchangeName": exchangeName,
+ };
+ }
+
+ factory Trade.fromMap(Map map) {
+ return Trade(
+ uuid: map["uuid"] as String,
+ tradeId: map["tradeId"] as String,
+ rateType: map["rateType"] as String,
+ direction: map["direction"] as String,
+ timestamp: DateTime.parse(map["timestamp"] as String),
+ updatedAt: DateTime.parse(map["updatedAt"] as String),
+ payInCurrency: map["payInCurrency"] as String,
+ payInAmount: map["payInAmount"] as String,
+ payInAddress: map["payInAddress"] as String,
+ payInNetwork: map["payInNetwork"] as String,
+ payInExtraId: map["payInExtraId"] as String,
+ payInTxid: map["payInTxid"] as String,
+ payOutCurrency: map["payOutCurrency"] as String,
+ payOutAmount: map["payOutAmount"] as String,
+ payOutAddress: map["payOutAddress"] as String,
+ payOutNetwork: map["payOutNetwork"] as String,
+ payOutExtraId: map["payOutExtraId"] as String,
+ payOutTxid: map["payOutTxid"] as String,
+ refundAddress: map["refundAddress"] as String,
+ refundExtraId: map["refundExtraId"] as String,
+ status: map["status"] as String,
+ exchangeName: map["exchangeName"] as String,
+ );
+ }
+
+ factory Trade.fromExchangeTransaction(
+ ExchangeTransaction exTx, bool reversed) {
+ return Trade(
+ uuid: exTx.uuid,
+ tradeId: exTx.id,
+ rateType: "",
+ direction: reversed ? "reverse" : "direct",
+ timestamp: exTx.date,
+ updatedAt: DateTime.tryParse(exTx.statusObject!.updatedAt) ?? exTx.date,
+ payInCurrency: exTx.fromCurrency,
+ payInAmount: exTx.statusObject!.amountSendDecimal.isEmpty
+ ? exTx.statusObject!.expectedSendAmountDecimal
+ : exTx.statusObject!.amountSendDecimal,
+ payInAddress: exTx.payinAddress,
+ payInNetwork: "",
+ payInExtraId: exTx.payinExtraId,
+ payInTxid: exTx.statusObject!.payinHash,
+ payOutCurrency: exTx.toCurrency,
+ payOutAmount: exTx.amount,
+ payOutAddress: exTx.payoutAddress,
+ payOutNetwork: "",
+ payOutExtraId: exTx.payoutExtraId,
+ payOutTxid: exTx.statusObject!.payoutHash,
+ refundAddress: exTx.refundAddress,
+ refundExtraId: exTx.refundExtraId,
+ status: exTx.statusObject!.status.name,
+ exchangeName: ChangeNowExchange.exchangeName,
+ );
+ }
+
+ @override
+ String toString() {
+ return toMap().toString();
+ }
+}
diff --git a/lib/models/exchange/response_objects/trade.g.dart b/lib/models/exchange/response_objects/trade.g.dart
new file mode 100644
index 000000000..12d77bd83
--- /dev/null
+++ b/lib/models/exchange/response_objects/trade.g.dart
@@ -0,0 +1,104 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'trade.dart';
+
+// **************************************************************************
+// TypeAdapterGenerator
+// **************************************************************************
+
+class TradeAdapter extends TypeAdapter {
+ @override
+ final int typeId = 22;
+
+ @override
+ Trade read(BinaryReader reader) {
+ final numOfFields = reader.readByte();
+ final fields = {
+ for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
+ };
+ return Trade(
+ uuid: fields[0] as String,
+ tradeId: fields[1] as String,
+ rateType: fields[2] as String,
+ direction: fields[3] as String,
+ timestamp: fields[4] as DateTime,
+ updatedAt: fields[5] as DateTime,
+ payInCurrency: fields[6] as String,
+ payInAmount: fields[7] as String,
+ payInAddress: fields[8] as String,
+ payInNetwork: fields[9] as String,
+ payInExtraId: fields[10] as String,
+ payInTxid: fields[11] as String,
+ payOutCurrency: fields[12] as String,
+ payOutAmount: fields[13] as String,
+ payOutAddress: fields[14] as String,
+ payOutNetwork: fields[15] as String,
+ payOutExtraId: fields[16] as String,
+ payOutTxid: fields[17] as String,
+ refundAddress: fields[18] as String,
+ refundExtraId: fields[19] as String,
+ status: fields[20] as String,
+ exchangeName: fields[21] as String,
+ );
+ }
+
+ @override
+ void write(BinaryWriter writer, Trade obj) {
+ writer
+ ..writeByte(22)
+ ..writeByte(0)
+ ..write(obj.uuid)
+ ..writeByte(1)
+ ..write(obj.tradeId)
+ ..writeByte(2)
+ ..write(obj.rateType)
+ ..writeByte(3)
+ ..write(obj.direction)
+ ..writeByte(4)
+ ..write(obj.timestamp)
+ ..writeByte(5)
+ ..write(obj.updatedAt)
+ ..writeByte(6)
+ ..write(obj.payInCurrency)
+ ..writeByte(7)
+ ..write(obj.payInAmount)
+ ..writeByte(8)
+ ..write(obj.payInAddress)
+ ..writeByte(9)
+ ..write(obj.payInNetwork)
+ ..writeByte(10)
+ ..write(obj.payInExtraId)
+ ..writeByte(11)
+ ..write(obj.payInTxid)
+ ..writeByte(12)
+ ..write(obj.payOutCurrency)
+ ..writeByte(13)
+ ..write(obj.payOutAmount)
+ ..writeByte(14)
+ ..write(obj.payOutAddress)
+ ..writeByte(15)
+ ..write(obj.payOutNetwork)
+ ..writeByte(16)
+ ..write(obj.payOutExtraId)
+ ..writeByte(17)
+ ..write(obj.payOutTxid)
+ ..writeByte(18)
+ ..write(obj.refundAddress)
+ ..writeByte(19)
+ ..write(obj.refundExtraId)
+ ..writeByte(20)
+ ..write(obj.status)
+ ..writeByte(21)
+ ..write(obj.exchangeName);
+ }
+
+ @override
+ int get hashCode => typeId.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is TradeAdapter &&
+ runtimeType == other.runtimeType &&
+ typeId == other.typeId;
+}
diff --git a/lib/models/exchange/simpleswap/sp_available_currencies.dart b/lib/models/exchange/simpleswap/sp_available_currencies.dart
new file mode 100644
index 000000000..787ad88ab
--- /dev/null
+++ b/lib/models/exchange/simpleswap/sp_available_currencies.dart
@@ -0,0 +1,30 @@
+import 'package:stackwallet/models/exchange/response_objects/currency.dart';
+import 'package:stackwallet/models/exchange/response_objects/pair.dart';
+
+class SPAvailableCurrencies {
+ final List floatingRateCurrencies = [];
+ final List fixedRateCurrencies = [];
+
+ final List floatingRatePairs = [];
+ final List fixedRatePairs = [];
+
+ void updateFloatingCurrencies(List newCurrencies) {
+ floatingRateCurrencies.clear();
+ floatingRateCurrencies.addAll(newCurrencies);
+ }
+
+ void updateFixedCurrencies(List newCurrencies) {
+ fixedRateCurrencies.clear();
+ fixedRateCurrencies.addAll(newCurrencies);
+ }
+
+ void updateFloatingPairs(List newPairs) {
+ floatingRatePairs.clear();
+ floatingRatePairs.addAll(newPairs);
+ }
+
+ void updateFixedPairs(List newPairs) {
+ fixedRatePairs.clear();
+ fixedRatePairs.addAll(newPairs);
+ }
+}
diff --git a/lib/models/exchange/simpleswap/sp_currency.dart b/lib/models/exchange/simpleswap/sp_currency.dart
new file mode 100644
index 000000000..5aabc20d4
--- /dev/null
+++ b/lib/models/exchange/simpleswap/sp_currency.dart
@@ -0,0 +1,99 @@
+import 'package:stackwallet/utilities/logger.dart';
+
+class SPCurrency {
+ /// currency name
+ final String name;
+
+ /// currency symbol
+ final String symbol;
+
+ /// currency network
+ final String network;
+
+ /// has this currency extra id parameter
+ final bool hasExtraId;
+
+ /// name of extra id (if exists)
+ final String? extraId;
+
+ /// relative url for currency icon svg
+ final String image;
+
+ /// informational messages about the currency they are changing
+ final List warningsFrom;
+
+ /// informational messages about the currency for which they are exchanged
+ final List warningsTo;
+
+ SPCurrency({
+ required this.name,
+ required this.symbol,
+ required this.network,
+ required this.hasExtraId,
+ required this.extraId,
+ required this.image,
+ required this.warningsFrom,
+ required this.warningsTo,
+ });
+
+ factory SPCurrency.fromJson(Map json) {
+ try {
+ return SPCurrency(
+ name: json["name"] as String,
+ symbol: json["symbol"] as String,
+ network: json["network"] as String? ?? "",
+ hasExtraId: json["has_extra_id"] as bool,
+ extraId: json["extra_id"] as String?,
+ image: json["image"] as String,
+ warningsFrom: json["warnings_from"] as List,
+ warningsTo: json["warnings_to"] as List,
+ );
+ } catch (e, s) {
+ Logging.instance.log("SPCurrency.fromJson failed to parse: $e\n$s",
+ level: LogLevel.Error);
+ rethrow;
+ }
+ }
+
+ Map toJson() {
+ final map = {
+ "name": name,
+ "symbol": symbol,
+ "network": network,
+ "has_extra_id": hasExtraId,
+ "extra_id": extraId,
+ "image": image,
+ "warnings_from": warningsFrom,
+ "warnings_to": warningsTo,
+ };
+
+ return map;
+ }
+
+ SPCurrency copyWith({
+ String? name,
+ String? symbol,
+ String? network,
+ bool? hasExtraId,
+ String? extraId,
+ String? image,
+ List? warningsFrom,
+ List? warningsTo,
+ }) {
+ return SPCurrency(
+ name: name ?? this.name,
+ symbol: symbol ?? this.symbol,
+ network: network ?? this.network,
+ hasExtraId: hasExtraId ?? this.hasExtraId,
+ extraId: extraId ?? this.extraId,
+ image: image ?? this.image,
+ warningsFrom: warningsFrom ?? this.warningsFrom,
+ warningsTo: warningsTo ?? this.warningsTo,
+ );
+ }
+
+ @override
+ String toString() {
+ return "SPCurrency: ${toJson()}";
+ }
+}
diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart
index 3ddf8932a..d77ad6b8c 100644
--- a/lib/pages/exchange_view/confirm_change_now_send.dart
+++ b/lib/pages/exchange_view/confirm_change_now_send.dart
@@ -2,7 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
+import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/models/trade_wallet_lookup.dart';
import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/sending_transaction_dialog.dart';
@@ -34,7 +34,7 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget {
final Map transactionInfo;
final String walletId;
final String routeOnSuccessName;
- final ExchangeTransaction trade;
+ final Trade trade;
@override
ConsumerState createState() =>
@@ -46,7 +46,7 @@ class _ConfirmChangeNowSendViewState
late final Map transactionInfo;
late final String walletId;
late final String routeOnSuccessName;
- late final ExchangeTransaction trade;
+ late final Trade trade;
Future _attemptSend(BuildContext context) async {
unawaited(showDialog(
@@ -75,7 +75,7 @@ class _ConfirmChangeNowSendViewState
tradeWalletLookup: TradeWalletLookup(
uuid: const Uuid().v1(),
txid: txid,
- tradeId: trade.id,
+ tradeId: trade.tradeId,
walletIds: [walletId],
),
);
@@ -207,7 +207,7 @@ class _ConfirmChangeNowSendViewState
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
- "ChangeNOW address",
+ "${trade.exchangeName} address",
style: STextStyles.smallMed12(context),
),
const SizedBox(
@@ -309,7 +309,7 @@ class _ConfirmChangeNowSendViewState
style: STextStyles.smallMed12(context),
),
Text(
- trade.id,
+ trade.tradeId,
style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right,
),
diff --git a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart
index 82a8ba0ea..d7577e960 100644
--- a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart
+++ b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
-import 'package:stackwallet/models/exchange/change_now/currency.dart';
-import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.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';
diff --git a/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart
index 28ed9accb..7c3b935b7 100644
--- a/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart
+++ b/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
-import 'package:stackwallet/models/exchange/change_now/currency.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';
diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart
new file mode 100644
index 000000000..4085ed401
--- /dev/null
+++ b/lib/pages/exchange_view/exchange_form.dart
@@ -0,0 +1,1343 @@
+import 'dart:async';
+
+import 'package:decimal/decimal.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
+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';
+import 'package:stackwallet/notifications/show_flush_bar.dart';
+import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart';
+import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.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/sub_widgets/exchange_provider_options.dart';
+import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
+import 'package:stackwallet/pages/exchange_view/sub_widgets/rate_type_toggle.dart';
+import 'package:stackwallet/providers/providers.dart';
+import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
+import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/enums/coin_enum.dart';
+import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/theme/stack_colors.dart';
+import 'package:stackwallet/widgets/custom_loading_overlay.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/loading_indicator.dart';
+import 'package:stackwallet/widgets/stack_dialog.dart';
+import 'package:tuple/tuple.dart';
+
+class ExchangeForm extends ConsumerStatefulWidget {
+ const ExchangeForm({
+ Key? key,
+ this.walletId,
+ this.coin,
+ }) : super(key: key);
+
+ final String? walletId;
+ final Coin? coin;
+
+ @override
+ ConsumerState createState() => _ExchangeFormState();
+}
+
+class _ExchangeFormState extends ConsumerState {
+ late final String? walletId;
+ late final Coin? coin;
+ late final bool walletInitiated;
+
+ late final TextEditingController _sendController;
+ late final TextEditingController _receiveController;
+ final FocusNode _sendFocusNode = FocusNode();
+ final FocusNode _receiveFocusNode = FocusNode();
+
+ bool _swapLock = false;
+
+ void sendFieldOnChanged(String value) async {
+ final newFromAmount = Decimal.tryParse(value);
+
+ ref.read(exchangeFormStateProvider).fromAmount =
+ newFromAmount ?? Decimal.zero;
+
+ if (newFromAmount == null) {
+ _receiveController.text =
+ ref.read(prefsChangeNotifierProvider).exchangeRateType ==
+ ExchangeRateType.estimated
+ ? "-"
+ : "";
+ }
+ }
+
+ void selectSendCurrency() async {
+ if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
+ ExchangeRateType.estimated) {
+ final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "-";
+ // ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? "-";
+
+ if (walletInitiated &&
+ fromTicker.toLowerCase() == coin!.ticker.toLowerCase()) {
+ // do not allow changing away from wallet coin
+ return;
+ }
+
+ List currencies;
+ switch (ref.read(currentExchangeNameStateProvider.state).state) {
+ case ChangeNowExchange.exchangeName:
+ currencies =
+ ref.read(availableChangeNowCurrenciesProvider).currencies;
+ break;
+ case SimpleSwapExchange.exchangeName:
+ currencies = ref
+ .read(availableSimpleswapCurrenciesProvider)
+ .floatingRateCurrencies;
+ break;
+ default:
+ currencies = [];
+ }
+
+ await _showFloatingRateSelectionSheet(
+ currencies: currencies,
+ excludedTicker: ref.read(exchangeFormStateProvider).toTicker ?? "-",
+ fromTicker: fromTicker,
+ onSelected: (from) =>
+ ref.read(exchangeFormStateProvider).updateFrom(from, true));
+ } else {
+ final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "";
+ final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "";
+
+ if (walletInitiated &&
+ fromTicker.toLowerCase() == coin!.ticker.toLowerCase()) {
+ // do not allow changing away from wallet coin
+ return;
+ }
+
+ switch (ref.read(currentExchangeNameStateProvider.state).state) {
+ case ChangeNowExchange.exchangeName:
+ await _showFixedRateSelectionSheet(
+ excludedTicker: toTicker,
+ fromTicker: fromTicker,
+ onSelected: (selectedFromTicker) async {
+ try {
+ final market = ref
+ .read(availableChangeNowCurrenciesProvider)
+ .markets
+ .firstWhere(
+ (e) => e.to == toTicker && e.from == selectedFromTicker,
+ );
+
+ await ref
+ .read(exchangeFormStateProvider)
+ .updateMarket(market, true);
+ } catch (e) {
+ unawaited(showDialog(
+ context: context,
+ builder: (_) => const StackDialog(
+ title: "Fixed rate market error",
+ message:
+ "Could not find the specified fixed rate trade pair",
+ ),
+ ));
+ return;
+ }
+ },
+ );
+ break;
+ case SimpleSwapExchange.exchangeName:
+ await _showFloatingRateSelectionSheet(
+ currencies: ref
+ .read(availableSimpleswapCurrenciesProvider)
+ .fixedRateCurrencies,
+ excludedTicker:
+ ref.read(exchangeFormStateProvider).toTicker ?? "-",
+ fromTicker: fromTicker,
+ onSelected: (from) =>
+ ref.read(exchangeFormStateProvider).updateFrom(from, true));
+ break;
+ default:
+ // TODO show error?
+ }
+ }
+ }
+
+ void selectReceiveCurrency() async {
+ if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
+ ExchangeRateType.estimated) {
+ final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "";
+
+ if (walletInitiated &&
+ toTicker.toLowerCase() == coin!.ticker.toLowerCase()) {
+ // do not allow changing away from wallet coin
+ return;
+ }
+
+ List currencies;
+ switch (ref.read(currentExchangeNameStateProvider.state).state) {
+ case ChangeNowExchange.exchangeName:
+ currencies =
+ ref.read(availableChangeNowCurrenciesProvider).currencies;
+ break;
+ case SimpleSwapExchange.exchangeName:
+ currencies = ref
+ .read(availableSimpleswapCurrenciesProvider)
+ .floatingRateCurrencies;
+ break;
+ default:
+ currencies = [];
+ }
+
+ await _showFloatingRateSelectionSheet(
+ currencies: currencies,
+ excludedTicker: ref.read(exchangeFormStateProvider).fromTicker ?? "",
+ fromTicker: ref.read(exchangeFormStateProvider).fromTicker ?? "",
+ onSelected: (to) =>
+ ref.read(exchangeFormStateProvider).updateTo(to, true));
+ } else {
+ final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "";
+ final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "";
+
+ if (walletInitiated &&
+ toTicker.toLowerCase() == coin!.ticker.toLowerCase()) {
+ // do not allow changing away from wallet coin
+ return;
+ }
+
+ switch (ref.read(currentExchangeNameStateProvider.state).state) {
+ case ChangeNowExchange.exchangeName:
+ await _showFixedRateSelectionSheet(
+ excludedTicker: fromTicker,
+ fromTicker: fromTicker,
+ onSelected: (selectedToTicker) async {
+ try {
+ final market = ref
+ .read(availableChangeNowCurrenciesProvider)
+ .markets
+ .firstWhere(
+ (e) => e.to == selectedToTicker && e.from == fromTicker,
+ );
+
+ await ref
+ .read(exchangeFormStateProvider)
+ .updateMarket(market, true);
+ } catch (e) {
+ unawaited(showDialog(
+ context: context,
+ builder: (_) => const StackDialog(
+ title: "Fixed rate market error",
+ message:
+ "Could not find the specified fixed rate trade pair",
+ ),
+ ));
+ return;
+ }
+ },
+ );
+ break;
+ case SimpleSwapExchange.exchangeName:
+ await _showFloatingRateSelectionSheet(
+ currencies: ref
+ .read(availableSimpleswapCurrenciesProvider)
+ .fixedRateCurrencies,
+ excludedTicker:
+ ref.read(exchangeFormStateProvider).fromTicker ?? "",
+ fromTicker: ref.read(exchangeFormStateProvider).fromTicker ?? "",
+ onSelected: (to) =>
+ ref.read(exchangeFormStateProvider).updateTo(to, true));
+ break;
+ default:
+ // TODO show error?
+ }
+ }
+ }
+
+ void receiveFieldOnChanged(String value) async {
+ final newToAmount = Decimal.tryParse(value);
+ final isEstimated =
+ ref.read(prefsChangeNotifierProvider).exchangeRateType ==
+ ExchangeRateType.estimated;
+ if (!isEstimated) {
+ ref.read(exchangeFormStateProvider).toAmount =
+ newToAmount ?? Decimal.zero;
+ }
+ if (newToAmount == null) {
+ _sendController.text = "";
+ }
+ }
+
+ Future _swap() async {
+ _swapLock = true;
+ _sendFocusNode.unfocus();
+ _receiveFocusNode.unfocus();
+
+ unawaited(
+ showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => WillPopScope(
+ onWillPop: () async => false,
+ child: Container(
+ color: Theme.of(context)
+ .extension()!
+ .overlay
+ .withOpacity(0.6),
+ child: const CustomLoadingOverlay(
+ message: "Updating exchange rate",
+ eventBus: null,
+ ),
+ ),
+ ),
+ ),
+ );
+
+ if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
+ ExchangeRateType.estimated) {
+ await ref.read(exchangeFormStateProvider).swap();
+ } else {
+ switch (ref.read(currentExchangeNameStateProvider.state).state) {
+ case ChangeNowExchange.exchangeName:
+ final from = ref.read(exchangeFormStateProvider).fromTicker;
+ final to = ref.read(exchangeFormStateProvider).toTicker;
+
+ if (to != null && from != null) {
+ final markets = ref
+ .read(availableChangeNowCurrenciesProvider)
+ .markets
+ .where((e) => e.from == to && e.to == from);
+
+ if (markets.isNotEmpty) {
+ await ref
+ .read(exchangeFormStateProvider)
+ .swap(market: markets.first);
+ }
+ }
+ break;
+ case SimpleSwapExchange.exchangeName:
+ await ref.read(exchangeFormStateProvider).swap();
+ break;
+ default:
+ //
+ }
+ }
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ _swapLock = false;
+ }
+
+ Future _showFloatingRateSelectionSheet({
+ required List currencies,
+ required String excludedTicker,
+ required String fromTicker,
+ required void Function(Currency) onSelected,
+ }) async {
+ _sendFocusNode.unfocus();
+ _receiveFocusNode.unfocus();
+
+ List allPairs;
+
+ switch (ref.read(currentExchangeNameStateProvider.state).state) {
+ case ChangeNowExchange.exchangeName:
+ allPairs = ref.read(availableChangeNowCurrenciesProvider).pairs;
+ break;
+ case SimpleSwapExchange.exchangeName:
+ allPairs = ref.read(exchangeFormStateProvider).exchangeType ==
+ ExchangeRateType.fixed
+ ? ref.read(availableSimpleswapCurrenciesProvider).fixedRatePairs
+ : ref.read(availableSimpleswapCurrenciesProvider).floatingRatePairs;
+ break;
+ default:
+ allPairs = [];
+ }
+
+ List availablePairs;
+ if (fromTicker.isEmpty ||
+ fromTicker == "-" ||
+ excludedTicker.isEmpty ||
+ excludedTicker == "-") {
+ availablePairs = allPairs;
+ } else if (excludedTicker == fromTicker) {
+ availablePairs = allPairs
+ .where((e) => e.from == excludedTicker)
+ .toList(growable: false);
+ } else {
+ availablePairs =
+ allPairs.where((e) => e.to == excludedTicker).toList(growable: false);
+ }
+
+ final List tickers = currencies.where((e) {
+ if (excludedTicker == fromTicker) {
+ return e.ticker != excludedTicker &&
+ availablePairs.where((e2) => e2.to == e.ticker).isNotEmpty;
+ } else {
+ return e.ticker != excludedTicker &&
+ availablePairs.where((e2) => e2.from == e.ticker).isNotEmpty;
+ }
+ }).toList(growable: false);
+
+ final result = await Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) => FloatingRateCurrencySelectionView(
+ currencies: tickers,
+ ),
+ ),
+ );
+
+ if (mounted && result is Currency) {
+ onSelected(result);
+ }
+ }
+
+ String? _fetchIconUrlFromTicker(String? ticker) {
+ if (ticker == null) return null;
+
+ Iterable possibleCurrencies;
+
+ switch (ref.read(currentExchangeNameStateProvider.state).state) {
+ case ChangeNowExchange.exchangeName:
+ possibleCurrencies = ref
+ .read(availableChangeNowCurrenciesProvider)
+ .currencies
+ .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase());
+ break;
+ case SimpleSwapExchange.exchangeName:
+ possibleCurrencies = [
+ ...ref
+ .read(availableSimpleswapCurrenciesProvider)
+ .fixedRateCurrencies
+ .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase()),
+ ...ref
+ .read(availableSimpleswapCurrenciesProvider)
+ .floatingRateCurrencies
+ .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase()),
+ ];
+ break;
+ default:
+ possibleCurrencies = [];
+ }
+
+ for (final currency in possibleCurrencies) {
+ if (currency.image.isNotEmpty) {
+ return currency.image;
+ }
+ }
+
+ return null;
+ }
+
+ Future _showFixedRateSelectionSheet({
+ required String excludedTicker,
+ required String fromTicker,
+ required void Function(String) onSelected,
+ }) async {
+ _sendFocusNode.unfocus();
+ _receiveFocusNode.unfocus();
+
+ List marketsThatPairWithExcludedTicker = [];
+
+ if (excludedTicker == "" ||
+ excludedTicker == "-" ||
+ fromTicker == "" ||
+ fromTicker == "-") {
+ marketsThatPairWithExcludedTicker =
+ ref.read(availableChangeNowCurrenciesProvider).markets;
+ } else if (excludedTicker == fromTicker) {
+ marketsThatPairWithExcludedTicker = ref
+ .read(availableChangeNowCurrenciesProvider)
+ .markets
+ .where((e) => e.from == excludedTicker && e.to != excludedTicker)
+ .toList(growable: false);
+ } else {
+ marketsThatPairWithExcludedTicker = ref
+ .read(availableChangeNowCurrenciesProvider)
+ .markets
+ .where((e) => e.to == excludedTicker && e.from != excludedTicker)
+ .toList(growable: false);
+ }
+
+ final result = await Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) => FixedRateMarketPairCoinSelectionView(
+ markets: marketsThatPairWithExcludedTicker,
+ currencies: ref.read(availableChangeNowCurrenciesProvider).currencies,
+ isFrom: excludedTicker != fromTicker,
+ ),
+ ),
+ );
+
+ if (mounted && result is String) {
+ onSelected(result);
+ }
+ }
+
+ void onRateTypeChanged(ExchangeRateType rateType) async {
+ _receiveFocusNode.unfocus();
+ _sendFocusNode.unfocus();
+
+ unawaited(
+ showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (_) => WillPopScope(
+ onWillPop: () async => false,
+ child: Container(
+ color: Theme.of(context)
+ .extension()!
+ .overlay
+ .withOpacity(0.6),
+ child: const CustomLoadingOverlay(
+ message: "Updating exchange rate",
+ eventBus: null,
+ ),
+ ),
+ ),
+ ),
+ );
+
+ final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "-";
+ final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "-";
+
+ ref.read(exchangeFormStateProvider).exchangeType = rateType;
+ ref.read(exchangeFormStateProvider).reversed = false;
+ switch (rateType) {
+ case ExchangeRateType.estimated:
+ if (!(toTicker == "-" || fromTicker == "-")) {
+ late final Iterable available;
+
+ switch (ref.read(currentExchangeNameStateProvider.state).state) {
+ case ChangeNowExchange.exchangeName:
+ available = ref
+ .read(availableChangeNowCurrenciesProvider)
+ .pairs
+ .where((e) => e.to == toTicker && e.from == fromTicker);
+ break;
+ case SimpleSwapExchange.exchangeName:
+ available = ref
+ .read(availableSimpleswapCurrenciesProvider)
+ .floatingRatePairs
+ .where((e) => e.to == toTicker && e.from == fromTicker);
+ break;
+ default:
+ available = [];
+ }
+
+ if (available.isNotEmpty) {
+ late final Iterable availableCurrencies;
+ switch (ref.read(currentExchangeNameStateProvider.state).state) {
+ case ChangeNowExchange.exchangeName:
+ availableCurrencies = ref
+ .read(availableChangeNowCurrenciesProvider)
+ .currencies
+ .where(
+ (e) => e.ticker == fromTicker || e.ticker == toTicker);
+ break;
+ case SimpleSwapExchange.exchangeName:
+ availableCurrencies = ref
+ .read(availableSimpleswapCurrenciesProvider)
+ .floatingRateCurrencies
+ .where(
+ (e) => e.ticker == fromTicker || e.ticker == toTicker);
+ break;
+ default:
+ availableCurrencies = [];
+ }
+
+ if (availableCurrencies.length > 1) {
+ final from =
+ availableCurrencies.firstWhere((e) => e.ticker == fromTicker);
+ final to =
+ availableCurrencies.firstWhere((e) => e.ticker == toTicker);
+
+ final newFromAmount = Decimal.tryParse(_sendController.text);
+ ref.read(exchangeFormStateProvider).fromAmount =
+ newFromAmount ?? Decimal.zero;
+ if (newFromAmount == null) {
+ _receiveController.text = "";
+ }
+
+ await ref.read(exchangeFormStateProvider).updateTo(to, false);
+ await ref.read(exchangeFormStateProvider).updateFrom(from, true);
+
+ _receiveController.text =
+ ref.read(exchangeFormStateProvider).toAmountString.isEmpty
+ ? "-"
+ : ref.read(exchangeFormStateProvider).toAmountString;
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ return;
+ }
+ }
+ }
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ unawaited(
+ showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message:
+ "Estimated rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last estimated rate pair.",
+ context: context,
+ ),
+ );
+ break;
+ case ExchangeRateType.fixed:
+ if (!(toTicker == "-" || fromTicker == "-")) {
+ switch (ref.read(currentExchangeNameStateProvider.state).state) {
+ case ChangeNowExchange.exchangeName:
+ FixedRateMarket? market;
+ try {
+ market = ref
+ .read(availableChangeNowCurrenciesProvider)
+ .markets
+ .firstWhere(
+ (e) => e.from == fromTicker && e.to == toTicker);
+ } catch (_) {
+ market = null;
+ }
+
+ final newFromAmount = Decimal.tryParse(_sendController.text);
+ ref.read(exchangeFormStateProvider).fromAmount =
+ newFromAmount ?? Decimal.zero;
+
+ if (newFromAmount == null) {
+ _receiveController.text = "";
+ }
+
+ await ref
+ .read(exchangeFormStateProvider)
+ .updateMarket(market, false);
+ await ref
+ .read(exchangeFormStateProvider)
+ .setFromAmountAndCalculateToAmount(
+ Decimal.tryParse(_sendController.text) ?? Decimal.zero,
+ true,
+ );
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ return;
+ case SimpleSwapExchange.exchangeName:
+ final available = ref
+ .read(availableSimpleswapCurrenciesProvider)
+ .floatingRatePairs
+ .where((e) => e.to == toTicker && e.from == fromTicker);
+ if (available.isNotEmpty) {
+ final availableCurrencies = ref
+ .read(availableSimpleswapCurrenciesProvider)
+ .fixedRateCurrencies
+ .where(
+ (e) => e.ticker == fromTicker || e.ticker == toTicker);
+ if (availableCurrencies.length > 1) {
+ final from = availableCurrencies
+ .firstWhere((e) => e.ticker == fromTicker);
+ final to = availableCurrencies
+ .firstWhere((e) => e.ticker == toTicker);
+
+ final newFromAmount = Decimal.tryParse(_sendController.text);
+ ref.read(exchangeFormStateProvider).fromAmount =
+ newFromAmount ?? Decimal.zero;
+ if (newFromAmount == null) {
+ _receiveController.text = "";
+ }
+
+ await ref.read(exchangeFormStateProvider).updateTo(to, false);
+ await ref
+ .read(exchangeFormStateProvider)
+ .updateFrom(from, true);
+
+ _receiveController.text =
+ ref.read(exchangeFormStateProvider).toAmountString.isEmpty
+ ? "-"
+ : ref.read(exchangeFormStateProvider).toAmountString;
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ return;
+ }
+ }
+
+ break;
+ default:
+ //
+ }
+ }
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ unawaited(
+ showFloatingFlushBar(
+ type: FlushBarType.warning,
+ message:
+ "Fixed rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last fixed rate pair.",
+ context: context,
+ ),
+ );
+ break;
+ }
+ }
+
+ void onExchangePressed() async {
+ final rateType = ref.read(prefsChangeNotifierProvider).exchangeRateType;
+ final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "";
+ final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "";
+ final sendAmount = ref.read(exchangeFormStateProvider).fromAmount!;
+ final estimate = ref.read(exchangeFormStateProvider).estimate!;
+
+ String rate;
+
+ switch (rateType) {
+ case ExchangeRateType.estimated:
+ bool isAvailable = false;
+ late final Iterable availableFloatingPairs;
+
+ switch (ref.read(currentExchangeNameStateProvider.state).state) {
+ case ChangeNowExchange.exchangeName:
+ availableFloatingPairs = ref
+ .read(availableChangeNowCurrenciesProvider)
+ .pairs
+ .where((e) => e.to == toTicker && e.from == fromTicker);
+ break;
+ case SimpleSwapExchange.exchangeName:
+ availableFloatingPairs = ref
+ .read(availableSimpleswapCurrenciesProvider)
+ .floatingRatePairs
+ .where((e) => e.to == toTicker && e.from == fromTicker);
+ break;
+ default:
+ availableFloatingPairs = [];
+ }
+
+ for (final pair in availableFloatingPairs) {
+ if (pair.from == fromTicker && pair.to == toTicker) {
+ isAvailable = true;
+ break;
+ }
+ }
+
+ if (!isAvailable) {
+ unawaited(showDialog(
+ context: context,
+ barrierDismissible: true,
+ builder: (_) => StackDialog(
+ title: "Selected trade pair unavailable",
+ message:
+ "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades",
+ ),
+ ));
+ return;
+ }
+ rate =
+ "1 ${fromTicker.toUpperCase()} ~${(estimate.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}";
+ break;
+ case ExchangeRateType.fixed:
+ bool? shouldCancel;
+
+ if (estimate.warningMessage != null &&
+ estimate.warningMessage!.isNotEmpty) {
+ shouldCancel = await showDialog(
+ context: context,
+ barrierDismissible: true,
+ builder: (_) => StackDialog(
+ title: "Failed to update trade estimate",
+ message:
+ "${estimate.warningMessage!}\n\nDo you want to attempt trade anyways?",
+ leftButton: TextButton(
+ style: Theme.of(context)
+ .extension()!
+ .getSecondaryEnabledButtonColor(context),
+ child: Text(
+ "Cancel",
+ style: STextStyles.itemSubtitle12(context),
+ ),
+ onPressed: () {
+ // notify return to cancel
+ Navigator.of(context).pop(true);
+ },
+ ),
+ rightButton: TextButton(
+ style: Theme.of(context)
+ .extension()!
+ .getPrimaryEnabledButtonColor(context),
+ child: Text(
+ "Attempt",
+ style: STextStyles.button(context),
+ ),
+ onPressed: () {
+ // continue and try to attempt trade
+ Navigator.of(context).pop(false);
+ },
+ ),
+ ),
+ );
+ }
+
+ if (shouldCancel is bool && shouldCancel) {
+ return;
+ }
+ rate =
+ "1 ${fromTicker.toUpperCase()} ~${ref.read(exchangeFormStateProvider).rate!.toStringAsFixed(8)} ${toTicker.toUpperCase()}";
+ break;
+ }
+
+ final model = IncompleteExchangeModel(
+ sendTicker: fromTicker.toUpperCase(),
+ receiveTicker: toTicker.toUpperCase(),
+ rateInfo: rate,
+ sendAmount: estimate.reversed ? estimate.estimatedAmount : sendAmount,
+ receiveAmount: estimate.reversed
+ ? ref.read(exchangeFormStateProvider).toAmount!
+ : estimate.estimatedAmount,
+ rateType: rateType,
+ rateId: estimate.rateId,
+ reversed: estimate.reversed,
+ );
+
+ if (mounted) {
+ if (walletInitiated) {
+ ref.read(exchangeSendFromWalletIdStateProvider.state).state =
+ Tuple2(walletId!, coin!);
+ unawaited(
+ Navigator.of(context).pushNamed(
+ Step2View.routeName,
+ arguments: model,
+ ),
+ );
+ } else {
+ ref.read(exchangeSendFromWalletIdStateProvider.state).state = null;
+ unawaited(
+ Navigator.of(context).pushNamed(
+ Step1View.routeName,
+ arguments: model,
+ ),
+ );
+ }
+ }
+ }
+
+ bool isWalletCoin(Coin? coin, bool isSend) {
+ if (coin == null) {
+ return false;
+ }
+
+ String? ticker;
+
+ if (isSend) {
+ ticker = ref.read(exchangeFormStateProvider).fromTicker;
+ } else {
+ ticker = ref.read(exchangeFormStateProvider).toTicker;
+ }
+
+ if (ticker == null) {
+ return false;
+ }
+
+ return coin.ticker.toUpperCase() == ticker.toUpperCase();
+ }
+
+ @override
+ void initState() {
+ _sendController = TextEditingController();
+ _receiveController = TextEditingController();
+
+ walletId = widget.walletId;
+ coin = widget.coin;
+ walletInitiated = walletId != null && coin != null;
+
+ if (walletInitiated) {
+ WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+ ref.read(exchangeFormStateProvider).clearAmounts(true);
+ // ref.read(fixedRateExchangeFormProvider);
+ });
+ } else {
+ final isEstimated =
+ ref.read(prefsChangeNotifierProvider).exchangeRateType ==
+ ExchangeRateType.estimated;
+ _sendController.text =
+ ref.read(exchangeFormStateProvider).fromAmountString;
+ _receiveController.text = isEstimated
+ ? "-" //ref.read(estimatedRateExchangeFormProvider).toAmountString
+ : ref.read(exchangeFormStateProvider).toAmountString;
+ }
+
+ _sendFocusNode.addListener(() async {
+ if (!_sendFocusNode.hasFocus) {
+ final newFromAmount = Decimal.tryParse(_sendController.text);
+ await ref
+ .read(exchangeFormStateProvider)
+ .setFromAmountAndCalculateToAmount(
+ newFromAmount ?? Decimal.zero, true);
+
+ if (newFromAmount == null) {
+ _receiveController.text =
+ ref.read(prefsChangeNotifierProvider).exchangeRateType ==
+ ExchangeRateType.estimated
+ ? "-"
+ : "";
+ }
+ }
+ });
+ _receiveFocusNode.addListener(() async {
+ if (!_receiveFocusNode.hasFocus) {
+ final newToAmount = Decimal.tryParse(_receiveController.text);
+ if (ref.read(prefsChangeNotifierProvider).exchangeRateType !=
+ ExchangeRateType.estimated) {
+ await ref
+ .read(exchangeFormStateProvider)
+ .setToAmountAndCalculateFromAmount(
+ newToAmount ?? Decimal.zero, true);
+ }
+ if (newToAmount == null) {
+ _sendController.text = "";
+ }
+ }
+ });
+
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ _receiveController.dispose();
+ _sendController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ debugPrint("BUILD: $runtimeType");
+
+ ref.listen(currentExchangeNameStateProvider, (previous, next) {
+ ref.read(exchangeFormStateProvider).exchange = ref.read(exchangeProvider);
+ });
+
+ final isEstimated = ref.watch(prefsChangeNotifierProvider
+ .select((pref) => pref.exchangeRateType)) ==
+ ExchangeRateType.estimated;
+
+ ref.listen(
+ exchangeFormStateProvider.select((value) => value.toAmountString),
+ (previous, String next) {
+ if (!_receiveFocusNode.hasFocus) {
+ _receiveController.text = isEstimated &&
+ ref.watch(exchangeProvider).name ==
+ SimpleSwapExchange.exchangeName &&
+ next.isEmpty
+ ? "-"
+ : next;
+ debugPrint("RECEIVE AMOUNT LISTENER ACTIVATED");
+ if (_swapLock) {
+ _sendController.text =
+ ref.read(exchangeFormStateProvider).fromAmountString;
+ }
+ }
+ });
+ ref.listen(
+ exchangeFormStateProvider.select((value) => value.fromAmountString),
+ (previous, String next) {
+ if (!_sendFocusNode.hasFocus) {
+ _sendController.text = next;
+ debugPrint("SEND AMOUNT LISTENER ACTIVATED");
+ if (_swapLock) {
+ _receiveController.text = isEstimated
+ ? ref.read(exchangeFormStateProvider).toAmountString.isEmpty
+ ? "-"
+ : ref.read(exchangeFormStateProvider).toAmountString
+ : ref.read(exchangeFormStateProvider).toAmountString;
+ }
+ }
+ });
+
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Text(
+ "You will send",
+ style: STextStyles.itemSubtitle(context).copyWith(
+ color: Theme.of(context).extension()!.textDark3,
+ ),
+ ),
+ const SizedBox(
+ height: 4,
+ ),
+ TextFormField(
+ style: STextStyles.smallMed14(context).copyWith(
+ color: Theme.of(context).extension()!.textDark,
+ ),
+ focusNode: _sendFocusNode,
+ controller: _sendController,
+ textAlign: TextAlign.right,
+ onTap: () {
+ if (_sendController.text == "-") {
+ _sendController.text = "";
+ }
+ },
+ onChanged: sendFieldOnChanged,
+ 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),
+ ],
+ decoration: InputDecoration(
+ contentPadding: const EdgeInsets.only(
+ top: 12,
+ right: 12,
+ ),
+ hintText: "0",
+ hintStyle: STextStyles.fieldLabel(context).copyWith(
+ fontSize: 14,
+ ),
+ prefixIcon: FittedBox(
+ fit: BoxFit.scaleDown,
+ alignment: Alignment.centerLeft,
+ child: GestureDetector(
+ onTap: selectSendCurrency,
+ child: Container(
+ color: Colors.transparent,
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Row(
+ children: [
+ Container(
+ width: 18,
+ height: 18,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(18),
+ ),
+ child: Builder(
+ builder: (context) {
+ final image = _fetchIconUrlFromTicker(ref.watch(
+ exchangeFormStateProvider
+ .select((value) => value.fromTicker)));
+
+ if (image != null && image.isNotEmpty) {
+ return Center(
+ child: SvgPicture.network(
+ image,
+ height: 18,
+ placeholderBuilder: (_) => Container(
+ width: 18,
+ height: 18,
+ decoration: BoxDecoration(
+ color: Theme.of(context)
+ .extension()!
+ .textFieldDefaultBG,
+ borderRadius: BorderRadius.circular(
+ 18,
+ ),
+ ),
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(
+ 18,
+ ),
+ child: const LoadingIndicator(),
+ ),
+ ),
+ ),
+ );
+ } else {
+ return Container(
+ width: 18,
+ height: 18,
+ decoration: BoxDecoration(
+ // color: Theme.of(context).extension()!.accentColorDark
+ borderRadius: BorderRadius.circular(18),
+ ),
+ child: SvgPicture.asset(
+ Assets.svg.circleQuestion,
+ width: 18,
+ height: 18,
+ color: Theme.of(context)
+ .extension()!
+ .textFieldDefaultBG,
+ ),
+ );
+ }
+ },
+ ),
+ ),
+ const SizedBox(
+ width: 6,
+ ),
+ Text(
+ ref.watch(exchangeFormStateProvider.select((value) =>
+ value.fromTicker?.toUpperCase())) ??
+ "-",
+ style: STextStyles.smallMed14(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark,
+ ),
+ ),
+ if (!isWalletCoin(coin, true))
+ const SizedBox(
+ width: 6,
+ ),
+ if (!isWalletCoin(coin, true))
+ SvgPicture.asset(
+ Assets.svg.chevronDown,
+ width: 5,
+ height: 2.5,
+ color: Theme.of(context)
+ .extension()!
+ .textDark,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(
+ height: 4,
+ ),
+ Stack(
+ children: [
+ Positioned.fill(
+ child: Align(
+ alignment: Alignment.bottomLeft,
+ child: Text(
+ "You will receive",
+ style: STextStyles.itemSubtitle(context).copyWith(
+ color:
+ Theme.of(context).extension()!.textDark3,
+ ),
+ ),
+ ),
+ ),
+ Center(
+ child: Column(
+ children: [
+ const SizedBox(
+ height: 6,
+ ),
+ GestureDetector(
+ onTap: () async {
+ await _swap();
+ },
+ child: Padding(
+ padding: const EdgeInsets.all(4),
+ child: SvgPicture.asset(
+ Assets.svg.swap,
+ width: 20,
+ height: 20,
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark,
+ ),
+ ),
+ ),
+ const SizedBox(
+ height: 6,
+ ),
+ ],
+ ),
+ ),
+ Positioned.fill(
+ child: Align(
+ alignment: ref.watch(exchangeFormStateProvider
+ .select((value) => value.reversed))
+ ? Alignment.bottomRight
+ : Alignment.topRight,
+ child: Text(
+ ref.watch(exchangeFormStateProvider
+ .select((value) => value.warning)),
+ style: STextStyles.errorSmall(context),
+ ),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 4,
+ ),
+ TextFormField(
+ style: STextStyles.smallMed14(context).copyWith(
+ color: Theme.of(context).extension()!.textDark,
+ ),
+ focusNode: _receiveFocusNode,
+ controller: _receiveController,
+ readOnly: ref.watch(prefsChangeNotifierProvider
+ .select((value) => value.exchangeRateType)) ==
+ ExchangeRateType.estimated ||
+ ref.watch(exchangeProvider).name ==
+ SimpleSwapExchange.exchangeName,
+ onTap: () {
+ if (!(ref.read(prefsChangeNotifierProvider).exchangeRateType ==
+ ExchangeRateType.estimated) &&
+ _receiveController.text == "-") {
+ _receiveController.text = "";
+ }
+ },
+ onChanged: receiveFieldOnChanged,
+ textAlign: TextAlign.right,
+ 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),
+ ],
+ decoration: InputDecoration(
+ contentPadding: const EdgeInsets.only(
+ top: 12,
+ right: 12,
+ ),
+ hintText: "0",
+ hintStyle: STextStyles.fieldLabel(context).copyWith(
+ fontSize: 14,
+ ),
+ prefixIcon: FittedBox(
+ fit: BoxFit.scaleDown,
+ child: GestureDetector(
+ onTap: selectReceiveCurrency,
+ child: Container(
+ color: Colors.transparent,
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Row(
+ children: [
+ Container(
+ width: 18,
+ height: 18,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(18),
+ ),
+ child: Builder(
+ builder: (context) {
+ final image = _fetchIconUrlFromTicker(ref.watch(
+ exchangeFormStateProvider
+ .select((value) => value.toTicker)));
+
+ if (image != null && image.isNotEmpty) {
+ return Center(
+ child: SvgPicture.network(
+ image,
+ height: 18,
+ placeholderBuilder: (_) => Container(
+ width: 18,
+ height: 18,
+ decoration: BoxDecoration(
+ color: Theme.of(context)
+ .extension()!
+ .textFieldDefaultBG,
+ borderRadius: BorderRadius.circular(18),
+ ),
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(
+ 18,
+ ),
+ child: const LoadingIndicator(),
+ ),
+ ),
+ ),
+ );
+ } else {
+ return Container(
+ width: 18,
+ height: 18,
+ decoration: BoxDecoration(
+ // color: Theme.of(context).extension()!.accentColorDark
+ borderRadius: BorderRadius.circular(18),
+ ),
+ child: SvgPicture.asset(
+ Assets.svg.circleQuestion,
+ width: 18,
+ height: 18,
+ color: Theme.of(context)
+ .extension()!
+ .textFieldDefaultBG,
+ ),
+ );
+ }
+ },
+ ),
+ ),
+ const SizedBox(
+ width: 6,
+ ),
+ Text(
+ ref.watch(exchangeFormStateProvider.select(
+ (value) => value.toTicker?.toUpperCase())) ??
+ "-",
+ style: STextStyles.smallMed14(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark,
+ ),
+ ),
+ if (!isWalletCoin(coin, false))
+ const SizedBox(
+ width: 6,
+ ),
+ if (!isWalletCoin(coin, false))
+ SvgPicture.asset(
+ Assets.svg.chevronDown,
+ width: 5,
+ height: 2.5,
+ color: Theme.of(context)
+ .extension()!
+ .textDark,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ RateTypeToggle(
+ onChanged: onRateTypeChanged,
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ ExchangeProviderOptions(
+ from: ref.watch(exchangeFormStateProvider).fromTicker,
+ to: ref.watch(exchangeFormStateProvider).toTicker,
+ fromAmount: ref.watch(exchangeFormStateProvider).fromAmount,
+ toAmount: ref.watch(exchangeFormStateProvider).toAmount,
+ fixedRate: ref.watch(prefsChangeNotifierProvider
+ .select((value) => value.exchangeRateType)) ==
+ ExchangeRateType.fixed,
+ reversed: ref.watch(
+ exchangeFormStateProvider.select((value) => value.reversed)),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ PrimaryButton(
+ enabled: ref.watch(
+ exchangeFormStateProvider.select((value) => value.canExchange)),
+ onPressed: ref.watch(exchangeFormStateProvider
+ .select((value) => value.canExchange))
+ ? onExchangePressed
+ : null,
+ label: "Exchange",
+ )
+ ],
+ );
+ }
+}
diff --git a/lib/pages/exchange_view/exchange_loading_overlay.dart b/lib/pages/exchange_view/exchange_loading_overlay.dart
index e4e6c1f27..15d8dccec 100644
--- a/lib/pages/exchange_view/exchange_loading_overlay.dart
+++ b/lib/pages/exchange_view/exchange_loading_overlay.dart
@@ -70,7 +70,7 @@ class _ExchangeLoadingOverlayViewState
.overlay
.withOpacity(0.7),
child: const CustomLoadingOverlay(
- message: "Loading ChangeNOW data", eventBus: null),
+ message: "Loading Exchange data", eventBus: null),
),
if ((_statusEst == ChangeNowLoadStatus.failed ||
_statusFixed == ChangeNowLoadStatus.failed) &&
@@ -85,9 +85,9 @@ class _ExchangeLoadingOverlayViewState
mainAxisAlignment: MainAxisAlignment.end,
children: [
StackDialog(
- title: "Failed to fetch ChangeNow data",
+ title: "Failed to fetch Exchange data",
message:
- "ChangeNOW requires a working internet connection. Tap OK to try fetching again.",
+ "Exchange requires a working internet connection. Tap OK to try fetching again.",
rightButton: TextButton(
style: Theme.of(context)
.extension()!
diff --git a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart
index 5681018de..800d1e146 100644
--- a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart
+++ b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart
@@ -348,23 +348,9 @@ class _Step2ViewState extends ConsumerState {
"sendViewScanQrButtonKey"),
onTap: () async {
try {
- // ref
- // .read(
- // shouldShowLockscreenOnResumeStateProvider
- // .state)
- // .state = false;
final qrResult =
await scanner.scan();
- // Future.delayed(
- // const Duration(seconds: 2),
- // () => ref
- // .read(
- // shouldShowLockscreenOnResumeStateProvider
- // .state)
- // .state = true,
- // );
-
final results =
AddressUtils.parseUri(
qrResult.rawContent);
@@ -385,16 +371,10 @@ class _Step2ViewState extends ConsumerState {
setState(() {});
}
} on PlatformException catch (e, s) {
- // ref
- // .read(
- // shouldShowLockscreenOnResumeStateProvider
- // .state)
- // .state = true;
- // here we ignore the exception caused by not giving permission
- // to use the camera to scan a qr code
Logging.instance.log(
- "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
- level: LogLevel.Warning);
+ "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
+ level: LogLevel.Warning,
+ );
}
},
child: const QrCodeIcon(),
@@ -585,23 +565,9 @@ class _Step2ViewState extends ConsumerState {
"sendViewScanQrButtonKey"),
onTap: () async {
try {
- // ref
- // .read(
- // shouldShowLockscreenOnResumeStateProvider
- // .state)
- // .state = false;
final qrResult =
await scanner.scan();
- // Future.delayed(
- // const Duration(seconds: 2),
- // () => ref
- // .read(
- // shouldShowLockscreenOnResumeStateProvider
- // .state)
- // .state = true,
- // );
-
final results =
AddressUtils.parseUri(
qrResult.rawContent);
@@ -622,16 +588,10 @@ class _Step2ViewState extends ConsumerState {
setState(() {});
}
} on PlatformException catch (e, s) {
- // ref
- // .read(
- // shouldShowLockscreenOnResumeStateProvider
- // .state)
- // .state = true;
- // here we ignore the exception caused by not giving permission
- // to use the camera to scan a qr code
Logging.instance.log(
- "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
- level: LogLevel.Warning);
+ "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
+ level: LogLevel.Warning,
+ );
}
},
child: const QrCodeIcon(),
@@ -680,8 +640,9 @@ class _Step2ViewState extends ConsumerState {
child: TextButton(
onPressed: () {
Navigator.of(context).pushNamed(
- Step3View.routeName,
- arguments: model);
+ Step3View.routeName,
+ arguments: model,
+ );
},
style: Theme.of(context)
.extension()!
diff --git a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart
index a7b34571b..0f03d4216 100644
--- a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart
+++ b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart
@@ -2,14 +2,14 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:stackwallet/models/exchange/change_now/change_now_response.dart';
-import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
+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/change_now_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/notifications_api.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
@@ -243,33 +243,24 @@ class _Step3ViewState extends ConsumerState {
),
);
- ChangeNowResponse
- response;
- if (model.rateType ==
- ExchangeRateType.estimated) {
- response = await ref
- .read(changeNowProvider)
- .createStandardExchangeTransaction(
- fromTicker: model.sendTicker,
- toTicker: model.receiveTicker,
- receivingAddress:
- model.recipientAddress!,
- amount: model.sendAmount,
- refundAddress: model.refundAddress!,
- );
- } else {
- response = await ref
- .read(changeNowProvider)
- .createFixedRateExchangeTransaction(
- fromTicker: model.sendTicker,
- toTicker: model.receiveTicker,
- receivingAddress:
- model.recipientAddress!,
- amount: model.sendAmount,
- refundAddress: model.refundAddress!,
- rateId: model.rateId!,
- );
- }
+ final ExchangeResponse response =
+ await ref
+ .read(exchangeProvider)
+ .createTrade(
+ from: model.sendTicker,
+ to: model.receiveTicker,
+ fixedRate: model.rateType !=
+ ExchangeRateType.estimated,
+ amount: model.reversed
+ ? model.receiveAmount
+ : model.sendAmount,
+ addressTo: model.recipientAddress!,
+ extraId: null,
+ addressRefund: model.refundAddress!,
+ refundExtraId: "",
+ rateId: model.rateId,
+ reversed: model.reversed,
+ );
if (response.value == null) {
if (mounted) {
@@ -293,20 +284,9 @@ class _Step3ViewState extends ConsumerState {
shouldNotifyListeners: true,
);
- final statusResponse = await ref
- .read(changeNowProvider)
- .getTransactionStatus(
- id: response.value!.id);
+ String status = response.value!.status;
- String status = "Waiting";
- if (statusResponse.value != null) {
- status = statusResponse.value!.status.name;
- }
-
- model.trade = response.value!.copyWith(
- statusString: status,
- statusObject: statusResponse.value!,
- );
+ model.trade = response.value!;
// extra info if status is waiting
if (status == "Waiting") {
@@ -318,12 +298,12 @@ class _Step3ViewState extends ConsumerState {
}
unawaited(NotificationApi.showNotification(
- changeNowId: model.trade!.id,
+ changeNowId: model.trade!.tradeId,
title: status,
- body: "Trade ID ${model.trade!.id}",
+ body: "Trade ID ${model.trade!.tradeId}",
walletId: "",
iconAssetName: Assets.svg.arrowRotate,
- date: model.trade!.date,
+ date: model.trade!.timestamp,
shouldWatchForUpdates: true,
coinName: "coinName",
));
diff --git a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart
index 222ccfc08..0921f68e0 100644
--- a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart
+++ b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart
@@ -5,7 +5,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
-import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/exchange_view/confirm_change_now_send.dart';
@@ -13,8 +12,6 @@ import 'package:stackwallet/pages/exchange_view/send_from_view.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart';
import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart';
-import 'package:stackwallet/providers/exchange/change_now_provider.dart';
-import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/utilities/assets.dart';
@@ -51,7 +48,6 @@ class _Step4ViewState extends ConsumerState {
late final ClipboardInterface clipboard;
String _statusString = "New";
- ChangeNowTransactionStatus _status = ChangeNowTransactionStatus.New;
Timer? _statusTimer;
@@ -69,13 +65,11 @@ class _Step4ViewState extends ConsumerState {
}
Future _updateStatus() async {
- final statusResponse = await ref
- .read(changeNowProvider)
- .getTransactionStatus(id: model.trade!.id);
+ final statusResponse =
+ await ref.read(exchangeProvider).updateTrade(model.trade!);
String status = "Waiting";
if (statusResponse.value != null) {
- _status = statusResponse.value!.status;
- status = _status.name;
+ status = statusResponse.value!.status;
}
// extra info if status is waiting
@@ -112,7 +106,7 @@ class _Step4ViewState extends ConsumerState {
@override
Widget build(BuildContext context) {
final bool isWalletCoin =
- _isWalletCoinAndHasWallet(model.trade!.fromCurrency, ref);
+ _isWalletCoinAndHasWallet(model.trade!.payInCurrency, ref);
return Scaffold(
backgroundColor: Theme.of(context).extension()!.background,
appBar: AppBar(
@@ -164,7 +158,7 @@ class _Step4ViewState extends ConsumerState {
height: 8,
),
Text(
- "Send ${model.sendTicker.toUpperCase()} to the address below. Once it is received, ChangeNOW will send the ${model.receiveTicker.toUpperCase()} to the recipient address you provided. You can find this trade details and check its status in the list of trades.",
+ "Send ${model.sendTicker.toUpperCase()} to the address below. Once it is received, ${model.trade!.exchangeName} will send the ${model.receiveTicker.toUpperCase()} to the recipient address you provided. You can find this trade details and check its status in the list of trades.",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
@@ -272,7 +266,7 @@ class _Step4ViewState extends ConsumerState {
GestureDetector(
onTap: () async {
final data = ClipboardData(
- text: model.trade!.payinAddress);
+ text: model.trade!.payInAddress);
await clipboard.setData(data);
unawaited(showFloatingFlushBar(
type: FlushBarType.info,
@@ -305,7 +299,7 @@ class _Step4ViewState extends ConsumerState {
height: 4,
),
Text(
- model.trade!.payinAddress,
+ model.trade!.payInAddress,
style: STextStyles.itemSubtitle12(context),
),
],
@@ -325,7 +319,7 @@ class _Step4ViewState extends ConsumerState {
Row(
children: [
Text(
- model.trade!.id,
+ model.trade!.tradeId,
style: STextStyles.itemSubtitle12(context),
),
const SizedBox(
@@ -333,8 +327,8 @@ class _Step4ViewState extends ConsumerState {
),
GestureDetector(
onTap: () async {
- final data =
- ClipboardData(text: model.trade!.id);
+ final data = ClipboardData(
+ text: model.trade!.tradeId);
await clipboard.setData(data);
unawaited(showFloatingFlushBar(
type: FlushBarType.info,
@@ -372,7 +366,7 @@ class _Step4ViewState extends ConsumerState {
STextStyles.itemSubtitle(context).copyWith(
color: Theme.of(context)
.extension()!
- .colorForStatus(_status),
+ .colorForStatus(_statusString),
),
),
],
@@ -408,7 +402,7 @@ class _Step4ViewState extends ConsumerState {
child: QrImage(
// TODO: grab coin uri scheme from somewhere
// data: "${coin.uriScheme}:$receivingAddress",
- data: model.trade!.payinAddress,
+ data: model.trade!.payInAddress,
size: MediaQuery.of(context)
.size
.width /
@@ -496,7 +490,7 @@ class _Step4ViewState extends ConsumerState {
Format.decimalAmountToSatoshis(
model.sendAmount);
final address =
- model.trade!.payinAddress;
+ model.trade!.payInAddress;
try {
bool wasCancelled = false;
@@ -534,7 +528,7 @@ class _Step4ViewState extends ConsumerState {
}
txData["note"] =
- "${model.trade!.fromCurrency.toUpperCase()}/${model.trade!.toCurrency.toUpperCase()} exchange";
+ "${model.trade!.payInCurrency.toUpperCase()}/${model.trade!.payOutCurrency.toUpperCase()} exchange";
txData["address"] = address;
if (mounted) {
@@ -611,10 +605,10 @@ class _Step4ViewState extends ConsumerState {
coin:
coinFromTickerCaseInsensitive(
model.trade!
- .fromCurrency),
+ .payInCurrency),
amount: model.sendAmount,
address:
- model.trade!.payinAddress,
+ model.trade!.payInAddress,
trade: model.trade!,
);
},
diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart
index ad618dd0c..84e054eac 100644
--- a/lib/pages/exchange_view/exchange_view.dart
+++ b/lib/pages/exchange_view/exchange_view.dart
@@ -1,46 +1,17 @@
import 'dart:async';
-import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_svg/flutter_svg.dart';
-import 'package:flutter_svg/svg.dart';
-import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart';
-import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart';
-import 'package:stackwallet/models/exchange/change_now/currency.dart';
-import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart';
-import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
-import 'package:stackwallet/notifications/show_flush_bar.dart';
-import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart';
-import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart';
-import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_1_view.dart';
-import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
+import 'package:stackwallet/pages/exchange_view/exchange_form.dart';
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
-import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart';
-import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart';
-import 'package:stackwallet/providers/exchange/change_now_provider.dart';
-import 'package:stackwallet/providers/exchange/changenow_initial_load_status.dart';
-import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart';
-import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart';
-import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart';
-import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart';
-import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart';
import 'package:stackwallet/providers/global/trades_service_provider.dart';
import 'package:stackwallet/providers/providers.dart';
-import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
-import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
-import 'package:stackwallet/widgets/custom_loading_overlay.dart';
-import 'package:stackwallet/widgets/loading_indicator.dart';
-import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:stackwallet/widgets/trade_card.dart';
import 'package:tuple/tuple.dart';
-const kFixedRateEnabled = true;
-
class ExchangeView extends ConsumerStatefulWidget {
const ExchangeView({Key? key}) : super(key: key);
@@ -49,317 +20,20 @@ class ExchangeView extends ConsumerStatefulWidget {
}
class _ExchangeViewState extends ConsumerState {
- late final TextEditingController _sendController;
- late final TextEditingController _receiveController;
-
- bool _swapLock = false;
-
- Future _swap() async {
- _swapLock = true;
- _sendFocusNode.unfocus();
- _receiveFocusNode.unfocus();
-
- unawaited(
- showDialog(
- context: context,
- barrierDismissible: false,
- builder: (_) => WillPopScope(
- onWillPop: () async => false,
- child: Container(
- color: Theme.of(context)
- .extension()!
- .overlay
- .withOpacity(0.6),
- child: const CustomLoadingOverlay(
- message: "Updating exchange rate",
- eventBus: null,
- ),
- ),
- ),
- ),
- );
-
- if (ref.watch(prefsChangeNotifierProvider
- .select((pref) => pref.exchangeRateType)) ==
- ExchangeRateType.estimated) {
- await ref.read(estimatedRateExchangeFormProvider).swap();
- } else {
- final from = ref.read(fixedRateExchangeFormProvider).market?.from;
- final to = ref.read(fixedRateExchangeFormProvider).market?.to;
-
- if (to != null && from != null) {
- final markets = ref
- .read(fixedRateMarketPairsStateProvider.state)
- .state
- .where((e) => e.from == to && e.to == from);
-
- if (markets.isNotEmpty) {
- await ref.read(fixedRateExchangeFormProvider).swap(markets.first);
- }
- }
- }
- if (mounted) {
- Navigator.of(context).pop();
- }
- _swapLock = false;
- }
-
- Future _showFloatingRateSelectionSheet({
- required List currencies,
- required String excludedTicker,
- required String fromTicker,
- required void Function(Currency) onSelected,
- }) async {
- _sendFocusNode.unfocus();
- _receiveFocusNode.unfocus();
-
- List availablePairs = [];
- if (fromTicker.isEmpty ||
- fromTicker == "-" ||
- excludedTicker.isEmpty ||
- excludedTicker == "-") {
- availablePairs =
- ref.read(availableFloatingRatePairsStateProvider.state).state;
- } else if (excludedTicker == fromTicker) {
- availablePairs = ref
- .read(availableFloatingRatePairsStateProvider.state)
- .state
- .where((e) => e.fromTicker == excludedTicker)
- .toList(growable: false);
- } else {
- availablePairs = ref
- .read(availableFloatingRatePairsStateProvider.state)
- .state
- .where((e) => e.toTicker == excludedTicker)
- .toList(growable: false);
- }
-
- final List tickers = currencies.where((e) {
- if (excludedTicker == fromTicker) {
- return e.ticker != excludedTicker &&
- availablePairs.where((e2) => e2.toTicker == e.ticker).isNotEmpty;
- } else {
- return e.ticker != excludedTicker &&
- availablePairs.where((e2) => e2.fromTicker == e.ticker).isNotEmpty;
- }
- }).toList(growable: false);
-
- final result = await Navigator.of(context).push(
- MaterialPageRoute(
- builder: (_) => FloatingRateCurrencySelectionView(
- currencies: tickers,
- ),
- ),
- );
-
- if (mounted && result is Currency) {
- onSelected(result);
- }
- }
-
- String? _fetchIconUrlFromTickerForFixedRateFlow(String? ticker) {
- if (ticker == null) return null;
-
- final possibleCurrencies = ref
- .read(availableChangeNowCurrenciesStateProvider.state)
- .state
- .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase());
-
- for (final currency in possibleCurrencies) {
- if (currency.image.isNotEmpty) {
- return currency.image;
- }
- }
-
- return null;
- }
-
- Future _showFixedRateSelectionSheet({
- required String excludedTicker,
- required String fromTicker,
- required void Function(String) onSelected,
- }) async {
- _sendFocusNode.unfocus();
- _receiveFocusNode.unfocus();
-
- List marketsThatPairWithExcludedTicker = [];
-
- if (excludedTicker == "" ||
- excludedTicker == "-" ||
- fromTicker == "" ||
- fromTicker == "-") {
- marketsThatPairWithExcludedTicker =
- ref.read(fixedRateMarketPairsStateProvider.state).state;
- } else if (excludedTicker == fromTicker) {
- marketsThatPairWithExcludedTicker = ref
- .read(fixedRateMarketPairsStateProvider.state)
- .state
- .where((e) => e.from == excludedTicker && e.to != excludedTicker)
- .toList(growable: false);
- } else {
- marketsThatPairWithExcludedTicker = ref
- .read(fixedRateMarketPairsStateProvider.state)
- .state
- .where((e) => e.to == excludedTicker && e.from != excludedTicker)
- .toList(growable: false);
- }
-
- final result = await Navigator.of(context).push(
- MaterialPageRoute(
- builder: (_) => FixedRateMarketPairCoinSelectionView(
- markets: marketsThatPairWithExcludedTicker,
- currencies:
- ref.read(availableChangeNowCurrenciesStateProvider.state).state,
- isFrom: excludedTicker != fromTicker,
- ),
- ),
- );
-
- if (mounted && result is String) {
- onSelected(result);
- }
- }
-
@override
void initState() {
- _sendController = TextEditingController();
- _receiveController = TextEditingController();
-
- final isEstimated =
- ref.read(prefsChangeNotifierProvider).exchangeRateType ==
- ExchangeRateType.estimated;
- _sendController.text = isEstimated
- ? ref.read(estimatedRateExchangeFormProvider).fromAmountString
- : ref.read(fixedRateExchangeFormProvider).fromAmountString;
- _receiveController.text = isEstimated
- ? "-" //ref.read(estimatedRateExchangeFormProvider).toAmountString
- : ref.read(fixedRateExchangeFormProvider).toAmountString;
-
- _sendFocusNode.addListener(() async {
- if (!_sendFocusNode.hasFocus) {
- final newFromAmount = Decimal.tryParse(_sendController.text);
- if (newFromAmount != null) {
- if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
- ExchangeRateType.estimated) {
- await ref
- .read(estimatedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(newFromAmount, true);
- } else {
- await ref
- .read(fixedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(newFromAmount, true);
- }
- } else {
- if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
- ExchangeRateType.estimated) {
- await ref
- .read(estimatedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(Decimal.zero, true);
- } else {
- await ref
- .read(fixedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(Decimal.zero, true);
- }
- _receiveController.text =
- ref.read(prefsChangeNotifierProvider).exchangeRateType ==
- ExchangeRateType.estimated
- ? "-"
- : "";
- }
- }
- });
- _receiveFocusNode.addListener(() async {
- if (!_receiveFocusNode.hasFocus) {
- final newToAmount = Decimal.tryParse(_receiveController.text);
- if (newToAmount != null) {
- if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
- ExchangeRateType.estimated) {
- // await ref
- // .read(estimatedRateExchangeFormProvider)
- // .setToAmountAndCalculateFromAmount(newToAmount, true);
- } else {
- await ref
- .read(fixedRateExchangeFormProvider)
- .setToAmountAndCalculateFromAmount(newToAmount, true);
- }
- } else {
- if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
- ExchangeRateType.estimated) {
- // await ref
- // .read(estimatedRateExchangeFormProvider)
- // .setToAmountAndCalculateFromAmount(Decimal.zero, true);
- } else {
- await ref
- .read(fixedRateExchangeFormProvider)
- .setToAmountAndCalculateFromAmount(Decimal.zero, true);
- }
- _sendController.text = "";
- }
- }
- });
-
super.initState();
}
@override
void dispose() {
- _receiveController.dispose();
- _sendController.dispose();
super.dispose();
}
- String calcSend = "";
- String calcReceive = "";
-
- final FocusNode _sendFocusNode = FocusNode();
- final FocusNode _receiveFocusNode = FocusNode();
-
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
- final isEstimated = ref.watch(prefsChangeNotifierProvider
- .select((pref) => pref.exchangeRateType)) ==
- ExchangeRateType.estimated;
-
- ref.listen(
- isEstimated
- ? estimatedRateExchangeFormProvider
- .select((value) => value.toAmountString)
- : fixedRateExchangeFormProvider.select(
- (value) => value.toAmountString), (previous, String next) {
- if (!_receiveFocusNode.hasFocus) {
- _receiveController.text = isEstimated && next.isEmpty ? "-" : next;
- debugPrint("RECEIVE AMOUNT LISTENER ACTIVATED");
- if (_swapLock) {
- _sendController.text = isEstimated
- ? ref.read(estimatedRateExchangeFormProvider).fromAmountString
- : ref.read(fixedRateExchangeFormProvider).fromAmountString;
- }
- }
- });
- ref.listen(
- isEstimated
- ? estimatedRateExchangeFormProvider
- .select((value) => value.fromAmountString)
- : fixedRateExchangeFormProvider.select(
- (value) => value.fromAmountString), (previous, String next) {
- if (!_sendFocusNode.hasFocus) {
- _sendController.text = next;
- debugPrint("SEND AMOUNT LISTENER ACTIVATED");
- if (_swapLock) {
- _receiveController.text = isEstimated
- ? ref
- .read(estimatedRateExchangeFormProvider)
- .toAmountString
- .isEmpty
- ? "-"
- : ref.read(estimatedRateExchangeFormProvider).toAmountString
- : ref.read(fixedRateExchangeFormProvider).toAmountString;
- }
- }
- });
-
return SafeArea(
child: NestedScrollView(
floatHeaderSlivers: true,
@@ -367,1076 +41,10 @@ class _ExchangeViewState extends ConsumerState {
return [
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
- sliver: SliverToBoxAdapter(
+ sliver: const SliverToBoxAdapter(
child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- const SizedBox(
- height: 20,
- ),
- Text(
- "You will send",
- style: STextStyles.itemSubtitle(context).copyWith(
- color: Theme.of(context)
- .extension()!
- .textDark3,
- ),
- ),
- const SizedBox(
- height: 4,
- ),
- TextFormField(
- style: STextStyles.smallMed14(context).copyWith(
- color: Theme.of(context)
- .extension()!
- .textDark,
- ),
- focusNode: _sendFocusNode,
- controller: _sendController,
- textAlign: TextAlign.right,
- onTap: () {
- if (_sendController.text == "-") {
- _sendController.text = "";
- }
- },
- onChanged: (value) async {
- final newFromAmount = Decimal.tryParse(value);
- if (newFromAmount != null) {
- if (ref
- .read(prefsChangeNotifierProvider)
- .exchangeRateType ==
- ExchangeRateType.estimated) {
- await ref
- .read(estimatedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(
- newFromAmount, false);
- } else {
- await ref
- .read(fixedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(
- newFromAmount, false);
- }
- } else {
- if (ref
- .read(prefsChangeNotifierProvider)
- .exchangeRateType ==
- ExchangeRateType.estimated) {
- await ref
- .read(estimatedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(
- Decimal.zero, false);
- } else {
- await ref
- .read(fixedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(
- Decimal.zero, false);
- }
- _receiveController.text = isEstimated ? "-" : "";
- }
- },
- 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),
- ],
- decoration: InputDecoration(
- contentPadding: const EdgeInsets.only(
- top: 12,
- right: 12,
- ),
- hintText: "0",
- hintStyle: STextStyles.fieldLabel(context).copyWith(
- fontSize: 14,
- ),
- prefixIcon: FittedBox(
- fit: BoxFit.scaleDown,
- alignment: Alignment.centerLeft,
- child: GestureDetector(
- onTap: () async {
- if (ref
- .read(prefsChangeNotifierProvider)
- .exchangeRateType ==
- ExchangeRateType.estimated) {
- await _showFloatingRateSelectionSheet(
- currencies: ref
- .read(
- availableChangeNowCurrenciesStateProvider
- .state)
- .state,
- excludedTicker: ref
- .read(
- estimatedRateExchangeFormProvider)
- .to
- ?.ticker ??
- "-",
- fromTicker: ref
- .read(
- estimatedRateExchangeFormProvider)
- .from
- ?.ticker ??
- "-",
- onSelected: (from) => ref
- .read(
- estimatedRateExchangeFormProvider)
- .updateFrom(from, true));
- } else {
- final toTicker = ref
- .read(fixedRateExchangeFormProvider)
- .market
- ?.to ??
- "";
- final fromTicker = ref
- .read(fixedRateExchangeFormProvider)
- .market
- ?.from ??
- "";
- await _showFixedRateSelectionSheet(
- excludedTicker: toTicker,
- fromTicker: fromTicker,
- onSelected: (selectedFromTicker) async {
- try {
- final market = ref
- .read(
- fixedRateMarketPairsStateProvider
- .state)
- .state
- .firstWhere(
- (e) =>
- e.to == toTicker &&
- e.from == selectedFromTicker,
- );
-
- await ref
- .read(fixedRateExchangeFormProvider)
- .updateMarket(market, true);
- } catch (e) {
- unawaited(showDialog(
- context: context,
- builder: (_) => const StackDialog(
- title: "Fixed rate market error",
- message:
- "Could not find the specified fixed rate trade pair",
- ),
- ));
- return;
- }
- },
- );
- }
- },
- child: Container(
- color: Colors.transparent,
- child: Padding(
- padding: const EdgeInsets.all(12),
- child: Row(
- children: [
- Container(
- width: 18,
- height: 18,
- decoration: BoxDecoration(
- borderRadius:
- BorderRadius.circular(18),
- ),
- child: Builder(
- builder: (context) {
- String? image;
- if (ref.watch(
- prefsChangeNotifierProvider
- .select((value) => value
- .exchangeRateType)) ==
- ExchangeRateType.estimated) {
- image = ref
- .watch(
- estimatedRateExchangeFormProvider
- .select((value) =>
- value.from))
- ?.image;
- } else {
- image = _fetchIconUrlFromTickerForFixedRateFlow(
- ref.watch(
- fixedRateExchangeFormProvider
- .select((value) =>
- value.market
- ?.from)));
- }
- if (image != null &&
- image.isNotEmpty) {
- return Center(
- child: SvgPicture.network(
- image,
- height: 18,
- placeholderBuilder: (_) =>
- Container(
- width: 18,
- height: 18,
- decoration: BoxDecoration(
- color: Theme.of(context)
- .extension<
- StackColors>()!
- .textFieldDefaultBG,
- borderRadius:
- BorderRadius.circular(
- 18,
- ),
- ),
- child: ClipRRect(
- borderRadius:
- BorderRadius.circular(
- 18,
- ),
- child:
- const LoadingIndicator(),
- ),
- ),
- ),
- );
- } else {
- return Container(
- width: 18,
- height: 18,
- decoration: BoxDecoration(
- // color: Theme.of(context).extension()!.accentColorDark
- borderRadius:
- BorderRadius.circular(18),
- ),
- child: SvgPicture.asset(
- Assets.svg.circleQuestion,
- width: 18,
- height: 18,
- color: Theme.of(context)
- .extension()!
- .textFieldDefaultBG,
- ),
- );
- }
- },
- ),
- ),
- const SizedBox(
- width: 6,
- ),
- Text(
- isEstimated
- ? ref.watch(
- estimatedRateExchangeFormProvider
- .select((value) => value
- .from?.ticker
- .toUpperCase())) ??
- "-"
- : ref.watch(
- fixedRateExchangeFormProvider
- .select((value) => value
- .market?.from
- .toUpperCase())) ??
- "-",
- style: STextStyles.smallMed14(context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textDark,
- ),
- ),
- const SizedBox(
- width: 6,
- ),
- SvgPicture.asset(
- Assets.svg.chevronDown,
- width: 5,
- height: 2.5,
- color: Theme.of(context)
- .extension()!
- .textDark,
- ),
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- const SizedBox(
- height: 4,
- ),
- Stack(
- children: [
- Positioned.fill(
- child: Align(
- alignment: Alignment.bottomLeft,
- child: Text(
- "You will receive",
- style:
- STextStyles.itemSubtitle(context).copyWith(
- color: Theme.of(context)
- .extension()!
- .textDark3,
- ),
- ),
- ),
- ),
- Center(
- child: Column(
- children: [
- const SizedBox(
- height: 6,
- ),
- GestureDetector(
- onTap: () async {
- await _swap();
- },
- child: Padding(
- padding: const EdgeInsets.all(4),
- child: SvgPicture.asset(
- Assets.svg.swap,
- width: 20,
- height: 20,
- color: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- ),
- ),
- const SizedBox(
- height: 6,
- ),
- ],
- ),
- ),
- Positioned.fill(
- child: Align(
- alignment: Alignment.topRight,
- child: Text(
- isEstimated
- ? ref.watch(
- estimatedRateExchangeFormProvider
- .select((value) =>
- value.minimumSendWarning))
- : ref.watch(fixedRateExchangeFormProvider
- .select((value) =>
- value.sendAmountWarning)),
- style: STextStyles.errorSmall(context),
- ),
- ),
- ),
- ],
- ),
- const SizedBox(
- height: 4,
- ),
- TextFormField(
- style: STextStyles.smallMed14(context).copyWith(
- color: Theme.of(context)
- .extension()!
- .textDark,
- ),
- focusNode: _receiveFocusNode,
- controller: _receiveController,
- readOnly: ref
- .read(prefsChangeNotifierProvider)
- .exchangeRateType ==
- ExchangeRateType.estimated,
- onTap: () {
- if (!isEstimated && _receiveController.text == "-") {
- _receiveController.text = "";
- }
- },
- onChanged: (value) async {
- final newToAmount = Decimal.tryParse(value);
- if (newToAmount != null) {
- if (ref
- .read(prefsChangeNotifierProvider)
- .exchangeRateType ==
- ExchangeRateType.estimated) {
- // await ref
- // .read(estimatedRateExchangeFormProvider)
- // .setToAmountAndCalculateFromAmount(
- // newToAmount, false);
- } else {
- await ref
- .read(fixedRateExchangeFormProvider)
- .setToAmountAndCalculateFromAmount(
- newToAmount, false);
- }
- } else {
- if (ref
- .read(prefsChangeNotifierProvider)
- .exchangeRateType ==
- ExchangeRateType.estimated) {
- // await ref
- // .read(estimatedRateExchangeFormProvider)
- // .setToAmountAndCalculateFromAmount(
- // Decimal.zero, false);
- } else {
- await ref
- .read(fixedRateExchangeFormProvider)
- .setToAmountAndCalculateFromAmount(
- Decimal.zero, false);
- }
- _sendController.text = "";
- }
- },
- textAlign: TextAlign.right,
- 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),
- ],
- decoration: InputDecoration(
- contentPadding: const EdgeInsets.only(
- top: 12,
- right: 12,
- ),
- hintText: "0",
- hintStyle: STextStyles.fieldLabel(context).copyWith(
- fontSize: 14,
- ),
- prefixIcon: FittedBox(
- fit: BoxFit.scaleDown,
- child: GestureDetector(
- onTap: () async {
- if (ref
- .read(prefsChangeNotifierProvider)
- .exchangeRateType ==
- ExchangeRateType.estimated) {
- await _showFloatingRateSelectionSheet(
- currencies: ref
- .read(
- availableChangeNowCurrenciesStateProvider
- .state)
- .state,
- excludedTicker: ref
- .read(
- estimatedRateExchangeFormProvider)
- .from
- ?.ticker ??
- "",
- fromTicker: ref
- .read(
- estimatedRateExchangeFormProvider)
- .from
- ?.ticker ??
- "",
- onSelected: (to) => ref
- .read(
- estimatedRateExchangeFormProvider)
- .updateTo(to, true));
- } else {
- final fromTicker = ref
- .read(fixedRateExchangeFormProvider)
- .market
- ?.from ??
- "";
- await _showFixedRateSelectionSheet(
- excludedTicker: fromTicker,
- fromTicker: fromTicker,
- onSelected: (selectedToTicker) async {
- try {
- final market = ref
- .read(
- fixedRateMarketPairsStateProvider
- .state)
- .state
- .firstWhere(
- (e) =>
- e.to == selectedToTicker &&
- e.from == fromTicker,
- );
-
- await ref
- .read(fixedRateExchangeFormProvider)
- .updateMarket(market, true);
- } catch (e) {
- unawaited(showDialog(
- context: context,
- builder: (_) => const StackDialog(
- title: "Fixed rate market error",
- message:
- "Could not find the specified fixed rate trade pair",
- ),
- ));
- return;
- }
- },
- );
- }
- },
- child: Container(
- color: Colors.transparent,
- child: Padding(
- padding: const EdgeInsets.all(12),
- child: Row(
- children: [
- Container(
- width: 18,
- height: 18,
- decoration: BoxDecoration(
- borderRadius:
- BorderRadius.circular(18),
- ),
- child: Builder(
- builder: (context) {
- String? image;
- if (ref.watch(
- prefsChangeNotifierProvider
- .select((value) => value
- .exchangeRateType)) ==
- ExchangeRateType.estimated) {
- image = ref
- .watch(
- estimatedRateExchangeFormProvider
- .select((value) =>
- value.to))
- ?.image;
- } else {
- image = _fetchIconUrlFromTickerForFixedRateFlow(
- ref.watch(
- fixedRateExchangeFormProvider
- .select((value) =>
- value.market
- ?.to)));
- }
- if (image != null &&
- image.isNotEmpty) {
- return Center(
- child: SvgPicture.network(
- image,
- height: 18,
- placeholderBuilder: (_) =>
- Container(
- width: 18,
- height: 18,
- decoration: BoxDecoration(
- color: Theme.of(context)
- .extension<
- StackColors>()!
- .textFieldDefaultBG,
- borderRadius:
- BorderRadius.circular(
- 18),
- ),
- child: ClipRRect(
- borderRadius:
- BorderRadius.circular(
- 18,
- ),
- child:
- const LoadingIndicator(),
- ),
- ),
- ),
- );
- } else {
- return Container(
- width: 18,
- height: 18,
- decoration: BoxDecoration(
- // color: Theme.of(context).extension()!.accentColorDark
- borderRadius:
- BorderRadius.circular(18),
- ),
- child: SvgPicture.asset(
- Assets.svg.circleQuestion,
- width: 18,
- height: 18,
- color: Theme.of(context)
- .extension()!
- .textFieldDefaultBG,
- ),
- );
- }
- },
- ),
- ),
- const SizedBox(
- width: 6,
- ),
- Text(
- isEstimated
- ? ref.watch(
- estimatedRateExchangeFormProvider
- .select((value) => value
- .to?.ticker
- .toUpperCase())) ??
- "-"
- : ref.watch(
- fixedRateExchangeFormProvider
- .select((value) => value
- .market?.to
- .toUpperCase())) ??
- "-",
- style: STextStyles.smallMed14(context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textDark,
- ),
- ),
- const SizedBox(
- width: 6,
- ),
- SvgPicture.asset(
- Assets.svg.chevronDown,
- width: 5,
- height: 2.5,
- color: Theme.of(context)
- .extension()!
- .textDark,
- ),
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- // if (ref
- // .watch(exchangeFormSateProvider
- // .select((value) => value.minimumReceiveWarning))
- // .isNotEmpty)
- // SizedBox(
- // height: 4,
- // ),
- //
- // if (ref
- // .watch(exchangeFormSateProvider
- // .select((value) => value.minimumReceiveWarning))
- // .isNotEmpty)
- // Row(
- // children: [
- // Spacer(),
- // Text(
- // ref.watch(exchangeFormSateProvider.select(
- // (value) => value.minimumReceiveWarning)),
- // style: STextStyles.errorSmall(context),
- // ),
- // ],
- // ),
-
- const SizedBox(
- height: 12,
- ),
- RateInfo(
- onChanged: (rateType) async {
- _receiveFocusNode.unfocus();
- _sendFocusNode.unfocus();
- switch (rateType) {
- case ExchangeRateType.estimated:
- final market = ref
- .read(fixedRateExchangeFormProvider)
- .market;
- final fromTicker = market?.from ?? "";
- final toTicker = market?.to ?? "";
- if (!(fromTicker.isEmpty ||
- toTicker.isEmpty ||
- toTicker == "-" ||
- fromTicker == "-")) {
- final available = ref
- .read(
- availableFloatingRatePairsStateProvider
- .state)
- .state
- .where((e) =>
- e.toTicker == toTicker &&
- e.fromTicker == fromTicker);
- if (available.isNotEmpty) {
- final availableCurrencies = ref
- .read(
- availableChangeNowCurrenciesStateProvider
- .state)
- .state
- .where((e) =>
- e.ticker == fromTicker ||
- e.ticker == toTicker);
- if (availableCurrencies.length > 1) {
- final from = availableCurrencies.firstWhere(
- (e) => e.ticker == fromTicker);
- final to = availableCurrencies.firstWhere(
- (e) => e.ticker == toTicker);
-
- final newFromAmount =
- Decimal.tryParse(_sendController.text);
- if (newFromAmount != null) {
- await ref
- .read(
- estimatedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(
- newFromAmount, false);
- } else {
- await ref
- .read(
- estimatedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(
- Decimal.zero, false);
-
- _receiveController.text = "";
- }
-
- await ref
- .read(estimatedRateExchangeFormProvider)
- .updateTo(to, false);
- await ref
- .read(estimatedRateExchangeFormProvider)
- .updateFrom(from, true);
- return;
- }
- }
- }
- unawaited(showFloatingFlushBar(
- type: FlushBarType.warning,
- message:
- "Estimated rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last estimated rate pair.",
- context: context,
- ));
- break;
- case ExchangeRateType.fixed:
- final fromTicker = ref
- .read(estimatedRateExchangeFormProvider)
- .from
- ?.ticker ??
- "";
- final toTicker = ref
- .read(estimatedRateExchangeFormProvider)
- .to
- ?.ticker ??
- "";
- if (!(fromTicker.isEmpty ||
- toTicker.isEmpty ||
- toTicker == "-" ||
- fromTicker == "-")) {
- FixedRateMarket? market;
- try {
- market = ref
- .read(fixedRateMarketPairsStateProvider
- .state)
- .state
- .firstWhere((e) =>
- e.from == fromTicker &&
- e.to == toTicker);
- } catch (_) {
- market = null;
- }
-
- final newFromAmount =
- Decimal.tryParse(_sendController.text);
- if (newFromAmount != null) {
- await ref
- .read(fixedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(
- newFromAmount, false);
- } else {
- await ref
- .read(fixedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(
- Decimal.zero, false);
-
- _receiveController.text = "";
- }
-
- await ref
- .read(fixedRateExchangeFormProvider)
- .updateMarket(market, false);
- await ref
- .read(fixedRateExchangeFormProvider)
- .setFromAmountAndCalculateToAmount(
- Decimal.tryParse(_sendController.text) ??
- Decimal.zero,
- true,
- );
- return;
- }
- unawaited(showFloatingFlushBar(
- type: FlushBarType.warning,
- message:
- "Fixed rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last fixed rate pair.",
- context: context,
- ));
- break;
- }
- },
- ),
- const SizedBox(
- height: 12,
- ),
- TextButton(
- style: ((ref
- .read(prefsChangeNotifierProvider)
- .exchangeRateType ==
- ExchangeRateType.estimated)
- ? ref.watch(estimatedRateExchangeFormProvider
- .select((value) => value.canExchange))
- : ref.watch(fixedRateExchangeFormProvider
- .select((value) => value.canExchange)))
- ? Theme.of(context)
- .extension()!
- .getPrimaryEnabledButtonColor(context)
- : Theme.of(context)
- .extension()!
- .getPrimaryDisabledButtonColor(context),
- onPressed: ((ref
- .read(prefsChangeNotifierProvider)
- .exchangeRateType ==
- ExchangeRateType.estimated)
- ? ref.watch(estimatedRateExchangeFormProvider
- .select((value) => value.canExchange))
- : ref.watch(fixedRateExchangeFormProvider
- .select((value) => value.canExchange)))
- ? () async {
- if (ref
- .read(prefsChangeNotifierProvider)
- .exchangeRateType ==
- ExchangeRateType.estimated) {
- final fromTicker = ref
- .read(
- estimatedRateExchangeFormProvider)
- .from
- ?.ticker ??
- "";
- final toTicker = ref
- .read(
- estimatedRateExchangeFormProvider)
- .to
- ?.ticker ??
- "";
-
- bool isAvailable = false;
- final availableFloatingPairs = ref
- .read(
- availableFloatingRatePairsStateProvider
- .state)
- .state;
- for (final pair in availableFloatingPairs) {
- if (pair.fromTicker == fromTicker &&
- pair.toTicker == toTicker) {
- isAvailable = true;
- break;
- }
- }
-
- if (!isAvailable) {
- unawaited(showDialog(
- context: context,
- barrierDismissible: true,
- builder: (_) => StackDialog(
- title:
- "Selected trade pair unavailable",
- message:
- "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades",
- ),
- ));
- return;
- }
-
- final sendAmount = Decimal.parse(ref
- .read(estimatedRateExchangeFormProvider)
- .fromAmountString);
-
- final rateType = ref
- .read(prefsChangeNotifierProvider)
- .exchangeRateType;
-
- final response = await ref
- .read(changeNowProvider)
- .getEstimatedExchangeAmount(
- fromTicker: fromTicker,
- toTicker: toTicker,
- fromAmount: sendAmount,
- );
-
- if (response.value == null) {
- unawaited(showDialog(
- context: context,
- barrierDismissible: true,
- builder: (_) => StackDialog(
- title:
- "Failed to update trade estimate",
- message: response.exception?.toString(),
- ),
- ));
- return;
- }
-
- String rate =
- "1 ${fromTicker.toUpperCase()} ~${(response.value!.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}";
-
- final model = IncompleteExchangeModel(
- sendTicker: fromTicker.toUpperCase(),
- receiveTicker: toTicker.toUpperCase(),
- rateInfo: rate,
- sendAmount: sendAmount,
- receiveAmount:
- response.value!.estimatedAmount,
- rateId: response.value!.rateId,
- rateType: rateType,
- );
-
- if (mounted) {
- ref
- .read(
- exchangeSendFromWalletIdStateProvider
- .state)
- .state = null;
- unawaited(Navigator.of(context).pushNamed(
- Step1View.routeName,
- arguments: model,
- ));
- }
- } else {
- final fromTicker = ref
- .read(fixedRateExchangeFormProvider)
- .market
- ?.from ??
- "";
- final toTicker = ref
- .read(fixedRateExchangeFormProvider)
- .market
- ?.to ??
- "";
-
- final sendAmount = Decimal.parse(ref
- .read(fixedRateExchangeFormProvider)
- .fromAmountString);
-
- final rateType = ref
- .read(prefsChangeNotifierProvider)
- .exchangeRateType;
-
- final response = await ref
- .read(changeNowProvider)
- .getEstimatedExchangeAmountV2(
- fromTicker: fromTicker,
- toTicker: toTicker,
- fromOrTo: CNEstimateType.direct,
- amount: sendAmount,
- flow: CNFlowType.fixedRate,
- );
-
- bool? shouldCancel;
-
- if (response.value == null) {
- unawaited(showDialog(
- context: context,
- barrierDismissible: true,
- builder: (_) => StackDialog(
- title:
- "Failed to update trade estimate",
- message: response.exception?.toString(),
- ),
- ));
- return;
- } else if (response.value!.warningMessage !=
- null &&
- response
- .value!.warningMessage!.isNotEmpty) {
- shouldCancel = await showDialog(
- context: context,
- barrierDismissible: true,
- builder: (_) => StackDialog(
- title:
- "Failed to update trade estimate",
- message:
- "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?",
- leftButton: TextButton(
- style: Theme.of(context)
- .extension()!
- .getSecondaryEnabledButtonColor(
- context),
- child: Text(
- "Cancel",
- style: STextStyles.itemSubtitle12(
- context),
- ),
- onPressed: () {
- // notify return to cancel
- Navigator.of(context).pop(true);
- },
- ),
- rightButton: TextButton(
- style: Theme.of(context)
- .extension()!
- .getPrimaryEnabledButtonColor(
- context),
- child: Text(
- "Attempt",
- style: STextStyles.button(context),
- ),
- onPressed: () {
- // continue and try to attempt trade
- Navigator.of(context).pop(false);
- },
- ),
- ),
- );
- }
-
- if (shouldCancel is bool && shouldCancel) {
- return;
- }
-
- String rate =
- "1 ${fromTicker.toUpperCase()} ~${ref.read(fixedRateExchangeFormProvider).rate!.toStringAsFixed(8)} ${toTicker.toUpperCase()}";
-
- final model = IncompleteExchangeModel(
- sendTicker: fromTicker,
- receiveTicker: toTicker,
- rateInfo: rate,
- sendAmount: sendAmount,
- receiveAmount: response.value!.toAmount,
- rateId: response.value!.rateId,
- rateType: rateType,
- );
-
- if (mounted) {
- ref
- .read(
- exchangeSendFromWalletIdStateProvider
- .state)
- .state = null;
- unawaited(Navigator.of(context).pushNamed(
- Step1View.routeName,
- arguments: model,
- ));
- }
- }
- }
- : null,
- child: Text(
- "Exchange",
- style: STextStyles.button(context),
- ),
- ),
- const SizedBox(
- height: 20,
- ),
- // Text(
- // "Trades",
- // style: STextStyles.itemSubtitle(context).copyWith(
- // color: Theme.of(context).extension()!.textDark3,
- // ),
- // ),
- // SizedBox(
- // height: 12,
- // ),
- ],
- ),
+ padding: EdgeInsets.symmetric(horizontal: 16),
+ child: ExchangeForm(),
),
),
)
@@ -1455,7 +63,8 @@ class _ExchangeViewState extends ConsumerState {
slivers: [
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
- buildContext),
+ buildContext,
+ ),
),
SliverToBoxAdapter(
child: Padding(
@@ -1490,7 +99,7 @@ class _ExchangeViewState extends ConsumerState {
key: Key("tradeCard_${trades[index].uuid}"),
trade: trades[index],
onTap: () async {
- final String tradeId = trades[index].id;
+ final String tradeId = trades[index].tradeId;
final lookup = ref
.read(tradeSentFromStackLookupProvider)
@@ -1571,137 +180,3 @@ class _ExchangeViewState extends ConsumerState {
);
}
}
-
-class RateInfo extends ConsumerWidget {
- const RateInfo({
- Key? key,
- required this.onChanged,
- }) : super(key: key);
-
- final void Function(ExchangeRateType) onChanged;
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final type = ref.watch(
- prefsChangeNotifierProvider.select((pref) => pref.exchangeRateType));
- final isEstimated = type == ExchangeRateType.estimated;
-
- return Container(
- decoration: BoxDecoration(
- color: Theme.of(context).extension()!.popupBG,
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- ),
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 12),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- TextButton(
- onPressed: kFixedRateEnabled
- ? () async {
- if (isEstimated) {
- if (ref
- .read(
- changeNowFixedInitialLoadStatusStateProvider
- .state)
- .state ==
- ChangeNowLoadStatus.loading) {
- bool userPoppedDialog = false;
- await showDialog(
- context: context,
- builder: (context) => Consumer(
- builder: (context, ref, __) {
- return StackOkDialog(
- title: "Loading rate data...",
- message:
- "Performing initial fetch of ChangeNOW fixed rate market data",
- onOkPressed: (value) {
- userPoppedDialog = value == "OK";
- },
- );
- },
- ),
- );
- if (ref
- .read(
- changeNowFixedInitialLoadStatusStateProvider
- .state)
- .state ==
- ChangeNowLoadStatus.loading) {
- return;
- }
- }
- }
-
- unawaited(showModalBottomSheet(
- backgroundColor: Colors.transparent,
- context: context,
- shape: const RoundedRectangleBorder(
- borderRadius: BorderRadius.vertical(
- top: Radius.circular(20),
- ),
- ),
- builder: (_) => const ExchangeRateSheet(),
- ).then((value) {
- if (value is ExchangeRateType && value != type) {
- onChanged(value);
- }
- }));
- }
- : null,
- style: Theme.of(context).textButtonTheme.style?.copyWith(
- minimumSize: MaterialStateProperty.all(
- const Size(0, 0),
- ),
- padding: MaterialStateProperty.all(
- const EdgeInsets.all(2),
- ),
- backgroundColor: MaterialStateProperty.all(
- Colors.transparent,
- ),
- ),
- child: Row(
- children: [
- Text(
- isEstimated ? "Estimated rate" : "Fixed rate",
- style: STextStyles.itemSubtitle(context),
- ),
- const SizedBox(
- width: 6,
- ),
- if (kFixedRateEnabled)
- SvgPicture.asset(
- Assets.svg.chevronDown,
- width: 5,
- height: 2.5,
- color: Theme.of(context)
- .extension()!
- .infoItemLabel,
- ),
- ],
- ),
- ),
- FittedBox(
- fit: BoxFit.scaleDown,
- child: ConstrainedBox(
- constraints: const BoxConstraints(
- minWidth: 1,
- ),
- child: Text(
- isEstimated
- ? ref.watch(estimatedRateExchangeFormProvider
- .select((value) => value.rateDisplayString))
- : ref.watch(fixedRateExchangeFormProvider
- .select((value) => value.rateDisplayString)),
- style: STextStyles.itemSubtitle12(context),
- ),
- ),
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart
index 74a1f62d4..51c420c82 100644
--- a/lib/pages/exchange_view/send_from_view.dart
+++ b/lib/pages/exchange_view/send_from_view.dart
@@ -4,7 +4,7 @@ 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/models/exchange/change_now/exchange_transaction.dart';
+import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/pages/exchange_view/confirm_change_now_send.dart';
import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart';
@@ -36,7 +36,7 @@ class SendFromView extends ConsumerStatefulWidget {
final Coin coin;
final Decimal amount;
final String address;
- final ExchangeTransaction trade;
+ final Trade trade;
@override
ConsumerState createState() => _SendFromViewState();
@@ -46,7 +46,7 @@ class _SendFromViewState extends ConsumerState {
late final Coin coin;
late final Decimal amount;
late final String address;
- late final ExchangeTransaction trade;
+ late final Trade trade;
String formatAmount(Decimal amount, Coin coin) {
switch (coin) {
@@ -148,7 +148,7 @@ class SendFromCard extends ConsumerStatefulWidget {
final String walletId;
final Decimal amount;
final String address;
- final ExchangeTransaction trade;
+ final Trade trade;
@override
ConsumerState createState() => _SendFromCardState();
@@ -158,7 +158,7 @@ class _SendFromCardState extends ConsumerState {
late final String walletId;
late final Decimal amount;
late final String address;
- late final ExchangeTransaction trade;
+ late final Trade trade;
@override
void initState() {
@@ -230,7 +230,7 @@ class _SendFromCardState extends ConsumerState {
}
txData["note"] =
- "${trade.fromCurrency.toUpperCase()}/${trade.toCurrency.toUpperCase()} exchange";
+ "${trade.payInCurrency.toUpperCase()}/${trade.payOutCurrency.toUpperCase()} exchange";
txData["address"] = address;
if (mounted) {
diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart
new file mode 100644
index 000000000..20d39f986
--- /dev/null
+++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart
@@ -0,0 +1,380 @@
+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/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_response.dart';
+import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.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/format.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/animated_text.dart';
+import 'package:stackwallet/widgets/rounded_white_container.dart';
+
+class ExchangeProviderOptions extends ConsumerWidget {
+ const ExchangeProviderOptions({
+ Key? key,
+ required this.from,
+ required this.to,
+ required this.fromAmount,
+ required this.toAmount,
+ required this.fixedRate,
+ required this.reversed,
+ }) : super(key: key);
+
+ final String? from;
+ final String? to;
+ final Decimal? fromAmount;
+ final Decimal? toAmount;
+ final bool fixedRate;
+ final bool reversed;
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ return RoundedWhiteContainer(
+ child: Column(
+ children: [
+ GestureDetector(
+ onTap: () {
+ if (ref.read(currentExchangeNameStateProvider.state).state !=
+ ChangeNowExchange.exchangeName) {
+ ref.read(currentExchangeNameStateProvider.state).state =
+ ChangeNowExchange.exchangeName;
+ }
+ },
+ child: Container(
+ color: Colors.transparent,
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ width: 20,
+ height: 20,
+ child: Radio(
+ activeColor: Theme.of(context)
+ .extension()!
+ .radioButtonIconEnabled,
+ value: ChangeNowExchange.exchangeName,
+ groupValue: ref
+ .watch(currentExchangeNameStateProvider.state)
+ .state,
+ onChanged: (value) {
+ if (value is String) {
+ ref
+ .read(currentExchangeNameStateProvider.state)
+ .state = value;
+ }
+ },
+ ),
+ ),
+ const SizedBox(
+ width: 14,
+ ),
+ SvgPicture.asset(
+ Assets.exchange.changeNow,
+ width: 24,
+ height: 24,
+ ),
+ const SizedBox(
+ width: 10,
+ ),
+ Expanded(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ ChangeNowExchange.exchangeName,
+ style: STextStyles.titleBold12(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark2,
+ ),
+ ),
+ if (from != null &&
+ to != null &&
+ toAmount != null &&
+ toAmount! > Decimal.zero &&
+ fromAmount != null &&
+ fromAmount! > Decimal.zero)
+ FutureBuilder(
+ future: ChangeNowExchange().getEstimate(
+ from!,
+ to!,
+ reversed ? toAmount! : fromAmount!,
+ fixedRate,
+ reversed,
+ ),
+ builder: (context,
+ AsyncSnapshot>
+ 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);
+ }
+ return Text(
+ "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed(
+ value: rate,
+ locale: ref.watch(
+ localeServiceChangeNotifierProvider
+ .select((value) => value.locale),
+ ),
+ decimalPlaces: to!.toUpperCase() ==
+ Coin.monero.ticker.toUpperCase()
+ ? Constants.decimalPlacesMonero
+ : Constants.decimalPlaces,
+ )} ${to!.toUpperCase()}",
+ style: STextStyles.itemSubtitle12(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .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()!
+ .textSubtitle1,
+ ),
+ );
+ }
+ } else {
+ return AnimatedText(
+ stringsToLoopThrough: const [
+ "Loading",
+ "Loading.",
+ "Loading..",
+ "Loading...",
+ ],
+ style: STextStyles.itemSubtitle12(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .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()!
+ .textSubtitle1,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ GestureDetector(
+ onTap: () {
+ if (ref.read(currentExchangeNameStateProvider.state).state !=
+ SimpleSwapExchange.exchangeName) {
+ ref.read(currentExchangeNameStateProvider.state).state =
+ SimpleSwapExchange.exchangeName;
+ }
+ },
+ child: Container(
+ color: Colors.transparent,
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ width: 20,
+ height: 20,
+ child: Radio(
+ activeColor: Theme.of(context)
+ .extension()!
+ .radioButtonIconEnabled,
+ value: SimpleSwapExchange.exchangeName,
+ groupValue: ref
+ .watch(currentExchangeNameStateProvider.state)
+ .state,
+ onChanged: (value) {
+ if (value is String) {
+ ref
+ .read(currentExchangeNameStateProvider.state)
+ .state = value;
+ }
+ },
+ ),
+ ),
+ const SizedBox(
+ width: 14,
+ ),
+ SvgPicture.asset(
+ Assets.exchange.simpleSwap,
+ width: 24,
+ height: 24,
+ ),
+ const SizedBox(
+ width: 10,
+ ),
+ Expanded(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ SimpleSwapExchange.exchangeName,
+ style: STextStyles.titleBold12(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark2,
+ ),
+ ),
+ if (from != null &&
+ to != null &&
+ toAmount != null &&
+ toAmount! > Decimal.zero &&
+ fromAmount != null &&
+ fromAmount! > Decimal.zero)
+ FutureBuilder(
+ future: SimpleSwapExchange().getEstimate(
+ from!,
+ to!,
+ // reversed ? toAmount! : fromAmount!,
+ fromAmount!,
+ fixedRate,
+ // reversed,
+ false,
+ ),
+ builder: (context,
+ AsyncSnapshot>
+ snapshot) {
+ if (snapshot.connectionState ==
+ ConnectionState.done &&
+ snapshot.hasData) {
+ final estimate = snapshot.data?.value;
+ if (estimate != null) {
+ Decimal rate = (estimate.estimatedAmount /
+ fromAmount!)
+ .toDecimal(scaleOnInfinitePrecision: 12);
+
+ return Text(
+ "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed(
+ value: rate,
+ locale: ref.watch(
+ localeServiceChangeNotifierProvider
+ .select((value) => value.locale),
+ ),
+ decimalPlaces: to!.toUpperCase() ==
+ Coin.monero.ticker.toUpperCase()
+ ? Constants.decimalPlacesMonero
+ : Constants.decimalPlaces,
+ )} ${to!.toUpperCase()}",
+ style: STextStyles.itemSubtitle12(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ );
+ } else {
+ Logging.instance.log(
+ "$runtimeType failed to fetch rate for SimpleSwap: ${snapshot.data}",
+ level: LogLevel.Warning,
+ );
+ return Text(
+ "Failed to fetch rate",
+ style: STextStyles.itemSubtitle12(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ );
+ }
+ } else {
+ return AnimatedText(
+ stringsToLoopThrough: const [
+ "Loading",
+ "Loading.",
+ "Loading..",
+ "Loading...",
+ ],
+ style: STextStyles.itemSubtitle12(context)
+ .copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ );
+ }
+ },
+ ),
+ // if (!(from != null &&
+ // to != null &&
+ // (reversed
+ // ? toAmount != null && toAmount! > Decimal.zero
+ // : fromAmount != null &&
+ // fromAmount! > Decimal.zero)))
+ 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()!
+ .textSubtitle1,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart
new file mode 100644
index 000000000..31460c75f
--- /dev/null
+++ b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart
@@ -0,0 +1,132 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
+import 'package:stackwallet/providers/providers.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/theme/stack_colors.dart';
+import 'package:stackwallet/widgets/rounded_container.dart';
+import 'package:stackwallet/widgets/rounded_white_container.dart';
+
+class RateTypeToggle extends ConsumerWidget {
+ const RateTypeToggle({
+ Key? key,
+ this.onChanged,
+ }) : super(key: key);
+
+ final void Function(ExchangeRateType)? onChanged;
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ debugPrint("BUILD: $runtimeType");
+ final estimated = ref.watch(prefsChangeNotifierProvider
+ .select((value) => value.exchangeRateType)) ==
+ ExchangeRateType.estimated;
+
+ return RoundedWhiteContainer(
+ padding: const EdgeInsets.all(0),
+ child: Row(
+ children: [
+ Expanded(
+ child: GestureDetector(
+ onTap: () {
+ if (!estimated) {
+ ref.read(prefsChangeNotifierProvider).exchangeRateType =
+ ExchangeRateType.estimated;
+ onChanged?.call(ExchangeRateType.estimated);
+ }
+ },
+ child: RoundedContainer(
+ color: estimated
+ ? Theme.of(context)
+ .extension()!
+ .textFieldDefaultBG
+ : Colors.transparent,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SvgPicture.asset(
+ Assets.svg.lock,
+ width: 12,
+ height: 14,
+ color: estimated
+ ? Theme.of(context).extension()!.textDark
+ : Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ const SizedBox(
+ width: 5,
+ ),
+ Text(
+ "Estimate rate",
+ style: STextStyles.smallMed12(context).copyWith(
+ color: estimated
+ ? Theme.of(context)
+ .extension()!
+ .textDark
+ : Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ Expanded(
+ child: GestureDetector(
+ onTap: () {
+ if (estimated) {
+ ref.read(prefsChangeNotifierProvider).exchangeRateType =
+ ExchangeRateType.fixed;
+ onChanged?.call(ExchangeRateType.fixed);
+ }
+ },
+ child: RoundedContainer(
+ color: !estimated
+ ? Theme.of(context)
+ .extension()!
+ .textFieldDefaultBG
+ : Colors.transparent,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SvgPicture.asset(
+ Assets.svg.lock,
+ width: 12,
+ height: 14,
+ color: !estimated
+ ? Theme.of(context).extension()!.textDark
+ : Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ const SizedBox(
+ width: 5,
+ ),
+ Text(
+ "Fixed rate",
+ style: STextStyles.smallMed12(context).copyWith(
+ color: !estimated
+ ? Theme.of(context)
+ .extension()!
+ .textDark
+ : Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart
index ae44faeef..bba561ead 100644
--- a/lib/pages/exchange_view/trade_details_view.dart
+++ b/lib/pages/exchange_view/trade_details_view.dart
@@ -13,10 +13,11 @@ import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart';
import 'package:stackwallet/pages/exchange_view/send_from_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
-import 'package:stackwallet/providers/exchange/change_now_provider.dart';
-import 'package:stackwallet/providers/exchange/trade_note_service_provider.dart';
import 'package:stackwallet/providers/global/trades_service_provider.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/simpleswap/simpleswap_exchange.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@@ -82,18 +83,16 @@ class _TradeDetailsViewState extends ConsumerState {
final trade = ref
.read(tradesServiceProvider)
.trades
- .firstWhere((e) => e.id == tradeId);
+ .firstWhere((e) => e.tradeId == tradeId);
- if (mounted && trade.statusObject == null ||
- trade.statusObject!.amountSendDecimal.isEmpty) {
- final status = await ref
- .read(changeNowProvider)
- .getTransactionStatus(id: trade.id);
+ if (mounted) {
+ final exchange = Exchange.fromName(trade.exchangeName);
+ final response = await exchange.updateTrade(trade);
- if (mounted && status.value != null) {
- await ref.read(tradesServiceProvider).edit(
- trade: trade.copyWith(statusObject: status.value),
- shouldNotifyListeners: true);
+ if (mounted && response.value != null) {
+ await ref
+ .read(tradesServiceProvider)
+ .edit(trade: response.value!, shouldNotifyListeners: true);
}
}
});
@@ -132,23 +131,29 @@ class _TradeDetailsViewState extends ConsumerState {
final bool sentFromStack =
transactionIfSentFromStack != null && walletId != null;
- final trade = ref.watch(tradesServiceProvider
- .select((value) => value.trades.firstWhere((e) => e.id == tradeId)));
+ final trade = ref.watch(tradesServiceProvider.select(
+ (value) => value.trades.firstWhere((e) => e.tradeId == tradeId)));
final bool hasTx = sentFromStack ||
- !(trade.statusObject?.status == ChangeNowTransactionStatus.New ||
- trade.statusObject?.status == ChangeNowTransactionStatus.Waiting ||
- trade.statusObject?.status == ChangeNowTransactionStatus.Refunded ||
- trade.statusObject?.status == ChangeNowTransactionStatus.Failed);
+ !(trade.status == "New" ||
+ trade.status == "new" ||
+ trade.status == "Waiting" ||
+ trade.status == "waiting" ||
+ trade.status == "Refunded" ||
+ trade.status == "refunded" ||
+ trade.status == "Closed" ||
+ trade.status == "closed" ||
+ trade.status == "Expired" ||
+ trade.status == "expired" ||
+ trade.status == "Failed" ||
+ trade.status == "failed");
debugPrint("sentFromStack: $sentFromStack");
debugPrint("hasTx: $hasTx");
debugPrint("trade: ${trade.toString()}");
- final sendAmount = Decimal.tryParse(
- trade.statusObject?.amountSendDecimal ?? "") ??
- Decimal.tryParse(trade.statusObject?.expectedSendAmountDecimal ?? "") ??
- Decimal.parse("-1");
+ final sendAmount =
+ Decimal.tryParse(trade.payInAmount) ?? Decimal.parse("-1");
return Scaffold(
backgroundColor: Theme.of(context).extension()!.background,
@@ -180,7 +185,7 @@ class _TradeDetailsViewState extends ConsumerState {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(
- "${trade.fromCurrency.toUpperCase()} → ${trade.toCurrency.toUpperCase()}",
+ "${trade.payInCurrency.toUpperCase()} → ${trade.payOutCurrency.toUpperCase()}",
style: STextStyles.titleBold12(context),
),
const SizedBox(
@@ -190,7 +195,7 @@ class _TradeDetailsViewState extends ConsumerState {
"${Format.localizedStringAsFixed(value: sendAmount, locale: ref.watch(
localeServiceChangeNotifierProvider
.select((value) => value.locale),
- ), decimalPlaces: trade.fromCurrency.toLowerCase() == "xmr" ? 12 : 8)} ${trade.fromCurrency.toUpperCase()}",
+ ), decimalPlaces: trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8)} ${trade.payInCurrency.toUpperCase()}",
style: STextStyles.itemSubtitle(context),
),
],
@@ -203,9 +208,7 @@ class _TradeDetailsViewState extends ConsumerState {
),
child: Center(
child: SvgPicture.asset(
- _fetchIconAssetForStatus(
- trade.statusObject?.status.name ??
- trade.statusString),
+ _fetchIconAssetForStatus(trade.status),
width: 32,
height: 32,
),
@@ -229,15 +232,11 @@ class _TradeDetailsViewState extends ConsumerState {
height: 4,
),
SelectableText(
- trade.statusObject?.status.name ?? trade.statusString,
+ trade.status,
style: STextStyles.itemSubtitle(context).copyWith(
- color: trade.statusObject != null
- ? Theme.of(context)
- .extension()!
- .colorForStatus(trade.statusObject!.status)
- : Theme.of(context)
- .extension()!
- .accentColorDark,
+ color: Theme.of(context)
+ .extension()!
+ .colorForStatus(trade.status),
),
),
// ),
@@ -258,8 +257,8 @@ class _TradeDetailsViewState extends ConsumerState {
text: TextSpan(
text:
"You must send at least ${sendAmount.toStringAsFixed(
- trade.fromCurrency.toLowerCase() == "xmr" ? 12 : 8,
- )} ${trade.fromCurrency.toUpperCase()}. ",
+ trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8,
+ )} ${trade.payInCurrency.toUpperCase()}. ",
style: STextStyles.label700(context).copyWith(
color: Theme.of(context)
.extension()!
@@ -269,10 +268,10 @@ class _TradeDetailsViewState extends ConsumerState {
TextSpan(
text:
"If you send less than ${sendAmount.toStringAsFixed(
- trade.fromCurrency.toLowerCase() == "xmr"
+ trade.payInCurrency.toLowerCase() == "xmr"
? 12
: 8,
- )} ${trade.fromCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.",
+ )} ${trade.payInCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.",
style: STextStyles.label(context).copyWith(
color: Theme.of(context)
.extension()!
@@ -308,7 +307,7 @@ class _TradeDetailsViewState extends ConsumerState {
GestureDetector(
onTap: () {
final Coin coin = coinFromTickerCaseInsensitive(
- trade.fromCurrency);
+ trade.payInCurrency);
Navigator.of(context).pushNamed(
TransactionDetailsView.routeName,
@@ -334,14 +333,14 @@ class _TradeDetailsViewState extends ConsumerState {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
- "ChangeNOW address",
+ "${trade.exchangeName} address",
style: STextStyles.itemSubtitle(context),
),
const SizedBox(
height: 4,
),
SelectableText(
- trade.payinAddress,
+ trade.payInAddress,
style: STextStyles.itemSubtitle12(context),
),
],
@@ -360,12 +359,12 @@ class _TradeDetailsViewState extends ConsumerState {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
- "Send ${trade.fromCurrency.toUpperCase()} to this address",
+ "Send ${trade.payInCurrency.toUpperCase()} to this address",
style: STextStyles.itemSubtitle(context),
),
GestureDetector(
onTap: () async {
- final address = trade.payinAddress;
+ final address = trade.payInAddress;
await Clipboard.setData(
ClipboardData(
text: address,
@@ -403,7 +402,7 @@ class _TradeDetailsViewState extends ConsumerState