diff --git a/README.md b/README.md
index fe380e5aba..37321c848b 100644
--- a/README.md
+++ b/README.md
@@ -17,15 +17,22 @@ Highlights include:
- Custom Nodes.
- Open source software.
-## Build and run
+## Building
### Prerequisites
- The only OS supported for building is Ubuntu 20.04
- A machine with at least 100 GB of Storage
+
+The following prerequisities can be installed with the setup script `scripts/setup.sh` or manually as described below:
+
- Flutter 3.0.5 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install)
- Dart SDK Requirement (>=2.17.0, up until <3.0.0)
- Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies)
-After that download the project and init the submodules
+### Scripted setup
+[`scripts/setup.sh`](https://github.com/cypherstack/stack_wallet/blob/main/scripts/setup.sh) is provided as a tool to set up a stock Ubuntu 20.04 installation for building: download the script and run it anywhere. This script should skip the entire [Manual setup](#manual-setup) section below and prepare you for [running](#running). It will set up the stack_wallet repository in `~/projects/stack_wallet` and build it there.
+
+### Manual setup
+After installing the prerequisites listed above, download the code and init the submodules
```
git clone https://github.com/cypherstack/stack_wallet.git
cd stack_wallet
@@ -57,7 +64,6 @@ cd scripts
cd ..
```
-
Remove pre-installed system libraries for the following packages built by cryptography plugins in the crypto_plugins folder: `boost iconv libjson-dev libsecret openssl sodium unbound zmq`. You can use
```
sudo apt list --installed | grep boost
@@ -76,7 +82,7 @@ cd scripts/android/
cd ../..
```
-Building plugins for testing on Linux
+Building plugins for Linux
```
cd scripts/linux/
@@ -85,10 +91,33 @@ cd scripts/linux/
cd ../..
```
-Finally, plug in your android device or use the emulator available via Android Studio and then run the following commands:
+## Running
+### Android
+Plug in your android device or use the emulator available via Android Studio and then run the following commands:
```
flutter pub get
-flutter run
+flutter run android
```
Note on Emulators: Only x86_64 emulators are supported, x86 emulators will not work
+
+### Linux
+Plug in your android device or use the emulator available via Android Studio and then run the following commands:
+```
+flutter pub get Linux
+flutter run linux
+```
+
+## Android Studio
+Android Studio is the recommended IDE for development, not just for launching on Android devices and emulators but also for Linux desktop development. Install it and configure it as follows:
+```
+# setup android studio
+sudo apt install -y openjdk-11-jdk
+sudo snap install android-studio --classic
+```
+
+Use Tools > SDK Manager to install the SDK Tools > Android SDK (API 30), SDK Tools > NDK, SDK Tools > Android SDK command line tools, and SDK Tools > CMake
+
+Then install the Flutter plugin and restart the IDE. In Android Studio's options for the Flutter language, enable auto format on save to match the project's code style. If you have problems with the Dart SDK, make sure to run `flutter` in a terminal to download it (use `source ~/.bashrc` to update your environment variables if you're still using the same terminal from which you ran `setup.sh`)
+
+Make a Pixel 4 (API 30) x86_64 emulator with 2GB of storage space for emulation
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 0000000000..f901f35623
--- /dev/null
+++ b/assets/svg/exchange_icons/change_now_logo_1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svg/exchange_icons/simpleswap-icon.svg b/assets/svg/exchange_icons/simpleswap-icon.svg
new file mode 100644
index 0000000000..b3986ef2f2
--- /dev/null
+++ b/assets/svg/exchange_icons/simpleswap-icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/svg/persona-easy-1.svg b/assets/svg/persona-easy-1.svg
new file mode 100644
index 0000000000..1e15b8dab9
--- /dev/null
+++ b/assets/svg/persona-easy-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svg/persona-incognito-1.svg b/assets/svg/persona-incognito-1.svg
new file mode 100644
index 0000000000..c31c285b12
--- /dev/null
+++ b/assets/svg/persona-incognito-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 98c80851c2..c76f92b0e6 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 50;
+ objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
@@ -451,7 +451,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 63;
+ CURRENT_PROJECT_VERSION = 75;
DEVELOPMENT_TEAM = 4DQKUWSG6C;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -505,7 +505,7 @@
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
);
- MARKETING_VERSION = 1.4.48;
+ MARKETING_VERSION = 1.5.5;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -635,7 +635,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 63;
+ CURRENT_PROJECT_VERSION = 75;
DEVELOPMENT_TEAM = 4DQKUWSG6C;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -689,7 +689,7 @@
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
);
- MARKETING_VERSION = 1.4.48;
+ MARKETING_VERSION = 1.5.5;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -711,7 +711,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 63;
+ CURRENT_PROJECT_VERSION = 75;
DEVELOPMENT_TEAM = 4DQKUWSG6C;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -765,7 +765,7 @@
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
);
- MARKETING_VERSION = 1.4.48;
+ MARKETING_VERSION = 1.5.5;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
PRODUCT_NAME = "$(TARGET_NAME)";
diff --git a/lib/hive/db.dart b/lib/hive/db.dart
index c8e148923c..062d029a50 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 da08c6765f..04c48ae498 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 && _prefs.externalCalls) {
+ 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 d30b9817e1..0000000000
--- 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 a45a8d665e..0000000000
--- 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 0000000000..aaf6a5d05d
--- /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 cf70f812f2..dccf6b3b32 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 0ea153876b..233c242c92 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 3161d83c19..0000000000
--- 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 0000000000..2e237dd1c5
--- /dev/null
+++ b/lib/models/exchange/exchange_form_state.dart
@@ -0,0 +1,422 @@
+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/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/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;
+
+ @override
+ String toString() {
+ return 'ExchangeFormState: {_exchange: $_exchange, _exchangeType: $_exchangeType, reversed: $reversed, fromAmount: $fromAmount, toAmount: $toAmount, minAmount: $minAmount, maxAmount: $maxAmount, rate: $rate, estimate: $estimate, _market: $_market, _from: $_from, _to: $_to, _onError: $_onError}';
+ }
+
+ String? get fromTicker {
+ switch (exchangeType) {
+ case ExchangeRateType.estimated:
+ return _from?.ticker;
+ case ExchangeRateType.fixed:
+ switch (exchange?.name) {
+ case SimpleSwapExchange.exchangeName:
+ return _from?.ticker;
+ case ChangeNowExchange.exchangeName:
+ return market?.from;
+ default:
+ return null;
+ }
+ }
+ }
+
+ String? get toTicker {
+ switch (exchangeType) {
+ case ExchangeRateType.estimated:
+ return _to?.ticker;
+ case ExchangeRateType.fixed:
+ switch (exchange?.name) {
+ case SimpleSwapExchange.exchangeName:
+ return _to?.ticker;
+ case ChangeNowExchange.exchangeName:
+ return market?.to;
+ default:
+ return null;
+ }
+ }
+ }
+
+ 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! &&
+ toAmount! > Decimal.zero) {
+ 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! &&
+ fromAmount! > Decimal.zero) {
+ 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 {
+ if (exchange?.name == ChangeNowExchange.exchangeName &&
+ exchangeType == ExchangeRateType.fixed) {
+ return _market != null &&
+ fromAmount != null &&
+ toAmount != null &&
+ warning.isEmpty;
+ } else {
+ return fromAmount != null &&
+ fromAmount != Decimal.zero &&
+ toAmount != null &&
+ rate != null &&
+ warning.isEmpty;
+ }
+ }
+
+ void clearAmounts(bool shouldNotifyListeners) {
+ fromAmount = null;
+ toAmount = null;
+ minAmount = null;
+ maxAmount = null;
+ rate = null;
+
+ if (shouldNotifyListeners) {
+ notifyListeners();
+ }
+ }
+
+ Future 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;
+
+ if (exchangeType == ExchangeRateType.fixed &&
+ exchange?.name == ChangeNowExchange.exchangeName) {
+ await updateMarket(market, false);
+ } else {
+ final Currency? newTo = from;
+ final Currency? newFrom = to;
+
+ _to = newTo;
+ _from = newFrom;
+
+ await updateRanges(shouldNotifyListeners: false);
+
+ await updateEstimate(
+ shouldNotifyListeners: false,
+ reversed: reversed,
+ );
+ }
+
+ 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 b751931584..0000000000
--- 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 cd80e402f2..58219dc5d5 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 add8728792..0850f9a38c 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 0000000000..7df490079f
--- /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 9b8b2db808..7e2b363c90 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 0000000000..1f12acf08f
--- /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 0000000000..715a3ecce7
--- /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 0000000000..7fc019aa97
--- /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 0000000000..12d77bd836
--- /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 0000000000..787ad88ab8
--- /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 0000000000..5aabc20d46
--- /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/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart
index 68f116a959..def0724b5c 100644
--- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart
+++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart
@@ -275,7 +275,7 @@ class _RestoreWalletViewState extends ConsumerState {
// without using them
await manager.recoverFromMnemonic(
mnemonic: mnemonic,
- maxUnusedAddressGap: 20,
+ maxUnusedAddressGap: widget.coin == Coin.firo ? 50 : 20,
maxNumberOfIndexesToCheck: 1000,
height: height,
);
diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart
index 3ddf8932aa..d77ad6b8c9 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 82a8ba0ea0..d7577e9604 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 28ed9accbc..7c3b935b76 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 0000000000..7b04f90b37
--- /dev/null
+++ b/lib/pages/exchange_view/exchange_form.dart
@@ -0,0 +1,1346 @@
+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/logger.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.fixed &&
+ ref.read(exchangeFormStateProvider).exchange?.name ==
+ 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);
+ } else {
+ Logging.instance.log(
+ "swap to fixed rate market failed",
+ level: LogLevel.Warning,
+ );
+ }
+ }
+ } else {
+ await ref.read(exchangeFormStateProvider).swap();
+ }
+ 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();
+ }
+ if (!(fromTicker == "-" || toTicker == "-")) {
+ 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,
+ ),
+ if (ref.read(exchangeFormStateProvider).fromAmount != null &&
+ ref.read(exchangeFormStateProvider).fromAmount != Decimal.zero)
+ const SizedBox(
+ height: 8,
+ ),
+ if (ref.read(exchangeFormStateProvider).fromAmount != null &&
+ ref.read(exchangeFormStateProvider).fromAmount != Decimal.zero)
+ 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 e4e6c1f279..15d8dccec6 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 5681018de9..800d1e146a 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 a7b34571b1..0f03d42169 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 = "Waiting";
- if (statusResponse.value != null) {
- status = statusResponse.value!.status.name;
- }
+ String status = response.value!.status;
- 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 222ccfc082..0921f68e07 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 ad618dd0cc..84e054eacc 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 74a1f62d42..51c420c829 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 0000000000..20d39f986c
--- /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 0000000000..31460c75f2
--- /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 ae44faeef9..bba561ead8 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