From 2dfdc31a44abe38251437e0fa847b5e0b08af79b Mon Sep 17 00:00:00 2001 From: sneurlax Date: Tue, 25 Apr 2023 17:55:46 -0500 Subject: [PATCH 01/65] build docs update emphasize that android studio must be manually installed before setup.sh is run --- docs/building.md | 52 +++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/docs/building.md b/docs/building.md index 879008f09..78556038b 100644 --- a/docs/building.md +++ b/docs/building.md @@ -1,23 +1,31 @@ # Building +## Prerequisites -Here you will find instructions on how to install the necessary tools for building and running the app. - -### Prerequisites - -- The OS'es supported for building is Ubuntu (20.04) and Fedora (37 - Work In Progress) +- The only OS supported for building is Ubuntu (20.04) - A machine with at least 100 GB of Storage +- [Android Studio](https://developer.android.com/studio) [its dependencies](https://developer.android.com/studio/install#64bit-libs) -The following prerequisites can be installed with the setup script [`scripts/setup.sh`](./../scripts/setup.sh) or manually as described below: +### 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. It is used for Android builds which can be otherwise be made from the commandline but which are not scripted yet. -- Flutter 3.7.11 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install) -- Dart SDK Requirement (>=2.19.0, up until <3.0.0) (normally included with a flutter install) -- Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies) +Follow instructions here [https://developer.android.com/studio/install#linux](https://developer.android.com/studio/install#linux) or install via snap: +``` +# setup android studio +sudo apt install -y openjdk-11-jdk +sudo snap install android-studio --classic +``` -### Scripted setup +Use Tools > SDK Manager to install the SDK Tools > SDK Platforms > Android 11.0 (R) (API 30), SDK Tools > NDK, SDK Tools > Android SDK command line tools, and SDK Tools > CMake -[`scripts/setup.sh`](./../scripts/setup.sh) is provided as a tool to set up 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. +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`) -### Manual setup +Make a Pixel 4 (API 30) x86_64 emulator with 2GB of storage space for emulation + +## Scripted setup + +[`scripts/setup.sh`](./../scripts/setup.sh) is provided as a tool to set up 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. You will still need to install Android Studio manually as described above. + +## Manual setup > If you have installed with script, skip to [running](#running) @@ -105,10 +113,6 @@ cd scripts/linux/ cd ../.. ``` -#### Fedora (37) (Work In Progress) - -This is a work in progress, please use Ubuntu for now. - ## Running ### Android Plug in your android device or use the emulator available via Android Studio and then run the following commands: @@ -125,19 +129,3 @@ Plug in your android device or use the emulator available via Android Studio and 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. - -Follow instructions here [https://developer.android.com/studio/install#linux](https://developer.android.com/studio/install#linux) or install via snap: -``` -# 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 From 17e6cf97cf763e91e2202c758a8a6e90e5e7ef90 Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Tue, 25 Apr 2023 18:02:45 -0500 Subject: [PATCH 02/65] build linux plugins too --- scripts/setup.sh | 2 ++ 1 file changed, 2 insertions(+) mode change 100644 => 100755 scripts/setup.sh diff --git a/scripts/setup.sh b/scripts/setup.sh old mode 100644 new mode 100755 index 8be59f8c4..cfbe9a6c2 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -49,3 +49,5 @@ rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-andro cd $STACK_WALLET cd scripts/android ./build_all.sh +cd ../linux +./build_all.sh From 9c414068d30f626a357f8f1d6e3cf56ec3edefe5 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 28 Apr 2023 10:30:31 -0600 Subject: [PATCH 03/65] trocador api and response DTOs --- .../response_objects/trocador_coin.dart | 44 +++ .../response_objects/trocador_quote.dart | 43 +++ .../response_objects/trocador_rate.dart | 84 +++++ .../response_objects/trocador_trade.dart | 116 +++++++ .../response_objects/trocador_trade_new.dart | 106 ++++++ .../exchange/trocador/trocador_api.dart | 306 ++++++++++++++++++ .../exchange/trocador/trocador_exchange.dart | 0 7 files changed, 699 insertions(+) create mode 100644 lib/services/exchange/trocador/response_objects/trocador_coin.dart create mode 100644 lib/services/exchange/trocador/response_objects/trocador_quote.dart create mode 100644 lib/services/exchange/trocador/response_objects/trocador_rate.dart create mode 100644 lib/services/exchange/trocador/response_objects/trocador_trade.dart create mode 100644 lib/services/exchange/trocador/response_objects/trocador_trade_new.dart create mode 100644 lib/services/exchange/trocador/trocador_api.dart create mode 100644 lib/services/exchange/trocador/trocador_exchange.dart diff --git a/lib/services/exchange/trocador/response_objects/trocador_coin.dart b/lib/services/exchange/trocador/response_objects/trocador_coin.dart new file mode 100644 index 000000000..aea01d2f1 --- /dev/null +++ b/lib/services/exchange/trocador/response_objects/trocador_coin.dart @@ -0,0 +1,44 @@ +import 'package:decimal/decimal.dart'; + +class TrocadorCoin { + final String name; + final String ticker; + final String network; + final bool memo; + final String image; + final Decimal minimum; + final Decimal maximum; + + TrocadorCoin({ + required this.name, + required this.ticker, + required this.network, + required this.memo, + required this.image, + required this.minimum, + required this.maximum, + }); + + factory TrocadorCoin.fromMap(Map json) => TrocadorCoin( + name: json['name'] as String, + ticker: json['ticker'] as String, + network: json['network'] as String, + memo: json['memo'] as bool, + image: json['image'] as String, + minimum: Decimal.parse(json['minimum'].toString()), + maximum: Decimal.parse(json['maximum'].toString()), + ); + + @override + String toString() { + return 'TrocadorCoin( ' + 'name: $name, ' + 'ticker: $ticker, ' + 'network: $network, ' + 'memo: $memo, ' + 'image: $image, ' + 'minimum: $minimum, ' + 'maximum: $maximum ' + ')'; + } +} diff --git a/lib/services/exchange/trocador/response_objects/trocador_quote.dart b/lib/services/exchange/trocador/response_objects/trocador_quote.dart new file mode 100644 index 000000000..7b4a86984 --- /dev/null +++ b/lib/services/exchange/trocador/response_objects/trocador_quote.dart @@ -0,0 +1,43 @@ +import 'package:decimal/decimal.dart'; + +class TrocadorQuote { + final String provider; + final String kycRating; + final int insurance; + final bool fixed; + final Decimal amountTo; + final Decimal waste; + + TrocadorQuote({ + required this.provider, + required this.kycRating, + required this.insurance, + required this.fixed, + required this.amountTo, + required this.waste, + }); + + factory TrocadorQuote.fromMap(Map map) { + return TrocadorQuote( + provider: map['provider'] as String, + kycRating: map['kycrating'] as String, + insurance: map['insurance'] as int, + // wtf trocador? + fixed: map['fixed'] == "True", + amountTo: Decimal.parse(map['amount_to'].toString()), + waste: Decimal.parse(map['waste'].toString()), + ); + } + + @override + String toString() { + return 'TrocadorQuote( ' + 'provider: $provider, ' + 'kycRating: $kycRating, ' + 'insurance: $insurance, ' + 'fixed: $fixed, ' + 'amountTo: $amountTo, ' + 'waste: $waste ' + ')'; + } +} diff --git a/lib/services/exchange/trocador/response_objects/trocador_rate.dart b/lib/services/exchange/trocador/response_objects/trocador_rate.dart new file mode 100644 index 000000000..5c3d18da2 --- /dev/null +++ b/lib/services/exchange/trocador/response_objects/trocador_rate.dart @@ -0,0 +1,84 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_quote.dart'; + +class TrocadorRate { + final String tradeId; + final DateTime date; + final String tickerFrom; + final String tickerTo; + final String coinFrom; + final String coinTo; + final String networkFrom; + final String networkTo; + final Decimal amountFrom; + final Decimal amountTo; + final String provider; + final bool fixed; + final bool payment; + final String status; + final List quotes; + + TrocadorRate({ + required this.tradeId, + required this.date, + required this.tickerFrom, + required this.tickerTo, + required this.coinFrom, + required this.coinTo, + required this.networkFrom, + required this.networkTo, + required this.amountFrom, + required this.amountTo, + required this.provider, + required this.fixed, + required this.payment, + required this.status, + required this.quotes, + }); + + factory TrocadorRate.fromMap(Map map) { + final list = + List>.from(map['quotes']['quotes'] as List); + final quotes = list.map((quote) => TrocadorQuote.fromMap(quote)).toList(); + + return TrocadorRate( + tradeId: map['trade_id'] as String, + date: DateTime.parse(map['date'] as String), + tickerFrom: map['ticker_from'] as String, + tickerTo: map['ticker_to'] as String, + coinFrom: map['coin_from'] as String, + coinTo: map['coin_to'] as String, + networkFrom: map['network_from'] as String, + networkTo: map['network_to'] as String, + amountFrom: Decimal.parse(map['amount_from'].toString()), + amountTo: Decimal.parse(map['amount_to'].toString()), + provider: map['provider'] as String, + fixed: map['fixed'] as bool, + payment: map['payment'] as bool, + status: map['status'] as String, + quotes: quotes, + ); + } + + @override + String toString() { + final quotesString = quotes.map((quote) => quote.toString()).join(', '); + return 'TrocadorRate(' + 'tradeId: $tradeId, ' + 'date: $date, ' + 'tickerFrom: $tickerFrom, ' + 'tickerTo: $tickerTo, ' + 'coinFrom: $coinFrom, ' + 'coinTo: $coinTo, ' + 'networkFrom: $networkFrom, ' + 'networkTo: $networkTo, ' + 'amountFrom: $amountFrom, ' + 'amountTo: $amountTo, ' + 'provider: $provider, ' + 'fixed: $fixed, ' + 'payment: $payment, ' + 'status: $status, ' + 'quotes: [$quotesString] ' + ')'; + } +} diff --git a/lib/services/exchange/trocador/response_objects/trocador_trade.dart b/lib/services/exchange/trocador/response_objects/trocador_trade.dart new file mode 100644 index 000000000..ce0f1b933 --- /dev/null +++ b/lib/services/exchange/trocador/response_objects/trocador_trade.dart @@ -0,0 +1,116 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_quote.dart'; + +class TrocadorTrade { + final String tradeId; + final DateTime date; + final String tickerFrom; + final String tickerTo; + final String coinFrom; + final String coinTo; + final String networkFrom; + final String networkTo; + final Decimal amountFrom; + final Decimal amountTo; + final String provider; + final bool fixed; + final String status; + final String addressProvider; + final String addressProviderMemo; + final String addressUser; + final String addressUserMemo; + final String refundAddress; + final String refundAddressMemo; + final String password; + final String idProvider; + final List quotes; + final bool payment; + + TrocadorTrade({ + required this.tradeId, + required this.date, + required this.tickerFrom, + required this.tickerTo, + required this.coinFrom, + required this.coinTo, + required this.networkFrom, + required this.networkTo, + required this.amountFrom, + required this.amountTo, + required this.provider, + required this.fixed, + required this.status, + required this.addressProvider, + required this.addressProviderMemo, + required this.addressUser, + required this.addressUserMemo, + required this.refundAddress, + required this.refundAddressMemo, + required this.password, + required this.idProvider, + required this.quotes, + required this.payment, + }); + + factory TrocadorTrade.fromMap(Map map) { + final list = + List>.from(map['quotes']['quotes'] as List); + final quotes = list.map((quote) => TrocadorQuote.fromMap(quote)).toList(); + + return TrocadorTrade( + tradeId: map['trade_id'] as String, + date: DateTime.parse(map['date'] as String), + tickerFrom: map['ticker_from'] as String, + tickerTo: map['ticker_to'] as String, + coinFrom: map['coin_from'] as String, + coinTo: map['coin_to'] as String, + networkFrom: map['network_from'] as String, + networkTo: map['network_to'] as String, + amountFrom: Decimal.parse(map['amount_from'].toString()), + amountTo: Decimal.parse(map['amount_to'].toString()), + provider: map['provider'] as String, + fixed: map['fixed'] as bool, + status: map['status'] as String, + addressProvider: map['address_provider'] as String, + addressProviderMemo: map['address_provider_memo'] as String, + addressUser: map['address_user'] as String, + addressUserMemo: map['address_user_memo'] as String, + refundAddress: map['refund_address'] as String, + refundAddressMemo: map['refund_address_memo'] as String, + password: map['password'] as String, + idProvider: map['id_provider'] as String, + quotes: quotes, + payment: map['payment'] as bool, + ); + } + + @override + String toString() { + final quotesString = quotes.map((quote) => quote.toString()).join(', '); + return 'TrocadorTrade( ' + 'tradeId: $tradeId, ' + 'date: $date, ' + 'tickerFrom: $tickerFrom, ' + 'tickerTo: $tickerTo, ' + 'coinFrom: $coinFrom, ' + 'coinTo: $coinTo, ' + 'networkFrom: $networkFrom, ' + 'networkTo: $networkTo, ' + 'amountFrom: $amountFrom, ' + 'amountTo: $amountTo, ' + 'provider: $provider, ' + 'fixed: $fixed, ' + 'status: $status, ' + 'addressProvider: $addressProvider, ' + 'addressProviderMemo: $addressProviderMemo, ' + 'addressUser: $addressUser, ' + 'addressUserMemo: $addressUserMemo, ' + 'refundAddress: $refundAddress, ' + 'refundAddressMemo: $refundAddressMemo, ' + 'password: $password, ' + 'idProvider: $idProvider, ' + 'quotes: [ $quotesString ], ' + 'payment: $payment ' + ')'; + } +} diff --git a/lib/services/exchange/trocador/response_objects/trocador_trade_new.dart b/lib/services/exchange/trocador/response_objects/trocador_trade_new.dart new file mode 100644 index 000000000..7228ff781 --- /dev/null +++ b/lib/services/exchange/trocador/response_objects/trocador_trade_new.dart @@ -0,0 +1,106 @@ +import 'package:decimal/decimal.dart'; + +class TrocadorTradeNew { + final String tradeId; + final DateTime date; + final String tickerFrom; + final String tickerTo; + final String coinFrom; + final String coinTo; + final String networkFrom; + final String networkTo; + final Decimal amountFrom; + final Decimal amountTo; + final String provider; + final bool fixed; + final String status; + final String? addressProvider; + final String? addressProviderMemo; + final String addressUser; + final String addressUserMemo; + final String refundAddress; + final String refundAddressMemo; + final String password; + final String idProvider; + final bool payment; + + TrocadorTradeNew({ + required this.tradeId, + required this.date, + required this.tickerFrom, + required this.tickerTo, + required this.coinFrom, + required this.coinTo, + required this.networkFrom, + required this.networkTo, + required this.amountFrom, + required this.amountTo, + required this.provider, + required this.fixed, + required this.status, + required this.addressProvider, + required this.addressProviderMemo, + required this.addressUser, + required this.addressUserMemo, + required this.refundAddress, + required this.refundAddressMemo, + required this.password, + required this.idProvider, + required this.payment, + }); + + factory TrocadorTradeNew.fromMap(Map map) { + return TrocadorTradeNew( + tradeId: map['trade_id'] as String, + date: DateTime.parse(map['date'] as String), + tickerFrom: map['ticker_from'] as String, + tickerTo: map['ticker_to'] as String, + coinFrom: map['coin_from'] as String, + coinTo: map['coin_to'] as String, + networkFrom: map['network_from'] as String, + networkTo: map['network_to'] as String, + amountFrom: Decimal.parse(map['amount_from'].toString()), + amountTo: Decimal.parse(map['amount_to'].toString()), + provider: map['provider'] as String, + fixed: map['fixed'] as bool, + status: map['status'] as String, + addressProvider: map['address_provider'] as String, + addressProviderMemo: map['address_provider_memo'] as String, + addressUser: map['address_user'] as String, + addressUserMemo: map['address_user_memo'] as String, + refundAddress: map['refund_address'] as String, + refundAddressMemo: map['refund_address_memo'] as String, + password: map['password'] as String, + idProvider: map['id_provider'] as String, + payment: map['payment'] as bool, + ); + } + + @override + String toString() { + return 'TrocadorTradeNew( ' + 'tradeId: $tradeId, ' + 'date: $date, ' + 'tickerFrom: $tickerFrom, ' + 'tickerTo: $tickerTo, ' + 'coinFrom: $coinFrom, ' + 'coinTo: $coinTo, ' + 'networkFrom: $networkFrom, ' + 'networkTo: $networkTo, ' + 'amountFrom: $amountFrom, ' + 'amountTo: $amountTo, ' + 'provider: $provider, ' + 'fixed: $fixed, ' + 'status: $status, ' + 'addressProvider: $addressProvider, ' + 'addressProviderMemo: $addressProviderMemo, ' + 'addressUser: $addressUser, ' + 'addressUserMemo: $addressUserMemo, ' + 'refundAddress: $refundAddress, ' + 'refundAddressMemo: $refundAddressMemo, ' + 'password: $password, ' + 'idProvider: $idProvider, ' + 'payment: $payment ' + ')'; + } +} diff --git a/lib/services/exchange/trocador/trocador_api.dart b/lib/services/exchange/trocador/trocador_api.dart new file mode 100644 index 000000000..e4d2ca582 --- /dev/null +++ b/lib/services/exchange/trocador/trocador_api.dart @@ -0,0 +1,306 @@ +import 'dart:convert'; + +import 'package:flutter_native_splash/cli_commands.dart'; +import 'package:http/http.dart' as http; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_coin.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_rate.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_trade.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_trade_new.dart'; +import 'package:stackwallet/utilities/logger.dart'; + +const kTrocadorApiKey = "8rFqf7QLxX1mUBiNPEMaLUpV2biz6n"; +const kTrocadorRefCode = "9eHm9BkQfS"; + +abstract class TrocadorAPI { + static const String authority = "trocador.app"; + static const String onionAuthority = + "trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion"; + + static const String markup = "1"; + static const String minKYCRating = "C"; + + static Uri _buildUri({ + required String method, + required bool isOnion, + Map? params, + }) { + return isOnion + ? Uri.http(onionAuthority, "api/$method", params) + : Uri.https(authority, "api/$method", params); + } + + static Future _makeGetRequest(Uri uri) async { + int code = -1; + try { + print("URI: $uri"); + final response = await http.get( + uri, + headers: {'Content-Type': 'application/json'}, + ); + + code = response.statusCode; + + print("CODE: $code"); + print("BODY: ${response.body}"); + + final json = jsonDecode(response.body); + + return json; + } catch (e, s) { + Logging.instance.log( + "_makeRequest($uri) HTTP:$code threw: $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + /// fetch all supported coins + static Future>> getCoins({ + required bool isOnion, + }) async { + final uri = _buildUri( + isOnion: isOnion, + method: "coins", + params: { + "api_key": kTrocadorApiKey, + "ref": kTrocadorRefCode, + }, + ); + + try { + final json = await _makeGetRequest(uri); + + if (json is List) { + final list = List>.from(json); + final List coins = list + .map( + (e) => TrocadorCoin.fromMap(e), + ) + .toList(); + + return ExchangeResponse(value: coins); + } else { + throw Exception("unexpected json: $json"); + } + } catch (e, s) { + Logging.instance.log("getCoins exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + /// get trade info + static Future> getTrade({ + required bool isOnion, + required String tradeId, + }) async { + final uri = _buildUri( + isOnion: isOnion, + method: "trade", + params: { + "api_key": kTrocadorApiKey, + "ref": kTrocadorRefCode, + "id": tradeId, + }, + ); + + try { + final json = await _makeGetRequest(uri); + final map = Map.from(json as Map); + + return ExchangeResponse(value: TrocadorTrade.fromMap(map)); + } catch (e, s) { + Logging.instance.log("getTrade exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + /// get standard/floating rate + static Future> getNewStandardRate({ + required bool isOnion, + required String fromTicker, + required String fromNetwork, + required String toTicker, + required String toNetwork, + required String fromAmount, + }) async { + final params = { + "api_key": kTrocadorApiKey, + "ref": kTrocadorRefCode, + "ticker_from": fromTicker, + "network_from": fromNetwork, + "ticker_to": toTicker, + "network_to": toNetwork, + "amount_from": fromAmount, + "min_kycrating": minKYCRating, + "markup": markup, + }; + + return await _getNewRate(isOnion: isOnion, params: params); + } + + /// get fixed rate/payment rate + static Future> getNewPaymentRate({ + required bool isOnion, + required String fromTicker, + required String fromNetwork, + required String toTicker, + required String toNetwork, + required String toAmount, + }) async { + final params = { + "api_key": kTrocadorApiKey, + "ref": kTrocadorRefCode, + "ticker_from": fromTicker, + "network_from": fromNetwork, + "ticker_to": toTicker, + "network_to": toNetwork, + "amount_to": toAmount, + "min_kycrating": minKYCRating, + "markup": markup, + }; + + return await _getNewRate(isOnion: isOnion, params: params); + } + + static Future> _getNewRate({ + required bool isOnion, + required Map params, + }) async { + final uri = _buildUri( + isOnion: isOnion, + method: "new_rate", + params: params, + ); + + try { + final json = await _makeGetRequest(uri); + final map = Map.from(json as Map); + + return ExchangeResponse(value: TrocadorRate.fromMap(map)); + } catch (e, s) { + Logging.instance + .log("getNewRate exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + /// create new floating rate/standard trade + static Future> createNewStandardRateTrade({ + required bool isOnion, + required String? rateId, + required String fromTicker, + required String fromNetwork, + required String toTicker, + required String toNetwork, + required String fromAmount, + required String receivingAddress, + required String? receivingMemo, + required String refundAddress, + required String? refundMemo, + required String exchangeProvider, + required bool isFixedRate, + }) async { + final Map params = { + "api_key": kTrocadorApiKey, + "ref": kTrocadorRefCode, + "id": rateId ?? "", + "ticker_from": fromTicker, + "network_from": fromNetwork, + "ticker_to": toTicker, + "network_to": toNetwork, + "amount_from": fromAmount, + "address": receivingAddress, + "address_memo": receivingMemo ?? "0", + "refund": refundAddress, + "refund_memo": refundMemo ?? "0", + "provider": exchangeProvider, + "fixed": isFixedRate.toString().capitalize(), + "payment": "False", + "min_kycrating": minKYCRating, + "markup": markup, + }; + + return await _getNewTrade(isOnion: isOnion, params: params); + } + + static Future> createNewPaymentRateTrade({ + required bool isOnion, + required String fromTicker, + required String fromNetwork, + required String toTicker, + required String toNetwork, + required String toAmount, + required String receivingAddress, + required String? receivingMemo, + required String refundAddress, + required String? refundMemo, + required String exchangeProvider, + required bool isFixedRate, + }) async { + final params = { + "api_key": kTrocadorApiKey, + "ref": kTrocadorRefCode, + "ticker_from": fromTicker, + "network_from": fromNetwork, + "ticker_to": toTicker, + "network_to": toNetwork, + "amount_to": toAmount, + "address": receivingAddress, + "address_memo": receivingMemo ?? "0", + "refund": refundAddress, + "refund_memo": refundMemo ?? "0", + "provider": exchangeProvider, + "fixed": isFixedRate.toString().capitalize(), + "payment": "True", + "min_kycrating": minKYCRating, + "markup": markup, + }; + + return await _getNewTrade(isOnion: isOnion, params: params); + } + + static Future> _getNewTrade({ + required bool isOnion, + required Map params, + }) async { + final uri = _buildUri( + isOnion: isOnion, + method: "new_trade", + params: params, + ); + + try { + final json = await _makeGetRequest(uri); + final map = Map.from(json as Map); + + return ExchangeResponse(value: TrocadorTradeNew.fromMap(map)); + } catch (e, s) { + Logging.instance + .log("_getNewTrade exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } +} diff --git a/lib/services/exchange/trocador/trocador_exchange.dart b/lib/services/exchange/trocador/trocador_exchange.dart new file mode 100644 index 000000000..e69de29bb From da9b679d3c52610542f0a2cf8dc7f844b4b231f3 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 28 Apr 2023 14:26:51 -0600 Subject: [PATCH 04/65] trocador api tweaks and clean up --- .../response_objects/trocador_trade.dart | 15 +++---- .../response_objects/trocador_trade_new.dart | 4 +- .../exchange/trocador/trocador_api.dart | 40 ++++++++++++------- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/lib/services/exchange/trocador/response_objects/trocador_trade.dart b/lib/services/exchange/trocador/response_objects/trocador_trade.dart index ce0f1b933..5768a9423 100644 --- a/lib/services/exchange/trocador/response_objects/trocador_trade.dart +++ b/lib/services/exchange/trocador/response_objects/trocador_trade.dart @@ -1,5 +1,4 @@ import 'package:decimal/decimal.dart'; -import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_quote.dart'; class TrocadorTrade { final String tradeId; @@ -23,7 +22,10 @@ class TrocadorTrade { final String refundAddressMemo; final String password; final String idProvider; - final List quotes; + + // dynamic is the devil but this could be anything... because json. + final dynamic quotes; + final bool payment; TrocadorTrade({ @@ -53,10 +55,6 @@ class TrocadorTrade { }); factory TrocadorTrade.fromMap(Map map) { - final list = - List>.from(map['quotes']['quotes'] as List); - final quotes = list.map((quote) => TrocadorQuote.fromMap(quote)).toList(); - return TrocadorTrade( tradeId: map['trade_id'] as String, date: DateTime.parse(map['date'] as String), @@ -79,14 +77,13 @@ class TrocadorTrade { refundAddressMemo: map['refund_address_memo'] as String, password: map['password'] as String, idProvider: map['id_provider'] as String, - quotes: quotes, + quotes: map['quotes'], payment: map['payment'] as bool, ); } @override String toString() { - final quotesString = quotes.map((quote) => quote.toString()).join(', '); return 'TrocadorTrade( ' 'tradeId: $tradeId, ' 'date: $date, ' @@ -109,7 +106,7 @@ class TrocadorTrade { 'refundAddressMemo: $refundAddressMemo, ' 'password: $password, ' 'idProvider: $idProvider, ' - 'quotes: [ $quotesString ], ' + 'quotes: $quotes, ' 'payment: $payment ' ')'; } diff --git a/lib/services/exchange/trocador/response_objects/trocador_trade_new.dart b/lib/services/exchange/trocador/response_objects/trocador_trade_new.dart index 7228ff781..4dc8ae380 100644 --- a/lib/services/exchange/trocador/response_objects/trocador_trade_new.dart +++ b/lib/services/exchange/trocador/response_objects/trocador_trade_new.dart @@ -14,8 +14,8 @@ class TrocadorTradeNew { final String provider; final bool fixed; final String status; - final String? addressProvider; - final String? addressProviderMemo; + final String addressProvider; + final String addressProviderMemo; final String addressUser; final String addressUserMemo; final String refundAddress; diff --git a/lib/services/exchange/trocador/trocador_api.dart b/lib/services/exchange/trocador/trocador_api.dart index e4d2ca582..36935cc0b 100644 --- a/lib/services/exchange/trocador/trocador_api.dart +++ b/lib/services/exchange/trocador/trocador_api.dart @@ -34,7 +34,7 @@ abstract class TrocadorAPI { static Future _makeGetRequest(Uri uri) async { int code = -1; try { - print("URI: $uri"); + // print("URI: $uri"); final response = await http.get( uri, headers: {'Content-Type': 'application/json'}, @@ -42,8 +42,8 @@ abstract class TrocadorAPI { code = response.statusCode; - print("CODE: $code"); - print("BODY: ${response.body}"); + // print("CODE: $code"); + // print("BODY: ${response.body}"); final json = jsonDecode(response.body); @@ -113,7 +113,7 @@ abstract class TrocadorAPI { try { final json = await _makeGetRequest(uri); - final map = Map.from(json as Map); + final map = Map.from((json as List).first as Map); return ExchangeResponse(value: TrocadorTrade.fromMap(map)); } catch (e, s) { @@ -128,7 +128,7 @@ abstract class TrocadorAPI { } /// get standard/floating rate - static Future> getNewStandardRate({ + static Future> getNewStandardRate({ required bool isOnion, required String fromTicker, required String fromNetwork, @@ -139,11 +139,12 @@ abstract class TrocadorAPI { final params = { "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, - "ticker_from": fromTicker, + "ticker_from": fromTicker.toLowerCase(), "network_from": fromNetwork, - "ticker_to": toTicker, + "ticker_to": toTicker.toLowerCase(), "network_to": toNetwork, "amount_from": fromAmount, + "payment": "false", "min_kycrating": minKYCRating, "markup": markup, }; @@ -152,7 +153,7 @@ abstract class TrocadorAPI { } /// get fixed rate/payment rate - static Future> getNewPaymentRate({ + static Future> getNewPaymentRate({ required bool isOnion, required String fromTicker, required String fromNetwork, @@ -163,11 +164,12 @@ abstract class TrocadorAPI { final params = { "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, - "ticker_from": fromTicker, + "ticker_from": fromTicker.toLowerCase(), "network_from": fromNetwork, - "ticker_to": toTicker, + "ticker_to": toTicker.toLowerCase(), "network_to": toNetwork, "amount_to": toAmount, + "payment": "true", "min_kycrating": minKYCRating, "markup": markup, }; @@ -221,10 +223,9 @@ abstract class TrocadorAPI { final Map params = { "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, - "id": rateId ?? "", - "ticker_from": fromTicker, + "ticker_from": fromTicker.toLowerCase(), "network_from": fromNetwork, - "ticker_to": toTicker, + "ticker_to": toTicker.toLowerCase(), "network_to": toNetwork, "amount_from": fromAmount, "address": receivingAddress, @@ -238,11 +239,16 @@ abstract class TrocadorAPI { "markup": markup, }; + if (rateId != null) { + params["id"] = rateId; + } + return await _getNewTrade(isOnion: isOnion, params: params); } static Future> createNewPaymentRateTrade({ required bool isOnion, + required String? rateId, required String fromTicker, required String fromNetwork, required String toTicker, @@ -258,9 +264,9 @@ abstract class TrocadorAPI { final params = { "api_key": kTrocadorApiKey, "ref": kTrocadorRefCode, - "ticker_from": fromTicker, + "ticker_from": fromTicker.toLowerCase(), "network_from": fromNetwork, - "ticker_to": toTicker, + "ticker_to": toTicker.toLowerCase(), "network_to": toNetwork, "amount_to": toAmount, "address": receivingAddress, @@ -274,6 +280,10 @@ abstract class TrocadorAPI { "markup": markup, }; + if (rateId != null) { + params["id"] = rateId; + } + return await _getNewTrade(isOnion: isOnion, params: params); } From da9b921ddc1345374746daf8b86ff8504c06d0e5 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 28 Apr 2023 14:29:10 -0600 Subject: [PATCH 05/65] pass around estimate instead of just rateId --- lib/models/exchange/incomplete_exchange.dart | 15 ++++++------ .../exchange/response_objects/estimate.dart | 3 +++ lib/pages/exchange_view/exchange_form.dart | 2 +- .../exchange_step_views/step_3_view.dart | 24 ++++++++++--------- .../change_now/change_now_exchange.dart | 4 ++-- lib/services/exchange/exchange.dart | 2 +- .../majestic_bank/majestic_bank_exchange.dart | 2 +- .../simpleswap/simpleswap_exchange.dart | 2 +- 8 files changed, 30 insertions(+), 24 deletions(-) diff --git a/lib/models/exchange/incomplete_exchange.dart b/lib/models/exchange/incomplete_exchange.dart index 864a25490..2680b24e0 100644 --- a/lib/models/exchange/incomplete_exchange.dart +++ b/lib/models/exchange/incomplete_exchange.dart @@ -1,5 +1,6 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; @@ -39,13 +40,13 @@ class IncompleteExchangeModel extends ChangeNotifier { } } - String? _rateId; + Estimate? _estimate; - String? get rateId => _rateId; + Estimate? get estimate => _estimate; - set rateId(String? rateId) { - if (_rateId != rateId) { - _rateId = rateId; + set estimate(Estimate? estimate) { + if (_estimate != estimate) { + _estimate = estimate; notifyListeners(); } } @@ -70,6 +71,6 @@ class IncompleteExchangeModel extends ChangeNotifier { required this.rateType, required this.reversed, required this.walletInitiated, - String? rateId, - }) : _rateId = rateId; + Estimate? estimate, + }) : _estimate = estimate; } diff --git a/lib/models/exchange/response_objects/estimate.dart b/lib/models/exchange/response_objects/estimate.dart index 7df490079..d93fcb560 100644 --- a/lib/models/exchange/response_objects/estimate.dart +++ b/lib/models/exchange/response_objects/estimate.dart @@ -7,6 +7,7 @@ class Estimate { final bool reversed; final String? warningMessage; final String? rateId; + final String? exchangeProvider; Estimate({ required this.estimatedAmount, @@ -14,6 +15,7 @@ class Estimate { required this.reversed, this.warningMessage, this.rateId, + this.exchangeProvider, }); factory Estimate.fromMap(Map map) { @@ -38,6 +40,7 @@ class Estimate { "reversed": reversed, "warningMessage": warningMessage, "rateId": rateId, + "exchangeProvider": exchangeProvider, }; } diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 11b4f4e51..3ecee0994 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -554,7 +554,7 @@ class _ExchangeFormState extends ConsumerState { ? ref.read(exchangeFormStateProvider).receiveAmount! : estimate.estimatedAmount, rateType: rateType, - rateId: estimate.rateId, + estimate: estimate, reversed: estimate.reversed, walletInitiated: walletInitiated, ); 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 22c356b5d..a7c954c59 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 @@ -271,24 +271,26 @@ class _Step3ViewState extends ConsumerState { ? model.refundAddress! : "", refundExtraId: "", - rateId: model.rateId, + estimate: model.estimate, reversed: model.reversed, ); if (response.value == null) { if (mounted) { Navigator.of(context).pop(); - } - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Failed to create trade", - message: - response.exception?.toString(), - ), - )); + unawaited( + showDialog( + context: context, + barrierDismissible: true, + builder: (_) => StackDialog( + title: "Failed to create trade", + message: response.exception + ?.toString(), + ), + ), + ); + } return; } diff --git a/lib/services/exchange/change_now/change_now_exchange.dart b/lib/services/exchange/change_now/change_now_exchange.dart index 3189ff84e..f5b68d300 100644 --- a/lib/services/exchange/change_now/change_now_exchange.dart +++ b/lib/services/exchange/change_now/change_now_exchange.dart @@ -31,7 +31,7 @@ class ChangeNowExchange extends Exchange { String? extraId, required String addressRefund, required String refundExtraId, - String? rateId, + Estimate? estimate, required bool reversed, }) async { late final ExchangeResponse response; @@ -41,7 +41,7 @@ class ChangeNowExchange extends Exchange { toTicker: to, receivingAddress: addressTo, amount: amount, - rateId: rateId!, + rateId: estimate!.rateId!, extraId: extraId ?? "", refundAddress: addressRefund, refundExtraId: refundExtraId, diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart index e4f5ce8d5..4aae5a0b8 100644 --- a/lib/services/exchange/exchange.dart +++ b/lib/services/exchange/exchange.dart @@ -69,7 +69,7 @@ abstract class Exchange { String? extraId, required String addressRefund, required String refundExtraId, - String? rateId, + Estimate? estimate, required bool reversed, }); } diff --git a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart index dcc63c68b..0980abc62 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart @@ -44,7 +44,7 @@ class MajesticBankExchange extends Exchange { String? extraId, required String addressRefund, required String refundExtraId, - String? rateId, + Estimate? estimate, required bool reversed, }) async { ExchangeResponse? response; diff --git a/lib/services/exchange/simpleswap/simpleswap_exchange.dart b/lib/services/exchange/simpleswap/simpleswap_exchange.dart index 1157bd09e..2822d4a1e 100644 --- a/lib/services/exchange/simpleswap/simpleswap_exchange.dart +++ b/lib/services/exchange/simpleswap/simpleswap_exchange.dart @@ -30,7 +30,7 @@ class SimpleSwapExchange extends Exchange { String? extraId, required String addressRefund, required String refundExtraId, - String? rateId, + Estimate? estimate, required bool reversed, }) async { return await SimpleSwapAPI.instance.createNewExchange( From d3b3092281855356c721a4a9e15cfe42ae0d1cb2 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 28 Apr 2023 14:29:30 -0600 Subject: [PATCH 06/65] code readability --- lib/models/exchange/aggregate_currency.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/models/exchange/aggregate_currency.dart b/lib/models/exchange/aggregate_currency.dart index 6cd1ef6cf..1bbc767a5 100644 --- a/lib/models/exchange/aggregate_currency.dart +++ b/lib/models/exchange/aggregate_currency.dart @@ -5,8 +5,9 @@ import 'package:tuple/tuple.dart'; class AggregateCurrency { final Map _map = {}; - AggregateCurrency( - {required List> exchangeCurrencyPairs}) { + AggregateCurrency({ + required List> exchangeCurrencyPairs, + }) { assert(exchangeCurrencyPairs.isNotEmpty); for (final item in exchangeCurrencyPairs) { From 05c4d01ee4b2bd43eb6dfff9ebe9b24535775ad3 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 28 Apr 2023 14:31:26 -0600 Subject: [PATCH 07/65] initial Trocador integration --- lib/models/exchange/exchange_form_state.dart | 11 + .../exchange_provider_options.dart | 251 +++++++++++- .../exchange_view/trade_details_view.dart | 8 + lib/services/exchange/exchange.dart | 3 + .../exchange/trocador/trocador_exchange.dart | 359 ++++++++++++++++++ 5 files changed, 631 insertions(+), 1 deletion(-) diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart index 32578ebd9..c08357e1a 100644 --- a/lib/models/exchange/exchange_form_state.dart +++ b/lib/models/exchange/exchange_form_state.dart @@ -5,6 +5,7 @@ import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -346,6 +347,16 @@ class ExchangeFormState extends ChangeNotifier { _exchange = ChangeNowExchange.instance; } break; + case TrocadorExchange.exchangeName: + if (!_exchangeSupported( + exchangeName: exchange.name, + sendCurrency: sendCurrency, + receiveCurrency: receiveCurrency, + exchangeRateType: exchangeRateType, + )) { + _exchange = ChangeNowExchange.instance; + } + break; } await _updateRanges(shouldNotifyListeners: false); diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart index f770278f3..72aa3a8f1 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -9,6 +9,7 @@ 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/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -76,6 +77,11 @@ class _ExchangeProviderOptionsState sendCurrency: sendCurrency, receiveCurrency: receivingCurrency, ); + final showTrocador = exchangeSupported( + exchangeName: TrocadorExchange.exchangeName, + sendCurrency: sendCurrency, + receiveCurrency: receivingCurrency, + ); return RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(0) : const EdgeInsets.all(12), @@ -505,7 +511,250 @@ class _ExchangeProviderOptionsState ); } else { Logging.instance.log( - "$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}", + "$runtimeType failed to fetch rate for Majestic Bank: ${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 (!(sendCurrency != null && + receivingCurrency != 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, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + if ((showChangeNow || showMajesticBank) && showTrocador) + isDesktop + ? Container( + height: 1, + color: + Theme.of(context).extension()!.background, + ) + : const SizedBox( + height: 16, + ), + + if (showTrocador) + ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: child, + ), + child: GestureDetector( + onTap: () { + if (ref.read(exchangeFormStateProvider).exchange.name != + TrocadorExchange.exchangeName) { + showLoading( + whileFuture: + ref.read(exchangeFormStateProvider).updateExchange( + exchange: TrocadorExchange.instance, + shouldUpdateData: true, + shouldNotifyListeners: true, + ), + context: context, + isDesktop: isDesktop, + message: "Updating rates", + ); + } + }, + child: Container( + color: Colors.transparent, + child: Padding( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: Padding( + padding: + EdgeInsets.only(top: isDesktop ? 20.0 : 15.0), + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: TrocadorExchange.exchangeName, + groupValue: ref.watch(exchangeFormStateProvider + .select((value) => value.exchange.name)), + onChanged: (_) { + if (ref + .read(exchangeFormStateProvider) + .exchange + .name != + TrocadorExchange.exchangeName) { + ref + .read(exchangeFormStateProvider) + .updateExchange( + exchange: TrocadorExchange.instance, + shouldUpdateData: true, + shouldNotifyListeners: true, + ); + } + }, + ), + ), + ), + const SizedBox( + width: 14, + ), + Padding( + padding: const EdgeInsets.only(top: 5.0), + child: SizedBox( + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + child: SvgPicture.asset( + Assets.exchange.majesticBankBlue, + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + ), + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + TrocadorExchange.exchangeName, + style: + STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark2, + ), + ), + if (sendCurrency != null && + receivingCurrency != null && + toAmount != null && + toAmount > Decimal.zero && + fromAmount != null && + fromAmount > Decimal.zero) + FutureBuilder( + future: TrocadorExchange.instance.getEstimate( + sendCurrency.ticker, + receivingCurrency.ticker, + widget.reversed ? toAmount : fromAmount, + widget.fixedRate, + widget.reversed, + ), + builder: (context, + AsyncSnapshot> + snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + final estimate = snapshot.data?.value; + if (estimate != null) { + Coin coin; + try { + coin = coinFromTickerCaseInsensitive( + receivingCurrency.ticker); + } catch (_) { + coin = Coin.bitcoin; + } + Amount rate; + if (estimate.reversed) { + rate = (toAmount / + estimate.estimatedAmount) + .toDecimal( + scaleOnInfinitePrecision: 18) + .toAmount( + fractionDigits: coin.decimals, + ); + } else { + rate = (estimate.estimatedAmount / + fromAmount) + .toDecimal( + scaleOnInfinitePrecision: 18) + .toAmount( + fractionDigits: coin.decimals, + ); + } + + return Text( + "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale), + ), + )} ${receivingCurrency.ticker.toUpperCase()}", + style: STextStyles.itemSubtitle12( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } else if (snapshot.data?.exception + is PairUnavailableException) { + return Text( + "Unsupported pair", + style: STextStyles.itemSubtitle12( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } else { + Logging.instance.log( + "$runtimeType failed to fetch rate for Trocador: ${snapshot.data}", level: LogLevel.Warning, ); return Text( diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index f0cb7b7a8..5f8e9d471 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -20,6 +20,7 @@ import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dar import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; @@ -1205,6 +1206,13 @@ class _TradeDetailsViewState extends ConsumerState { url = "https://majesticbank.sc/track?trx=${trade.tradeId}"; break; + + default: + if (trade.exchangeName + .startsWith(TrocadorExchange.exchangeName)) { + url = + "https://trocador.app/en/checkout${trade.tradeId}"; + } } return ConditionalParent( condition: isDesktop, diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart index 4aae5a0b8..1023e5150 100644 --- a/lib/services/exchange/exchange.dart +++ b/lib/services/exchange/exchange.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dar import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; abstract class Exchange { static Exchange get defaultExchange => ChangeNowExchange.instance; @@ -20,6 +21,8 @@ abstract class Exchange { return SimpleSwapExchange.instance; case MajesticBankExchange.exchangeName: return MajesticBankExchange.instance; + case TrocadorExchange.exchangeName: + return TrocadorExchange.instance; default: throw ArgumentError("Unknown exchange name"); } diff --git a/lib/services/exchange/trocador/trocador_exchange.dart b/lib/services/exchange/trocador/trocador_exchange.dart index e69de29bb..a4bfb26a3 100644 --- a/lib/services/exchange/trocador/trocador_exchange.dart +++ b/lib/services/exchange/trocador/trocador_exchange.dart @@ -0,0 +1,359 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_coin.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_api.dart'; +import 'package:uuid/uuid.dart'; + +class TrocadorExchange extends Exchange { + TrocadorExchange._(); + + static TrocadorExchange? _instance; + static TrocadorExchange get instance => _instance ??= TrocadorExchange._(); + + static const exchangeName = "Trocador"; + + static const onlySupportedNetwork = "Mainnet"; + + @override + Future> createTrade({ + required String from, + required String to, + required bool fixedRate, + required Decimal amount, + required String addressTo, + String? extraId, + required String addressRefund, + required String refundExtraId, + Estimate? estimate, + required bool reversed, + }) async { + final response = reversed + ? await TrocadorAPI.createNewPaymentRateTrade( + isOnion: false, + rateId: estimate?.rateId, + fromTicker: from.toLowerCase(), + fromNetwork: onlySupportedNetwork, + toTicker: to.toLowerCase(), + toNetwork: onlySupportedNetwork, + toAmount: amount.toString(), + receivingAddress: addressTo, + receivingMemo: null, + refundAddress: addressRefund, + refundMemo: null, + exchangeProvider: estimate!.exchangeProvider!, + isFixedRate: fixedRate, + ) + : await TrocadorAPI.createNewStandardRateTrade( + isOnion: false, + rateId: estimate?.rateId, + fromTicker: from.toLowerCase(), + fromNetwork: onlySupportedNetwork, + toTicker: to.toLowerCase(), + toNetwork: onlySupportedNetwork, + fromAmount: amount.toString(), + receivingAddress: addressTo, + receivingMemo: null, + refundAddress: addressRefund, + refundMemo: null, + exchangeProvider: estimate!.exchangeProvider!, + isFixedRate: fixedRate, + ); + + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final trade = response.value!; + + return ExchangeResponse( + value: Trade( + uuid: const Uuid().v1(), + tradeId: trade.tradeId, + rateType: fixedRate ? "fixed" : "floating", + direction: reversed ? "reversed" : "direct", + timestamp: trade.date, + updatedAt: trade.date, + payInCurrency: trade.coinFrom, + payInAmount: trade.amountFrom.toString(), + payInAddress: trade.addressProvider, + payInNetwork: trade.networkFrom, + payInExtraId: trade.addressProviderMemo, + payInTxid: "", + payOutCurrency: trade.coinTo, + payOutAmount: trade.amountTo.toString(), + payOutAddress: trade.addressUser, + payOutNetwork: trade.networkTo, + payOutExtraId: trade.addressUserMemo, + payOutTxid: "", + refundAddress: trade.refundAddress, + refundExtraId: trade.refundAddressMemo, + status: trade.status, + exchangeName: "$exchangeName (${trade.provider})", + ), + ); + } + + List? _cachedCurrencies; + + @override + Future>> getAllCurrencies( + bool fixedRate) async { + _cachedCurrencies ??= (await TrocadorAPI.getCoins(isOnion: false)).value; + + _cachedCurrencies?.removeWhere((e) => e.network != onlySupportedNetwork); + + final value = _cachedCurrencies + ?.map( + (e) => Currency( + exchangeName: exchangeName, + ticker: e.ticker, + name: e.name, + network: e.network, + image: e.image, + isFiat: false, + rateType: SupportedRateType.both, + isStackCoin: Currency.checkIsStackCoin(e.ticker), + tokenContract: null, + isAvailable: true, + ), + ) + .toList(); + + if (value == null) { + return ExchangeResponse( + exception: ExchangeException( + "Failed to fetch trocador coins", + ExchangeExceptionType.generic, + ), + ); + } else { + return ExchangeResponse(value: value); + } + } + + @override + Future>> getAllPairs(bool fixedRate) async { + final response = await getAllCurrencies(fixedRate); + + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final List pairs = []; + + for (int i = 0; i < response.value!.length; i++) { + final a = response.value![i]; + + for (int j = i + 1; j < response.value!.length; j++) { + final b = response.value![j]; + + pairs.add( + Pair( + exchangeName: exchangeName, + from: a.ticker, + to: b.ticker, + rateType: SupportedRateType.both, + ), + ); + pairs.add( + Pair( + exchangeName: exchangeName, + to: a.ticker, + from: b.ticker, + rateType: SupportedRateType.both, + ), + ); + } + } + + return ExchangeResponse(value: pairs); + } + + @override + Future> getEstimate( + String from, + String to, + Decimal amount, + bool fixedRate, + bool reversed, + ) async { + final isPayment = reversed || fixedRate; + + final response = isPayment + ? await TrocadorAPI.getNewPaymentRate( + isOnion: false, + fromTicker: from, + fromNetwork: onlySupportedNetwork, + toTicker: to, + toNetwork: onlySupportedNetwork, + toAmount: amount.toString(), + ) + : await TrocadorAPI.getNewStandardRate( + isOnion: false, + fromTicker: from, + fromNetwork: onlySupportedNetwork, + toTicker: to, + toNetwork: onlySupportedNetwork, + fromAmount: amount.toString(), + ); + + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + return ExchangeResponse( + value: Estimate( + estimatedAmount: + isPayment ? response.value!.amountFrom : response.value!.amountTo, + fixedRate: isPayment, + reversed: isPayment, + exchangeProvider: response.value!.provider, + rateId: response.value!.tradeId, + ), + ); + } + + @override + Future>> getPairedCurrencies( + String forCurrency, bool fixedRate) async { + // TODO: implement getPairedCurrencies + throw UnimplementedError(); + } + + @override + Future>> getPairsFor( + String currency, + bool fixedRate, + ) async { + final response = await getAllPairs(fixedRate); + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final pairs = response.value!.where( + (e) => + e.from.toUpperCase() == currency.toUpperCase() || + e.to.toUpperCase() == currency.toUpperCase(), + ); + + return ExchangeResponse(value: pairs.toList()); + } + + @override + Future> getRange( + String from, + String to, + bool fixedRate, + ) async { + if (_cachedCurrencies == null) { + await getAllCurrencies(fixedRate); + } + if (_cachedCurrencies == null) { + return ExchangeResponse( + exception: ExchangeException( + "Failed to updated trocador cached coins to get min/max range", + ExchangeExceptionType.generic, + ), + ); + } + + final fromCoin = _cachedCurrencies! + .firstWhere((e) => e.ticker.toLowerCase() == from.toLowerCase()); + + return ExchangeResponse( + value: Range( + max: fromCoin.maximum, + min: fromCoin.minimum, + ), + ); + } + + @override + Future> getTrade(String tradeId) async { + // TODO: implement getTrade + throw UnimplementedError(); + } + + @override + Future>> getTrades() async { + // TODO: implement getTrades + throw UnimplementedError(); + } + + @override + String get name => exchangeName; + + @override + Future> updateTrade(Trade trade) async { + final response = await TrocadorAPI.getTrade( + isOnion: false, + tradeId: trade.tradeId, + ); + + if (response.value != null) { + final updated = response.value!; + final updatedTrade = Trade( + uuid: trade.uuid, + tradeId: updated.tradeId, + rateType: trade.rateType, + direction: trade.direction, + timestamp: trade.timestamp, + updatedAt: DateTime.now(), + payInCurrency: updated.coinFrom, + payInAmount: updated.amountFrom.toString(), + payInAddress: updated.addressProvider, + payInNetwork: trade.payInNetwork, + payInExtraId: trade.payInExtraId, + payInTxid: trade.payInTxid, + payOutCurrency: updated.coinTo, + payOutAmount: updated.amountTo.toString(), + payOutAddress: updated.addressUser, + payOutNetwork: trade.payOutNetwork, + payOutExtraId: trade.payOutExtraId, + payOutTxid: trade.payOutTxid, + refundAddress: trade.refundAddress, + refundExtraId: trade.refundExtraId, + status: updated.status, + exchangeName: "$exchangeName (${updated.provider})", + ); + + return ExchangeResponse(value: updatedTrade); + } else { + if (response.exception?.type == ExchangeExceptionType.orderNotFound) { + final updatedTrade = Trade( + uuid: trade.uuid, + tradeId: trade.tradeId, + rateType: trade.rateType, + direction: trade.direction, + timestamp: trade.timestamp, + updatedAt: DateTime.now(), + payInCurrency: trade.payInCurrency, + payInAmount: trade.payInAmount, + payInAddress: trade.payInAddress, + payInNetwork: trade.payInNetwork, + payInExtraId: trade.payInExtraId, + payInTxid: trade.payInTxid, + payOutCurrency: trade.payOutCurrency, + payOutAmount: trade.payOutAmount, + payOutAddress: trade.payOutAddress, + payOutNetwork: trade.payOutNetwork, + payOutExtraId: trade.payOutExtraId, + payOutTxid: trade.payOutTxid, + refundAddress: trade.refundAddress, + refundExtraId: trade.refundExtraId, + status: "Unknown", + exchangeName: trade.exchangeName, + ); + return ExchangeResponse(value: updatedTrade); + } + return ExchangeResponse(exception: response.exception); + } + } +} From 31a29be929bb322b2f6fc15c650b7ca949467b07 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 28 Apr 2023 14:32:18 -0600 Subject: [PATCH 08/65] estimate/rateId fix and mounted check --- .../exchange_steps/step_scaffold.dart | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart index 203ae2d9b..9976f77ff 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart @@ -98,24 +98,24 @@ class _StepScaffoldState extends ConsumerState { extraId: null, addressRefund: ref.read(desktopExchangeModelProvider)!.refundAddress!, refundExtraId: "", - rateId: ref.read(desktopExchangeModelProvider)!.rateId, + estimate: ref.read(desktopExchangeModelProvider)!.estimate, reversed: ref.read(desktopExchangeModelProvider)!.reversed, ); if (response.value == null) { if (mounted) { Navigator.of(context).pop(); - } - unawaited( - showDialog( - context: context, - barrierDismissible: true, - builder: (_) => SimpleDesktopDialog( - title: "Failed to create trade", - message: response.exception?.toString() ?? ""), - ), - ); + unawaited( + showDialog( + context: context, + barrierDismissible: true, + builder: (_) => SimpleDesktopDialog( + title: "Failed to create trade", + message: response.exception?.toString() ?? ""), + ), + ); + } return false; } From 73b7af77f65642d66e091443b102dc7343f4577f Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 28 Apr 2023 14:32:44 -0600 Subject: [PATCH 09/65] add trocador currencies to loading/caching process --- .../exchange_data_loading_service.dart | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/services/exchange/exchange_data_loading_service.dart b/lib/services/exchange/exchange_data_loading_service.dart index 2da6016f4..e4b842c94 100644 --- a/lib/services/exchange/exchange_data_loading_service.dart +++ b/lib/services/exchange/exchange_data_loading_service.dart @@ -7,6 +7,7 @@ import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; @@ -128,6 +129,7 @@ class ExchangeDataLoadingService { // loadSimpleswapFixedRateCurrencies(ref), // loadSimpleswapFloatingRateCurrencies(ref), loadMajesticBankCurrencies(), + loadTrocadorCurrencies(), ]); // quicker to load available currencies on the fly for a specific base currency @@ -302,6 +304,28 @@ class ExchangeDataLoadingService { } } + Future loadTrocadorCurrencies() async { + final exchange = TrocadorExchange.instance; + final responseCurrencies = await exchange.getAllCurrencies(false); + + if (responseCurrencies.value != null) { + await isar.writeTxn(() async { + final idsToDelete = await isar.currencies + .where() + .exchangeNameEqualTo(TrocadorExchange.exchangeName) + .idProperty() + .findAll(); + await isar.currencies.deleteAll(idsToDelete); + await isar.currencies.putAll(responseCurrencies.value!); + }); + } else { + Logging.instance.log( + "loadTrocadorCurrencies: $responseCurrencies", + level: LogLevel.Warning, + ); + } + } + // Future loadMajesticBankPairs() async { // final exchange = MajesticBankExchange.instance; // From 33ec3f5a253de288cc047c6adb792b5c7c932dbf Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 28 Apr 2023 14:40:36 -0600 Subject: [PATCH 10/65] hack to isolate Trocador exchange name --- lib/services/exchange/exchange.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart index 1023e5150..c4979788b 100644 --- a/lib/services/exchange/exchange.dart +++ b/lib/services/exchange/exchange.dart @@ -24,6 +24,11 @@ abstract class Exchange { case TrocadorExchange.exchangeName: return TrocadorExchange.instance; default: + final split = name.split(" "); + if (split.length >= 2) { + // silly way to check for 'Trocador ($providerName)' + return fromName(split.first); + } throw ArgumentError("Unknown exchange name"); } } From d14fd88e010ef383e4e0ce6826e9cb265848b630 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 28 Apr 2023 15:27:38 -0600 Subject: [PATCH 11/65] trocador debug prints --- lib/services/exchange/trocador/trocador_api.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/services/exchange/trocador/trocador_api.dart b/lib/services/exchange/trocador/trocador_api.dart index 36935cc0b..317ebba53 100644 --- a/lib/services/exchange/trocador/trocador_api.dart +++ b/lib/services/exchange/trocador/trocador_api.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:flutter_native_splash/cli_commands.dart'; import 'package:http/http.dart' as http; import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; @@ -34,7 +35,7 @@ abstract class TrocadorAPI { static Future _makeGetRequest(Uri uri) async { int code = -1; try { - // print("URI: $uri"); + debugPrint("URI: $uri"); final response = await http.get( uri, headers: {'Content-Type': 'application/json'}, @@ -42,8 +43,8 @@ abstract class TrocadorAPI { code = response.statusCode; - // print("CODE: $code"); - // print("BODY: ${response.body}"); + debugPrint("CODE: $code"); + debugPrint("BODY: ${response.body}"); final json = jsonDecode(response.body); From e8cab19c4751ae3b5f57cdf1ef9d9c22582db27f Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 28 Apr 2023 17:02:55 -0600 Subject: [PATCH 12/65] WIP trocador assets and kyc info --- assets/svg/exchange_icons/trocador.svg | 3 + assets/svg/trocador_rating_a.svg | 10 ++ assets/svg/trocador_rating_b.svg | 10 ++ assets/svg/trocador_rating_c.svg | 10 ++ assets/svg/trocador_rating_d.svg | 10 ++ .../exchange_provider_options.dart | 7 +- lib/utilities/assets.dart | 6 + .../exchange/trocador/trocador_kyc_icon.dart | 54 ++++++++ .../trocador/trocador_kyc_info_button.dart | 28 ++++ .../trocador/trocador_rating_type_enum.dart | 6 + lib/widgets/trocador_kyc_rating_info.dart | 122 ++++++++++++++++++ pubspec.yaml | 4 + 12 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 assets/svg/exchange_icons/trocador.svg create mode 100644 assets/svg/trocador_rating_a.svg create mode 100644 assets/svg/trocador_rating_b.svg create mode 100644 assets/svg/trocador_rating_c.svg create mode 100644 assets/svg/trocador_rating_d.svg create mode 100644 lib/widgets/exchange/trocador/trocador_kyc_icon.dart create mode 100644 lib/widgets/exchange/trocador/trocador_kyc_info_button.dart create mode 100644 lib/widgets/exchange/trocador/trocador_rating_type_enum.dart create mode 100644 lib/widgets/trocador_kyc_rating_info.dart diff --git a/assets/svg/exchange_icons/trocador.svg b/assets/svg/exchange_icons/trocador.svg new file mode 100644 index 000000000..b3d9171ff --- /dev/null +++ b/assets/svg/exchange_icons/trocador.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/trocador_rating_a.svg b/assets/svg/trocador_rating_a.svg new file mode 100644 index 000000000..1e75af73b --- /dev/null +++ b/assets/svg/trocador_rating_a.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/svg/trocador_rating_b.svg b/assets/svg/trocador_rating_b.svg new file mode 100644 index 000000000..5d678305a --- /dev/null +++ b/assets/svg/trocador_rating_b.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/svg/trocador_rating_c.svg b/assets/svg/trocador_rating_c.svg new file mode 100644 index 000000000..87ecf6b24 --- /dev/null +++ b/assets/svg/trocador_rating_c.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/svg/trocador_rating_d.svg b/assets/svg/trocador_rating_d.svg new file mode 100644 index 000000000..8973c7e65 --- /dev/null +++ b/assets/svg/trocador_rating_d.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart index 72aa3a8f1..6fcf69a74 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -20,6 +20,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_info_button.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class ExchangeProviderOptions extends ConsumerStatefulWidget { @@ -652,7 +654,7 @@ class _ExchangeProviderOptionsState width: isDesktop ? 32 : 24, height: isDesktop ? 32 : 24, child: SvgPicture.asset( - Assets.exchange.majesticBankBlue, + Assets.exchange.trocador, width: isDesktop ? 32 : 24, height: isDesktop ? 32 : 24, ), @@ -805,6 +807,9 @@ class _ExchangeProviderOptionsState ], ), ), + const TrocadorKYCInfoButton( + kycType: TrocadorKYCType.a, + ), ], ), ), diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index b6e77f878..645d5d317 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -63,6 +63,7 @@ class _EXCHANGE { String get simpleSwap => "${_path}simpleswap-icon.svg"; String get majesticBankBlue => "${_path}mb_blue.svg"; String get majesticBankGreen => "${_path}mb_green.svg"; + String get trocador => "${_path}trocador.svg"; } class _BUY { @@ -285,6 +286,11 @@ class _SVG { String get list => "assets/svg/list-ul.svg"; String get unclaimedPaynym => "assets/svg/unclaimed.png"; + String get trocadorRatingA => "assets/svg/trocador_rating_a.svg"; + String get trocadorRatingB => "assets/svg/trocador_rating_b.svg"; + String get trocadorRatingC => "assets/svg/trocador_rating_c.svg"; + String get trocadorRatingD => "assets/svg/trocador_rating_d.svg"; + // TODO provide proper assets String get bitcoinTestnet => "assets/svg/coin_icons/Bitcoin.svg"; String get bitcoincashTestnet => "assets/svg/coin_icons/Bitcoincash.svg"; diff --git a/lib/widgets/exchange/trocador/trocador_kyc_icon.dart b/lib/widgets/exchange/trocador/trocador_kyc_icon.dart new file mode 100644 index 000000000..18250932b --- /dev/null +++ b/lib/widgets/exchange/trocador/trocador_kyc_icon.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; + +class TrocadorKYCIcon extends StatelessWidget { + const TrocadorKYCIcon({ + Key? key, + required this.kycType, + this.width = 18, + this.height = 18, + }) : super(key: key); + + final TrocadorKYCType kycType; + final double width; + final double height; + + String _getAssetName(TrocadorKYCType type) { + switch (type) { + case TrocadorKYCType.a: + return Assets.svg.trocadorRatingA; + case TrocadorKYCType.b: + return Assets.svg.trocadorRatingB; + case TrocadorKYCType.c: + return Assets.svg.trocadorRatingC; + case TrocadorKYCType.d: + return Assets.svg.trocadorRatingD; + } + } + + Color _getColor(TrocadorKYCType type, BuildContext context) { + switch (type) { + case TrocadorKYCType.a: + return Theme.of(context).extension()!.accentColorGreen; + case TrocadorKYCType.b: + return const Color(0xFF7AA500); + case TrocadorKYCType.c: + return Theme.of(context).extension()!.accentColorYellow; + case TrocadorKYCType.d: + return const Color(0xFFF37B58); + } + } + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + _getAssetName(kycType), + width: width, + height: height, + color: _getColor(kycType, context), + ); + } +} diff --git a/lib/widgets/exchange/trocador/trocador_kyc_info_button.dart b/lib/widgets/exchange/trocador/trocador_kyc_info_button.dart new file mode 100644 index 000000000..785ca9457 --- /dev/null +++ b/lib/widgets/exchange/trocador/trocador_kyc_info_button.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_icon.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; +import 'package:stackwallet/widgets/trocador_kyc_rating_info.dart'; + +class TrocadorKYCInfoButton extends StatelessWidget { + const TrocadorKYCInfoButton({ + Key? key, + required this.kycType, + }) : super(key: key); + + final TrocadorKYCType kycType; + + @override + Widget build(BuildContext context) { + return IconButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => const TrocadorKYCRatingInfo(), + ); + }, + icon: TrocadorKYCIcon( + kycType: kycType, + ), + ); + } +} diff --git a/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart b/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart new file mode 100644 index 000000000..e0a11465d --- /dev/null +++ b/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart @@ -0,0 +1,6 @@ +enum TrocadorKYCType { + a, + b, + c, + d; +} diff --git a/lib/widgets/trocador_kyc_rating_info.dart b/lib/widgets/trocador_kyc_rating_info.dart new file mode 100644 index 000000000..bfd786e9c --- /dev/null +++ b/lib/widgets/trocador_kyc_rating_info.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_icon.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class TrocadorKYCRatingInfo extends StatelessWidget { + const TrocadorKYCRatingInfo({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final small = MediaQuery.of(context).size.width <= 500; + return ConditionalParent( + condition: !small, + builder: (child) => child, + child: ConditionalParent( + condition: small, + builder: (child) { + return StackDialogBase( + child: child, + ); + }, + child: Column( + children: [ + Text( + "Trocador KYC Rating", + style: STextStyles.pageTitleH2(context), + ), + const SizedBox( + height: 16, + ), + const _Rating( + kycType: TrocadorKYCType.a, + text: "Never asks for user verification.", + ), + const SizedBox( + height: 16, + ), + const _Rating( + kycType: TrocadorKYCType.b, + text: "Rarely asks for verification. Refunds if refused.", + ), + const SizedBox( + height: 16, + ), + const _Rating( + kycType: TrocadorKYCType.c, + text: + "Rarely asks for verification. Refunds if refused, unless a " + "legal order prevents it.", + ), + const SizedBox( + height: 16, + ), + const _Rating( + kycType: TrocadorKYCType.d, + text: + "Rarely asks for verification. In case of refusal may block " + "funds indefinitely without a legal order.", + ), + if (small) + Padding( + padding: const EdgeInsets.only( + top: 16, + ), + child: Row( + children: [ + const Spacer(), + const SizedBox( + width: 16, + ), + Expanded( + child: SecondaryButton( + label: "Close", + onPressed: Navigator.of(context).pop, + ), + ) + ], + ), + ), + ], + ), + ), + ); + } +} + +class _Rating extends StatelessWidget { + const _Rating({ + Key? key, + required this.kycType, + required this.text, + }) : super(key: key); + + final TrocadorKYCType kycType; + final String text; + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TrocadorKYCIcon( + kycType: kycType, + width: 20, + height: 20, + ), + const SizedBox( + width: 8, + ), + Flexible( + child: Text( + text, + style: STextStyles.subtitle(context), + ), + ), + ], + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index d84d0d15d..9cda8de02 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -327,6 +327,10 @@ flutter: - assets/svg/framed-gear.svg - assets/svg/list-ul.svg - assets/svg/cc.svg + - assets/svg/trocador_rating_a.svg + - assets/svg/trocador_rating_b.svg + - assets/svg/trocador_rating_c.svg + - assets/svg/trocador_rating_d.svg # coin icons From e81521e3741ede409a08f3086bb3043f074086f5 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 1 May 2023 16:26:12 -0600 Subject: [PATCH 13/65] WIP exchange process refactor to handle multiple sub providers per exchange provider --- lib/models/exchange/exchange_form_state.dart | 27 +- .../exchange/response_objects/estimate.dart | 15 +- lib/pages/exchange_view/exchange_form.dart | 6 +- .../sub_widgets/exchange_provider_option.dart | 306 ++++++ .../exchange_provider_options.dart | 950 +----------------- .../exchange/change_now/change_now_api.dart | 2 + .../change_now/change_now_exchange.dart | 7 +- lib/services/exchange/exchange.dart | 2 +- .../majestic_bank/majestic_bank_exchange.dart | 5 +- .../simpleswap/simpleswap_exchange.dart | 15 +- .../response_objects/trocador_quote.dart | 8 +- .../exchange/trocador/trocador_exchange.dart | 53 +- lib/utilities/assets.dart | 20 + .../trocador/trocador_rating_type_enum.dart | 9 + 14 files changed, 468 insertions(+), 957 deletions(-) create mode 100644 lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart index c08357e1a..3678f01fd 100644 --- a/lib/models/exchange/exchange_form_state.dart +++ b/lib/models/exchange/exchange_form_state.dart @@ -13,6 +13,12 @@ class ExchangeFormState extends ChangeNotifier { Exchange? _exchange; Exchange get exchange => _exchange ??= Exchange.defaultExchange; + String? _providerName; + // default to exchange name that isn't trocador + String get providerName => _providerName ??= Exchange.defaultExchange.name; + + String get combinedExchangeId => "${exchange.name} ($providerName)"; + ExchangeRateType _exchangeRateType = ExchangeRateType.estimated; ExchangeRateType get exchangeRateType => _exchangeRateType; set exchangeRateType(ExchangeRateType exchangeRateType) { @@ -20,8 +26,8 @@ class ExchangeFormState extends ChangeNotifier { // } - Estimate? _estimate; - Estimate? get estimate => _estimate; + List _estimates = []; + List get estimates => _estimates; bool _reversed = false; bool get reversed => _reversed; @@ -149,10 +155,12 @@ class ExchangeFormState extends ChangeNotifier { Future updateExchange({ required Exchange exchange, + required String providerName, required bool shouldUpdateData, required bool shouldNotifyListeners, }) async { _exchange = exchange; + _providerName = providerName; if (shouldUpdateData) { await _updateRangesAndEstimate( shouldNotifyListeners: false, @@ -173,6 +181,7 @@ class ExchangeFormState extends ChangeNotifier { required bool shouldNotifyListeners, }) { _exchange = null; + _providerName = null; _reversed = false; _rate = null; _sendAmount = null; @@ -445,7 +454,7 @@ class ExchangeFormState extends ChangeNotifier { ); return; } - final response = await exchange.getEstimate( + final response = await exchange.getEstimates( sendCurrency!.ticker, receiveCurrency!.ticker, amount, @@ -461,12 +470,16 @@ class ExchangeFormState extends ChangeNotifier { return; } - _estimate = response.value!; + _estimates = response.value!; if (reversed) { - _sendAmount = _estimate!.estimatedAmount; + _sendAmount = _estimates + .firstWhere((e) => e.exchangeProvider == providerName) + .estimatedAmount; } else { - _receiveAmount = _estimate!.estimatedAmount; + _receiveAmount = _estimates + .firstWhere((e) => e.exchangeProvider == providerName) + .estimatedAmount; } _rate = @@ -518,7 +531,7 @@ class ExchangeFormState extends ChangeNotifier { "\n\t reversed: $reversed," "\n\t sendAmount: $sendAmount," "\n\t receiveAmount: $receiveAmount," - "\n\t estimate: $estimate," + "\n\t estimates: $estimates," "\n\t minSendAmount: $minSendAmount," "\n\t maxSendAmount: $maxSendAmount," "\n\t minReceiveAmount: $minReceiveAmount," diff --git a/lib/models/exchange/response_objects/estimate.dart b/lib/models/exchange/response_objects/estimate.dart index d93fcb560..9284c8340 100644 --- a/lib/models/exchange/response_objects/estimate.dart +++ b/lib/models/exchange/response_objects/estimate.dart @@ -7,7 +7,8 @@ class Estimate { final bool reversed; final String? warningMessage; final String? rateId; - final String? exchangeProvider; + final String exchangeProvider; + final String? kycRating; Estimate({ required this.estimatedAmount, @@ -15,10 +16,15 @@ class Estimate { required this.reversed, this.warningMessage, this.rateId, - this.exchangeProvider, + required this.exchangeProvider, + this.kycRating, }); - factory Estimate.fromMap(Map map) { + factory Estimate.fromMap( + Map map, { + required String exchangeProvider, + String? kycRating, + }) { try { return Estimate( estimatedAmount: Decimal.parse(map["estimatedAmount"] as String), @@ -26,6 +32,8 @@ class Estimate { reversed: map["reversed"] as bool, warningMessage: map["warningMessage"] as String?, rateId: map["rateId"] as String?, + exchangeProvider: exchangeProvider, + kycRating: kycRating, ); } catch (e, s) { Logging.instance.log("Estimate.fromMap(): $e\n$s", level: LogLevel.Error); @@ -41,6 +49,7 @@ class Estimate { "warningMessage": warningMessage, "rateId": rateId, "exchangeProvider": exchangeProvider, + "kycRating": kycRating, }; } diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 3ecee0994..9c22cd36a 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -409,7 +409,11 @@ class _ExchangeFormState extends ConsumerState { final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; final sendAmount = ref.read(exchangeFormStateProvider).sendAmount!; - final estimate = ref.read(exchangeFormStateProvider).estimate!; + final estimate = ref.read(exchangeFormStateProvider).estimates.firstWhere( + (e) => + e.exchangeProvider == + ref.read(exchangeFormStateProvider).providerName, + ); if (rateType == ExchangeRateType.fixed && toTicker.toUpperCase() == "WOW") { await showDialog( diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart new file mode 100644 index 000000000..ed161553c --- /dev/null +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart @@ -0,0 +1,306 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart'; +import 'package:stackwallet/providers/global/locale_provider.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/animated_text.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_info_button.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; + +class ExchangeProviderOption extends ConsumerStatefulWidget { + const ExchangeProviderOption({ + Key? key, + required this.exchange, + required this.exchangeProvider, + required this.fixedRate, + required this.reversed, + }) : super(key: key); + + final Exchange exchange; + final String exchangeProvider; + final bool fixedRate; + final bool reversed; + + @override + ConsumerState createState() => + _ExchangeProviderOptionState(); +} + +class _ExchangeProviderOptionState + extends ConsumerState { + final isDesktop = Util.isDesktop; + late final String _id; + + @override + void initState() { + _id = "${widget.exchange.name} (${widget.exchangeProvider})"; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final sendCurrency = ref + .watch(exchangeFormStateProvider.select((value) => value.sendCurrency)); + final receivingCurrency = ref.watch( + exchangeFormStateProvider.select((value) => value.receiveCurrency)); + final fromAmount = ref + .watch(exchangeFormStateProvider.select((value) => value.sendAmount)); + final toAmount = ref.watch( + exchangeFormStateProvider.select((value) => value.receiveAmount)); + + final selected = ref.watch(exchangeFormStateProvider + .select((value) => value.combinedExchangeId)) == + _id; + + return ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: child, + ), + child: GestureDetector( + onTap: () { + if (!selected) { + showLoading( + whileFuture: ref.read(exchangeFormStateProvider).updateExchange( + exchange: widget.exchange, + shouldUpdateData: true, + shouldNotifyListeners: true, + providerName: widget.exchangeProvider, + ), + context: context, + message: "Updating rates", + isDesktop: isDesktop, + ); + } + }, + child: Container( + color: Colors.transparent, + child: Padding( + padding: + isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: Padding( + padding: EdgeInsets.only(top: isDesktop ? 20.0 : 15.0), + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: _id, + groupValue: ref.watch(exchangeFormStateProvider + .select((value) => value.combinedExchangeId)), + onChanged: (_) { + if (!selected) { + ref.read(exchangeFormStateProvider).updateExchange( + exchange: widget.exchange, + shouldUpdateData: true, + shouldNotifyListeners: true, + providerName: widget.exchangeProvider, + ); + } + }, + ), + ), + ), + const SizedBox( + width: 14, + ), + Padding( + padding: const EdgeInsets.only(top: 5.0), + child: SizedBox( + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + child: SvgPicture.asset( + Assets.exchange.getIconFor( + exchangeName: widget.exchange.name, + ), + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + ), + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.exchangeProvider, + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark2, + ), + ), + if (sendCurrency != null && + receivingCurrency != null && + toAmount != null && + toAmount > Decimal.zero && + fromAmount != null && + fromAmount > Decimal.zero) + FutureBuilder( + future: widget.exchange.getEstimates( + sendCurrency.ticker, + receivingCurrency.ticker, + widget.reversed ? toAmount : fromAmount, + widget.fixedRate, + widget.reversed, + ), + builder: (context, + AsyncSnapshot>> + snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + final estimates = snapshot.data?.value; + if (estimates != null && + estimates + .where((e) => + e.exchangeProvider == + widget.exchangeProvider) + .isNotEmpty) { + final estimate = estimates.firstWhere((e) => + e.exchangeProvider == + widget.exchangeProvider); + int decimals; + try { + decimals = coinFromTickerCaseInsensitive( + receivingCurrency.ticker) + .decimals; + } catch (_) { + decimals = 8; // some reasonable alternative + } + Amount rate; + if (estimate.reversed) { + rate = (toAmount / estimate.estimatedAmount) + .toDecimal(scaleOnInfinitePrecision: 18) + .toAmount(fractionDigits: decimals); + } else { + rate = (estimate.estimatedAmount / fromAmount) + .toDecimal(scaleOnInfinitePrecision: 18) + .toAmount(fractionDigits: decimals); + } + + return ConditionalParent( + condition: widget.exchange.name == + TrocadorExchange.exchangeName, + builder: (child) { + return Row( + children: [ + child, + TrocadorKYCInfoButton( + kycType: TrocadorKYCType.fromString( + estimate.kycRating!, + ), + ), + ], + ); + }, + child: Text( + "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${receivingCurrency.ticker.toUpperCase()}", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ); + } else if (snapshot.data?.exception + is PairUnavailableException) { + return Text( + "Unsupported pair", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } else { + Logging.instance.log( + "$runtimeType failed to fetch rate for $_id}: ${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 (!(sendCurrency != null && + receivingCurrency != 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/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart index 6fcf69a74..feeb6b661 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -1,27 +1,13 @@ -import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart'; import 'package:stackwallet/models/exchange/aggregate_currency.dart'; -import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_option.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/majestic_bank/majestic_bank_exchange.dart'; import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; -import 'package:stackwallet/utilities/amount/amount.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/show_loading.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/animated_text.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; -import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_info_button.dart'; -import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class ExchangeProviderOptions extends ConsumerStatefulWidget { @@ -63,11 +49,16 @@ class _ExchangeProviderOptionsState @override Widget build(BuildContext context) { - final sendCurrency = ref.watch(exchangeFormStateProvider).sendCurrency; - final receivingCurrency = - ref.watch(exchangeFormStateProvider).receiveCurrency; - final fromAmount = ref.watch(exchangeFormStateProvider).sendAmount; - final toAmount = ref.watch(exchangeFormStateProvider).receiveAmount; + final sendCurrency = ref.watch( + exchangeFormStateProvider.select( + (value) => value.sendCurrency, + ), + ); + final receivingCurrency = ref.watch( + exchangeFormStateProvider.select( + (value) => value.receiveCurrency, + ), + ); final showChangeNow = exchangeSupported( exchangeName: ChangeNowExchange.exchangeName, @@ -93,239 +84,12 @@ class _ExchangeProviderOptionsState child: Column( children: [ if (showChangeNow) - ConditionalParent( - condition: isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - child: child, - ), - child: GestureDetector( - onTap: () { - if (ref.read(exchangeFormStateProvider).exchange.name != - ChangeNowExchange.exchangeName) { - showLoading( - whileFuture: - ref.read(exchangeFormStateProvider).updateExchange( - exchange: ChangeNowExchange.instance, - shouldUpdateData: true, - shouldNotifyListeners: true, - ), - context: context, - message: "Updating rates", - isDesktop: isDesktop, - ); - } - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Padding( - padding: - EdgeInsets.only(top: isDesktop ? 20.0 : 15.0), - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: ChangeNowExchange.exchangeName, - groupValue: ref.watch(exchangeFormStateProvider - .select((value) => value.exchange.name)), - onChanged: (_) { - if (ref - .read(exchangeFormStateProvider) - .exchange - .name != - ChangeNowExchange.exchangeName) { - ref - .read(exchangeFormStateProvider) - .updateExchange( - exchange: ChangeNowExchange.instance, - shouldUpdateData: true, - shouldNotifyListeners: true, - ); - } - }, - ), - ), - ), - const SizedBox( - width: 14, - ), - Padding( - padding: const EdgeInsets.only(top: 5.0), - child: SizedBox( - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - child: SvgPicture.asset( - Assets.exchange.changeNow, - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - ), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - ChangeNowExchange.exchangeName, - style: - STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), - ), - if (sendCurrency != null && - receivingCurrency != null && - toAmount != null && - toAmount > Decimal.zero && - fromAmount != null && - fromAmount > Decimal.zero) - FutureBuilder( - future: - ChangeNowExchange.instance.getEstimate( - sendCurrency.ticker, - receivingCurrency.ticker, - widget.reversed ? toAmount : fromAmount, - widget.fixedRate, - widget.reversed, - ), - builder: (context, - AsyncSnapshot> - snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - final estimate = snapshot.data?.value; - if (estimate != null) { - Coin coin; - try { - coin = coinFromTickerCaseInsensitive( - receivingCurrency.ticker); - } catch (_) { - coin = Coin.bitcoin; - } - Amount rate; - if (estimate.reversed) { - rate = (toAmount / - estimate.estimatedAmount) - .toDecimal( - scaleOnInfinitePrecision: 18) - .toAmount( - fractionDigits: - coin.decimals); - } else { - rate = (estimate.estimatedAmount / - fromAmount) - .toDecimal( - scaleOnInfinitePrecision: 18) - .toAmount( - fractionDigits: - coin.decimals); - } - - return Text( - "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale), - ), - )} ${receivingCurrency.ticker.toUpperCase()}", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else if (snapshot.data?.exception - is PairUnavailableException) { - return Text( - "Unsupported pair", - 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 (!(sendCurrency != null && - receivingCurrency != 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, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ), + ExchangeProviderOption( + exchange: ChangeNowExchange.instance, + exchangeProvider: ChangeNowExchange.exchangeName, + fixedRate: widget.fixedRate, + reversed: widget.reversed, ), - if (showChangeNow && showMajesticBank) isDesktop ? Container( @@ -336,239 +100,12 @@ class _ExchangeProviderOptionsState : const SizedBox( height: 16, ), - if (showMajesticBank) - ConditionalParent( - condition: isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - child: child, - ), - child: GestureDetector( - onTap: () { - if (ref.read(exchangeFormStateProvider).exchange.name != - MajesticBankExchange.exchangeName) { - showLoading( - whileFuture: - ref.read(exchangeFormStateProvider).updateExchange( - exchange: MajesticBankExchange.instance, - shouldUpdateData: true, - shouldNotifyListeners: true, - ), - context: context, - isDesktop: isDesktop, - message: "Updating rates", - ); - } - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Padding( - padding: - EdgeInsets.only(top: isDesktop ? 20.0 : 15.0), - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: MajesticBankExchange.exchangeName, - groupValue: ref.watch(exchangeFormStateProvider - .select((value) => value.exchange.name)), - onChanged: (_) { - if (ref - .read(exchangeFormStateProvider) - .exchange - .name != - MajesticBankExchange.exchangeName) { - ref - .read(exchangeFormStateProvider) - .updateExchange( - exchange: MajesticBankExchange.instance, - shouldUpdateData: true, - shouldNotifyListeners: true, - ); - } - }, - ), - ), - ), - const SizedBox( - width: 14, - ), - Padding( - padding: const EdgeInsets.only(top: 5.0), - child: SizedBox( - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - child: SvgPicture.asset( - Assets.exchange.majesticBankBlue, - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - ), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - MajesticBankExchange.exchangeName, - style: - STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), - ), - if (sendCurrency != null && - receivingCurrency != null && - toAmount != null && - toAmount > Decimal.zero && - fromAmount != null && - fromAmount > Decimal.zero) - FutureBuilder( - future: - MajesticBankExchange.instance.getEstimate( - sendCurrency.ticker, - receivingCurrency.ticker, - widget.reversed ? toAmount : fromAmount, - widget.fixedRate, - widget.reversed, - ), - builder: (context, - AsyncSnapshot> - snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - final estimate = snapshot.data?.value; - if (estimate != null) { - Coin coin; - try { - coin = coinFromTickerCaseInsensitive( - receivingCurrency.ticker); - } catch (_) { - coin = Coin.bitcoin; - } - Amount rate; - if (estimate.reversed) { - rate = (toAmount / - estimate.estimatedAmount) - .toDecimal( - scaleOnInfinitePrecision: 18) - .toAmount( - fractionDigits: coin.decimals, - ); - } else { - rate = (estimate.estimatedAmount / - fromAmount) - .toDecimal( - scaleOnInfinitePrecision: 18) - .toAmount( - fractionDigits: coin.decimals, - ); - } - - return Text( - "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale), - ), - )} ${receivingCurrency.ticker.toUpperCase()}", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else if (snapshot.data?.exception - is PairUnavailableException) { - return Text( - "Unsupported pair", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - Logging.instance.log( - "$runtimeType failed to fetch rate for Majestic Bank: ${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 (!(sendCurrency != null && - receivingCurrency != 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, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ), + ExchangeProviderOption( + exchange: MajesticBankExchange.instance, + exchangeProvider: MajesticBankExchange.exchangeName, + fixedRate: widget.fixedRate, + reversed: widget.reversed, ), if ((showChangeNow || showMajesticBank) && showTrocador) isDesktop @@ -580,448 +117,13 @@ class _ExchangeProviderOptionsState : const SizedBox( height: 16, ), - if (showTrocador) - ConditionalParent( - condition: isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - child: child, - ), - child: GestureDetector( - onTap: () { - if (ref.read(exchangeFormStateProvider).exchange.name != - TrocadorExchange.exchangeName) { - showLoading( - whileFuture: - ref.read(exchangeFormStateProvider).updateExchange( - exchange: TrocadorExchange.instance, - shouldUpdateData: true, - shouldNotifyListeners: true, - ), - context: context, - isDesktop: isDesktop, - message: "Updating rates", - ); - } - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Padding( - padding: - EdgeInsets.only(top: isDesktop ? 20.0 : 15.0), - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: TrocadorExchange.exchangeName, - groupValue: ref.watch(exchangeFormStateProvider - .select((value) => value.exchange.name)), - onChanged: (_) { - if (ref - .read(exchangeFormStateProvider) - .exchange - .name != - TrocadorExchange.exchangeName) { - ref - .read(exchangeFormStateProvider) - .updateExchange( - exchange: TrocadorExchange.instance, - shouldUpdateData: true, - shouldNotifyListeners: true, - ); - } - }, - ), - ), - ), - const SizedBox( - width: 14, - ), - Padding( - padding: const EdgeInsets.only(top: 5.0), - child: SizedBox( - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - child: SvgPicture.asset( - Assets.exchange.trocador, - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - ), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - TrocadorExchange.exchangeName, - style: - STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), - ), - if (sendCurrency != null && - receivingCurrency != null && - toAmount != null && - toAmount > Decimal.zero && - fromAmount != null && - fromAmount > Decimal.zero) - FutureBuilder( - future: TrocadorExchange.instance.getEstimate( - sendCurrency.ticker, - receivingCurrency.ticker, - widget.reversed ? toAmount : fromAmount, - widget.fixedRate, - widget.reversed, - ), - builder: (context, - AsyncSnapshot> - snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - final estimate = snapshot.data?.value; - if (estimate != null) { - Coin coin; - try { - coin = coinFromTickerCaseInsensitive( - receivingCurrency.ticker); - } catch (_) { - coin = Coin.bitcoin; - } - Amount rate; - if (estimate.reversed) { - rate = (toAmount / - estimate.estimatedAmount) - .toDecimal( - scaleOnInfinitePrecision: 18) - .toAmount( - fractionDigits: coin.decimals, - ); - } else { - rate = (estimate.estimatedAmount / - fromAmount) - .toDecimal( - scaleOnInfinitePrecision: 18) - .toAmount( - fractionDigits: coin.decimals, - ); - } - - return Text( - "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale), - ), - )} ${receivingCurrency.ticker.toUpperCase()}", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else if (snapshot.data?.exception - is PairUnavailableException) { - return Text( - "Unsupported pair", - style: STextStyles.itemSubtitle12( - context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - Logging.instance.log( - "$runtimeType failed to fetch rate for Trocador: ${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 (!(sendCurrency != null && - receivingCurrency != 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 TrocadorKYCInfoButton( - kycType: TrocadorKYCType.a, - ), - ], - ), - ), - ), - ), + ExchangeProviderOption( + fixedRate: widget.fixedRate, + reversed: widget.reversed, + exchange: TrocadorExchange.instance, + exchangeProvider: 'LetsExchange', ), - // if (isDesktop) - // Container( - // height: 1, - // color: Theme.of(context).extension()!.background, - // ), - // if (!isDesktop) - // const SizedBox( - // height: 16, - // ), - // ConditionalParent( - // condition: isDesktop, - // builder: (child) => MouseRegion( - // cursor: SystemMouseCursors.click, - // child: child, - // ), - // child: GestureDetector( - // onTap: () { - // if (ref.read(currentExchangeNameStateProvider.state).state != - // SimpleSwapExchange.exchangeName) { - // // ref.read(currentExchangeNameStateProvider.state).state = - // // SimpleSwapExchange.exchangeName; - // ref.read(exchangeFormStateProvider).exchange = - // Exchange.fromName(ref - // .read(currentExchangeNameStateProvider.state) - // .state); - // } - // }, - // child: Container( - // color: Colors.transparent, - // child: Padding( - // padding: isDesktop - // ? const EdgeInsets.all(16) - // : const EdgeInsets.all(0), - // child: Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // SizedBox( - // width: 20, - // height: 20, - // child: 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; - // ref.read(exchangeFormStateProvider).exchange = - // Exchange.fromName(ref - // .read(currentExchangeNameStateProvider - // .state) - // .state); - // } - // }, - // ), - // ), - // const SizedBox( - // width: 14, - // ), - // // SvgPicture.asset( - // // Assets.exchange.simpleSwap, - // // width: isDesktop ? 32 : 24, - // // height: isDesktop ? 32 : 24, - // // ), - // // const SizedBox( - // // width: 10, - // // ), - // // Expanded( - // // child: Column( - // // mainAxisAlignment: MainAxisAlignment.start, - // // mainAxisSize: MainAxisSize.min, - // // crossAxisAlignment: CrossAxisAlignment.start, - // // children: [ - // // Text( - // // 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); - // // - // // Coin coin; - // // try { - // // coin = - // // coinFromTickerCaseInsensitive(to!); - // // } catch (_) { - // // coin = Coin.bitcoin; - // // } - // // return Text( - // // "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( - // // value: rate, - // // locale: ref.watch( - // // localeServiceChangeNotifierProvider - // // .select( - // // (value) => value.locale), - // // ), - // // decimalPlaces: - // // Constants.decimalPlacesForCoin( - // // coin), - // // )} ${to!.toUpperCase()}", - // // style: - // // STextStyles.itemSubtitle12(context) - // // .copyWith( - // // color: Theme.of(context) - // // .extension()! - // // .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/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart index 57823a813..6f050d2e2 100644 --- a/lib/services/exchange/change_now/change_now_api.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -483,6 +483,7 @@ class ChangeNowAPI { reversed: false, rateId: value.rateId, warningMessage: value.warningMessage, + exchangeProvider: ChangeNowExchange.exchangeName, ), ); } catch (_) { @@ -566,6 +567,7 @@ class ChangeNowAPI { reversed: reversed, rateId: value.rateId, warningMessage: value.warningMessage, + exchangeProvider: ChangeNowExchange.exchangeName, ), ); } catch (_) { diff --git a/lib/services/exchange/change_now/change_now_exchange.dart b/lib/services/exchange/change_now/change_now_exchange.dart index f5b68d300..a6037b511 100644 --- a/lib/services/exchange/change_now/change_now_exchange.dart +++ b/lib/services/exchange/change_now/change_now_exchange.dart @@ -128,7 +128,7 @@ class ChangeNowExchange extends Exchange { } @override - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, @@ -151,7 +151,10 @@ class ChangeNowExchange extends Exchange { fromAmount: amount, ); } - return response; + return ExchangeResponse( + value: response.value == null ? null : [response.value!], + exception: response.exception, + ); } @override diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart index c4979788b..1db451457 100644 --- a/lib/services/exchange/exchange.dart +++ b/lib/services/exchange/exchange.dart @@ -60,7 +60,7 @@ abstract class Exchange { bool fixedRate, ); - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, diff --git a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart index 0980abc62..95031490c 100644 --- a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart +++ b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart @@ -170,7 +170,7 @@ class MajesticBankExchange extends Exchange { } @override - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, @@ -192,8 +192,9 @@ class MajesticBankExchange extends Exchange { estimatedAmount: reversed ? calc.fromAmount : calc.receiveAmount, fixedRate: fixedRate, reversed: reversed, + exchangeProvider: MajesticBankExchange.exchangeName, ); - return ExchangeResponse(value: estimate); + return ExchangeResponse(value: [estimate]); } @override diff --git a/lib/services/exchange/simpleswap/simpleswap_exchange.dart b/lib/services/exchange/simpleswap/simpleswap_exchange.dart index 2822d4a1e..0bfe93a38 100644 --- a/lib/services/exchange/simpleswap/simpleswap_exchange.dart +++ b/lib/services/exchange/simpleswap/simpleswap_exchange.dart @@ -89,7 +89,7 @@ class SimpleSwapExchange extends Exchange { } @override - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, @@ -109,11 +109,14 @@ class SimpleSwapExchange extends Exchange { } return ExchangeResponse( - value: Estimate( - estimatedAmount: Decimal.parse(response.value!), - fixedRate: fixedRate, - reversed: reversed, - ), + value: [ + Estimate( + estimatedAmount: Decimal.parse(response.value!), + fixedRate: fixedRate, + reversed: reversed, + exchangeProvider: SimpleSwapExchange.exchangeName, + ), + ], ); } diff --git a/lib/services/exchange/trocador/response_objects/trocador_quote.dart b/lib/services/exchange/trocador/response_objects/trocador_quote.dart index 7b4a86984..ada8725f5 100644 --- a/lib/services/exchange/trocador/response_objects/trocador_quote.dart +++ b/lib/services/exchange/trocador/response_objects/trocador_quote.dart @@ -5,7 +5,8 @@ class TrocadorQuote { final String kycRating; final int insurance; final bool fixed; - final Decimal amountTo; + final Decimal? amountTo; + final Decimal? amountFrom; final Decimal waste; TrocadorQuote({ @@ -14,6 +15,7 @@ class TrocadorQuote { required this.insurance, required this.fixed, required this.amountTo, + required this.amountFrom, required this.waste, }); @@ -24,7 +26,8 @@ class TrocadorQuote { insurance: map['insurance'] as int, // wtf trocador? fixed: map['fixed'] == "True", - amountTo: Decimal.parse(map['amount_to'].toString()), + amountTo: Decimal.tryParse(map['amount_to'].toString()), + amountFrom: Decimal.tryParse(map['amount_from'].toString()), waste: Decimal.parse(map['waste'].toString()), ); } @@ -37,6 +40,7 @@ class TrocadorQuote { 'insurance: $insurance, ' 'fixed: $fixed, ' 'amountTo: $amountTo, ' + 'amountFrom: $amountFrom, ' 'waste: $waste ' ')'; } diff --git a/lib/services/exchange/trocador/trocador_exchange.dart b/lib/services/exchange/trocador/trocador_exchange.dart index a4bfb26a3..8085cfca5 100644 --- a/lib/services/exchange/trocador/trocador_exchange.dart +++ b/lib/services/exchange/trocador/trocador_exchange.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:decimal/decimal.dart'; import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; @@ -8,6 +10,7 @@ import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_coin.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_quote.dart'; import 'package:stackwallet/services/exchange/trocador/trocador_api.dart'; import 'package:uuid/uuid.dart'; @@ -177,7 +180,7 @@ class TrocadorExchange extends Exchange { } @override - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, @@ -208,15 +211,47 @@ class TrocadorExchange extends Exchange { return ExchangeResponse(exception: response.exception); } + final List estimates = []; + final List cOrLowerQuotes = []; + + for (final quote in response.value!.quotes) { + if (quote.fixed == isPayment) { + final rating = quote.kycRating.toLowerCase(); + if (rating == "a" || rating == "b") { + estimates.add( + Estimate( + estimatedAmount: isPayment ? quote.amountFrom! : quote.amountTo!, + fixedRate: quote.fixed, + reversed: isPayment, + exchangeProvider: quote.provider, + rateId: response.value!.tradeId, + kycRating: quote.kycRating, + ), + ); + } else { + cOrLowerQuotes.add(quote); + } + } + } + + cOrLowerQuotes.sort((a, b) => b.waste.compareTo(a.waste)); + + for (int i = 0; i < max(3, cOrLowerQuotes.length); i++) { + final quote = cOrLowerQuotes[i]; + estimates.add( + Estimate( + estimatedAmount: isPayment ? quote.amountFrom! : quote.amountTo!, + fixedRate: quote.fixed, + reversed: isPayment, + exchangeProvider: quote.provider, + rateId: response.value!.tradeId, + kycRating: quote.kycRating, + ), + ); + } + return ExchangeResponse( - value: Estimate( - estimatedAmount: - isPayment ? response.value!.amountFrom : response.value!.amountTo, - fixedRate: isPayment, - reversed: isPayment, - exchangeProvider: response.value!.provider, - rateId: response.value!.tradeId, - ), + value: estimates, ); } diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 645d5d317..6444b2896 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -64,6 +68,22 @@ class _EXCHANGE { String get majesticBankBlue => "${_path}mb_blue.svg"; String get majesticBankGreen => "${_path}mb_green.svg"; String get trocador => "${_path}trocador.svg"; + + String getIconFor({required String exchangeName}) { + switch (exchangeName) { + case SimpleSwapExchange.exchangeName: + return simpleSwap; + case ChangeNowExchange.exchangeName: + return changeNow; + case MajesticBankExchange.exchangeName: + return majesticBankBlue; + case TrocadorExchange.exchangeName: + return trocador; + default: + throw ArgumentError("Invalid exchange name passed to " + "Assets.exchange.getIconFor()"); + } + } } class _BUY { diff --git a/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart b/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart index e0a11465d..538ee8c37 100644 --- a/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart +++ b/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart @@ -3,4 +3,13 @@ enum TrocadorKYCType { b, c, d; + + static TrocadorKYCType fromString(String type) { + for (final result in values) { + if (result.name == type.toLowerCase()) { + return result; + } + } + throw ArgumentError("Invalid trocador kyc type: $type"); + } } From 89f806f3945dadc0bb312e3fdef80e592eacad18 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 2 May 2023 08:24:37 -0600 Subject: [PATCH 14/65] prevent double loading of data --- .../sub_widgets/exchange_provider_option.dart | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart index ed161553c..7c6d625ed 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart @@ -13,7 +13,6 @@ import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; @@ -76,17 +75,18 @@ class _ExchangeProviderOptionState child: GestureDetector( onTap: () { if (!selected) { - showLoading( - whileFuture: ref.read(exchangeFormStateProvider).updateExchange( - exchange: widget.exchange, - shouldUpdateData: true, - shouldNotifyListeners: true, - providerName: widget.exchangeProvider, - ), - context: context, - message: "Updating rates", - isDesktop: isDesktop, - ); + // showLoading( + // whileFuture: + ref.read(exchangeFormStateProvider).updateExchange( + exchange: widget.exchange, + shouldUpdateData: true, + shouldNotifyListeners: true, + providerName: widget.exchangeProvider, + // ), + // context: context, + // message: "Updating rates", + // isDesktop: isDesktop, + ); } }, child: Container( @@ -113,7 +113,7 @@ class _ExchangeProviderOptionState if (!selected) { ref.read(exchangeFormStateProvider).updateExchange( exchange: widget.exchange, - shouldUpdateData: true, + shouldUpdateData: false, shouldNotifyListeners: true, providerName: widget.exchangeProvider, ); From 3564426248c952674dcc98d801c76e34194865f8 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 2 May 2023 10:22:35 -0600 Subject: [PATCH 15/65] show trocador provider options --- lib/models/exchange/exchange_form_state.dart | 21 +- .../sub_widgets/exchange_provider_option.dart | 377 ++++++++++-------- .../exchange_provider_options.dart | 9 +- .../exchange/trocador/trocador_exchange.dart | 20 +- 4 files changed, 240 insertions(+), 187 deletions(-) diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart index 3678f01fd..52fe0bffd 100644 --- a/lib/models/exchange/exchange_form_state.dart +++ b/lib/models/exchange/exchange_form_state.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:stackwallet/models/exchange/aggregate_currency.dart'; @@ -158,16 +160,23 @@ class ExchangeFormState extends ChangeNotifier { required String providerName, required bool shouldUpdateData, required bool shouldNotifyListeners, + bool shouldAwait = true, }) async { _exchange = exchange; _providerName = providerName; if (shouldUpdateData) { - await _updateRangesAndEstimate( - shouldNotifyListeners: false, - ); - } - - if (shouldNotifyListeners) { + if (shouldAwait) { + await _updateRangesAndEstimate( + shouldNotifyListeners: shouldNotifyListeners, + ); + } else { + unawaited( + _updateRangesAndEstimate( + shouldNotifyListeners: shouldNotifyListeners, + ), + ); + } + } else if (shouldNotifyListeners) { _notify(); } } diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart index 7c6d625ed..0d0f453ea 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart @@ -8,7 +8,6 @@ import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart import 'package:stackwallet/providers/global/locale_provider.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; -import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -21,35 +20,25 @@ import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_info_button.dart'; import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; -class ExchangeProviderOption extends ConsumerStatefulWidget { - const ExchangeProviderOption({ +class ExchangeOption extends ConsumerStatefulWidget { + const ExchangeOption({ Key? key, required this.exchange, - required this.exchangeProvider, required this.fixedRate, required this.reversed, }) : super(key: key); final Exchange exchange; - final String exchangeProvider; final bool fixedRate; final bool reversed; @override - ConsumerState createState() => - _ExchangeProviderOptionState(); + ConsumerState createState() => + _ExchangeMultiProviderOptionState(); } -class _ExchangeProviderOptionState - extends ConsumerState { +class _ExchangeMultiProviderOptionState extends ConsumerState { final isDesktop = Util.isDesktop; - late final String _id; - - @override - void initState() { - _id = "${widget.exchange.name} (${widget.exchangeProvider})"; - super.initState(); - } @override Widget build(BuildContext context) { @@ -62,9 +51,179 @@ class _ExchangeProviderOptionState final toAmount = ref.watch( exchangeFormStateProvider.select((value) => value.receiveAmount)); - final selected = ref.watch(exchangeFormStateProvider + return AnimatedSize( + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOutCubicEmphasized, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (sendCurrency != null && + receivingCurrency != null && + toAmount != null && + toAmount > Decimal.zero && + fromAmount != null && + fromAmount > Decimal.zero) + FutureBuilder( + future: widget.exchange.getEstimates( + sendCurrency.ticker, + receivingCurrency.ticker, + widget.reversed ? toAmount : fromAmount, + widget.fixedRate, + widget.reversed, + ), + builder: (context, + AsyncSnapshot>> snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + final estimates = snapshot.data?.value; + + if (estimates != null && estimates.isNotEmpty) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (int i = 0; i < estimates.length; i++) + Builder( + builder: (context) { + final e = estimates[i]; + int decimals; + try { + decimals = coinFromTickerCaseInsensitive( + receivingCurrency.ticker) + .decimals; + } catch (_) { + decimals = 8; // some reasonable alternative + } + Amount rate; + if (e.reversed) { + rate = (toAmount / e.estimatedAmount) + .toDecimal(scaleOnInfinitePrecision: 18) + .toAmount(fractionDigits: decimals); + } else { + rate = (e.estimatedAmount / fromAmount) + .toDecimal(scaleOnInfinitePrecision: 18) + .toAmount(fractionDigits: decimals); + } + + final rateString = + "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${receivingCurrency.ticker.toUpperCase()}"; + + return ConditionalParent( + condition: i > 0, + builder: (child) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + isDesktop + ? Container( + height: 1, + color: Theme.of(context) + .extension()! + .background, + ) + : const SizedBox( + height: 16, + ), + child, + ], + ), + child: _ProviderOption( + key: Key(widget.exchange.name + + e.exchangeProvider), + exchange: widget.exchange, + providerName: e.exchangeProvider, + rateString: rateString, + kycRating: e.kycRating, + ), + ); + }, + ), + ], + ); + } else if (snapshot.data?.exception + is PairUnavailableException) { + return _ProviderOption( + exchange: widget.exchange, + providerName: widget.exchange.name, + rateString: "Unsupported pair", + ); + } else { + Logging.instance.log( + "$runtimeType failed to fetch rate for ${widget.exchange.name}: ${snapshot.data}", + level: LogLevel.Warning, + ); + + return _ProviderOption( + exchange: widget.exchange, + providerName: widget.exchange.name, + rateString: "Failed to fetch rate", + ); + } + } else { + // show loading + return _ProviderOption( + exchange: widget.exchange, + providerName: widget.exchange.name, + rateString: "", + loadingString: true, + ); + } + }, + ), + ], + ), + ); + } +} + +class _ProviderOption extends ConsumerStatefulWidget { + const _ProviderOption({ + Key? key, + required this.exchange, + required this.providerName, + required this.rateString, + this.kycRating, + this.loadingString = false, + }) : super(key: key); + + final Exchange exchange; + final String providerName; + final String rateString; + final String? kycRating; + final bool loadingString; + + @override + ConsumerState<_ProviderOption> createState() => _ProviderOptionState(); +} + +class _ProviderOptionState extends ConsumerState<_ProviderOption> { + final isDesktop = Util.isDesktop; + + late final String _id; + + @override + void initState() { + _id = "${widget.exchange.name} (${widget.providerName})"; + super.initState(); + } + + @override + Widget build(BuildContext context) { + bool selected = ref.watch(exchangeFormStateProvider .select((value) => value.combinedExchangeId)) == _id; + String groupValue = ref.watch( + exchangeFormStateProvider.select((value) => value.combinedExchangeId)); + + if (ref.watch( + exchangeFormStateProvider.select((value) => value.exchange.name)) == + widget.providerName) { + selected = true; + groupValue = _id; + } return ConditionalParent( condition: isDesktop, @@ -75,17 +234,12 @@ class _ExchangeProviderOptionState child: GestureDetector( onTap: () { if (!selected) { - // showLoading( - // whileFuture: ref.read(exchangeFormStateProvider).updateExchange( exchange: widget.exchange, shouldUpdateData: true, shouldNotifyListeners: true, - providerName: widget.exchangeProvider, - // ), - // context: context, - // message: "Updating rates", - // isDesktop: isDesktop, + providerName: widget.providerName, + shouldAwait: false, ); } }, @@ -107,15 +261,15 @@ class _ExchangeProviderOptionState .extension()! .radioButtonIconEnabled, value: _id, - groupValue: ref.watch(exchangeFormStateProvider - .select((value) => value.combinedExchangeId)), + groupValue: groupValue, onChanged: (_) { if (!selected) { ref.read(exchangeFormStateProvider).updateExchange( exchange: widget.exchange, shouldUpdateData: false, shouldNotifyListeners: true, - providerName: widget.exchangeProvider, + providerName: widget.providerName, + shouldAwait: false, ); } }, @@ -149,153 +303,46 @@ class _ExchangeProviderOptionState crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - widget.exchangeProvider, + widget.providerName, style: STextStyles.titleBold12(context).copyWith( color: Theme.of(context) .extension()! .textDark2, ), ), - if (sendCurrency != null && - receivingCurrency != null && - toAmount != null && - toAmount > Decimal.zero && - fromAmount != null && - fromAmount > Decimal.zero) - FutureBuilder( - future: widget.exchange.getEstimates( - sendCurrency.ticker, - receivingCurrency.ticker, - widget.reversed ? toAmount : fromAmount, - widget.fixedRate, - widget.reversed, - ), - builder: (context, - AsyncSnapshot>> - snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - final estimates = snapshot.data?.value; - if (estimates != null && - estimates - .where((e) => - e.exchangeProvider == - widget.exchangeProvider) - .isNotEmpty) { - final estimate = estimates.firstWhere((e) => - e.exchangeProvider == - widget.exchangeProvider); - int decimals; - try { - decimals = coinFromTickerCaseInsensitive( - receivingCurrency.ticker) - .decimals; - } catch (_) { - decimals = 8; // some reasonable alternative - } - Amount rate; - if (estimate.reversed) { - rate = (toAmount / estimate.estimatedAmount) - .toDecimal(scaleOnInfinitePrecision: 18) - .toAmount(fractionDigits: decimals); - } else { - rate = (estimate.estimatedAmount / fromAmount) - .toDecimal(scaleOnInfinitePrecision: 18) - .toAmount(fractionDigits: decimals); - } - - return ConditionalParent( - condition: widget.exchange.name == - TrocadorExchange.exchangeName, - builder: (child) { - return Row( - children: [ - child, - TrocadorKYCInfoButton( - kycType: TrocadorKYCType.fromString( - estimate.kycRating!, - ), - ), - ], - ); - }, - child: Text( - "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${receivingCurrency.ticker.toUpperCase()}", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ); - } else if (snapshot.data?.exception - is PairUnavailableException) { - return Text( - "Unsupported pair", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - Logging.instance.log( - "$runtimeType failed to fetch rate for $_id}: ${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 (!(sendCurrency != null && - receivingCurrency != 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, - ), - ), + widget.loadingString + ? AnimatedText( + stringsToLoopThrough: const [ + "Loading", + "Loading.", + "Loading..", + "Loading...", + ], + style: + STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ) + : Text( + widget.rateString, + style: + STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), ], ), ), + if (widget.kycRating != null) + TrocadorKYCInfoButton( + kycType: TrocadorKYCType.fromString( + widget.kycRating!, + ), + ), ], ), ), diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart index feeb6b661..4d3b96f90 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -84,9 +84,8 @@ class _ExchangeProviderOptionsState child: Column( children: [ if (showChangeNow) - ExchangeProviderOption( + ExchangeOption( exchange: ChangeNowExchange.instance, - exchangeProvider: ChangeNowExchange.exchangeName, fixedRate: widget.fixedRate, reversed: widget.reversed, ), @@ -101,9 +100,8 @@ class _ExchangeProviderOptionsState height: 16, ), if (showMajesticBank) - ExchangeProviderOption( + ExchangeOption( exchange: MajesticBankExchange.instance, - exchangeProvider: MajesticBankExchange.exchangeName, fixedRate: widget.fixedRate, reversed: widget.reversed, ), @@ -118,11 +116,10 @@ class _ExchangeProviderOptionsState height: 16, ), if (showTrocador) - ExchangeProviderOption( + ExchangeOption( fixedRate: widget.fixedRate, reversed: widget.reversed, exchange: TrocadorExchange.instance, - exchangeProvider: 'LetsExchange', ), ], ), diff --git a/lib/services/exchange/trocador/trocador_exchange.dart b/lib/services/exchange/trocador/trocador_exchange.dart index 8085cfca5..e0becb11c 100644 --- a/lib/services/exchange/trocador/trocador_exchange.dart +++ b/lib/services/exchange/trocador/trocador_exchange.dart @@ -187,9 +187,7 @@ class TrocadorExchange extends Exchange { bool fixedRate, bool reversed, ) async { - final isPayment = reversed || fixedRate; - - final response = isPayment + final response = reversed ? await TrocadorAPI.getNewPaymentRate( isOnion: false, fromTicker: from, @@ -215,14 +213,15 @@ class TrocadorExchange extends Exchange { final List cOrLowerQuotes = []; for (final quote in response.value!.quotes) { - if (quote.fixed == isPayment) { + if (quote.fixed == fixedRate && + quote.provider.toLowerCase() != "changenow") { final rating = quote.kycRating.toLowerCase(); if (rating == "a" || rating == "b") { estimates.add( Estimate( - estimatedAmount: isPayment ? quote.amountFrom! : quote.amountTo!, + estimatedAmount: reversed ? quote.amountFrom! : quote.amountTo!, fixedRate: quote.fixed, - reversed: isPayment, + reversed: reversed, exchangeProvider: quote.provider, rateId: response.value!.tradeId, kycRating: quote.kycRating, @@ -236,13 +235,13 @@ class TrocadorExchange extends Exchange { cOrLowerQuotes.sort((a, b) => b.waste.compareTo(a.waste)); - for (int i = 0; i < max(3, cOrLowerQuotes.length); i++) { + for (int i = 0; i < min(3, cOrLowerQuotes.length); i++) { final quote = cOrLowerQuotes[i]; estimates.add( Estimate( - estimatedAmount: isPayment ? quote.amountFrom! : quote.amountTo!, + estimatedAmount: reversed ? quote.amountFrom! : quote.amountTo!, fixedRate: quote.fixed, - reversed: isPayment, + reversed: reversed, exchangeProvider: quote.provider, rateId: response.value!.tradeId, kycRating: quote.kycRating, @@ -251,7 +250,8 @@ class TrocadorExchange extends Exchange { } return ExchangeResponse( - value: estimates, + value: estimates + ..sort((a, b) => b.estimatedAmount.compareTo(a.estimatedAmount)), ); } From 898763411a2fbb747b6e9b1f92f9e137ca8bca03 Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Wed, 3 May 2023 12:51:58 -0500 Subject: [PATCH 16/65] add source to setup.sh after rustup is installed see #272 --- scripts/setup.sh | 1 + 1 file changed, 1 insertion(+) mode change 100644 => 100755 scripts/setup.sh diff --git a/scripts/setup.sh b/scripts/setup.sh old mode 100644 new mode 100755 index 8be59f8c4..732004772 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -42,6 +42,7 @@ sudo apt-get install -y unzip automake build-essential file pkg-config git pytho sudo apt install -y libc6-dev-i386 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +source "$HOME/.cargo/env" cargo install cargo-ndk rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android From b3f12d869a348aa1c75937c74ea502d17852587b Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Wed, 3 May 2023 12:55:15 -0500 Subject: [PATCH 17/65] update rustup to 1.68 --- docs/building.md | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/docs/building.md b/docs/building.md index 879008f09..cf927470d 100644 --- a/docs/building.md +++ b/docs/building.md @@ -41,8 +41,8 @@ sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2- Install [Rust](https://www.rust-lang.org/tools/install) with command: ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -rustup install 1.67.1 -rustup default 1.67.1 +rustup install 1.68 +rustup default 1.68 ``` Install the additional components for Rust: @@ -105,10 +105,6 @@ cd scripts/linux/ cd ../.. ``` -#### Fedora (37) (Work In Progress) - -This is a work in progress, please use Ubuntu for now. - ## Running ### Android Plug in your android device or use the emulator available via Android Studio and then run the following commands: @@ -125,19 +121,3 @@ Plug in your android device or use the emulator available via Android Studio and 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. - -Follow instructions here [https://developer.android.com/studio/install#linux](https://developer.android.com/studio/install#linux) or install via snap: -``` -# 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 From 606c84f1c7db463d4241cc7fed30bda4ebd1d96d Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Wed, 3 May 2023 12:55:49 -0500 Subject: [PATCH 18/65] reduce Fedora references --- docs/building.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/docs/building.md b/docs/building.md index cf927470d..4ba7eefa8 100644 --- a/docs/building.md +++ b/docs/building.md @@ -4,8 +4,9 @@ Here you will find instructions on how to install the necessary tools for buildi ### Prerequisites -- The OS'es supported for building is Ubuntu (20.04) and Fedora (37 - Work In Progress) -- A machine with at least 100 GB of Storage +- The only OS supported for building is Ubuntu 20.04. Advanced users may also be able to build on other Debian-based distributions like Fedora 37. +- Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies) +- 100 GB of storage The following prerequisites can be installed with the setup script [`scripts/setup.sh`](./../scripts/setup.sh) or manually as described below: @@ -19,14 +20,7 @@ The following prerequisites can be installed with the setup script [`scripts/set ### Manual setup -> If you have installed with script, skip to [running](#running) - -Please go to your Linux distribution's title below for instructions on how to manually setup: - -- [Ubuntu (20.04)](#ubuntu-2004) -- [Fedora (37) (Work In Progress)](#fedora-37-work-in-progress) - -#### Ubuntu (20.04) +> If you used the `setup.sh` script, skip to [running](#running) Install basic dependencies ``` From 66bd9f4cef1460c653b87be2334b403059d10ce0 Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Wed, 3 May 2023 12:56:31 -0500 Subject: [PATCH 19/65] emphasize and move Android Studio setup steps up in the docs order --- docs/building.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/building.md b/docs/building.md index 4ba7eefa8..65614203b 100644 --- a/docs/building.md +++ b/docs/building.md @@ -2,17 +2,36 @@ Here you will find instructions on how to install the necessary tools for building and running the app. -### Prerequisites +## Prerequisites - The only OS supported for building is Ubuntu 20.04. Advanced users may also be able to build on other Debian-based distributions like Fedora 37. - Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies) - 100 GB of storage -The following prerequisites can be installed with the setup script [`scripts/setup.sh`](./../scripts/setup.sh) or manually as described below: +Install Android Studio following the instructions below before proceeding, then the following prerequisites can be installed with the setup script [`scripts/setup.sh`](./../scripts/setup.sh) or manually as described below: - Flutter 3.7.11 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install) - Dart SDK Requirement (>=2.19.0, up until <3.0.0) (normally included with a flutter install) -- Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies) + +### 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. + +Follow instructions here [https://developer.android.com/studio/install#linux](https://developer.android.com/studio/install#linux) or install via snap: +``` +# setup android studio +sudo apt install -y openjdk-11-jdk +sudo snap install android-studio --classic +``` + +Use Tools > SDK Manager to install: + - SDK Tools > Android SDK (API 30) + - SDK Tools > NDK + - SDK Tools > Android SDK command line tools, + - SDK Tools > CMake + +Then in File > Settings > Plugins, 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 ### Scripted setup From 56373f882c2fc6575937b02483d47aa07546c79a Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Wed, 3 May 2023 13:12:17 -0500 Subject: [PATCH 20/65] update flutter versions --- docs/building.md | 2 +- scripts/setup.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/building.md b/docs/building.md index 65614203b..6667e581f 100644 --- a/docs/building.md +++ b/docs/building.md @@ -10,7 +10,7 @@ Here you will find instructions on how to install the necessary tools for buildi Install Android Studio following the instructions below before proceeding, then the following prerequisites can be installed with the setup script [`scripts/setup.sh`](./../scripts/setup.sh) or manually as described below: -- Flutter 3.7.11 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install) +- Flutter 3.7.12 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install) - Dart SDK Requirement (>=2.19.0, up until <3.0.0) (normally included with a flutter install) ### Android Studio diff --git a/scripts/setup.sh b/scripts/setup.sh index 732004772..62d6138b3 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -12,7 +12,7 @@ sudo apt install -y unzip pkg-config clang cmake ninja-build libgtk-3-dev cd $DEVELOPMENT git clone https://github.com/flutter/flutter.git cd flutter -git checkout 3.7.6 +git checkout 3.7.12 export FLUTTER_DIR=$(pwd)/bin echo 'export PATH="$PATH:'${FLUTTER_DIR}'"' >> ~/.bashrc source ~/.bashrc From e7f1d28bb492eccf6b6e3f99978e0be1aaad4a7d Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Wed, 3 May 2023 13:21:35 -0500 Subject: [PATCH 21/65] make flutter setup instructions more specific --- docs/building.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/building.md b/docs/building.md index 6667e581f..ad71dba36 100644 --- a/docs/building.md +++ b/docs/building.md @@ -29,7 +29,7 @@ Use Tools > SDK Manager to install: - SDK Tools > Android SDK command line tools, - SDK Tools > CMake -Then in File > Settings > Plugins, 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`) +Then in File > Settings > Plugins, install the Flutter plugin and restart the IDE. In File > Settings > Languages & Frameworks > Flutter > Editor, 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 From 34872ab6b74e032c80c3045d38f41e54634cb9e1 Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Wed, 3 May 2023 13:23:01 -0500 Subject: [PATCH 22/65] remove typo --- docs/building.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/building.md b/docs/building.md index ad71dba36..8622c7262 100644 --- a/docs/building.md +++ b/docs/building.md @@ -26,7 +26,7 @@ sudo snap install android-studio --classic Use Tools > SDK Manager to install: - SDK Tools > Android SDK (API 30) - SDK Tools > NDK - - SDK Tools > Android SDK command line tools, + - SDK Tools > Android SDK command line tools - SDK Tools > CMake Then in File > Settings > Plugins, install the Flutter plugin and restart the IDE. In File > Settings > Languages & Frameworks > Flutter > Editor, 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`) From 3a97623e5b297c5304497327a1a42b371af68a13 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 3 May 2023 14:03:31 -0600 Subject: [PATCH 23/65] WIP exchange screen state refactor --- lib/main.dart | 3 +- lib/models/exchange/active_pair.dart | 35 + lib/models/exchange/exchange_form_state.dart | 1122 +++++++++-------- lib/pages/exchange_view/exchange_form.dart | 420 +++--- .../exchange_step_views/step_2_view.dart | 5 +- .../exchange_step_views/step_3_view.dart | 8 +- .../exchange_step_views/step_4_view.dart | 6 +- lib/pages/exchange_view/exchange_view.dart | 6 +- .../sub_widgets/exchange_provider_option.dart | 312 ++--- .../exchange_provider_options.dart | 14 +- .../sub_widgets/rate_type_toggle.dart | 4 +- .../wallet_initiated_exchange_view.dart | 6 +- .../desktop_exchange_view.dart | 9 +- .../exchange_steps/step_scaffold.dart | 3 +- .../subwidgets/desktop_step_1.dart | 3 +- .../subwidgets/desktop_step_3.dart | 3 +- .../subwidgets/desktop_step_4.dart | 2 +- .../exchange_form_state_provider.dart | 117 +- .../exchange_data_loading_service.dart | 33 +- 19 files changed, 1169 insertions(+), 942 deletions(-) create mode 100644 lib/models/exchange/active_pair.dart diff --git a/lib/main.dart b/lib/main.dart index e08324572..10d1c4a12 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -299,7 +299,8 @@ class _MaterialAppWithThemeState extends ConsumerState await ref.read(prefsChangeNotifierProvider).isExternalCallsSet()) { if (Constants.enableExchange) { await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( - ref.read(exchangeFormStateProvider), + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), ); unawaited(ExchangeDataLoadingService.instance.loadAll()); } diff --git a/lib/models/exchange/active_pair.dart b/lib/models/exchange/active_pair.dart new file mode 100644 index 000000000..4a2e80eba --- /dev/null +++ b/lib/models/exchange/active_pair.dart @@ -0,0 +1,35 @@ +import 'package:flutter/foundation.dart'; +import 'package:stackwallet/models/exchange/aggregate_currency.dart'; + +class ActivePair extends ChangeNotifier { + AggregateCurrency? _send; + AggregateCurrency? _receive; + + AggregateCurrency? get send => _send; + AggregateCurrency? get receive => _receive; + + void setSend( + AggregateCurrency? newSend, { + bool notifyListeners = false, + }) { + _send = newSend; + if (notifyListeners) { + this.notifyListeners(); + } + } + + void setReceive( + AggregateCurrency? newReceive, { + bool notifyListeners = false, + }) { + _receive = newReceive; + if (notifyListeners) { + this.notifyListeners(); + } + } + + @override + String toString() { + return "ActivePair{ send: $send, receive: $receive }"; + } +} diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart index 52fe0bffd..5f7282ff9 100644 --- a/lib/models/exchange/exchange_form_state.dart +++ b/lib/models/exchange/exchange_form_state.dart @@ -1,552 +1,570 @@ -import 'dart:async'; - -import 'package:decimal/decimal.dart'; -import 'package:flutter/foundation.dart'; -import 'package:stackwallet/models/exchange/aggregate_currency.dart'; -import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; -import 'package:stackwallet/services/exchange/exchange.dart'; -import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; -import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; -import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; -import 'package:stackwallet/utilities/logger.dart'; - -class ExchangeFormState extends ChangeNotifier { - Exchange? _exchange; - Exchange get exchange => _exchange ??= Exchange.defaultExchange; - - String? _providerName; - // default to exchange name that isn't trocador - String get providerName => _providerName ??= Exchange.defaultExchange.name; - - String get combinedExchangeId => "${exchange.name} ($providerName)"; - - ExchangeRateType _exchangeRateType = ExchangeRateType.estimated; - ExchangeRateType get exchangeRateType => _exchangeRateType; - set exchangeRateType(ExchangeRateType exchangeRateType) { - _exchangeRateType = exchangeRateType; - // - } - - List _estimates = []; - List get estimates => _estimates; - - bool _reversed = false; - bool get reversed => _reversed; - set reversed(bool reversed) { - _reversed = reversed; - // - } - - Decimal? _rate; - Decimal? get rate => _rate; - // set rate(Decimal? rate) { - // _rate = rate; - // // - // } - - Decimal? _sendAmount; - Decimal? get sendAmount => _sendAmount; - // set sendAmount(Decimal? sendAmount) { - // _sendAmount = sendAmount; - // // - // } - - Decimal? _receiveAmount; - Decimal? get receiveAmount => _receiveAmount; - set receiveAmount(Decimal? receiveAmount) { - _receiveAmount = receiveAmount; - // - } - - AggregateCurrency? _sendCurrency; - AggregateCurrency? get sendCurrency => _sendCurrency; - // set sendCurrency(Currency? sendCurrency) { - // _sendCurrency = sendCurrency; - // // - // } - - AggregateCurrency? _receiveCurrency; - AggregateCurrency? get receiveCurrency => _receiveCurrency; - // set receiveCurrency(Currency? receiveCurrency) { - // _receiveCurrency = receiveCurrency; - // // - // } - - Decimal? _minSendAmount; - Decimal? get minSendAmount => _minSendAmount; - // set minSendAmount(Decimal? minSendAmount) { - // _minSendAmount = minSendAmount; - // // - // } - - Decimal? _minReceiveAmount; - Decimal? get minReceiveAmount => _minReceiveAmount; - // set minReceiveAmount(Decimal? minReceiveAmount) { - // _minReceiveAmount = minReceiveAmount; - // // - // } - - Decimal? _maxSendAmount; - Decimal? get maxSendAmount => _maxSendAmount; - // set maxSendAmount(Decimal? maxSendAmount) { - // _maxSendAmount = maxSendAmount; - // // - // } - - Decimal? _maxReceiveAmount; - Decimal? get maxReceiveAmount => _maxReceiveAmount; - // set maxReceiveAmount(Decimal? maxReceiveAmount) { - // _maxReceiveAmount = maxReceiveAmount; - // // - // } - - //============================================================================ - // computed properties - //============================================================================ - - String? get fromTicker => _sendCurrency?.ticker; - String? get toTicker => _receiveCurrency?.ticker; - - String get fromAmountString => _sendAmount?.toStringAsFixed(8) ?? ""; - String get toAmountString => _receiveAmount?.toStringAsFixed(8) ?? ""; - - bool get canExchange { - return sendCurrency != null && - receiveCurrency != null && - sendAmount != null && - sendAmount! >= Decimal.zero && - receiveAmount != null && - rate != null && - rate! >= Decimal.zero && - sendCurrency!.forExchange(exchange.name) != null && - receiveCurrency!.forExchange(exchange.name) != null && - warning.isEmpty; - } - - String get warning { - if (reversed) { - if (_receiveCurrency != null && _receiveAmount != null) { - if (_minReceiveAmount != null && - _receiveAmount! < _minReceiveAmount! && - _receiveAmount! > Decimal.zero) { - return "Min receive amount ${_minReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}"; - } else if (_maxReceiveAmount != null && - _receiveAmount! > _maxReceiveAmount!) { - return "Max receive amount ${_maxReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}"; - } - } - } else { - if (_sendCurrency != null && _sendAmount != null) { - if (_minSendAmount != null && - _sendAmount! < _minSendAmount! && - _sendAmount! > Decimal.zero) { - return "Min send amount ${_minSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}"; - } else if (_maxSendAmount != null && _sendAmount! > _maxSendAmount!) { - return "Max send amount ${_maxSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}"; - } - } - } - - return ""; - } - - //============================================================================ - // public state updaters - //============================================================================ - - Future updateExchange({ - required Exchange exchange, - required String providerName, - required bool shouldUpdateData, - required bool shouldNotifyListeners, - bool shouldAwait = true, - }) async { - _exchange = exchange; - _providerName = providerName; - if (shouldUpdateData) { - if (shouldAwait) { - await _updateRangesAndEstimate( - shouldNotifyListeners: shouldNotifyListeners, - ); - } else { - unawaited( - _updateRangesAndEstimate( - shouldNotifyListeners: shouldNotifyListeners, - ), - ); - } - } else if (shouldNotifyListeners) { - _notify(); - } - } - - void setCurrencies(AggregateCurrency? from, AggregateCurrency? to) { - _sendCurrency = from; - _receiveCurrency = to; - } - - void reset({ - required bool shouldNotifyListeners, - }) { - _exchange = null; - _providerName = null; - _reversed = false; - _rate = null; - _sendAmount = null; - _receiveAmount = null; - _sendCurrency = null; - _receiveCurrency = null; - _minSendAmount = null; - _minReceiveAmount = null; - _maxSendAmount = null; - _maxReceiveAmount = null; - - if (shouldNotifyListeners) { - _notify(); - } - } - - Future setSendAmountAndCalculateReceiveAmount( - Decimal? newSendAmount, - bool shouldNotifyListeners, - ) async { - if (newSendAmount == null) { - // todo: check if this breaks things and stuff - _receiveAmount = null; - _sendAmount = null; - } else { - if (newSendAmount <= Decimal.zero) { - _receiveAmount = Decimal.zero; - } - - _sendAmount = newSendAmount; - _reversed = false; - - await _updateRangesAndEstimate( - shouldNotifyListeners: false, - ); - } - - if (shouldNotifyListeners) { - _notify(); - } - } - - Future setReceivingAmountAndCalculateSendAmount( - Decimal? newReceiveAmount, - bool shouldNotifyListeners, - ) async { - if (newReceiveAmount == null) { - // todo: check if this breaks things and stuff - _receiveAmount = null; - _sendAmount = null; - } else { - if (newReceiveAmount <= Decimal.zero) { - _sendAmount = Decimal.zero; - } - - _receiveAmount = newReceiveAmount; - _reversed = true; - - await _updateRangesAndEstimate( - shouldNotifyListeners: false, - ); - } - - if (shouldNotifyListeners) { - _notify(); - } - } - - Future updateSendCurrency( - AggregateCurrency sendCurrency, - bool shouldNotifyListeners, - ) async { - try { - _sendCurrency = sendCurrency; - _minSendAmount = null; - _maxSendAmount = null; - - if (_receiveCurrency == null) { - _rate = null; - } else { - await _updateRangesAndEstimate( - shouldNotifyListeners: false, - ); - } - if (shouldNotifyListeners) { - _notify(); - } - } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); - } - } - - Future updateReceivingCurrency( - AggregateCurrency receiveCurrency, - bool shouldNotifyListeners, - ) async { - try { - _receiveCurrency = receiveCurrency; - _minReceiveAmount = null; - _maxReceiveAmount = null; - - if (_sendCurrency == null) { - _rate = null; - } else { - await _updateRangesAndEstimate( - shouldNotifyListeners: false, - ); - } - if (shouldNotifyListeners) { - _notify(); - } - } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); - } - } - - Future swap({ - required bool shouldNotifyListeners, - }) async { - final Decimal? temp = sendAmount; - _sendAmount = receiveAmount; - _receiveAmount = temp; - - _minSendAmount = null; - _maxSendAmount = null; - _minReceiveAmount = null; - _maxReceiveAmount = null; - - final AggregateCurrency? tmp = sendCurrency; - _sendCurrency = receiveCurrency; - _receiveCurrency = tmp; - - await _updateRangesAndEstimate( - shouldNotifyListeners: false, - ); - - if (shouldNotifyListeners) { - _notify(); - } - } - - Future refresh() => _updateRangesAndEstimate( - shouldNotifyListeners: true, - ); - - //============================================================================ - // private state updaters - //============================================================================ - - Future _updateRangesAndEstimate({ - required bool shouldNotifyListeners, - }) async { - try { - switch (exchange.name) { - case ChangeNowExchange.exchangeName: - if (!_exchangeSupported( - exchangeName: exchange.name, - sendCurrency: sendCurrency, - receiveCurrency: receiveCurrency, - exchangeRateType: exchangeRateType, - )) { - _exchange = MajesticBankExchange.instance; - } - break; - case MajesticBankExchange.exchangeName: - if (!_exchangeSupported( - exchangeName: exchange.name, - sendCurrency: sendCurrency, - receiveCurrency: receiveCurrency, - exchangeRateType: exchangeRateType, - )) { - _exchange = ChangeNowExchange.instance; - } - break; - case TrocadorExchange.exchangeName: - if (!_exchangeSupported( - exchangeName: exchange.name, - sendCurrency: sendCurrency, - receiveCurrency: receiveCurrency, - exchangeRateType: exchangeRateType, - )) { - _exchange = ChangeNowExchange.instance; - } - break; - } - - await _updateRanges(shouldNotifyListeners: false); - await _updateEstimate(shouldNotifyListeners: false); - if (shouldNotifyListeners) { - _notify(); - } - } catch (_) { - // - } - } - - Future _updateRanges({ - required bool shouldNotifyListeners, - }) async { - // if (exchange?.name == SimpleSwapExchange.exchangeName) { - // reversed = false; - // } - final _send = sendCurrency; - final _receive = receiveCurrency; - if (_send == null || _receive == null) { - Logging.instance.log( - "Tried to $runtimeType.updateRanges where ( $_send || $_receive) for: $exchange", - level: LogLevel.Info, - ); - return; - } - final response = await exchange.getRange( - _send.ticker, - _receive.ticker, - exchangeRateType == ExchangeRateType.fixed, - ); - - if (response.value == null) { - Logging.instance.log( - "Tried to $runtimeType.updateRanges for: $exchange where response: $response", - level: LogLevel.Info, - ); - return; - } - final responseReversed = await exchange.getRange( - _receive.ticker, - _send.ticker, - exchangeRateType == ExchangeRateType.fixed, - ); - - if (responseReversed.value == null) { - Logging.instance.log( - "Tried to $runtimeType.updateRanges for: $exchange where response: $responseReversed", - level: LogLevel.Info, - ); - return; - } - - final range = response.value!; - final rangeReversed = responseReversed.value!; - - _minSendAmount = range.min; - _maxSendAmount = range.max; - _minReceiveAmount = rangeReversed.min; - _maxReceiveAmount = rangeReversed.max; - - //todo: check if print needed - // debugPrint( - // "updated range for: $exchange for $_fromTicker-$_toTicker: $range"); - - if (shouldNotifyListeners) { - _notify(); - } - } - - Future _updateEstimate({ - required bool shouldNotifyListeners, - }) async { - // if (exchange?.name == SimpleSwapExchange.exchangeName) { - // reversed = false; - // } - final amount = reversed ? receiveAmount : sendAmount; - if (sendCurrency == null || - receiveCurrency == null || - amount == null || - amount <= Decimal.zero) { - Logging.instance.log( - "Tried to $runtimeType.updateEstimate for: $exchange where (from: $sendCurrency || to: $receiveCurrency || amount: $amount)", - level: LogLevel.Info, - ); - return; - } - final response = await exchange.getEstimates( - sendCurrency!.ticker, - receiveCurrency!.ticker, - amount, - exchangeRateType == ExchangeRateType.fixed, - reversed, - ); - - if (response.value == null) { - Logging.instance.log( - "Tried to $runtimeType.updateEstimate for: $exchange where response: $response", - level: LogLevel.Info, - ); - return; - } - - _estimates = response.value!; - - if (reversed) { - _sendAmount = _estimates - .firstWhere((e) => e.exchangeProvider == providerName) - .estimatedAmount; - } else { - _receiveAmount = _estimates - .firstWhere((e) => e.exchangeProvider == providerName) - .estimatedAmount; - } - - _rate = - (receiveAmount! / sendAmount!).toDecimal(scaleOnInfinitePrecision: 12); - - //todo: check if print needed - // debugPrint( - // "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate"); - - if (shouldNotifyListeners) { - _notify(); - } - } - - //============================================================================ - - void _notify() { - debugPrint("ExFState NOTIFY: ${toString()}"); - notifyListeners(); - } - - bool _exchangeSupported({ - required String exchangeName, - required AggregateCurrency? sendCurrency, - required AggregateCurrency? receiveCurrency, - required ExchangeRateType exchangeRateType, - }) { - final send = sendCurrency?.forExchange(exchangeName); - if (send == null) return false; - - final rcv = receiveCurrency?.forExchange(exchangeName); - if (rcv == null) return false; - - if (exchangeRateType == ExchangeRateType.fixed) { - return send.supportsFixedRate && rcv.supportsFixedRate; - } else { - return send.supportsEstimatedRate && rcv.supportsEstimatedRate; - } - } - - @override - String toString() { - return "{" - "\n\t exchange: $exchange," - "\n\t exchangeRateType: $exchangeRateType," - "\n\t sendCurrency: $sendCurrency," - "\n\t receiveCurrency: $receiveCurrency," - "\n\t rate: $rate," - "\n\t reversed: $reversed," - "\n\t sendAmount: $sendAmount," - "\n\t receiveAmount: $receiveAmount," - "\n\t estimates: $estimates," - "\n\t minSendAmount: $minSendAmount," - "\n\t maxSendAmount: $maxSendAmount," - "\n\t minReceiveAmount: $minReceiveAmount," - "\n\t maxReceiveAmount: $maxReceiveAmount," - "\n\t canExchange: $canExchange," - "\n\t warning: $warning," - "\n}"; - } -} +// import 'dart:async'; +// +// import 'package:decimal/decimal.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:stackwallet/models/exchange/aggregate_currency.dart'; +// import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +// import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +// import 'package:stackwallet/services/exchange/exchange.dart'; +// import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +// import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; +// import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; +// import 'package:stackwallet/utilities/logger.dart'; +// +// class ExchangeFormState extends ChangeNotifier { +// Exchange? _exchange; +// Exchange get exchange => _exchange ??= Exchange.defaultExchange; +// +// String? _providerName; +// // default to exchange name that isn't trocador +// String get providerName => _providerName ??= Exchange.defaultExchange.name; +// +// String get combinedExchangeId => "${exchange.name} ($providerName)"; +// +// ExchangeRateType _exchangeRateType = ExchangeRateType.estimated; +// ExchangeRateType get exchangeRateType => _exchangeRateType; +// set exchangeRateType(ExchangeRateType exchangeRateType) { +// _exchangeRateType = exchangeRateType; +// // +// } +// +// List _estimates = []; +// List get estimates => _estimates; +// +// bool _reversed = false; +// bool get reversed => _reversed; +// set reversed(bool reversed) { +// _reversed = reversed; +// // +// } +// +// Decimal? _rate; +// Decimal? get rate => _rate; +// // set rate(Decimal? rate) { +// // _rate = rate; +// // // +// // } +// +// Decimal? _sendAmount; +// Decimal? get sendAmount => _sendAmount; +// // set sendAmount(Decimal? sendAmount) { +// // _sendAmount = sendAmount; +// // // +// // } +// +// Decimal? _receiveAmount; +// Decimal? get receiveAmount => _receiveAmount; +// set receiveAmount(Decimal? receiveAmount) { +// _receiveAmount = receiveAmount; +// // +// } +// +// AggregateCurrency? _sendCurrency; +// AggregateCurrency? get sendCurrency => _sendCurrency; +// // set sendCurrency(Currency? sendCurrency) { +// // _sendCurrency = sendCurrency; +// // // +// // } +// +// AggregateCurrency? _receiveCurrency; +// AggregateCurrency? get receiveCurrency => _receiveCurrency; +// // set receiveCurrency(Currency? receiveCurrency) { +// // _receiveCurrency = receiveCurrency; +// // // +// // } +// +// Decimal? _minSendAmount; +// Decimal? get minSendAmount => _minSendAmount; +// // set minSendAmount(Decimal? minSendAmount) { +// // _minSendAmount = minSendAmount; +// // // +// // } +// +// Decimal? _minReceiveAmount; +// Decimal? get minReceiveAmount => _minReceiveAmount; +// // set minReceiveAmount(Decimal? minReceiveAmount) { +// // _minReceiveAmount = minReceiveAmount; +// // // +// // } +// +// Decimal? _maxSendAmount; +// Decimal? get maxSendAmount => _maxSendAmount; +// // set maxSendAmount(Decimal? maxSendAmount) { +// // _maxSendAmount = maxSendAmount; +// // // +// // } +// +// Decimal? _maxReceiveAmount; +// Decimal? get maxReceiveAmount => _maxReceiveAmount; +// // set maxReceiveAmount(Decimal? maxReceiveAmount) { +// // _maxReceiveAmount = maxReceiveAmount; +// // // +// // } +// +// //============================================================================ +// // computed properties +// //============================================================================ +// +// String? get fromTicker => _sendCurrency?.ticker; +// String? get toTicker => _receiveCurrency?.ticker; +// +// String get fromAmountString => _sendAmount?.toStringAsFixed(8) ?? ""; +// String get toAmountString => _receiveAmount?.toStringAsFixed(8) ?? ""; +// +// bool get canExchange { +// return sendCurrency != null && +// receiveCurrency != null && +// sendAmount != null && +// sendAmount! >= Decimal.zero && +// receiveAmount != null && +// rate != null && +// rate! >= Decimal.zero && +// sendCurrency!.forExchange(exchange.name) != null && +// receiveCurrency!.forExchange(exchange.name) != null && +// warning.isEmpty; +// } +// +// String get warning { +// if (reversed) { +// if (_receiveCurrency != null && _receiveAmount != null) { +// if (_minReceiveAmount != null && +// _receiveAmount! < _minReceiveAmount! && +// _receiveAmount! > Decimal.zero) { +// return "Min receive amount ${_minReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}"; +// } else if (_maxReceiveAmount != null && +// _receiveAmount! > _maxReceiveAmount!) { +// return "Max receive amount ${_maxReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}"; +// } +// } +// } else { +// if (_sendCurrency != null && _sendAmount != null) { +// if (_minSendAmount != null && +// _sendAmount! < _minSendAmount! && +// _sendAmount! > Decimal.zero) { +// return "Min send amount ${_minSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}"; +// } else if (_maxSendAmount != null && _sendAmount! > _maxSendAmount!) { +// return "Max send amount ${_maxSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}"; +// } +// } +// } +// +// return ""; +// } +// +// //============================================================================ +// // public state updaters +// //============================================================================ +// +// Future updateExchange({ +// required Exchange exchange, +// required String providerName, +// required bool shouldUpdateData, +// required bool shouldNotifyListeners, +// bool shouldAwait = true, +// }) async { +// _exchange = exchange; +// _providerName = providerName; +// // if (shouldNotifyListeners) { +// _notify(); +// // } +// if (shouldUpdateData) { +// if (shouldAwait) { +// await _updateRangesAndEstimate( +// shouldNotifyListeners: shouldNotifyListeners, +// ); +// } else { +// unawaited( +// _updateRangesAndEstimate( +// shouldNotifyListeners: shouldNotifyListeners, +// ), +// ); +// } +// } +// } +// +// void setCurrencies(AggregateCurrency? from, AggregateCurrency? to) { +// _sendCurrency = from; +// _receiveCurrency = to; +// } +// +// void reset({ +// required bool shouldNotifyListeners, +// }) { +// _exchange = null; +// _providerName = null; +// _reversed = false; +// _rate = null; +// _sendAmount = null; +// _receiveAmount = null; +// _sendCurrency = null; +// _receiveCurrency = null; +// _minSendAmount = null; +// _minReceiveAmount = null; +// _maxSendAmount = null; +// _maxReceiveAmount = null; +// +// if (shouldNotifyListeners) { +// _notify(); +// } +// } +// +// Future setSendAmountAndCalculateReceiveAmount( +// Decimal? newSendAmount, +// bool shouldNotifyListeners, +// ) async { +// if (newSendAmount == null) { +// // todo: check if this breaks things and stuff +// _receiveAmount = null; +// _sendAmount = null; +// } else { +// if (newSendAmount <= Decimal.zero) { +// _receiveAmount = null; +// } +// +// _sendAmount = newSendAmount; +// _reversed = false; +// +// if (_sendAmount! > Decimal.zero) { +// await _updateRangesAndEstimate( +// shouldNotifyListeners: false, +// ); +// } +// } +// +// if (shouldNotifyListeners) { +// _notify(); +// } +// } +// +// Future setReceivingAmountAndCalculateSendAmount( +// Decimal? newReceiveAmount, +// bool shouldNotifyListeners, +// ) async { +// if (newReceiveAmount == null) { +// // todo: check if this breaks things and stuff +// _receiveAmount = null; +// _sendAmount = null; +// } else { +// if (newReceiveAmount <= Decimal.zero) { +// _sendAmount = Decimal.zero; +// } +// +// _receiveAmount = newReceiveAmount; +// _reversed = true; +// +// await _updateRangesAndEstimate( +// shouldNotifyListeners: false, +// ); +// } +// +// if (shouldNotifyListeners) { +// _notify(); +// } +// } +// +// Future updateSendCurrency( +// AggregateCurrency sendCurrency, +// bool shouldNotifyListeners, +// ) async { +// try { +// _sendCurrency = sendCurrency; +// _minSendAmount = null; +// _maxSendAmount = null; +// +// if (_receiveCurrency == null) { +// _rate = null; +// } else { +// await _updateRangesAndEstimate( +// shouldNotifyListeners: false, +// ); +// } +// if (shouldNotifyListeners) { +// _notify(); +// } +// } catch (e, s) { +// Logging.instance.log("$e\n$s", level: LogLevel.Error); +// } +// } +// +// Future updateReceivingCurrency( +// AggregateCurrency receiveCurrency, +// bool shouldNotifyListeners, +// ) async { +// try { +// _receiveCurrency = receiveCurrency; +// _minReceiveAmount = null; +// _maxReceiveAmount = null; +// +// if (_sendCurrency == null) { +// _rate = null; +// } else { +// await _updateRangesAndEstimate( +// shouldNotifyListeners: false, +// ); +// } +// if (shouldNotifyListeners) { +// _notify(); +// } +// } catch (e, s) { +// Logging.instance.log("$e\n$s", level: LogLevel.Error); +// } +// } +// +// Future swap({ +// required bool shouldNotifyListeners, +// }) async { +// final Decimal? temp = sendAmount; +// _sendAmount = receiveAmount; +// _receiveAmount = temp; +// +// _minSendAmount = null; +// _maxSendAmount = null; +// _minReceiveAmount = null; +// _maxReceiveAmount = null; +// +// final AggregateCurrency? tmp = sendCurrency; +// _sendCurrency = receiveCurrency; +// _receiveCurrency = tmp; +// +// await _updateRangesAndEstimate( +// shouldNotifyListeners: false, +// ); +// +// if (shouldNotifyListeners) { +// _notify(); +// } +// } +// +// Future refresh() => _updateRangesAndEstimate( +// shouldNotifyListeners: true, +// ); +// +// //============================================================================ +// // private state updaters +// //============================================================================ +// +// Future _updateRangesAndEstimate({ +// required bool shouldNotifyListeners, +// bool shouldAwait = true, +// }) async { +// try { +// switch (exchange.name) { +// case ChangeNowExchange.exchangeName: +// if (!_exchangeSupported( +// exchangeName: exchange.name, +// sendCurrency: sendCurrency, +// receiveCurrency: receiveCurrency, +// exchangeRateType: exchangeRateType, +// )) { +// _exchange = MajesticBankExchange.instance; +// } +// break; +// case MajesticBankExchange.exchangeName: +// if (!_exchangeSupported( +// exchangeName: exchange.name, +// sendCurrency: sendCurrency, +// receiveCurrency: receiveCurrency, +// exchangeRateType: exchangeRateType, +// )) { +// _exchange = ChangeNowExchange.instance; +// } +// break; +// case TrocadorExchange.exchangeName: +// if (!_exchangeSupported( +// exchangeName: exchange.name, +// sendCurrency: sendCurrency, +// receiveCurrency: receiveCurrency, +// exchangeRateType: exchangeRateType, +// )) { +// _exchange = ChangeNowExchange.instance; +// } +// break; +// } +// +// if (shouldAwait) { +// await _updateRanges(shouldNotifyListeners: false); +// await _updateEstimate(shouldNotifyListeners: false); +// if (shouldNotifyListeners) { +// _notify(); +// } +// } else { +// unawaited( +// _updateRanges(shouldNotifyListeners: false).then( +// (_) => _updateEstimate(shouldNotifyListeners: false).then( +// (_) { +// if (shouldNotifyListeners) { +// _notify(); +// } +// }, +// ), +// ), +// ); +// } +// } catch (_) { +// // +// } +// } +// +// Future _updateRanges({ +// required bool shouldNotifyListeners, +// }) async { +// // if (exchange?.name == SimpleSwapExchange.exchangeName) { +// // reversed = false; +// // } +// final _send = sendCurrency; +// final _receive = receiveCurrency; +// if (_send == null || _receive == null) { +// Logging.instance.log( +// "Tried to $runtimeType.updateRanges where ( $_send || $_receive) for: $exchange", +// level: LogLevel.Info, +// ); +// return; +// } +// final response = await exchange.getRange( +// _send.ticker, +// _receive.ticker, +// exchangeRateType == ExchangeRateType.fixed, +// ); +// +// if (response.value == null) { +// Logging.instance.log( +// "Tried to $runtimeType.updateRanges for: $exchange where response: $response", +// level: LogLevel.Info, +// ); +// return; +// } +// final responseReversed = await exchange.getRange( +// _receive.ticker, +// _send.ticker, +// exchangeRateType == ExchangeRateType.fixed, +// ); +// +// if (responseReversed.value == null) { +// Logging.instance.log( +// "Tried to $runtimeType.updateRanges for: $exchange where response: $responseReversed", +// level: LogLevel.Info, +// ); +// return; +// } +// +// final range = response.value!; +// final rangeReversed = responseReversed.value!; +// +// _minSendAmount = range.min; +// _maxSendAmount = range.max; +// _minReceiveAmount = rangeReversed.min; +// _maxReceiveAmount = rangeReversed.max; +// +// //todo: check if print needed +// // debugPrint( +// // "updated range for: $exchange for $_fromTicker-$_toTicker: $range"); +// +// if (shouldNotifyListeners) { +// _notify(); +// } +// } +// +// Future _updateEstimate({ +// required bool shouldNotifyListeners, +// }) async { +// // if (exchange?.name == SimpleSwapExchange.exchangeName) { +// // reversed = false; +// // } +// final amount = reversed ? receiveAmount : sendAmount; +// if (sendCurrency == null || +// receiveCurrency == null || +// amount == null || +// amount <= Decimal.zero) { +// Logging.instance.log( +// "Tried to $runtimeType.updateEstimate for: $exchange where (from: $sendCurrency || to: $receiveCurrency || amount: $amount)", +// level: LogLevel.Info, +// ); +// return; +// } +// final response = await exchange.getEstimates( +// sendCurrency!.ticker, +// receiveCurrency!.ticker, +// amount, +// exchangeRateType == ExchangeRateType.fixed, +// reversed, +// ); +// +// if (response.value == null) { +// Logging.instance.log( +// "Tried to $runtimeType.updateEstimate for: $exchange where response: $response", +// level: LogLevel.Info, +// ); +// return; +// } +// +// _estimates = response.value!; +// +// if (reversed) { +// _sendAmount = _estimates +// .firstWhere((e) => e.exchangeProvider == providerName) +// .estimatedAmount; +// } else { +// _receiveAmount = _estimates +// .firstWhere((e) => e.exchangeProvider == providerName) +// .estimatedAmount; +// } +// +// _rate = +// (receiveAmount! / sendAmount!).toDecimal(scaleOnInfinitePrecision: 12); +// +// //todo: check if print needed +// // debugPrint( +// // "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate"); +// +// if (shouldNotifyListeners) { +// _notify(); +// } +// } +// +// //============================================================================ +// +// void _notify() { +// debugPrint("ExFState NOTIFY: ${toString()}"); +// notifyListeners(); +// } +// +// bool _exchangeSupported({ +// required String exchangeName, +// required AggregateCurrency? sendCurrency, +// required AggregateCurrency? receiveCurrency, +// required ExchangeRateType exchangeRateType, +// }) { +// final send = sendCurrency?.forExchange(exchangeName); +// if (send == null) return false; +// +// final rcv = receiveCurrency?.forExchange(exchangeName); +// if (rcv == null) return false; +// +// if (exchangeRateType == ExchangeRateType.fixed) { +// return send.supportsFixedRate && rcv.supportsFixedRate; +// } else { +// return send.supportsEstimatedRate && rcv.supportsEstimatedRate; +// } +// } +// +// @override +// String toString() { +// return "{" +// "\n\t exchange: $exchange," +// "\n\t exchangeRateType: $exchangeRateType," +// "\n\t sendCurrency: $sendCurrency," +// "\n\t receiveCurrency: $receiveCurrency," +// "\n\t rate: $rate," +// "\n\t reversed: $reversed," +// "\n\t sendAmount: $sendAmount," +// "\n\t receiveAmount: $receiveAmount," +// "\n\t estimates: $estimates," +// "\n\t minSendAmount: $minSendAmount," +// "\n\t maxSendAmount: $maxSendAmount," +// "\n\t minReceiveAmount: $minReceiveAmount," +// "\n\t maxReceiveAmount: $maxReceiveAmount," +// "\n\t canExchange: $canExchange," +// "\n\t warning: $warning," +// "\n}"; +// } +// } diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 9c22cd36a..90a084c01 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -9,6 +9,7 @@ import 'package:intl/intl.dart'; import 'package:isar/isar.dart'; import 'package:stackwallet/models/exchange/aggregate_currency.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; @@ -21,10 +22,13 @@ import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_ste import 'package:stackwallet/providers/providers.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/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_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/enums/exchange_rate_type_enum.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/utilities/util.dart'; @@ -61,6 +65,12 @@ class _ExchangeFormState extends ConsumerState { late final Coin? coin; late final bool walletInitiated; + final exchanges = [ + MajesticBankExchange.instance, + ChangeNowExchange.instance, + TrocadorExchange.instance, + ]; + late final TextEditingController _sendController; late final TextEditingController _receiveController; final isDesktop = Util.isDesktop; @@ -105,16 +115,17 @@ class _ExchangeFormState extends ConsumerState { } Timer? _sendFieldOnChangedTimer; - void sendFieldOnChanged(String value) async { + void sendFieldOnChanged(String value) { if (_sendFocusNode.hasFocus) { _sendFieldOnChangedTimer?.cancel(); _sendFieldOnChangedTimer = Timer(_valueCheckInterval, () async { final newFromAmount = _localizedStringToNum(value); - await ref - .read(exchangeFormStateProvider) - .setSendAmountAndCalculateReceiveAmount(newFromAmount, true); + ref.read(efSendAmountProvider.notifier).state = newFromAmount; + if (!_swapLock && !ref.read(efReversedProvider)) { + unawaited(update()); + } }); } } @@ -126,9 +137,10 @@ class _ExchangeFormState extends ConsumerState { _receiveFieldOnChangedTimer = Timer(_valueCheckInterval, () async { final newToAmount = _localizedStringToNum(value); - await ref - .read(exchangeFormStateProvider) - .setReceivingAmountAndCalculateSendAmount(newToAmount, true); + ref.read(efReceiveAmountProvider.notifier).state = newToAmount; + if (!_swapLock && ref.read(efReversedProvider)) { + unawaited(update()); + } }); } @@ -147,7 +159,7 @@ class _ExchangeFormState extends ConsumerState { } Future _getAggregateCurrency(Currency currency) async { - final rateType = ref.read(exchangeFormStateProvider).exchangeRateType; + final rateType = ref.read(efRateTypeProvider); final currencies = await ExchangeDataLoadingService.instance.isar.currencies .filter() .group((q) => rateType == ExchangeRateType.fixed @@ -178,8 +190,8 @@ class _ExchangeFormState extends ConsumerState { } void selectSendCurrency() async { - final type = (ref.read(exchangeFormStateProvider).exchangeRateType); - final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; + final type = ref.read(efRateTypeProvider); + final fromTicker = ref.read(efCurrencyPairProvider).send?.ticker ?? ""; if (walletInitiated) { if (widget.contract != null && @@ -194,24 +206,26 @@ class _ExchangeFormState extends ConsumerState { } final selectedCurrency = await _showCurrencySelectionSheet( - willChange: ref.read(exchangeFormStateProvider).sendCurrency?.ticker, + willChange: ref.read(efCurrencyPairProvider).send?.ticker, willChangeIsSend: true, - paired: ref.read(exchangeFormStateProvider).receiveCurrency?.ticker, + paired: ref.read(efCurrencyPairProvider).receive?.ticker, isFixedRate: type == ExchangeRateType.fixed, ); if (selectedCurrency != null) { await showUpdatingExchangeRate( whileFuture: _getAggregateCurrency(selectedCurrency).then( - (aggregateSelected) => ref - .read(exchangeFormStateProvider) - .updateSendCurrency(aggregateSelected, true)), + (aggregateSelected) => ref.read(efCurrencyPairProvider).setSend( + aggregateSelected, + notifyListeners: true, + ), + ), ); } } void selectReceiveCurrency() async { - final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; + final toTicker = ref.read(efCurrencyPairProvider).receive?.ticker ?? ""; if (walletInitiated && toTicker.toLowerCase() == coin!.ticker.toLowerCase()) { // do not allow changing away from wallet coin @@ -219,19 +233,20 @@ class _ExchangeFormState extends ConsumerState { } final selectedCurrency = await _showCurrencySelectionSheet( - willChange: ref.read(exchangeFormStateProvider).receiveCurrency?.ticker, + willChange: ref.read(efCurrencyPairProvider).receive?.ticker, willChangeIsSend: false, - paired: ref.read(exchangeFormStateProvider).sendCurrency?.ticker, - isFixedRate: ref.read(exchangeFormStateProvider).exchangeRateType == - ExchangeRateType.fixed, + paired: ref.read(efCurrencyPairProvider).send?.ticker, + isFixedRate: ref.read(efRateTypeProvider) == ExchangeRateType.fixed, ); if (selectedCurrency != null) { await showUpdatingExchangeRate( whileFuture: _getAggregateCurrency(selectedCurrency).then( - (aggregateSelected) => ref - .read(exchangeFormStateProvider) - .updateReceivingCurrency(aggregateSelected, true)), + (aggregateSelected) => ref.read(efCurrencyPairProvider).setReceive( + aggregateSelected, + notifyListeners: true, + ), + ), ); } } @@ -241,10 +256,25 @@ class _ExchangeFormState extends ConsumerState { _sendFocusNode.unfocus(); _receiveFocusNode.unfocus(); - await showUpdatingExchangeRate( - whileFuture: - ref.read(exchangeFormStateProvider).swap(shouldNotifyListeners: true), - ); + final temp = ref.read(efCurrencyPairProvider).send; + ref.read(efCurrencyPairProvider).setSend( + ref.read(efCurrencyPairProvider).receive, + notifyListeners: true, + ); + ref.read(efCurrencyPairProvider).setReceive( + temp, + notifyListeners: true, + ); + + // final reversed = ref.read(efReversedProvider); + + final amount = ref.read(efSendAmountProvider); + ref.read(efSendAmountProvider.notifier).state = + ref.read(efReceiveAmountProvider); + + ref.read(efReceiveAmountProvider.notifier).state = amount; + + unawaited(update()); _swapLock = false; } @@ -331,89 +361,20 @@ class _ExchangeFormState extends ConsumerState { } } - void onRateTypeChanged(ExchangeRateType newType) async { + void onRateTypeChanged(ExchangeRateType newType) { _receiveFocusNode.unfocus(); _sendFocusNode.unfocus(); - await showUpdatingExchangeRate( - whileFuture: _onRateTypeChangedFuture(newType), - ); - } - - Future _onRateTypeChangedFuture(ExchangeRateType newType) async { - ref.read(exchangeFormStateProvider).exchangeRateType = newType; - - final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "-"; - final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "-"; - - ref.read(exchangeFormStateProvider).reversed = false; - - if (!(toTicker == "-" || fromTicker == "-")) { - // final available = await ExchangeDataLoadingService.instance.isar.pairs - // .where() - // .exchangeNameEqualTo( - // ref.read(currentExchangeNameStateProvider.state).state) - // .filter() - // .fromEqualTo(fromTicker) - // .and() - // .toEqualTo(toTicker) - // .findAll(); - await ref.read(exchangeFormStateProvider).refresh(); - - // if (available.isNotEmpty) { - // final availableCurrencies = await ExchangeDataLoadingService - // .instance.isar.currencies - // .where() - // .exchangeNameEqualTo( - // ref.read(currentExchangeNameStateProvider.state).state) - // .filter() - // .tickerEqualTo(fromTicker) - // .or() - // .tickerEqualTo(toTicker) - // .findAll(); - // - // 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).receiveAmount = newFromAmount; - // if (newFromAmount == null) { - // _receiveController.text = ""; - // } - // - // await ref - // .read(exchangeFormStateProvider) - // .updateReceivingCurrency(to, false); - // await ref - // .read(exchangeFormStateProvider) - // .updateSendCurrency(from, true); - // - // _receiveController.text = - // ref.read(exchangeFormStateProvider).toAmountString.isEmpty - // ? "-" - // : ref.read(exchangeFormStateProvider).toAmountString; - // if (mounted) { - // Navigator.of(context, rootNavigator: isDesktop).pop(); - // } - // return; - // } - // } - } + ref.read(efRateTypeProvider.notifier).state = newType; + update(); } void onExchangePressed() async { - final rateType = ref.read(exchangeFormStateProvider).exchangeRateType; - final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; - final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; - final sendAmount = ref.read(exchangeFormStateProvider).sendAmount!; - final estimate = ref.read(exchangeFormStateProvider).estimates.firstWhere( - (e) => - e.exchangeProvider == - ref.read(exchangeFormStateProvider).providerName, - ); + final rateType = ref.read(efRateTypeProvider); + final fromTicker = ref.read(efCurrencyPairProvider).send?.ticker ?? ""; + final toTicker = ref.read(efCurrencyPairProvider).receive?.ticker ?? ""; + final estimate = ref.read(efEstimateProvider)!; + final sendAmount = ref.read(efSendAmountProvider)!; if (rateType == ExchangeRateType.fixed && toTicker.toUpperCase() == "WOW") { await showDialog( @@ -430,10 +391,16 @@ class _ExchangeFormState extends ConsumerState { String rate; + final amountToSend = + estimate.reversed ? estimate.estimatedAmount : sendAmount; + final amountToReceive = estimate.reversed + ? ref.read(efReceiveAmountProvider)! + : estimate.estimatedAmount; + switch (rateType) { case ExchangeRateType.estimated: rate = - "1 ${fromTicker.toUpperCase()} ~${(estimate.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + "1 ${fromTicker.toUpperCase()} ~${(amountToReceive / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; break; case ExchangeRateType.fixed: bool? shouldCancel; @@ -545,7 +512,9 @@ class _ExchangeFormState extends ConsumerState { return; } rate = - "1 ${fromTicker.toUpperCase()} ~${ref.read(exchangeFormStateProvider).rate!.toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + "1 ${fromTicker.toUpperCase()} ~${(amountToReceive / amountToSend).toDecimal( + scaleOnInfinitePrecision: 12, + ).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; break; } @@ -553,10 +522,8 @@ class _ExchangeFormState extends ConsumerState { sendTicker: fromTicker.toUpperCase(), receiveTicker: toTicker.toUpperCase(), rateInfo: rate, - sendAmount: estimate.reversed ? estimate.estimatedAmount : sendAmount, - receiveAmount: estimate.reversed - ? ref.read(exchangeFormStateProvider).receiveAmount! - : estimate.estimatedAmount, + sendAmount: amountToSend, + receiveAmount: amountToReceive, rateType: rateType, estimate: estimate, reversed: estimate.reversed, @@ -626,8 +593,8 @@ class _ExchangeFormState extends ConsumerState { } String? ticker = isSend - ? ref.read(exchangeFormStateProvider).fromTicker - : ref.read(exchangeFormStateProvider).toTicker; + ? ref.read(efCurrencyPairProvider).send?.ticker + : ref.read(efCurrencyPairProvider).receive?.ticker; if (ticker == null) { return false; @@ -636,6 +603,85 @@ class _ExchangeFormState extends ConsumerState { return coin.ticker.toUpperCase() == ticker.toUpperCase(); } + Future update() async { + ref.read(efRefreshingProvider.notifier).state = true; + for (final exchange in exchanges) { + ref.read(efEstimatesListProvider(exchange.name).notifier).state = null; + } + + final reversed = ref.read(efReversedProvider); + final amount = reversed + ? ref.read(efReceiveAmountProvider) + : ref.read(efSendAmountProvider); + + if (amount == null || amount <= Decimal.zero) { + ref.read(efRefreshingProvider.notifier).state = false; + return; + } + + final rateType = ref.read(efRateTypeProvider); + final pair = ref.read(efCurrencyPairProvider); + + for (final exchange in exchanges) { + final sendCurrency = pair.send?.forExchange(exchange.name); + final receiveCurrency = pair.receive?.forExchange(exchange.name); + + if (sendCurrency != null && receiveCurrency != null) { + final rangeResponse = await exchange.getRange( + reversed ? receiveCurrency.ticker : sendCurrency.ticker, + reversed ? sendCurrency.ticker : receiveCurrency.ticker, + rateType == ExchangeRateType.fixed, + ); + + if (rangeResponse.value == null) { + Logging.instance.log( + "Tried to $runtimeType.update Range for:" + " $exchange where response: $rangeResponse", + level: LogLevel.Info, + ); + } + + final estimateResponse = await exchange.getEstimates( + sendCurrency.ticker, + receiveCurrency.ticker, + amount, + rateType == ExchangeRateType.fixed, + reversed, + ); + + if (estimateResponse.value == null) { + Logging.instance.log( + "Tried to $runtimeType._fetchEstimateAndRange Estimate for:" + " $exchange where response: $estimateResponse", + level: LogLevel.Info, + ); + } + + if (estimateResponse.value != null && rangeResponse.value != null) { + ref.read(efEstimatesListProvider(exchange.name).notifier).state = + Tuple2(estimateResponse.value!, rangeResponse.value!); + } + } + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(efRefreshingProvider.notifier).state = false; + }); + } + + void updateSend(Estimate? estimate) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(efSendAmountProvider.notifier).state = estimate?.estimatedAmount; + }); + } + + void updateReceive(Estimate? estimate) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(efReceiveAmountProvider.notifier).state = + estimate?.estimatedAmount; + }); + } + @override void initState() { _sendController = TextEditingController(); @@ -645,9 +691,32 @@ class _ExchangeFormState extends ConsumerState { coin = widget.coin; walletInitiated = walletId != null && coin != null; + _sendFocusNode.addListener(() { + if (_sendFocusNode.hasFocus) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(efReversedProvider.notifier).state = false; + }); + } + }); + _receiveFocusNode.addListener(() { + if (_receiveFocusNode.hasFocus) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(efReversedProvider.notifier).state = true; + }); + } + }); + if (walletInitiated) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - ref.read(exchangeFormStateProvider).reset(shouldNotifyListeners: true); + ref.read(efSendAmountProvider.notifier).state = null; + ref.read(efReceiveAmountProvider.notifier).state = null; + ref.read(efReversedProvider.notifier).state = false; + ref.read(efWarningProvider.notifier).state = ""; + ref.read(efRefreshingProvider.notifier).state = false; + ref.read(efCurrencyPairProvider).setSend(null, notifyListeners: true); + ref + .read(efCurrencyPairProvider) + .setReceive(null, notifyListeners: true); ExchangeDataLoadingService.instance .getAggregateCurrency( widget.contract == null ? coin!.ticker : widget.contract!.symbol, @@ -656,17 +725,17 @@ class _ExchangeFormState extends ConsumerState { ) .then((value) { if (value != null) { - ref.read(exchangeFormStateProvider).updateSendCurrency(value, true); + ref.read(efCurrencyPairProvider).setSend( + value, + notifyListeners: true, + ); } }); }); } else { - _sendController.text = - ref.read(exchangeFormStateProvider).fromAmountString; - _receiveController.text = - ref.read(exchangeFormStateProvider).toAmountString; WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - ref.read(exchangeFormStateProvider).refresh(); + _sendController.text = ref.read(efSendAmountStringProvider); + _receiveController.text = ref.read(efReceiveAmountStringProvider); }); } @@ -684,37 +753,42 @@ class _ExchangeFormState extends ConsumerState { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - final rateType = ref.watch( - exchangeFormStateProvider.select((value) => value.exchangeRateType)); + final rateType = ref.watch(efRateTypeProvider); final isEstimated = rateType == ExchangeRateType.estimated; - ref.listen( - exchangeFormStateProvider.select((value) => value.toAmountString), - (previous, String next) { + ref.listen(efReceiveAmountStringProvider, (previous, String next) { if (!_receiveFocusNode.hasFocus) { _receiveController.text = isEstimated && next.isEmpty ? "-" : next; - if (_swapLock) { - _sendController.text = - ref.read(exchangeFormStateProvider).fromAmountString; - } + // if (_swapLock) { + _sendController.text = ref.read(efSendAmountStringProvider); + // } } }); - ref.listen( - exchangeFormStateProvider.select((value) => value.fromAmountString), - (previous, String next) { + ref.listen(efSendAmountStringProvider, (previous, String next) { if (!_sendFocusNode.hasFocus) { _sendController.text = next; - if (_swapLock) { - _receiveController.text = isEstimated - ? ref.read(exchangeFormStateProvider).toAmountString.isEmpty - ? "-" - : ref.read(exchangeFormStateProvider).toAmountString - : ref.read(exchangeFormStateProvider).toAmountString; - } + // if (_swapLock) { + _receiveController.text = + isEstimated && ref.read(efReceiveAmountStringProvider).isEmpty + ? "-" + : ref.read(efReceiveAmountStringProvider); + // } } }); + ref.listen(efEstimateProvider.notifier, (previous, next) { + if (ref.read(efReversedProvider)) { + updateSend((next as StateController).state); + } else { + updateReceive((next as StateController).state); + } + }); + + ref.listen(efCurrencyPairProvider, (previous, next) { + update(); + }); + return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -729,8 +803,9 @@ class _ExchangeFormState extends ConsumerState { height: isDesktop ? 10 : 4, ), ExchangeTextField( - key: Key( - "exchangeTextFieldKeyFor_${Theme.of(context).extension()!.themeType.name}"), + key: Key("exchangeTextFieldKeyFor_" + "${Theme.of(context).extension()!.themeType.name}" + "${ref.watch(efCurrencyPairProvider.select((value) => value.send?.ticker))}"), controller: _sendController, focusNode: _sendFocusNode, textStyle: STextStyles.smallMed14(context).copyWith( @@ -749,8 +824,8 @@ class _ExchangeFormState extends ConsumerState { onChanged: sendFieldOnChanged, onButtonTap: selectSendCurrency, isWalletCoin: isWalletCoin(coin, true), - currency: ref.watch( - exchangeFormStateProvider.select((value) => value.sendCurrency)), + currency: + ref.watch(efCurrencyPairProvider.select((value) => value.send)), ), SizedBox( height: isDesktop ? 10 : 4, @@ -758,15 +833,10 @@ class _ExchangeFormState extends ConsumerState { SizedBox( height: isDesktop ? 10 : 4, ), - if (ref - .watch( - exchangeFormStateProvider.select((value) => value.warning)) - .isNotEmpty && - !ref.watch( - exchangeFormStateProvider.select((value) => value.reversed))) + if (ref.watch(efWarningProvider).isNotEmpty && + !ref.watch(efReversedProvider)) Text( - ref.watch( - exchangeFormStateProvider.select((value) => value.warning)), + ref.watch(efWarningProvider), style: STextStyles.errorSmall(context), ), Row( @@ -830,8 +900,7 @@ class _ExchangeFormState extends ConsumerState { background: Theme.of(context).extension()!.textFieldDefaultBG, onTap: () { - if (!(ref.read(exchangeFormStateProvider).exchangeRateType == - ExchangeRateType.estimated) && + if (!(ref.read(efRateTypeProvider) == ExchangeRateType.estimated) && _receiveController.text == "-") { _receiveController.text = ""; } @@ -839,22 +908,16 @@ class _ExchangeFormState extends ConsumerState { onChanged: receiveFieldOnChanged, onButtonTap: selectReceiveCurrency, isWalletCoin: isWalletCoin(coin, true), - currency: ref.watch(exchangeFormStateProvider - .select((value) => value.receiveCurrency)), + currency: ref + .watch(efCurrencyPairProvider.select((value) => value.receive)), readOnly: (rateType) == ExchangeRateType.estimated && - ref.watch(exchangeFormStateProvider - .select((value) => value.exchange.name)) == + ref.watch(efExchangeProvider).name == ChangeNowExchange.exchangeName, ), - if (ref - .watch( - exchangeFormStateProvider.select((value) => value.warning)) - .isNotEmpty && - ref.watch( - exchangeFormStateProvider.select((value) => value.reversed))) + if (ref.watch(efWarningProvider).isNotEmpty && + ref.watch(efReversedProvider)) Text( - ref.watch( - exchangeFormStateProvider.select((value) => value.warning)), + ref.watch(efWarningProvider), style: STextStyles.errorSmall(context), ), SizedBox( @@ -867,27 +930,28 @@ class _ExchangeFormState extends ConsumerState { onChanged: onRateTypeChanged, ), ), - // these reads should be watch - if (ref.watch(exchangeFormStateProvider).sendAmount != null && - ref.watch(exchangeFormStateProvider).sendAmount != Decimal.zero) - SizedBox( - height: isDesktop ? 20 : 12, - ), - // these reads should be watch - if (ref.watch(exchangeFormStateProvider).sendAmount != null && - ref.watch(exchangeFormStateProvider).sendAmount != Decimal.zero) - ExchangeProviderOptions( + // AnimatedSize( + // duration: const Duration(milliseconds: 250), + // curve: Curves.easeInOutCirc, + // child: ref.watch(efSendAmountProvider).sendAmount != null && + // ref.watch(exchangeFormStateProvider).sendAmount != Decimal.zero + // ? + Padding( + padding: EdgeInsets.only(top: isDesktop ? 20 : 12), + child: ExchangeProviderOptions( fixedRate: rateType == ExchangeRateType.fixed, - reversed: ref.watch( - exchangeFormStateProvider.select((value) => value.reversed)), + reversed: ref.watch(efReversedProvider), ), + // : const SizedBox( + // height: 0, + // ), + ), SizedBox( height: isDesktop ? 20 : 12, ), PrimaryButton( buttonHeight: isDesktop ? ButtonHeight.l : null, - enabled: ref.watch( - exchangeFormStateProvider.select((value) => value.canExchange)), + enabled: ref.watch(efCanExchangeProvider), onPressed: onExchangePressed, label: "Swap", ) 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 a32df6e74..315228e13 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 @@ -124,9 +124,8 @@ class _Step2ViewState extends ConsumerState { @override Widget build(BuildContext context) { - final supportsRefund = ref.watch( - exchangeFormStateProvider.select((value) => value.exchange.name)) != - MajesticBankExchange.exchangeName; + final supportsRefund = + ref.watch(efExchangeProvider).name != MajesticBankExchange.exchangeName; return Background( child: Scaffold( 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 a7c954c59..1079621e6 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 @@ -52,9 +52,8 @@ class _Step3ViewState extends ConsumerState { @override Widget build(BuildContext context) { - final supportsRefund = ref.watch( - exchangeFormStateProvider.select((value) => value.exchange.name)) != - MajesticBankExchange.exchangeName; + final supportsRefund = + ref.watch(efExchangeProvider).name != MajesticBankExchange.exchangeName; return Background( child: Scaffold( @@ -254,8 +253,7 @@ class _Step3ViewState extends ConsumerState { final ExchangeResponse response = await ref - .read(exchangeFormStateProvider) - .exchange + .read(efExchangeProvider) .createTrade( from: model.sendTicker, to: model.receiveTicker, 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 b7bdf9e46..094e343d8 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 @@ -70,10 +70,8 @@ class _Step4ViewState extends ConsumerState { } Future _updateStatus() async { - final statusResponse = await ref - .read(exchangeFormStateProvider) - .exchange - .updateTrade(model.trade!); + final statusResponse = + await ref.read(efExchangeProvider).updateTrade(model.trade!); String status = "Waiting"; if (statusResponse.value != null) { status = statusResponse.value!.status; diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart index 61cf2fe94..a17764b91 100644 --- a/lib/pages/exchange_view/exchange_view.dart +++ b/lib/pages/exchange_view/exchange_view.dart @@ -37,7 +37,8 @@ class _ExchangeViewState extends ConsumerState { ExchangeDataLoadingService.instance.onLoadingComplete = () { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( - ref.read(exchangeFormStateProvider), + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), ); setState(() { _initialCachePopulationUnderway = false; @@ -53,7 +54,8 @@ class _ExchangeViewState extends ConsumerState { ExchangeDataLoadingService.instance.onLoadingComplete = () { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( - ref.read(exchangeFormStateProvider), + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), ); setState(() { _initialCachePopulationUnderway = false; diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart index 0d0f453ea..ff31907a0 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart @@ -2,12 +2,10 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart'; import 'package:stackwallet/providers/global/locale_provider.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; -import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -42,138 +40,121 @@ class _ExchangeMultiProviderOptionState extends ConsumerState { @override Widget build(BuildContext context) { - final sendCurrency = ref - .watch(exchangeFormStateProvider.select((value) => value.sendCurrency)); - final receivingCurrency = ref.watch( - exchangeFormStateProvider.select((value) => value.receiveCurrency)); - final fromAmount = ref - .watch(exchangeFormStateProvider.select((value) => value.sendAmount)); - final toAmount = ref.watch( - exchangeFormStateProvider.select((value) => value.receiveAmount)); + final sendCurrency = + ref.watch(efCurrencyPairProvider.select((value) => value.send)); + final receivingCurrency = + ref.watch(efCurrencyPairProvider.select((value) => value.receive)); + final reversed = ref.watch(efReversedProvider); + final amount = reversed + ? ref.watch(efReceiveAmountProvider) + : ref.watch(efSendAmountProvider); + + final estimates = ref.watch(efEstimatesListProvider(widget.exchange.name)); return AnimatedSize( duration: const Duration(milliseconds: 500), curve: Curves.easeInOutCubicEmphasized, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (sendCurrency != null && + child: Builder( + builder: (_) { + if (ref.watch(efRefreshingProvider)) { + // show loading + return _ProviderOption( + exchange: widget.exchange, + estimate: null, + rateString: "", + loadingString: true, + ); + } else if (sendCurrency != null && receivingCurrency != null && - toAmount != null && - toAmount > Decimal.zero && - fromAmount != null && - fromAmount > Decimal.zero) - FutureBuilder( - future: widget.exchange.getEstimates( - sendCurrency.ticker, - receivingCurrency.ticker, - widget.reversed ? toAmount : fromAmount, - widget.fixedRate, - widget.reversed, - ), - builder: (context, - AsyncSnapshot>> snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - final estimates = snapshot.data?.value; + amount != null && + amount > Decimal.zero) { + if (estimates != null && estimates.item1.isNotEmpty) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (int i = 0; i < estimates.item1.length; i++) + Builder( + builder: (context) { + final e = estimates.item1[i]; - if (estimates != null && estimates.isNotEmpty) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (int i = 0; i < estimates.length; i++) - Builder( - builder: (context) { - final e = estimates[i]; - int decimals; - try { - decimals = coinFromTickerCaseInsensitive( - receivingCurrency.ticker) - .decimals; - } catch (_) { - decimals = 8; // some reasonable alternative - } - Amount rate; - if (e.reversed) { - rate = (toAmount / e.estimatedAmount) - .toDecimal(scaleOnInfinitePrecision: 18) - .toAmount(fractionDigits: decimals); - } else { - rate = (e.estimatedAmount / fromAmount) - .toDecimal(scaleOnInfinitePrecision: 18) - .toAmount(fractionDigits: decimals); - } + int decimals; + try { + decimals = coinFromTickerCaseInsensitive( + receivingCurrency.ticker) + .decimals; + } catch (_) { + decimals = 8; // some reasonable alternative + } + Amount rate; + if (e.reversed) { + rate = (amount / e.estimatedAmount) + .toDecimal(scaleOnInfinitePrecision: 18) + .toAmount(fractionDigits: decimals); + } else { + rate = (e.estimatedAmount / amount) + .toDecimal(scaleOnInfinitePrecision: 18) + .toAmount(fractionDigits: decimals); + } - final rateString = - "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${receivingCurrency.ticker.toUpperCase()}"; - - return ConditionalParent( - condition: i > 0, - builder: (child) => Column( - mainAxisSize: MainAxisSize.min, - children: [ - isDesktop - ? Container( - height: 1, - color: Theme.of(context) - .extension()! - .background, - ) - : const SizedBox( - height: 16, - ), - child, - ], - ), - child: _ProviderOption( - key: Key(widget.exchange.name + - e.exchangeProvider), - exchange: widget.exchange, - providerName: e.exchangeProvider, - rateString: rateString, - kycRating: e.kycRating, - ), - ); - }, + final rateString = + "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), ), - ], - ); - } else if (snapshot.data?.exception - is PairUnavailableException) { - return _ProviderOption( - exchange: widget.exchange, - providerName: widget.exchange.name, - rateString: "Unsupported pair", - ); - } else { - Logging.instance.log( - "$runtimeType failed to fetch rate for ${widget.exchange.name}: ${snapshot.data}", - level: LogLevel.Warning, - ); + )} ${receivingCurrency.ticker.toUpperCase()}"; - return _ProviderOption( - exchange: widget.exchange, - providerName: widget.exchange.name, - rateString: "Failed to fetch rate", - ); - } - } else { - // show loading - return _ProviderOption( - exchange: widget.exchange, - providerName: widget.exchange.name, - rateString: "", - loadingString: true, - ); - } - }, - ), - ], + return ConditionalParent( + condition: i > 0, + builder: (child) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + isDesktop + ? Container( + height: 1, + color: Theme.of(context) + .extension()! + .background, + ) + : const SizedBox( + height: 16, + ), + child, + ], + ), + child: _ProviderOption( + key: Key(widget.exchange.name + e.exchangeProvider), + exchange: widget.exchange, + estimate: e, + rateString: rateString, + kycRating: e.kycRating, + ), + ); + }, + ), + ], + ); + } else { + Logging.instance.log( + "$runtimeType failed to fetch rate for ${widget.exchange.name}: $estimates", + level: LogLevel.Warning, + ); + + return _ProviderOption( + exchange: widget.exchange, + estimate: null, + rateString: "Failed to fetch rate", + ); + } + } else { + // show n/a + return _ProviderOption( + exchange: widget.exchange, + estimate: null, + rateString: "n/a", + ); + } + }, ), ); } @@ -183,14 +164,14 @@ class _ProviderOption extends ConsumerStatefulWidget { const _ProviderOption({ Key? key, required this.exchange, - required this.providerName, + required this.estimate, required this.rateString, this.kycRating, this.loadingString = false, }) : super(key: key); final Exchange exchange; - final String providerName; + final Estimate? estimate; final String rateString; final String? kycRating; final bool loadingString; @@ -206,25 +187,27 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { @override void initState() { - _id = "${widget.exchange.name} (${widget.providerName})"; + _id = + "${widget.exchange.name} (${widget.estimate?.exchangeProvider ?? widget.exchange.name})"; super.initState(); } @override Widget build(BuildContext context) { - bool selected = ref.watch(exchangeFormStateProvider - .select((value) => value.combinedExchangeId)) == - _id; - String groupValue = ref.watch( - exchangeFormStateProvider.select((value) => value.combinedExchangeId)); + String groupValue = ref.watch(currentCombinedExchangeIdProvider); - if (ref.watch( - exchangeFormStateProvider.select((value) => value.exchange.name)) == - widget.providerName) { - selected = true; + if (ref.watch(efExchangeProvider).name == + (widget.estimate?.exchangeProvider ?? widget.exchange.name)) { groupValue = _id; } + bool selected = groupValue == _id; + + print("========================================================"); + print("gourpValue: $groupValue"); + print("_id: $_id"); + print("========================================================"); + return ConditionalParent( condition: isDesktop, builder: (child) => MouseRegion( @@ -233,15 +216,20 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { ), child: GestureDetector( onTap: () { - if (!selected) { - ref.read(exchangeFormStateProvider).updateExchange( - exchange: widget.exchange, - shouldUpdateData: true, - shouldNotifyListeners: true, - providerName: widget.providerName, - shouldAwait: false, - ); - } + ref.read(efExchangeProvider.notifier).state = widget.exchange; + ref.read(efExchangeProviderNameProvider.notifier).state = + widget.estimate?.exchangeProvider ?? widget.exchange.name; + + // if (!selected) { + // ref.read(exchangeFormStateProvider).updateExchange( + // exchange: widget.exchange, + // shouldUpdateData: false, + // shouldNotifyListeners: false, + // providerName: + // widget.estimate?.exchangeProvider ?? widget.exchange.name, + // shouldAwait: false, + // ); + // } }, child: Container( color: Colors.transparent, @@ -263,15 +251,26 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { value: _id, groupValue: groupValue, onChanged: (_) { - if (!selected) { - ref.read(exchangeFormStateProvider).updateExchange( - exchange: widget.exchange, - shouldUpdateData: false, - shouldNotifyListeners: true, - providerName: widget.providerName, - shouldAwait: false, - ); - } + ref.read(efExchangeProvider.notifier).state = + widget.exchange; + ref + .read(efExchangeProviderNameProvider.notifier) + .state = + widget.estimate?.exchangeProvider ?? + widget.exchange.name; + // if (!selected) { + // + // + // ref.read(exchangeFormStateProvider).updateExchange( + // exchange: widget.exchange, + // shouldUpdateData: false, + // shouldNotifyListeners: false, + // providerName: + // widget.estimate?.exchangeProvider ?? + // widget.exchange.name, + // shouldAwait: false, + // ); + // } }, ), ), @@ -303,7 +302,8 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - widget.providerName, + widget.estimate?.exchangeProvider ?? + widget.exchange.name, style: STextStyles.titleBold12(context).copyWith( color: Theme.of(context) .extension()! diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart index 4d3b96f90..a145d36a4 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -49,16 +49,10 @@ class _ExchangeProviderOptionsState @override Widget build(BuildContext context) { - final sendCurrency = ref.watch( - exchangeFormStateProvider.select( - (value) => value.sendCurrency, - ), - ); - final receivingCurrency = ref.watch( - exchangeFormStateProvider.select( - (value) => value.receiveCurrency, - ), - ); + final sendCurrency = + ref.watch(efCurrencyPairProvider.select((value) => value.send)); + final receivingCurrency = + ref.watch(efCurrencyPairProvider.select((value) => value.receive)); final showChangeNow = exchangeSupported( exchangeName: ChangeNowExchange.exchangeName, diff --git a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart index f85c0cfcd..b83883509 100644 --- a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart +++ b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart @@ -29,9 +29,7 @@ class RateTypeToggle extends ConsumerWidget { onChanged?.call(ExchangeRateType.estimated); } }, - isOn: ref.watch(exchangeFormStateProvider - .select((value) => value.exchangeRateType)) == - ExchangeRateType.fixed, + isOn: ref.watch(efRateTypeProvider) == ExchangeRateType.fixed, onColor: isDesktop ? Theme.of(context) .extension()! diff --git a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart index 3c726b702..7085bd8aa 100644 --- a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart +++ b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart @@ -54,7 +54,8 @@ class _WalletInitiatedExchangeViewState ExchangeDataLoadingService.instance.onLoadingComplete = () { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( - ref.read(exchangeFormStateProvider), + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), ); setState(() { _initialCachePopulationUnderway = false; @@ -70,7 +71,8 @@ class _WalletInitiatedExchangeViewState ExchangeDataLoadingService.instance.onLoadingComplete = () { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( - ref.read(exchangeFormStateProvider), + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), ); setState(() { _initialCachePopulationUnderway = false; diff --git a/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart index 17cd1c778..36006b7c1 100644 --- a/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart +++ b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/exchange_view/exchange_form.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart'; import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; @@ -14,8 +15,6 @@ import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -import 'desktop_all_trades_view.dart'; - class DesktopExchangeView extends ConsumerStatefulWidget { const DesktopExchangeView({Key? key}) : super(key: key); @@ -38,7 +37,8 @@ class _DesktopExchangeViewState extends ConsumerState { ExchangeDataLoadingService.instance.onLoadingComplete = () { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( - ref.read(exchangeFormStateProvider), + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), ); setState(() { _initialCachePopulationUnderway = false; @@ -54,7 +54,8 @@ class _DesktopExchangeViewState extends ConsumerState { ExchangeDataLoadingService.instance.onLoadingComplete = () { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( - ref.read(exchangeFormStateProvider), + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), ); setState(() { _initialCachePopulationUnderway = false; diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart index 9976f77ff..e99c63821 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart @@ -84,8 +84,7 @@ class _StepScaffoldState extends ConsumerState { ); final ExchangeResponse response = await ref - .read(exchangeFormStateProvider) - .exchange + .read(efExchangeProvider) .createTrade( from: ref.read(desktopExchangeModelProvider)!.sendTicker, to: ref.read(desktopExchangeModelProvider)!.receiveTicker, diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart index 16c3b8116..dffe71bb9 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart @@ -38,8 +38,7 @@ class DesktopStep1 extends ConsumerWidget { children: [ DesktopStepItem( label: "Swap", - value: ref.watch(exchangeFormStateProvider - .select((value) => value.exchange.name)), + value: ref.watch(efExchangeProviderNameProvider), ), Container( height: 1, diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart index ef72553fa..a5d3fba43 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart @@ -36,8 +36,7 @@ class _DesktopStep3State extends ConsumerState { children: [ DesktopStepItem( label: "Swap", - value: ref.watch(exchangeFormStateProvider - .select((value) => value.exchange.name)), + value: ref.watch(efExchangeProviderNameProvider), ), Container( height: 1, diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart index 2654c2cea..bbc3fd30c 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart @@ -46,7 +46,7 @@ class _DesktopStep4State extends ConsumerState { } final statusResponse = - await ref.read(exchangeFormStateProvider).exchange.updateTrade(trade); + await ref.read(efExchangeProvider).updateTrade(trade); String status = "Waiting"; if (statusResponse.value != null) { status = statusResponse.value!.status; diff --git a/lib/providers/exchange/exchange_form_state_provider.dart b/lib/providers/exchange/exchange_form_state_provider.dart index cb7d32bfe..0eec46c92 100644 --- a/lib/providers/exchange/exchange_form_state_provider.dart +++ b/lib/providers/exchange/exchange_form_state_provider.dart @@ -1,5 +1,116 @@ +import 'package:decimal/decimal.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/exchange_form_state.dart'; +import 'package:stackwallet/models/exchange/active_pair.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; +import 'package:tuple/tuple.dart'; -final exchangeFormStateProvider = - ChangeNotifierProvider((ref) => ExchangeFormState()); +final efEstimatesListProvider = + StateProvider.family, Range>?, String>( + (ref, exchangeName) => null); + +final efRateTypeProvider = + StateProvider((ref) => ExchangeRateType.estimated); + +final efExchangeProvider = + StateProvider((ref) => Exchange.defaultExchange); +final efExchangeProviderNameProvider = + StateProvider((ref) => Exchange.defaultExchange.name); + +final currentCombinedExchangeIdProvider = Provider((ref) { + return "${ref.watch(efExchangeProvider).name}" + " (${ref.watch(efExchangeProviderNameProvider)})"; +}); + +final efSendAmountProvider = StateProvider((ref) => null); +final efReceiveAmountProvider = StateProvider((ref) => null); + +final efSendAmountStringProvider = StateProvider((ref) { + return ref.watch(efSendAmountProvider)?.toStringAsFixed(8) ?? ""; +}); +final efReceiveAmountStringProvider = StateProvider((ref) { + return ref.watch(efReceiveAmountProvider)?.toStringAsFixed(8) ?? ""; +}); + +final efReversedProvider = StateProvider((ref) => false); + +final efCurrencyPairProvider = ChangeNotifierProvider( + (ref) => ActivePair(), +); + +final efRangeProvider = StateProvider((ref) { + final exchange = ref.watch(efExchangeProvider); + return ref.watch(efEstimatesListProvider(exchange.name))?.item2; +}); + +final efEstimateProvider = StateProvider((ref) { + final exchange = ref.watch(efExchangeProvider); + final provider = ref.watch(efExchangeProviderNameProvider); + final reversed = ref.watch(efReversedProvider); + final fixedRate = ref.watch(efRateTypeProvider) == ExchangeRateType.fixed; + + final matches = + ref.watch(efEstimatesListProvider(exchange.name))?.item1.where((e) { + return e.exchangeProvider == provider && + e.fixedRate == fixedRate && + e.reversed == reversed; + }); + + Estimate? result; + + if (matches != null && matches.isNotEmpty) { + result = matches.first; + } else { + result = null; + } + + return result; +}); + +final efCanExchangeProvider = StateProvider((ref) { + final Estimate? estimate = ref.watch(efEstimateProvider); + // final Decimal? amount = ref.watch(efReversedProvider) + // ? ref.watch(efSendAmountProvider) + // : ref.watch(efReceiveAmountProvider); + + return estimate != null; +}); + +final efRefreshingProvider = StateProvider((ref) => false); + +final efWarningProvider = StateProvider((ref) { + // if (ref.watch(efReversedProvider)) { + // final _receiveCurrency = + // ref.watch(efCurrencyPairProvider.select((value) => value.receive)); + // final _receiveAmount = ref.watch(efReceiveAmountProvider); + // if (_receiveCurrency != null && _receiveAmount != null) { + // final range = ref.watch(efRangeProvider); + // if (range?.min != null && + // _receiveAmount < range!.min! && + // _receiveAmount > Decimal.zero) { + // return "Min receive amount ${range.min!.toString()} ${_receiveCurrency.ticker.toUpperCase()}"; + // } else if (range?.max != null && + // _receiveAmount > ref.watch(efRangeProvider)!.max!) { + // return "Max receive amount $range!.max!.toString()} ${_receiveCurrency.ticker.toUpperCase()}"; + // } + // } + // } else { + // final _sendCurrency = + // ref.watch(efCurrencyPairProvider.select((value) => value.send)); + // final _sendAmount = ref.watch(efSendAmountProvider); + // if (_sendCurrency != null && _sendAmount != null) { + // final range = ref.watch(efRangeProvider); + // if (range?.min != null && + // _sendAmount < range!.min! && + // _sendAmount > Decimal.zero) { + // return "Min send amount ${range.min!.toString()} ${_sendCurrency.ticker.toUpperCase()}"; + // } else if (range?.max != null && _sendAmount > range!.max!) { + // return "Max send amount ${range.max!.toString()} ${_sendCurrency.ticker.toUpperCase()}"; + // } + // } + // } + + return ""; +}); diff --git a/lib/services/exchange/exchange_data_loading_service.dart b/lib/services/exchange/exchange_data_loading_service.dart index e4b842c94..c0aa2e2b0 100644 --- a/lib/services/exchange/exchange_data_loading_service.dart +++ b/lib/services/exchange/exchange_data_loading_service.dart @@ -1,8 +1,8 @@ import 'package:flutter/foundation.dart'; import 'package:isar/isar.dart'; import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/models/exchange/active_pair.dart'; import 'package:stackwallet/models/exchange/aggregate_currency.dart'; -import 'package:stackwallet/models/exchange/exchange_form_state.dart'; import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; @@ -57,20 +57,29 @@ class ExchangeDataLoadingService { ); } - Future setCurrenciesIfEmpty(ExchangeFormState state) async { - if (state.sendCurrency == null && state.receiveCurrency == null) { + Future setCurrenciesIfEmpty( + ActivePair? pair, + ExchangeRateType rateType, + ) async { + if (pair?.send == null && pair?.receive == null) { if (await isar.currencies.count() > 0) { - final sendCurrency = await getAggregateCurrency( - "BTC", - state.exchangeRateType, - null, + pair?.setSend( + await getAggregateCurrency( + "BTC", + rateType, + null, + ), + notifyListeners: false, ); - final receiveCurrency = await getAggregateCurrency( - "XMR", - state.exchangeRateType, - null, + + pair?.setReceive( + await getAggregateCurrency( + "XMR", + rateType, + null, + ), + notifyListeners: false, ); - state.setCurrencies(sendCurrency, receiveCurrency); } } } From 51acd366b4da075ce856a0fd42c8f3ed8d6fec00 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 3 May 2023 14:40:39 -0600 Subject: [PATCH 24/65] fix: swap currency updating bug and some updating optimizations --- lib/pages/exchange_view/exchange_form.dart | 38 +++++++++++++--------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index e95151e9f..4ca1ba5fc 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -614,13 +614,15 @@ class _ExchangeFormState extends ConsumerState { ? ref.read(efReceiveAmountProvider) : ref.read(efSendAmountProvider); - if (amount == null || amount <= Decimal.zero) { + final pair = ref.read(efCurrencyPairProvider); + if (amount == null || + amount <= Decimal.zero || + pair.send == null || + pair.receive == null) { ref.read(efRefreshingProvider.notifier).state = false; return; } - final rateType = ref.read(efRateTypeProvider); - final pair = ref.read(efCurrencyPairProvider); for (final exchange in exchanges) { final sendCurrency = pair.send?.forExchange(exchange.name); @@ -657,7 +659,9 @@ class _ExchangeFormState extends ConsumerState { ); } - if (estimateResponse.value != null && rangeResponse.value != null) { + if (estimateResponse.value != null && + rangeResponse.value != null && + mounted) { ref.read(efEstimatesListProvider(exchange.name).notifier).state = Tuple2(estimateResponse.value!, rangeResponse.value!); } @@ -670,16 +674,12 @@ class _ExchangeFormState extends ConsumerState { } void updateSend(Estimate? estimate) { - WidgetsBinding.instance.addPostFrameCallback((_) { - ref.read(efSendAmountProvider.notifier).state = estimate?.estimatedAmount; - }); + ref.read(efSendAmountProvider.notifier).state = estimate?.estimatedAmount; } void updateReceive(Estimate? estimate) { - WidgetsBinding.instance.addPostFrameCallback((_) { - ref.read(efReceiveAmountProvider.notifier).state = - estimate?.estimatedAmount; - }); + ref.read(efReceiveAmountProvider.notifier).state = + estimate?.estimatedAmount; } @override @@ -699,7 +699,8 @@ class _ExchangeFormState extends ConsumerState { } }); _receiveFocusNode.addListener(() { - if (_receiveFocusNode.hasFocus) { + if (_receiveFocusNode.hasFocus && + ref.read(efExchangeProvider).name != ChangeNowExchange.exchangeName) { WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(efReversedProvider.notifier).state = true; }); @@ -746,6 +747,8 @@ class _ExchangeFormState extends ConsumerState { void dispose() { _receiveController.dispose(); _sendController.dispose(); + _receiveFocusNode.dispose(); + _sendFocusNode.dispose(); super.dispose(); } @@ -778,15 +781,18 @@ class _ExchangeFormState extends ConsumerState { }); ref.listen(efEstimateProvider.notifier, (previous, next) { + final estimate = (next as StateController).state; if (ref.read(efReversedProvider)) { - updateSend((next as StateController).state); + updateSend(estimate); } else { - updateReceive((next as StateController).state); + updateReceive(estimate); } }); ref.listen(efCurrencyPairProvider, (previous, next) { - update(); + if (!_swapLock) { + update(); + } }); return Column( @@ -883,7 +889,7 @@ class _ExchangeFormState extends ConsumerState { ), ), ), - ) + ), ), ], ), From 85a86efe66812077f29b41126e5fa729d98c4d71 Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Wed, 3 May 2023 16:26:44 -0500 Subject: [PATCH 25/65] add libsecret-1-dev to docs and setup script --- docs/building.md | 2 +- scripts/setup.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/building.md b/docs/building.md index 8622c7262..fd7e48a2c 100644 --- a/docs/building.md +++ b/docs/building.md @@ -69,7 +69,7 @@ rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-andro ``` Linux desktop specific dependencies: ``` -sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev meson python3-pip libgirepository1.0-dev valac xsltproc docbook-xsl +sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev meson python3-pip libgirepository1.0-dev valac xsltproc docbook-xsl libsecret-1-dev pip3 install --upgrade meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1 ``` diff --git a/scripts/setup.sh b/scripts/setup.sh index 35ed703a9..cec74fc79 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -37,7 +37,7 @@ sudo apt-get install -y unzip automake build-essential file pkg-config git pytho sudo apt-get install -y debhelper libclang-dev cargo rustc opencl-headers libssl-dev ocl-icd-opencl-dev -sudo apt-get install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless +sudo apt-get install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless libsecret-1-dev sudo apt install -y libc6-dev-i386 From 8790bdcc0c423a06e02b1f1ca57e3a0efb6bbcf1 Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Wed, 3 May 2023 16:43:40 -0500 Subject: [PATCH 26/65] add packages from manual build docs to setup script --- scripts/setup.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/setup.sh b/scripts/setup.sh index cec74fc79..21bf7c201 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -25,22 +25,25 @@ cd stack_wallet export STACK_WALLET=$(pwd) git submodule update --init --recursive -# Create template lib/external_api_keys.dart file if it doesn't already exist +# create template lib/external_api_keys.dart file if it doesn't already exist KEYS="$HOME/projects/stack_wallet/lib/external_api_keys.dart" if ! test -f "$KEYS"; then echo 'prebuild.sh: creating template lib/external_api_keys.dart file' printf 'const kChangeNowApiKey = "";\nconst kSimpleSwapApiKey = "";' > $KEYS fi -#install stack wallet dependencies +# install stack wallet dependencies sudo apt-get install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm sudo apt-get install -y debhelper libclang-dev cargo rustc opencl-headers libssl-dev ocl-icd-opencl-dev -sudo apt-get install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless libsecret-1-dev +sudo apt-get install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless sudo apt install -y libc6-dev-i386 +# linux desktop dependencies +sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev meson python3-pip libgirepository1.0-dev valac xsltproc docbook-xsl + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source "$HOME/.cargo/env" cargo install cargo-ndk From eeda23417c1ee49491017072b2ecf55e7a4bb894 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 3 May 2023 15:45:50 -0600 Subject: [PATCH 27/65] update swap update estimate input interval --- lib/pages/exchange_view/exchange_form.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 4ca1ba5fc..889374958 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -80,7 +80,7 @@ class _ExchangeFormState extends ConsumerState { bool _swapLock = false; // todo: check and adjust this value? - static const _valueCheckInterval = Duration(milliseconds: 300); + static const _valueCheckInterval = Duration(milliseconds: 1500); Future showUpdatingExchangeRate({ required Future whileFuture, From 9a5e80ebffbab88fcbf42e768af914d61f177b04 Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Wed, 3 May 2023 16:46:01 -0500 Subject: [PATCH 28/65] remove libsecret-1-dev from main docs gets installed via scripts/linux/build_secure_storage_deps.sh --- docs/building.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/building.md b/docs/building.md index fd7e48a2c..8622c7262 100644 --- a/docs/building.md +++ b/docs/building.md @@ -69,7 +69,7 @@ rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-andro ``` Linux desktop specific dependencies: ``` -sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev meson python3-pip libgirepository1.0-dev valac xsltproc docbook-xsl libsecret-1-dev +sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev meson python3-pip libgirepository1.0-dev valac xsltproc docbook-xsl pip3 install --upgrade meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1 ``` From 3d9a8c3a3ca2e53ffae9fdc082eebae6ee154646 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 3 May 2023 15:46:51 -0600 Subject: [PATCH 29/65] remove global swap warning message (will be replaced in future with per exchange info) --- lib/pages/exchange_view/exchange_form.dart | 25 +++++++------ .../exchange_form_state_provider.dart | 35 ------------------- 2 files changed, 12 insertions(+), 48 deletions(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 889374958..6e7deaa5a 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -712,7 +712,6 @@ class _ExchangeFormState extends ConsumerState { ref.read(efSendAmountProvider.notifier).state = null; ref.read(efReceiveAmountProvider.notifier).state = null; ref.read(efReversedProvider.notifier).state = false; - ref.read(efWarningProvider.notifier).state = ""; ref.read(efRefreshingProvider.notifier).state = false; ref.read(efCurrencyPairProvider).setSend(null, notifyListeners: true); ref @@ -839,12 +838,12 @@ class _ExchangeFormState extends ConsumerState { SizedBox( height: isDesktop ? 10 : 4, ), - if (ref.watch(efWarningProvider).isNotEmpty && - !ref.watch(efReversedProvider)) - Text( - ref.watch(efWarningProvider), - style: STextStyles.errorSmall(context), - ), + // if (ref.watch(efWarningProvider).isNotEmpty && + // !ref.watch(efReversedProvider)) + // Text( + // ref.watch(efWarningProvider), + // style: STextStyles.errorSmall(context), + // ), Row( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -924,12 +923,12 @@ class _ExchangeFormState extends ConsumerState { ref.watch(efExchangeProvider).name == ChangeNowExchange.exchangeName, ), - if (ref.watch(efWarningProvider).isNotEmpty && - ref.watch(efReversedProvider)) - Text( - ref.watch(efWarningProvider), - style: STextStyles.errorSmall(context), - ), + // if (ref.watch(efWarningProvider).isNotEmpty && + // ref.watch(efReversedProvider)) + // Text( + // ref.watch(efWarningProvider), + // style: STextStyles.errorSmall(context), + // ), SizedBox( height: isDesktop ? 20 : 12, ), diff --git a/lib/providers/exchange/exchange_form_state_provider.dart b/lib/providers/exchange/exchange_form_state_provider.dart index 0eec46c92..7effb6993 100644 --- a/lib/providers/exchange/exchange_form_state_provider.dart +++ b/lib/providers/exchange/exchange_form_state_provider.dart @@ -79,38 +79,3 @@ final efCanExchangeProvider = StateProvider((ref) { }); final efRefreshingProvider = StateProvider((ref) => false); - -final efWarningProvider = StateProvider((ref) { - // if (ref.watch(efReversedProvider)) { - // final _receiveCurrency = - // ref.watch(efCurrencyPairProvider.select((value) => value.receive)); - // final _receiveAmount = ref.watch(efReceiveAmountProvider); - // if (_receiveCurrency != null && _receiveAmount != null) { - // final range = ref.watch(efRangeProvider); - // if (range?.min != null && - // _receiveAmount < range!.min! && - // _receiveAmount > Decimal.zero) { - // return "Min receive amount ${range.min!.toString()} ${_receiveCurrency.ticker.toUpperCase()}"; - // } else if (range?.max != null && - // _receiveAmount > ref.watch(efRangeProvider)!.max!) { - // return "Max receive amount $range!.max!.toString()} ${_receiveCurrency.ticker.toUpperCase()}"; - // } - // } - // } else { - // final _sendCurrency = - // ref.watch(efCurrencyPairProvider.select((value) => value.send)); - // final _sendAmount = ref.watch(efSendAmountProvider); - // if (_sendCurrency != null && _sendAmount != null) { - // final range = ref.watch(efRangeProvider); - // if (range?.min != null && - // _sendAmount < range!.min! && - // _sendAmount > Decimal.zero) { - // return "Min send amount ${range.min!.toString()} ${_sendCurrency.ticker.toUpperCase()}"; - // } else if (range?.max != null && _sendAmount > range!.max!) { - // return "Max send amount ${range.max!.toString()} ${_sendCurrency.ticker.toUpperCase()}"; - // } - // } - // } - - return ""; -}); From 7c8721f4922ed758c4756b10eeaef89032d9c744 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 3 May 2023 15:47:12 -0600 Subject: [PATCH 30/65] fix: swap refreshing provider --- lib/providers/exchange/exchange_form_state_provider.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/providers/exchange/exchange_form_state_provider.dart b/lib/providers/exchange/exchange_form_state_provider.dart index 7effb6993..1148c97f6 100644 --- a/lib/providers/exchange/exchange_form_state_provider.dart +++ b/lib/providers/exchange/exchange_form_state_provider.dart @@ -71,11 +71,9 @@ final efEstimateProvider = StateProvider((ref) { final efCanExchangeProvider = StateProvider((ref) { final Estimate? estimate = ref.watch(efEstimateProvider); - // final Decimal? amount = ref.watch(efReversedProvider) - // ? ref.watch(efSendAmountProvider) - // : ref.watch(efReceiveAmountProvider); + final refreshing = ref.watch(efRefreshingProvider); - return estimate != null; + return !refreshing && estimate != null; }); final efRefreshingProvider = StateProvider((ref) => false); From be6a2c110fab2c78fe73700dfedf82bde9f6272d Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 3 May 2023 16:03:13 -0600 Subject: [PATCH 31/65] fix: import --- .../desktop_exchange/desktop_all_trades_view.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart b/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart index 34d688b21..7d344f47d 100644 --- a/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart +++ b/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.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/isar_models.dart'; @@ -29,8 +30,6 @@ import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; import 'package:tuple/tuple.dart'; -import '../../db/isar/main_db.dart'; - class DesktopAllTradesView extends ConsumerStatefulWidget { const DesktopAllTradesView({Key? key}) : super(key: key); From 292e49563abe7e7b25d44789853db32f8da57807 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 3 May 2023 16:04:47 -0600 Subject: [PATCH 32/65] fix: remove duplicate title --- .../desktop_exchange/subwidgets/desktop_trade_history.dart | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart index 707976425..d369a61df 100644 --- a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart @@ -269,13 +269,6 @@ class _DesktopTradeHistoryState extends ConsumerState { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Recent trades", - style: STextStyles.desktopTextExtraExtraSmall(context), - ), - const SizedBox( - height: 16, - ), RoundedWhiteContainer( child: Center( child: Text( From bde432194238f712b5484b95d48a6b70f41ba63a Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 3 May 2023 16:08:00 -0600 Subject: [PATCH 33/65] clean up prints --- .../sub_widgets/exchange_provider_option.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart index ff31907a0..83c22b0b3 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart @@ -201,12 +201,7 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { groupValue = _id; } - bool selected = groupValue == _id; - - print("========================================================"); - print("gourpValue: $groupValue"); - print("_id: $_id"); - print("========================================================"); + // bool selected = groupValue == _id; return ConditionalParent( condition: isDesktop, From 87ed2af7cee545cfa97cc2273dd3baa0dae3f324 Mon Sep 17 00:00:00 2001 From: julian-CStack Date: Thu, 4 May 2023 08:57:24 -0600 Subject: [PATCH 34/65] remove empty file --- lib/services/exchange/TMP.dart | 389 --------------------------------- 1 file changed, 389 deletions(-) delete mode 100644 lib/services/exchange/TMP.dart diff --git a/lib/services/exchange/TMP.dart b/lib/services/exchange/TMP.dart deleted file mode 100644 index 868ace14e..000000000 --- a/lib/services/exchange/TMP.dart +++ /dev/null @@ -1,389 +0,0 @@ -// import 'package:decimal/decimal.dart'; -// import 'package:flutter/foundation.dart'; -// import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -// import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; -// import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; -// import 'package:stackwallet/services/exchange/exchange.dart'; -// import 'package:stackwallet/utilities/logger.dart'; -// -// class ExchangeFormState extends ChangeNotifier { -// ExchangeFormState(this.exchangeRateType); -// final ExchangeRateType exchangeRateType; -// -// Exchange? _exchange; -// Exchange get exchange => -// _exchange ??= ChangeNowExchange(); // default to change now -// set exchange(Exchange value) { -// _exchange = value; -// _updateRangesAndEstimate( -// shouldNotifyListeners: true, -// ); -// } -// -// bool _reversed = false; -// bool get reversed => _reversed; -// // set reversed(bool reversed) { -// // _reversed = reversed; -// // // -// // } -// -// Decimal? _rate; -// Decimal? get rate => _rate; -// // set rate(Decimal? rate) { -// // _rate = rate; -// // // -// // } -// -// Decimal? _sendAmount; -// Decimal? get sendAmount => _sendAmount; -// // set sendAmount(Decimal? sendAmount) { -// // _sendAmount = sendAmount; -// // // -// // } -// -// Decimal? _receiveAmount; -// Decimal? get receiveAmount => _receiveAmount; -// // set receiveAmount(Decimal? receiveAmount) { -// // _receiveAmount = receiveAmount; -// // // -// // } -// -// Currency? _sendCurrency; -// Currency? get sendCurrency => _sendCurrency; -// // set sendCurrency(Currency? sendCurrency) { -// // _sendCurrency = sendCurrency; -// // // -// // } -// -// Currency? _receiveCurrency; -// Currency? get receiveCurrency => _receiveCurrency; -// // set receiveCurrency(Currency? receiveCurrency) { -// // _receiveCurrency = receiveCurrency; -// // // -// // } -// -// Decimal? _minSendAmount; -// Decimal? get minSendAmount => _minSendAmount; -// // set minSendAmount(Decimal? minSendAmount) { -// // _minSendAmount = minSendAmount; -// // // -// // } -// -// Decimal? _minReceiveAmount; -// Decimal? get minReceiveAmount => _minReceiveAmount; -// // set minReceiveAmount(Decimal? minReceiveAmount) { -// // _minReceiveAmount = minReceiveAmount; -// // // -// // } -// -// Decimal? _maxSendAmount; -// Decimal? get maxSendAmount => _maxSendAmount; -// // set maxSendAmount(Decimal? maxSendAmount) { -// // _maxSendAmount = maxSendAmount; -// // // -// // } -// -// Decimal? _maxReceiveAmount; -// Decimal? get maxReceiveAmount => _maxReceiveAmount; -// // set maxReceiveAmount(Decimal? maxReceiveAmount) { -// // _maxReceiveAmount = maxReceiveAmount; -// // // -// // } -// -// //============================================================================ -// // computed properties -// //============================================================================ -// -// String? get fromTicker => _sendCurrency?.ticker; -// -// String? get toTicker => _receiveCurrency?.ticker; -// -// String get warning { -// if (reversed) { -// if (_receiveCurrency != null && _receiveAmount != null) { -// if (_minReceiveAmount != null && -// _receiveAmount! < _minReceiveAmount! && -// _receiveAmount! > Decimal.zero) { -// return "Minimum amount ${_minReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}"; -// } else if (_maxReceiveAmount != null && -// _receiveAmount! > _maxReceiveAmount!) { -// return "Maximum amount ${_maxReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}"; -// } -// } -// } else { -// if (_sendCurrency != null && _sendAmount != null) { -// if (_minSendAmount != null && -// _sendAmount! < _minSendAmount! && -// _sendAmount! > Decimal.zero) { -// return "Minimum amount ${_minSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}"; -// } else if (_maxSendAmount != null && _sendAmount! > _maxSendAmount!) { -// return "Maximum amount ${_maxSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}"; -// } -// } -// } -// -// return ""; -// } -// -// //============================================================================ -// // public state updaters -// //============================================================================ -// -// void reset(bool shouldNotifyListeners) { -// _exchange = null; -// _reversed = false; -// _rate = null; -// _sendAmount = null; -// _receiveAmount = null; -// _sendCurrency = null; -// _receiveCurrency = null; -// _minSendAmount = null; -// _minReceiveAmount = null; -// _maxSendAmount = null; -// _maxReceiveAmount = null; -// -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// Future setFromAmountAndCalculateToAmount( -// Decimal? newSendAmount, -// bool shouldNotifyListeners, -// ) async { -// if (newSendAmount == null) { -// // todo: check if this breaks things and stuff -// _receiveAmount = null; -// _sendAmount = null; -// } else { -// if (newSendAmount <= Decimal.zero) { -// _receiveAmount = Decimal.zero; -// } -// -// _sendAmount = newSendAmount; -// _reversed = false; -// -// await _updateRangesAndEstimate( -// shouldNotifyListeners: false, -// ); -// } -// -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// Future setToAmountAndCalculateFromAmount( -// Decimal? newReceiveAmount, -// bool shouldNotifyListeners, -// ) async { -// if (newReceiveAmount == null) { -// // todo: check if this breaks things and stuff -// _receiveAmount = null; -// _sendAmount = null; -// } else { -// if (newReceiveAmount <= Decimal.zero) { -// _sendAmount = Decimal.zero; -// } -// -// _receiveAmount = newReceiveAmount; -// _reversed = true; -// -// await _updateRangesAndEstimate( -// shouldNotifyListeners: false, -// ); -// } -// -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// Future updateFrom( -// Currency sendCurrency, -// bool shouldNotifyListeners, -// ) async { -// try { -// _sendCurrency = sendCurrency; -// if (_receiveCurrency == null) { -// _rate = null; -// } else { -// await _updateRangesAndEstimate( -// shouldNotifyListeners: false, -// ); -// } -// } catch (e, s) { -// Logging.instance.log("$e\n$s", level: LogLevel.Error); -// } -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// Future updateTo( -// Currency receiveCurrency, -// bool shouldNotifyListeners, -// ) async { -// try { -// _receiveCurrency = receiveCurrency; -// -// if (_sendCurrency == null) { -// _rate = null; -// } else { -// await _updateRangesAndEstimate( -// shouldNotifyListeners: false, -// ); -// } -// } catch (e, s) { -// Logging.instance.log("$e\n$s", level: LogLevel.Error); -// } -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// Future swap( -// {required bool shouldNotifyListeners,}) async { -// final Decimal? temp = sendAmount; -// _sendAmount = receiveAmount; -// _receiveAmount = temp; -// -// _minSendAmount = null; -// _maxSendAmount = null; -// _minReceiveAmount = null; -// _maxReceiveAmount = null; -// -// final Currency? tmp = sendCurrency; -// _sendCurrency = receiveCurrency; -// _receiveCurrency = tmp; -// -// await _updateRangesAndEstimate( -// shouldNotifyListeners: false, -// ); -// } -// -// //============================================================================ -// // private state updaters -// //============================================================================ -// -// Future _updateRangesAndEstimate( -// {required bool shouldNotifyListeners,}) async { -// await _updateRanges(shouldNotifyListeners: false); -// await _updateEstimate(shouldNotifyListeners: false); -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// Future _updateRanges({required bool shouldNotifyListeners,}) async { -// // if (exchange?.name == SimpleSwapExchange.exchangeName) { -// // reversed = false; -// // } -// final _send = sendCurrency; -// final _receive = receiveCurrency; -// if (_send == null || _receive == null) { -// Logging.instance.log( -// "Tried to $runtimeType.updateRanges where ( $_send || $_receive) for: $exchange", -// level: LogLevel.Info, -// ); -// return; -// } -// final response = await exchange.getRange( -// _send.ticker, -// _receive.ticker, -// exchangeRateType == ExchangeRateType.fixed, -// ); -// -// if (response.value == null) { -// Logging.instance.log( -// "Tried to $runtimeType.updateRanges for: $exchange where response: $response", -// level: LogLevel.Info, -// ); -// return; -// } -// final responseReversed = await exchange.getRange( -// _receive.ticker, -// _send.ticker, -// exchangeRateType == ExchangeRateType.fixed, -// ); -// -// if (responseReversed.value == null) { -// Logging.instance.log( -// "Tried to $runtimeType.updateRanges for: $exchange where response: $responseReversed", -// level: LogLevel.Info, -// ); -// return; -// } -// -// final range = response.value!; -// final rangeReversed = responseReversed.value!; -// -// _minSendAmount = range.min; -// _maxSendAmount = range.max; -// _minReceiveAmount = rangeReversed.min; -// _maxReceiveAmount = rangeReversed.max; -// -// //todo: check if print needed -// // debugPrint( -// // "updated range for: $exchange for $_fromTicker-$_toTicker: $range"); -// -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// Future _updateEstimate({ -// required bool shouldNotifyListeners, -// }) async { -// // if (exchange?.name == SimpleSwapExchange.exchangeName) { -// // reversed = false; -// // } -// final amount = reversed ? receiveAmount : sendAmount; -// if (sendCurrency == null || -// receiveCurrency == null || -// amount == null || -// amount <= Decimal.zero) { -// Logging.instance.log( -// "Tried to $runtimeType.updateEstimate for: $exchange where (from: $sendCurrency || to: $receiveCurrency || amount: $amount)", -// level: LogLevel.Info, -// ); -// return; -// } -// final response = await exchange.getEstimate( -// sendCurrency!.ticker, -// receiveCurrency!.ticker, -// amount, -// exchangeRateType == ExchangeRateType.fixed, -// reversed, -// ); -// -// if (response.value == null) { -// Logging.instance.log( -// "Tried to $runtimeType.updateEstimate for: $exchange where response: $response", -// level: LogLevel.Info, -// ); -// return; -// } -// -// final estimate = response.value!; -// -// if (reversed) { -// _sendAmount = estimate.estimatedAmount; -// } else { -// _receiveAmount = estimate.estimatedAmount; -// } -// -// _rate = -// (receiveAmount! / sendAmount!).toDecimal(scaleOnInfinitePrecision: 12); -// -// //todo: check if print needed -// // debugPrint( -// // "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate"); -// -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// -// } From a1b7c9248ba4406d6e1a08ae3040328eb1887ce2 Mon Sep 17 00:00:00 2001 From: julian-CStack Date: Thu, 4 May 2023 08:58:50 -0600 Subject: [PATCH 35/65] tighten swap estimate refreshing --- lib/pages/exchange_view/exchange_form.dart | 36 ++++++++++++++----- .../exchange_form_state_provider.dart | 17 +++++++-- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 6e7deaa5a..65a83a3cd 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -604,7 +604,7 @@ class _ExchangeFormState extends ConsumerState { } Future update() async { - ref.read(efRefreshingProvider.notifier).state = true; + _addUpdate(); for (final exchange in exchanges) { ref.read(efEstimatesListProvider(exchange.name).notifier).state = null; } @@ -619,7 +619,7 @@ class _ExchangeFormState extends ConsumerState { amount <= Decimal.zero || pair.send == null || pair.receive == null) { - ref.read(efRefreshingProvider.notifier).state = false; + _removeUpdate(); return; } final rateType = ref.read(efRateTypeProvider); @@ -659,18 +659,36 @@ class _ExchangeFormState extends ConsumerState { ); } - if (estimateResponse.value != null && - rangeResponse.value != null && - mounted) { - ref.read(efEstimatesListProvider(exchange.name).notifier).state = - Tuple2(estimateResponse.value!, rangeResponse.value!); + if (mounted) { + if (estimateResponse.value != null && rangeResponse.value != null) { + ref.read(efEstimatesListProvider(exchange.name).notifier).state = + Tuple2(estimateResponse.value!, rangeResponse.value!); + } else { + ref.read(efEstimatesListProvider(exchange.name).notifier).state = + null; + } } } } - WidgetsBinding.instance.addPostFrameCallback((_) { + // WidgetsBinding.instance.addPostFrameCallback((_) { + _removeUpdate(); + // }); + } + + int _updateCount = 0; + + void _addUpdate() { + _updateCount++; + ref.read(efRefreshingProvider.notifier).state = true; + } + + void _removeUpdate() { + _updateCount--; + if (_updateCount <= 0) { + _updateCount = 0; ref.read(efRefreshingProvider.notifier).state = false; - }); + } } void updateSend(Estimate? estimate) { diff --git a/lib/providers/exchange/exchange_form_state_provider.dart b/lib/providers/exchange/exchange_form_state_provider.dart index 1148c97f6..937fcc7db 100644 --- a/lib/providers/exchange/exchange_form_state_provider.dart +++ b/lib/providers/exchange/exchange_form_state_provider.dart @@ -28,10 +28,23 @@ final efSendAmountProvider = StateProvider((ref) => null); final efReceiveAmountProvider = StateProvider((ref) => null); final efSendAmountStringProvider = StateProvider((ref) { - return ref.watch(efSendAmountProvider)?.toStringAsFixed(8) ?? ""; + final refreshing = ref.watch(efRefreshingProvider); + final reversed = ref.watch(efReversedProvider); + if (refreshing && reversed) { + return "-"; + } else { + return ref.watch(efSendAmountProvider)?.toStringAsFixed(8) ?? ""; + } }); final efReceiveAmountStringProvider = StateProvider((ref) { - return ref.watch(efReceiveAmountProvider)?.toStringAsFixed(8) ?? ""; + final refreshing = ref.watch(efRefreshingProvider); + final reversed = ref.watch(efReversedProvider); + + if (refreshing && reversed == false) { + return "-"; + } else { + return ref.watch(efReceiveAmountProvider)?.toStringAsFixed(8) ?? ""; + } }); final efReversedProvider = StateProvider((ref) => false); From 03a58f9dfb7b5e6c422b35e79ee23361a7fe57a4 Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Thu, 4 May 2023 10:44:47 -0500 Subject: [PATCH 36/65] rustc 1.68 -> 1.67.1 --- docs/building.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/building.md b/docs/building.md index 8622c7262..4409980d7 100644 --- a/docs/building.md +++ b/docs/building.md @@ -54,8 +54,8 @@ sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2- Install [Rust](https://www.rust-lang.org/tools/install) with command: ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -rustup install 1.68 -rustup default 1.68 +rustup install 1.67.1 +rustup default 1.67.1 ``` Install the additional components for Rust: From 99ba3f736d255f7c154a2c64ab52b59a136c6ec7 Mon Sep 17 00:00:00 2001 From: Josh Babb Date: Thu, 4 May 2023 10:45:07 -0500 Subject: [PATCH 37/65] remove desktop linux deps installed by scripts/linux/build_secure_storage_deps.sh --- scripts/setup.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/setup.sh b/scripts/setup.sh index 21bf7c201..7bf07ef5a 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -41,9 +41,6 @@ sudo apt-get install -y unzip automake build-essential file pkg-config git pytho sudo apt install -y libc6-dev-i386 -# linux desktop dependencies -sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev meson python3-pip libgirepository1.0-dev valac xsltproc docbook-xsl - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source "$HOME/.cargo/env" cargo install cargo-ndk From 1587b5b1e9232b5c484eba6f8920a84d2226f50b Mon Sep 17 00:00:00 2001 From: detherminal <76167420+detherminal@users.noreply.github.com> Date: Thu, 4 May 2023 21:10:37 +0300 Subject: [PATCH 38/65] feat: implement custom block explorer --- .../advanced_settings_view.dart | 38 +++++ .../advanced_views/manage_explorer_view.dart | 107 +++++++++++++ lib/route_generator.dart | 32 ++++ lib/utilities/block_explorers.dart | 36 ++++- lib/utilities/prefs.dart | 2 + lib/widgets/choose_coin_view.dart | 144 ++++++++++++++++++ 6 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart create mode 100644 lib/widgets/choose_coin_view.dart diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart index a19ed8668..db52f39a9 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/widgets/choose_coin_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart'; import 'package:stackwallet/pages/stack_privacy_calls.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; @@ -10,6 +11,9 @@ import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:tuple/tuple.dart'; + +import 'manage_explorer_view.dart'; class AdvancedSettingsView extends StatelessWidget { const AdvancedSettingsView({ @@ -221,6 +225,40 @@ class AdvancedSettingsView extends StatelessWidget { }, ), ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + Navigator.of(context).pushNamed(ChooseCoinView.routeName, + arguments: const Tuple3("Manage block explorers", "block explorer", ManageExplorerView.routeName)); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + children: [ + Text( + "Change block explorer", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), + ), + ), + ), ], ), ), diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart new file mode 100644 index 000000000..1eb48a757 --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/utilities/block_explorers.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +import '../../../../widgets/rounded_white_container.dart'; + +class ManageExplorerView extends ConsumerStatefulWidget { + const ManageExplorerView({ + Key? key, + required this.coin, + }) : super(key: key); + + static const String routeName = "/manageExplorer"; + + final Coin coin; + + @override + ConsumerState createState() => _ManageExplorerViewState(); +} + +class _ManageExplorerViewState extends ConsumerState { + + + late TextEditingController textEditingController; + + + @override + void initState() { + super.initState(); + textEditingController = TextEditingController(text: getBlockExplorerTransactionUrlFor(coin: widget.coin, txid: "[TXID]").toString().replaceAll("%5BTXID%5D", "[TXID]")); + } + + @override + Widget build(BuildContext context) { + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()! + .background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "${widget.coin.prettyName} block explorer", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Expanded(child: Column( + children: [ + TextField( + controller: textEditingController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 8,), + RoundedWhiteContainer( + child: Center( + child: Text( + "Edit your block explorer above. Keep in mind that every block explorer has a slightly different URL scheme.\n\n" + "Paste in your block explorer of choice, then edit in [TXID] where the transaction ID should go, and Stack Wallet will auto fill the transaction ID in that place of URL.", + style: STextStyles.itemSubtitle(context), + ), + ), + ), + ], + )), + Align( + alignment: Alignment.bottomCenter, + child: ConstrainedBox(constraints: const BoxConstraints( + minWidth: 480, + minHeight: 70, + ), + child: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + onPressed: () { + textEditingController.text = textEditingController.text.trim(); + setBlockExplorerForCoin(coin: widget.coin, url: Uri.parse(textEditingController.text)); + Navigator.of(context).pop(); + }, + child: Text( + "Save", + style: STextStyles.button(context), + ), + ), + ), + ) + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/route_generator.dart b/lib/route_generator.dart index a5bf762af..9ee8ded15 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -63,6 +63,7 @@ import 'package:stackwallet/pages/send_view/token_send_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/about_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/system_brightness_theme_selection_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/currency_view.dart'; @@ -99,6 +100,7 @@ import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_set import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart'; import 'package:stackwallet/pages/stack_privacy_calls.dart'; +import 'package:stackwallet/widgets/choose_coin_view.dart'; import 'package:stackwallet/pages/token_view/my_tokens_view.dart'; import 'package:stackwallet/pages/token_view/token_contract_details_view.dart'; import 'package:stackwallet/pages/token_view/token_view.dart'; @@ -206,6 +208,36 @@ class RouteGenerator { builder: (_) => const StackPrivacyCalls(isSettings: false), settings: RouteSettings(name: settings.name)); + case ChooseCoinView.routeName: + if (args is Tuple3) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ChooseCoinView( + title: args.item1, + coinAdditional: args.item2, + nextRouteName: args.item3, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ManageExplorerView.routeName: + if (args is Coin) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ManageExplorerView( + coin: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case WalletsView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index c628dbbb4..8130626b8 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -1,6 +1,10 @@ +import 'dart:ffi'; + import 'package:stackwallet/utilities/enums/coin_enum.dart'; -Uri getBlockExplorerTransactionUrlFor({ +import '../db/hive/db.dart'; + +Uri getDefaultBlockExplorerUrlFor({ required Coin coin, required String txid, }) { @@ -18,7 +22,7 @@ Uri getBlockExplorerTransactionUrlFor({ case Coin.dogecoinTestNet: return Uri.parse("https://chain.so/tx/DOGETEST/$txid"); case Coin.epicCash: - // TODO: Handle this case. + // TODO: Handle this case. throw UnimplementedError("missing block explorer for epic cash"); case Coin.ethereum: return Uri.parse("https://etherscan.io/tx/$txid"); @@ -41,3 +45,31 @@ Uri getBlockExplorerTransactionUrlFor({ return Uri.parse("https://chainz.cryptoid.info/part/tx.dws?$txid.htm"); } } + +int setBlockExplorerForCoin( + {required Coin coin, required Uri url} + ) { + var ticker = coin.ticker; + DB.instance.put( + boxName: DB.boxNameAllWalletsData, + key: "${ticker}blockExplorerUrl", + value: url); + return 0; +} + +Uri getBlockExplorerTransactionUrlFor({ + required Coin coin, + required String txid, +}) { + var ticker = coin.ticker; + var url = DB.instance.get( + boxName: DB.boxNameAllWalletsData, + key: "${ticker}blockExplorerUrl", + ); + if (url == null) { + return getDefaultBlockExplorerUrlFor(coin: coin, txid: txid); + } else { + url = url.replace("%5BTXID%5D", txid); + return Uri.parse(url.toString()); + } +} diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index 973541366..ea4720b17 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -7,6 +7,8 @@ import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; import 'package:uuid/uuid.dart'; +import 'enums/coin_enum.dart'; + class Prefs extends ChangeNotifier { Prefs._(); static final Prefs _instance = Prefs._(); diff --git a/lib/widgets/choose_coin_view.dart b/lib/widgets/choose_coin_view.dart new file mode 100644 index 000000000..b2e314f7c --- /dev/null +++ b/lib/widgets/choose_coin_view.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class ChooseCoinView extends ConsumerStatefulWidget { + const ChooseCoinView({ + Key? key, + required this.title, + required this.coinAdditional, + required this.nextRouteName, + }) : super(key: key); + + static const String routeName = "/chooseCoin"; + + final String title; + final String coinAdditional; + final String nextRouteName; + + @override + ConsumerState createState() => _ChooseCoinViewState(); +} + +class _ChooseCoinViewState extends ConsumerState { + List _coins = [...Coin.values]; + + @override + void initState() { + _coins = _coins.toList(); + _coins.remove(Coin.firoTestNet); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + bool showTestNet = ref.watch( + prefsChangeNotifierProvider.select((value) => value.showTestNetCoins), + ); + + List coins = showTestNet + ? _coins + : _coins.sublist(0, _coins.length - kTestNetCoinCount); + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + widget.title ?? "Choose Coin", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 12, + right: 12, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ...coins.map( + (coin) { + final count = ref + .watch(nodeServiceChangeNotifierProvider + .select((value) => value.getNodesFor(coin))) + .length; + + return Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + onPressed: () { + Navigator.of(context).pushNamed( + widget.nextRouteName, + arguments: coin, + ); + }, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.iconFor(coin: coin), + width: 24, + height: 24, + ), + const SizedBox( + width: 12, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${coin.prettyName} ${widget.coinAdditional ?? ""}", + style: STextStyles.titleBold12(context), + ), + ], + ) + ], + ), + ), + ), + ), + ); + }, + ), + ], + ), + ), + ), + ), + ); + } +} \ No newline at end of file From 1c33dd5062b8c0150abfc96537bb3cb74f7ee55a Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 4 May 2023 12:19:50 -0600 Subject: [PATCH 39/65] swap rate refresh fixes and error propagation --- lib/pages/exchange_view/exchange_form.dart | 121 ++++++++---------- .../sub_widgets/exchange_provider_option.dart | 83 ++++++------ .../exchange_form_state_provider.dart | 19 ++- 3 files changed, 105 insertions(+), 118 deletions(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 65a83a3cd..c4d11d162 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -10,6 +10,7 @@ import 'package:isar/isar.dart'; import 'package:stackwallet/models/exchange/aggregate_currency.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; @@ -28,7 +29,6 @@ import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.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/utilities/util.dart'; @@ -43,6 +43,9 @@ import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/textfields/exchange_textfield.dart'; import 'package:tuple/tuple.dart'; +import 'package:uuid/uuid.dart'; + +import '../../services/exchange/exchange_response.dart'; class ExchangeForm extends ConsumerStatefulWidget { const ExchangeForm({ @@ -604,7 +607,9 @@ class _ExchangeFormState extends ConsumerState { } Future update() async { - _addUpdate(); + final uuid = const Uuid().v1(); + _latestUuid = uuid; + _addUpdate(uuid); for (final exchange in exchanges) { ref.read(efEstimatesListProvider(exchange.name).notifier).state = null; } @@ -619,10 +624,12 @@ class _ExchangeFormState extends ConsumerState { amount <= Decimal.zero || pair.send == null || pair.receive == null) { - _removeUpdate(); + _removeUpdate(uuid); return; } final rateType = ref.read(efRateTypeProvider); + final Map>, Range?>> + results = {}; for (final exchange in exchanges) { final sendCurrency = pair.send?.forExchange(exchange.name); @@ -635,14 +642,6 @@ class _ExchangeFormState extends ConsumerState { rateType == ExchangeRateType.fixed, ); - if (rangeResponse.value == null) { - Logging.instance.log( - "Tried to $runtimeType.update Range for:" - " $exchange where response: $rangeResponse", - level: LogLevel.Info, - ); - } - final estimateResponse = await exchange.getEstimates( sendCurrency.ticker, receiveCurrency.ticker, @@ -651,43 +650,41 @@ class _ExchangeFormState extends ConsumerState { reversed, ); - if (estimateResponse.value == null) { - Logging.instance.log( - "Tried to $runtimeType._fetchEstimateAndRange Estimate for:" - " $exchange where response: $estimateResponse", - level: LogLevel.Info, - ); - } - - if (mounted) { - if (estimateResponse.value != null && rangeResponse.value != null) { - ref.read(efEstimatesListProvider(exchange.name).notifier).state = - Tuple2(estimateResponse.value!, rangeResponse.value!); - } else { - ref.read(efEstimatesListProvider(exchange.name).notifier).state = - null; - } - } + results.addAll( + { + exchange.name: Tuple2( + estimateResponse, + rangeResponse.value, + ), + }, + ); } } - // WidgetsBinding.instance.addPostFrameCallback((_) { - _removeUpdate(); - // }); + for (final exchange in exchanges) { + if (uuid == _latestUuid) { + ref.read(efEstimatesListProvider(exchange.name).notifier).state = + results[exchange.name]; + } + } + + _removeUpdate(uuid); } - int _updateCount = 0; + String? _latestUuid; + final Set _uuids = {}; - void _addUpdate() { - _updateCount++; + void _addUpdate(String uuid) { + _uuids.add(uuid); ref.read(efRefreshingProvider.notifier).state = true; } - void _removeUpdate() { - _updateCount--; - if (_updateCount <= 0) { - _updateCount = 0; - ref.read(efRefreshingProvider.notifier).state = false; + void _removeUpdate(String uuid) { + _uuids.remove(uuid); + if (_uuids.isEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(efRefreshingProvider.notifier).state = false; + }); } } @@ -711,16 +708,24 @@ class _ExchangeFormState extends ConsumerState { _sendFocusNode.addListener(() { if (_sendFocusNode.hasFocus) { + final reversed = ref.read(efReversedProvider); WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(efReversedProvider.notifier).state = false; + if (reversed == true) { + update(); + } }); } }); _receiveFocusNode.addListener(() { if (_receiveFocusNode.hasFocus && ref.read(efExchangeProvider).name != ChangeNowExchange.exchangeName) { + final reversed = ref.read(efReversedProvider); WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(efReversedProvider.notifier).state = true; + if (reversed != true) { + update(); + } }); } }); @@ -856,12 +861,6 @@ class _ExchangeFormState extends ConsumerState { SizedBox( height: isDesktop ? 10 : 4, ), - // if (ref.watch(efWarningProvider).isNotEmpty && - // !ref.watch(efReversedProvider)) - // Text( - // ref.watch(efWarningProvider), - // style: STextStyles.errorSmall(context), - // ), Row( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -926,27 +925,24 @@ class _ExchangeFormState extends ConsumerState { borderRadius: Constants.size.circularBorderRadius, background: Theme.of(context).extension()!.textFieldDefaultBG, - onTap: () { - if (!(ref.read(efRateTypeProvider) == ExchangeRateType.estimated) && - _receiveController.text == "-") { - _receiveController.text = ""; - } - }, + onTap: rateType == ExchangeRateType.estimated && + ref.watch(efExchangeProvider).name == + ChangeNowExchange.exchangeName + ? null + : () { + if (_sendController.text == "-") { + _sendController.text = ""; + } + }, onChanged: receiveFieldOnChanged, onButtonTap: selectReceiveCurrency, isWalletCoin: isWalletCoin(coin, true), currency: ref .watch(efCurrencyPairProvider.select((value) => value.receive)), - readOnly: (rateType) == ExchangeRateType.estimated && + readOnly: rateType == ExchangeRateType.estimated && ref.watch(efExchangeProvider).name == ChangeNowExchange.exchangeName, ), - // if (ref.watch(efWarningProvider).isNotEmpty && - // ref.watch(efReversedProvider)) - // Text( - // ref.watch(efWarningProvider), - // style: STextStyles.errorSmall(context), - // ), SizedBox( height: isDesktop ? 20 : 12, ), @@ -957,21 +953,12 @@ class _ExchangeFormState extends ConsumerState { onChanged: onRateTypeChanged, ), ), - // AnimatedSize( - // duration: const Duration(milliseconds: 250), - // curve: Curves.easeInOutCirc, - // child: ref.watch(efSendAmountProvider).sendAmount != null && - // ref.watch(exchangeFormStateProvider).sendAmount != Decimal.zero - // ? Padding( padding: EdgeInsets.only(top: isDesktop ? 20 : 12), child: ExchangeProviderOptions( fixedRate: rateType == ExchangeRateType.fixed, reversed: ref.watch(efReversedProvider), ), - // : const SizedBox( - // height: 0, - // ), ), SizedBox( height: isDesktop ? 20 : 12, diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart index 83c22b0b3..1212caf49 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart @@ -9,6 +9,7 @@ import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -31,11 +32,10 @@ class ExchangeOption extends ConsumerStatefulWidget { final bool reversed; @override - ConsumerState createState() => - _ExchangeMultiProviderOptionState(); + ConsumerState createState() => _ExchangeOptionState(); } -class _ExchangeMultiProviderOptionState extends ConsumerState { +class _ExchangeOptionState extends ConsumerState { final isDesktop = Util.isDesktop; @override @@ -49,7 +49,8 @@ class _ExchangeMultiProviderOptionState extends ConsumerState { ? ref.watch(efReceiveAmountProvider) : ref.watch(efSendAmountProvider); - final estimates = ref.watch(efEstimatesListProvider(widget.exchange.name)); + final data = ref.watch(efEstimatesListProvider(widget.exchange.name)); + final estimates = data?.item1.value; return AnimatedSize( duration: const Duration(milliseconds: 500), @@ -68,14 +69,14 @@ class _ExchangeMultiProviderOptionState extends ConsumerState { receivingCurrency != null && amount != null && amount > Decimal.zero) { - if (estimates != null && estimates.item1.isNotEmpty) { + if (estimates != null && estimates.isNotEmpty) { return Column( mainAxisSize: MainAxisSize.min, children: [ - for (int i = 0; i < estimates.item1.length; i++) + for (int i = 0; i < estimates.length; i++) Builder( builder: (context) { - final e = estimates.item1[i]; + final e = estimates[i]; int decimals; try { @@ -136,14 +137,37 @@ class _ExchangeMultiProviderOptionState extends ConsumerState { ); } else { Logging.instance.log( - "$runtimeType failed to fetch rate for ${widget.exchange.name}: $estimates", + "$runtimeType rate unavailable for ${widget.exchange.name}: $data", level: LogLevel.Warning, ); - return _ProviderOption( - exchange: widget.exchange, - estimate: null, - rateString: "Failed to fetch rate", + return Consumer( + builder: (_, ref, __) { + String? message; + + final range = data?.item2; + if (range != null) { + if (range.min != null && amount < range.min!) { + message ??= "Amount too small"; + } else if (range.max != null && amount > range.max!) { + message ??= "Amount too large"; + } + } else if (data?.item1.value == null) { + final rateType = ref.watch(efRateTypeProvider) == + ExchangeRateType.estimated + ? "estimated" + : "fixed"; + message ??= "Pair unavailable on $rateType rate flow"; + } + + return _ProviderOption( + exchange: widget.exchange, + estimate: null, + rateString: message ?? "Failed to fetch rate", + rateColor: + Theme.of(context).extension()!.textError, + ); + }, ); } } else { @@ -168,6 +192,7 @@ class _ProviderOption extends ConsumerStatefulWidget { required this.rateString, this.kycRating, this.loadingString = false, + this.rateColor, }) : super(key: key); final Exchange exchange; @@ -175,6 +200,7 @@ class _ProviderOption extends ConsumerStatefulWidget { final String rateString; final String? kycRating; final bool loadingString; + final Color? rateColor; @override ConsumerState<_ProviderOption> createState() => _ProviderOptionState(); @@ -201,8 +227,6 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { groupValue = _id; } - // bool selected = groupValue == _id; - return ConditionalParent( condition: isDesktop, builder: (child) => MouseRegion( @@ -214,17 +238,6 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { ref.read(efExchangeProvider.notifier).state = widget.exchange; ref.read(efExchangeProviderNameProvider.notifier).state = widget.estimate?.exchangeProvider ?? widget.exchange.name; - - // if (!selected) { - // ref.read(exchangeFormStateProvider).updateExchange( - // exchange: widget.exchange, - // shouldUpdateData: false, - // shouldNotifyListeners: false, - // providerName: - // widget.estimate?.exchangeProvider ?? widget.exchange.name, - // shouldAwait: false, - // ); - // } }, child: Container( color: Colors.transparent, @@ -253,19 +266,6 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { .state = widget.estimate?.exchangeProvider ?? widget.exchange.name; - // if (!selected) { - // - // - // ref.read(exchangeFormStateProvider).updateExchange( - // exchange: widget.exchange, - // shouldUpdateData: false, - // shouldNotifyListeners: false, - // providerName: - // widget.estimate?.exchangeProvider ?? - // widget.exchange.name, - // shouldAwait: false, - // ); - // } }, ), ), @@ -324,9 +324,10 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { widget.rateString, style: STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + color: widget.rateColor ?? + Theme.of(context) + .extension()! + .textSubtitle1, ), ), ], diff --git a/lib/providers/exchange/exchange_form_state_provider.dart b/lib/providers/exchange/exchange_form_state_provider.dart index 937fcc7db..7828e2771 100644 --- a/lib/providers/exchange/exchange_form_state_provider.dart +++ b/lib/providers/exchange/exchange_form_state_provider.dart @@ -4,12 +4,13 @@ import 'package:stackwallet/models/exchange/active_pair.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:tuple/tuple.dart'; -final efEstimatesListProvider = - StateProvider.family, Range>?, String>( - (ref, exchangeName) => null); +final efEstimatesListProvider = StateProvider.family< + Tuple2>, Range?>?, + String>((ref, exchangeName) => null); final efRateTypeProvider = StateProvider((ref) => ExchangeRateType.estimated); @@ -53,19 +54,17 @@ final efCurrencyPairProvider = ChangeNotifierProvider( (ref) => ActivePair(), ); -final efRangeProvider = StateProvider((ref) { - final exchange = ref.watch(efExchangeProvider); - return ref.watch(efEstimatesListProvider(exchange.name))?.item2; -}); - final efEstimateProvider = StateProvider((ref) { final exchange = ref.watch(efExchangeProvider); final provider = ref.watch(efExchangeProviderNameProvider); final reversed = ref.watch(efReversedProvider); final fixedRate = ref.watch(efRateTypeProvider) == ExchangeRateType.fixed; - final matches = - ref.watch(efEstimatesListProvider(exchange.name))?.item1.where((e) { + final matches = ref + .watch(efEstimatesListProvider(exchange.name)) + ?.item1 + .value + ?.where((e) { return e.exchangeProvider == provider && e.fixedRate == fixedRate && e.reversed == reversed; From 34d9360f2094b529f5e59e329eccd3421c318088 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 4 May 2023 12:20:08 -0600 Subject: [PATCH 40/65] delete empty/unused files --- lib/models/exchange/exchange_form_state.dart | 570 ------------------- lib/services/exchange/exchange_service.dart | 1 - 2 files changed, 571 deletions(-) delete mode 100644 lib/models/exchange/exchange_form_state.dart delete mode 100644 lib/services/exchange/exchange_service.dart diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart deleted file mode 100644 index 5f7282ff9..000000000 --- a/lib/models/exchange/exchange_form_state.dart +++ /dev/null @@ -1,570 +0,0 @@ -// import 'dart:async'; -// -// import 'package:decimal/decimal.dart'; -// import 'package:flutter/foundation.dart'; -// import 'package:stackwallet/models/exchange/aggregate_currency.dart'; -// import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; -// import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; -// import 'package:stackwallet/services/exchange/exchange.dart'; -// import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; -// import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; -// import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; -// import 'package:stackwallet/utilities/logger.dart'; -// -// class ExchangeFormState extends ChangeNotifier { -// Exchange? _exchange; -// Exchange get exchange => _exchange ??= Exchange.defaultExchange; -// -// String? _providerName; -// // default to exchange name that isn't trocador -// String get providerName => _providerName ??= Exchange.defaultExchange.name; -// -// String get combinedExchangeId => "${exchange.name} ($providerName)"; -// -// ExchangeRateType _exchangeRateType = ExchangeRateType.estimated; -// ExchangeRateType get exchangeRateType => _exchangeRateType; -// set exchangeRateType(ExchangeRateType exchangeRateType) { -// _exchangeRateType = exchangeRateType; -// // -// } -// -// List _estimates = []; -// List get estimates => _estimates; -// -// bool _reversed = false; -// bool get reversed => _reversed; -// set reversed(bool reversed) { -// _reversed = reversed; -// // -// } -// -// Decimal? _rate; -// Decimal? get rate => _rate; -// // set rate(Decimal? rate) { -// // _rate = rate; -// // // -// // } -// -// Decimal? _sendAmount; -// Decimal? get sendAmount => _sendAmount; -// // set sendAmount(Decimal? sendAmount) { -// // _sendAmount = sendAmount; -// // // -// // } -// -// Decimal? _receiveAmount; -// Decimal? get receiveAmount => _receiveAmount; -// set receiveAmount(Decimal? receiveAmount) { -// _receiveAmount = receiveAmount; -// // -// } -// -// AggregateCurrency? _sendCurrency; -// AggregateCurrency? get sendCurrency => _sendCurrency; -// // set sendCurrency(Currency? sendCurrency) { -// // _sendCurrency = sendCurrency; -// // // -// // } -// -// AggregateCurrency? _receiveCurrency; -// AggregateCurrency? get receiveCurrency => _receiveCurrency; -// // set receiveCurrency(Currency? receiveCurrency) { -// // _receiveCurrency = receiveCurrency; -// // // -// // } -// -// Decimal? _minSendAmount; -// Decimal? get minSendAmount => _minSendAmount; -// // set minSendAmount(Decimal? minSendAmount) { -// // _minSendAmount = minSendAmount; -// // // -// // } -// -// Decimal? _minReceiveAmount; -// Decimal? get minReceiveAmount => _minReceiveAmount; -// // set minReceiveAmount(Decimal? minReceiveAmount) { -// // _minReceiveAmount = minReceiveAmount; -// // // -// // } -// -// Decimal? _maxSendAmount; -// Decimal? get maxSendAmount => _maxSendAmount; -// // set maxSendAmount(Decimal? maxSendAmount) { -// // _maxSendAmount = maxSendAmount; -// // // -// // } -// -// Decimal? _maxReceiveAmount; -// Decimal? get maxReceiveAmount => _maxReceiveAmount; -// // set maxReceiveAmount(Decimal? maxReceiveAmount) { -// // _maxReceiveAmount = maxReceiveAmount; -// // // -// // } -// -// //============================================================================ -// // computed properties -// //============================================================================ -// -// String? get fromTicker => _sendCurrency?.ticker; -// String? get toTicker => _receiveCurrency?.ticker; -// -// String get fromAmountString => _sendAmount?.toStringAsFixed(8) ?? ""; -// String get toAmountString => _receiveAmount?.toStringAsFixed(8) ?? ""; -// -// bool get canExchange { -// return sendCurrency != null && -// receiveCurrency != null && -// sendAmount != null && -// sendAmount! >= Decimal.zero && -// receiveAmount != null && -// rate != null && -// rate! >= Decimal.zero && -// sendCurrency!.forExchange(exchange.name) != null && -// receiveCurrency!.forExchange(exchange.name) != null && -// warning.isEmpty; -// } -// -// String get warning { -// if (reversed) { -// if (_receiveCurrency != null && _receiveAmount != null) { -// if (_minReceiveAmount != null && -// _receiveAmount! < _minReceiveAmount! && -// _receiveAmount! > Decimal.zero) { -// return "Min receive amount ${_minReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}"; -// } else if (_maxReceiveAmount != null && -// _receiveAmount! > _maxReceiveAmount!) { -// return "Max receive amount ${_maxReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}"; -// } -// } -// } else { -// if (_sendCurrency != null && _sendAmount != null) { -// if (_minSendAmount != null && -// _sendAmount! < _minSendAmount! && -// _sendAmount! > Decimal.zero) { -// return "Min send amount ${_minSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}"; -// } else if (_maxSendAmount != null && _sendAmount! > _maxSendAmount!) { -// return "Max send amount ${_maxSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}"; -// } -// } -// } -// -// return ""; -// } -// -// //============================================================================ -// // public state updaters -// //============================================================================ -// -// Future updateExchange({ -// required Exchange exchange, -// required String providerName, -// required bool shouldUpdateData, -// required bool shouldNotifyListeners, -// bool shouldAwait = true, -// }) async { -// _exchange = exchange; -// _providerName = providerName; -// // if (shouldNotifyListeners) { -// _notify(); -// // } -// if (shouldUpdateData) { -// if (shouldAwait) { -// await _updateRangesAndEstimate( -// shouldNotifyListeners: shouldNotifyListeners, -// ); -// } else { -// unawaited( -// _updateRangesAndEstimate( -// shouldNotifyListeners: shouldNotifyListeners, -// ), -// ); -// } -// } -// } -// -// void setCurrencies(AggregateCurrency? from, AggregateCurrency? to) { -// _sendCurrency = from; -// _receiveCurrency = to; -// } -// -// void reset({ -// required bool shouldNotifyListeners, -// }) { -// _exchange = null; -// _providerName = null; -// _reversed = false; -// _rate = null; -// _sendAmount = null; -// _receiveAmount = null; -// _sendCurrency = null; -// _receiveCurrency = null; -// _minSendAmount = null; -// _minReceiveAmount = null; -// _maxSendAmount = null; -// _maxReceiveAmount = null; -// -// if (shouldNotifyListeners) { -// _notify(); -// } -// } -// -// Future setSendAmountAndCalculateReceiveAmount( -// Decimal? newSendAmount, -// bool shouldNotifyListeners, -// ) async { -// if (newSendAmount == null) { -// // todo: check if this breaks things and stuff -// _receiveAmount = null; -// _sendAmount = null; -// } else { -// if (newSendAmount <= Decimal.zero) { -// _receiveAmount = null; -// } -// -// _sendAmount = newSendAmount; -// _reversed = false; -// -// if (_sendAmount! > Decimal.zero) { -// await _updateRangesAndEstimate( -// shouldNotifyListeners: false, -// ); -// } -// } -// -// if (shouldNotifyListeners) { -// _notify(); -// } -// } -// -// Future setReceivingAmountAndCalculateSendAmount( -// Decimal? newReceiveAmount, -// bool shouldNotifyListeners, -// ) async { -// if (newReceiveAmount == null) { -// // todo: check if this breaks things and stuff -// _receiveAmount = null; -// _sendAmount = null; -// } else { -// if (newReceiveAmount <= Decimal.zero) { -// _sendAmount = Decimal.zero; -// } -// -// _receiveAmount = newReceiveAmount; -// _reversed = true; -// -// await _updateRangesAndEstimate( -// shouldNotifyListeners: false, -// ); -// } -// -// if (shouldNotifyListeners) { -// _notify(); -// } -// } -// -// Future updateSendCurrency( -// AggregateCurrency sendCurrency, -// bool shouldNotifyListeners, -// ) async { -// try { -// _sendCurrency = sendCurrency; -// _minSendAmount = null; -// _maxSendAmount = null; -// -// if (_receiveCurrency == null) { -// _rate = null; -// } else { -// await _updateRangesAndEstimate( -// shouldNotifyListeners: false, -// ); -// } -// if (shouldNotifyListeners) { -// _notify(); -// } -// } catch (e, s) { -// Logging.instance.log("$e\n$s", level: LogLevel.Error); -// } -// } -// -// Future updateReceivingCurrency( -// AggregateCurrency receiveCurrency, -// bool shouldNotifyListeners, -// ) async { -// try { -// _receiveCurrency = receiveCurrency; -// _minReceiveAmount = null; -// _maxReceiveAmount = null; -// -// if (_sendCurrency == null) { -// _rate = null; -// } else { -// await _updateRangesAndEstimate( -// shouldNotifyListeners: false, -// ); -// } -// if (shouldNotifyListeners) { -// _notify(); -// } -// } catch (e, s) { -// Logging.instance.log("$e\n$s", level: LogLevel.Error); -// } -// } -// -// Future swap({ -// required bool shouldNotifyListeners, -// }) async { -// final Decimal? temp = sendAmount; -// _sendAmount = receiveAmount; -// _receiveAmount = temp; -// -// _minSendAmount = null; -// _maxSendAmount = null; -// _minReceiveAmount = null; -// _maxReceiveAmount = null; -// -// final AggregateCurrency? tmp = sendCurrency; -// _sendCurrency = receiveCurrency; -// _receiveCurrency = tmp; -// -// await _updateRangesAndEstimate( -// shouldNotifyListeners: false, -// ); -// -// if (shouldNotifyListeners) { -// _notify(); -// } -// } -// -// Future refresh() => _updateRangesAndEstimate( -// shouldNotifyListeners: true, -// ); -// -// //============================================================================ -// // private state updaters -// //============================================================================ -// -// Future _updateRangesAndEstimate({ -// required bool shouldNotifyListeners, -// bool shouldAwait = true, -// }) async { -// try { -// switch (exchange.name) { -// case ChangeNowExchange.exchangeName: -// if (!_exchangeSupported( -// exchangeName: exchange.name, -// sendCurrency: sendCurrency, -// receiveCurrency: receiveCurrency, -// exchangeRateType: exchangeRateType, -// )) { -// _exchange = MajesticBankExchange.instance; -// } -// break; -// case MajesticBankExchange.exchangeName: -// if (!_exchangeSupported( -// exchangeName: exchange.name, -// sendCurrency: sendCurrency, -// receiveCurrency: receiveCurrency, -// exchangeRateType: exchangeRateType, -// )) { -// _exchange = ChangeNowExchange.instance; -// } -// break; -// case TrocadorExchange.exchangeName: -// if (!_exchangeSupported( -// exchangeName: exchange.name, -// sendCurrency: sendCurrency, -// receiveCurrency: receiveCurrency, -// exchangeRateType: exchangeRateType, -// )) { -// _exchange = ChangeNowExchange.instance; -// } -// break; -// } -// -// if (shouldAwait) { -// await _updateRanges(shouldNotifyListeners: false); -// await _updateEstimate(shouldNotifyListeners: false); -// if (shouldNotifyListeners) { -// _notify(); -// } -// } else { -// unawaited( -// _updateRanges(shouldNotifyListeners: false).then( -// (_) => _updateEstimate(shouldNotifyListeners: false).then( -// (_) { -// if (shouldNotifyListeners) { -// _notify(); -// } -// }, -// ), -// ), -// ); -// } -// } catch (_) { -// // -// } -// } -// -// Future _updateRanges({ -// required bool shouldNotifyListeners, -// }) async { -// // if (exchange?.name == SimpleSwapExchange.exchangeName) { -// // reversed = false; -// // } -// final _send = sendCurrency; -// final _receive = receiveCurrency; -// if (_send == null || _receive == null) { -// Logging.instance.log( -// "Tried to $runtimeType.updateRanges where ( $_send || $_receive) for: $exchange", -// level: LogLevel.Info, -// ); -// return; -// } -// final response = await exchange.getRange( -// _send.ticker, -// _receive.ticker, -// exchangeRateType == ExchangeRateType.fixed, -// ); -// -// if (response.value == null) { -// Logging.instance.log( -// "Tried to $runtimeType.updateRanges for: $exchange where response: $response", -// level: LogLevel.Info, -// ); -// return; -// } -// final responseReversed = await exchange.getRange( -// _receive.ticker, -// _send.ticker, -// exchangeRateType == ExchangeRateType.fixed, -// ); -// -// if (responseReversed.value == null) { -// Logging.instance.log( -// "Tried to $runtimeType.updateRanges for: $exchange where response: $responseReversed", -// level: LogLevel.Info, -// ); -// return; -// } -// -// final range = response.value!; -// final rangeReversed = responseReversed.value!; -// -// _minSendAmount = range.min; -// _maxSendAmount = range.max; -// _minReceiveAmount = rangeReversed.min; -// _maxReceiveAmount = rangeReversed.max; -// -// //todo: check if print needed -// // debugPrint( -// // "updated range for: $exchange for $_fromTicker-$_toTicker: $range"); -// -// if (shouldNotifyListeners) { -// _notify(); -// } -// } -// -// Future _updateEstimate({ -// required bool shouldNotifyListeners, -// }) async { -// // if (exchange?.name == SimpleSwapExchange.exchangeName) { -// // reversed = false; -// // } -// final amount = reversed ? receiveAmount : sendAmount; -// if (sendCurrency == null || -// receiveCurrency == null || -// amount == null || -// amount <= Decimal.zero) { -// Logging.instance.log( -// "Tried to $runtimeType.updateEstimate for: $exchange where (from: $sendCurrency || to: $receiveCurrency || amount: $amount)", -// level: LogLevel.Info, -// ); -// return; -// } -// final response = await exchange.getEstimates( -// sendCurrency!.ticker, -// receiveCurrency!.ticker, -// amount, -// exchangeRateType == ExchangeRateType.fixed, -// reversed, -// ); -// -// if (response.value == null) { -// Logging.instance.log( -// "Tried to $runtimeType.updateEstimate for: $exchange where response: $response", -// level: LogLevel.Info, -// ); -// return; -// } -// -// _estimates = response.value!; -// -// if (reversed) { -// _sendAmount = _estimates -// .firstWhere((e) => e.exchangeProvider == providerName) -// .estimatedAmount; -// } else { -// _receiveAmount = _estimates -// .firstWhere((e) => e.exchangeProvider == providerName) -// .estimatedAmount; -// } -// -// _rate = -// (receiveAmount! / sendAmount!).toDecimal(scaleOnInfinitePrecision: 12); -// -// //todo: check if print needed -// // debugPrint( -// // "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate"); -// -// if (shouldNotifyListeners) { -// _notify(); -// } -// } -// -// //============================================================================ -// -// void _notify() { -// debugPrint("ExFState NOTIFY: ${toString()}"); -// notifyListeners(); -// } -// -// bool _exchangeSupported({ -// required String exchangeName, -// required AggregateCurrency? sendCurrency, -// required AggregateCurrency? receiveCurrency, -// required ExchangeRateType exchangeRateType, -// }) { -// final send = sendCurrency?.forExchange(exchangeName); -// if (send == null) return false; -// -// final rcv = receiveCurrency?.forExchange(exchangeName); -// if (rcv == null) return false; -// -// if (exchangeRateType == ExchangeRateType.fixed) { -// return send.supportsFixedRate && rcv.supportsFixedRate; -// } else { -// return send.supportsEstimatedRate && rcv.supportsEstimatedRate; -// } -// } -// -// @override -// String toString() { -// return "{" -// "\n\t exchange: $exchange," -// "\n\t exchangeRateType: $exchangeRateType," -// "\n\t sendCurrency: $sendCurrency," -// "\n\t receiveCurrency: $receiveCurrency," -// "\n\t rate: $rate," -// "\n\t reversed: $reversed," -// "\n\t sendAmount: $sendAmount," -// "\n\t receiveAmount: $receiveAmount," -// "\n\t estimates: $estimates," -// "\n\t minSendAmount: $minSendAmount," -// "\n\t maxSendAmount: $maxSendAmount," -// "\n\t minReceiveAmount: $minReceiveAmount," -// "\n\t maxReceiveAmount: $maxReceiveAmount," -// "\n\t canExchange: $canExchange," -// "\n\t warning: $warning," -// "\n}"; -// } -// } diff --git a/lib/services/exchange/exchange_service.dart b/lib/services/exchange/exchange_service.dart deleted file mode 100644 index 036c522de..000000000 --- a/lib/services/exchange/exchange_service.dart +++ /dev/null @@ -1 +0,0 @@ -class ExchangeService {} From e3c0f58abc8fabf9fe24b72bc76c1862d3128bc5 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 4 May 2023 14:33:19 -0600 Subject: [PATCH 41/65] custom block explorer isar model/schema --- lib/db/isar/main_db.dart | 21 +++++++++++++ lib/models/isar/models/block_explorer.dart | 35 ++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 lib/models/isar/models/block_explorer.dart diff --git a/lib/db/isar/main_db.dart b/lib/db/isar/main_db.dart index 76a60676f..33b7e606f 100644 --- a/lib/db/isar/main_db.dart +++ b/lib/db/isar/main_db.dart @@ -2,6 +2,7 @@ import 'package:decimal/decimal.dart'; import 'package:flutter_native_splash/cli_commands.dart'; import 'package:isar/isar.dart'; import 'package:stackwallet/exceptions/main_db/main_db_exception.dart'; +import 'package:stackwallet/models/isar/models/block_explorer.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -33,6 +34,7 @@ class MainDB { AddressSchema, AddressLabelSchema, EthContractSchema, + TransactionBlockExplorerSchema, ], directory: (await StackFileSystem.applicationIsarDirectory()).path, // inspector: kDebugMode, @@ -43,6 +45,25 @@ class MainDB { return true; } + // tx block explorers + TransactionBlockExplorer? getTransactionBlockExplorer({required Coin coin}) { + return isar.transactionBlockExplorers + .where() + .tickerEqualTo(coin.ticker) + .findFirstSync(); + } + + Future putTransactionBlockExplorer( + TransactionBlockExplorer explorer) async { + try { + return await isar.writeTxn(() async { + return await isar.transactionBlockExplorers.put(explorer); + }); + } catch (e) { + throw MainDBException("failed putTransactionBlockExplorer: $explorer", e); + } + } + // addresses QueryBuilder getAddresses( String walletId) => diff --git a/lib/models/isar/models/block_explorer.dart b/lib/models/isar/models/block_explorer.dart new file mode 100644 index 000000000..cf01fa5e2 --- /dev/null +++ b/lib/models/isar/models/block_explorer.dart @@ -0,0 +1,35 @@ +import 'package:isar/isar.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +part 'block_explorer.g.dart'; + +@collection +class TransactionBlockExplorer { + TransactionBlockExplorer({ + required this.ticker, + required this.url, + }); + + Id id = Isar.autoIncrement; + + @Index(unique: true, replace: true) + late final String ticker; + + late final String url; + + @ignore + Coin? get coin { + try { + return coinFromTickerCaseInsensitive(ticker); + } catch (_) { + return null; + } + } + + Uri? getUrlFor({required String txid}) => Uri.tryParse( + url.replaceFirst( + "%5BTXID%5D", + txid, + ), + ); +} From 8ebd4f3a5f5c7dc8c7e4c29c411d1bf7e2b74562 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 4 May 2023 14:33:34 -0600 Subject: [PATCH 42/65] update generated files --- lib/models/isar/models/block_explorer.g.dart | 764 ++++++++++++++++++ test/cached_electrumx_test.mocks.dart | 13 + test/electrumx_test.mocks.dart | 13 + .../pages/send_view/send_view_test.mocks.dart | 13 + .../exchange/exchange_view_test.mocks.dart | 13 + .../node_options_sheet_test.mocks.dart | 13 + .../transaction_card_test.mocks.dart | 13 + 7 files changed, 842 insertions(+) create mode 100644 lib/models/isar/models/block_explorer.g.dart diff --git a/lib/models/isar/models/block_explorer.g.dart b/lib/models/isar/models/block_explorer.g.dart new file mode 100644 index 000000000..f524392d5 --- /dev/null +++ b/lib/models/isar/models/block_explorer.g.dart @@ -0,0 +1,764 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'block_explorer.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +extension GetTransactionBlockExplorerCollection on Isar { + IsarCollection get transactionBlockExplorers => + this.collection(); +} + +const TransactionBlockExplorerSchema = CollectionSchema( + name: r'TransactionBlockExplorer', + id: 4209077296238413906, + properties: { + r'ticker': PropertySchema( + id: 0, + name: r'ticker', + type: IsarType.string, + ), + r'url': PropertySchema( + id: 1, + name: r'url', + type: IsarType.string, + ) + }, + estimateSize: _transactionBlockExplorerEstimateSize, + serialize: _transactionBlockExplorerSerialize, + deserialize: _transactionBlockExplorerDeserialize, + deserializeProp: _transactionBlockExplorerDeserializeProp, + idName: r'id', + indexes: { + r'ticker': IndexSchema( + id: -8264639257510259247, + name: r'ticker', + unique: true, + replace: true, + properties: [ + IndexPropertySchema( + name: r'ticker', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ) + }, + links: {}, + embeddedSchemas: {}, + getId: _transactionBlockExplorerGetId, + getLinks: _transactionBlockExplorerGetLinks, + attach: _transactionBlockExplorerAttach, + version: '3.0.5', +); + +int _transactionBlockExplorerEstimateSize( + TransactionBlockExplorer object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.ticker.length * 3; + bytesCount += 3 + object.url.length * 3; + return bytesCount; +} + +void _transactionBlockExplorerSerialize( + TransactionBlockExplorer object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.ticker); + writer.writeString(offsets[1], object.url); +} + +TransactionBlockExplorer _transactionBlockExplorerDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = TransactionBlockExplorer( + ticker: reader.readString(offsets[0]), + url: reader.readString(offsets[1]), + ); + object.id = id; + return object; +} + +P _transactionBlockExplorerDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _transactionBlockExplorerGetId(TransactionBlockExplorer object) { + return object.id; +} + +List> _transactionBlockExplorerGetLinks( + TransactionBlockExplorer object) { + return []; +} + +void _transactionBlockExplorerAttach( + IsarCollection col, Id id, TransactionBlockExplorer object) { + object.id = id; +} + +extension TransactionBlockExplorerByIndex + on IsarCollection { + Future getByTicker(String ticker) { + return getByIndex(r'ticker', [ticker]); + } + + TransactionBlockExplorer? getByTickerSync(String ticker) { + return getByIndexSync(r'ticker', [ticker]); + } + + Future deleteByTicker(String ticker) { + return deleteByIndex(r'ticker', [ticker]); + } + + bool deleteByTickerSync(String ticker) { + return deleteByIndexSync(r'ticker', [ticker]); + } + + Future> getAllByTicker( + List tickerValues) { + final values = tickerValues.map((e) => [e]).toList(); + return getAllByIndex(r'ticker', values); + } + + List getAllByTickerSync( + List tickerValues) { + final values = tickerValues.map((e) => [e]).toList(); + return getAllByIndexSync(r'ticker', values); + } + + Future deleteAllByTicker(List tickerValues) { + final values = tickerValues.map((e) => [e]).toList(); + return deleteAllByIndex(r'ticker', values); + } + + int deleteAllByTickerSync(List tickerValues) { + final values = tickerValues.map((e) => [e]).toList(); + return deleteAllByIndexSync(r'ticker', values); + } + + Future putByTicker(TransactionBlockExplorer object) { + return putByIndex(r'ticker', object); + } + + Id putByTickerSync(TransactionBlockExplorer object, {bool saveLinks = true}) { + return putByIndexSync(r'ticker', object, saveLinks: saveLinks); + } + + Future> putAllByTicker(List objects) { + return putAllByIndex(r'ticker', objects); + } + + List putAllByTickerSync(List objects, + {bool saveLinks = true}) { + return putAllByIndexSync(r'ticker', objects, saveLinks: saveLinks); + } +} + +extension TransactionBlockExplorerQueryWhereSort on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QWhere> { + QueryBuilder + anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension TransactionBlockExplorerQueryWhere on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QWhereClause> { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder tickerEqualTo(String ticker) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'ticker', + value: [ticker], + )); + }); + } + + QueryBuilder tickerNotEqualTo(String ticker) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker', + lower: [], + upper: [ticker], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker', + lower: [ticker], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker', + lower: [ticker], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker', + lower: [], + upper: [ticker], + includeUpper: false, + )); + } + }); + } +} + +extension TransactionBlockExplorerQueryFilter on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QFilterCondition> { + QueryBuilder idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder tickerEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'ticker', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tickerContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tickerMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'ticker', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ticker', + value: '', + )); + }); + } + + QueryBuilder tickerIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'ticker', + value: '', + )); + }); + } + + QueryBuilder urlEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'url', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + urlContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + urlMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'url', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'url', + value: '', + )); + }); + } + + QueryBuilder urlIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'url', + value: '', + )); + }); + } +} + +extension TransactionBlockExplorerQueryObject on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QFilterCondition> {} + +extension TransactionBlockExplorerQueryLinks on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QFilterCondition> {} + +extension TransactionBlockExplorerQuerySortBy on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QSortBy> { + QueryBuilder + sortByTicker() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticker', Sort.asc); + }); + } + + QueryBuilder + sortByTickerDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticker', Sort.desc); + }); + } + + QueryBuilder + sortByUrl() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'url', Sort.asc); + }); + } + + QueryBuilder + sortByUrlDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'url', Sort.desc); + }); + } +} + +extension TransactionBlockExplorerQuerySortThenBy on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QSortThenBy> { + QueryBuilder + thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder + thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder + thenByTicker() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticker', Sort.asc); + }); + } + + QueryBuilder + thenByTickerDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticker', Sort.desc); + }); + } + + QueryBuilder + thenByUrl() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'url', Sort.asc); + }); + } + + QueryBuilder + thenByUrlDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'url', Sort.desc); + }); + } +} + +extension TransactionBlockExplorerQueryWhereDistinct on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QDistinct> { + QueryBuilder + distinctByTicker({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'ticker', caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByUrl({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'url', caseSensitive: caseSensitive); + }); + } +} + +extension TransactionBlockExplorerQueryProperty on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QQueryProperty> { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder + tickerProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'ticker'); + }); + } + + QueryBuilder + urlProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'url'); + }); + } +} diff --git a/test/cached_electrumx_test.mocks.dart b/test/cached_electrumx_test.mocks.dart index 6f96a5875..60e9af5c2 100644 --- a/test/cached_electrumx_test.mocks.dart +++ b/test/cached_electrumx_test.mocks.dart @@ -510,6 +510,19 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { returnValueForMissingStub: null, ); @override + bool get randomizePIN => (super.noSuchMethod( + Invocation.getter(#randomizePIN), + returnValue: false, + ) as bool); + @override + set randomizePIN(bool? randomizePIN) => super.noSuchMethod( + Invocation.setter( + #randomizePIN, + randomizePIN, + ), + returnValueForMissingStub: null, + ); + @override bool get useBiometrics => (super.noSuchMethod( Invocation.getter(#useBiometrics), returnValue: false, diff --git a/test/electrumx_test.mocks.dart b/test/electrumx_test.mocks.dart index e18e0aedf..d0ba787c0 100644 --- a/test/electrumx_test.mocks.dart +++ b/test/electrumx_test.mocks.dart @@ -231,6 +231,19 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs { returnValueForMissingStub: null, ); @override + bool get randomizePIN => (super.noSuchMethod( + Invocation.getter(#randomizePIN), + returnValue: false, + ) as bool); + @override + set randomizePIN(bool? randomizePIN) => super.noSuchMethod( + Invocation.setter( + #randomizePIN, + randomizePIN, + ), + returnValueForMissingStub: null, + ); + @override bool get useBiometrics => (super.noSuchMethod( Invocation.getter(#useBiometrics), returnValue: false, diff --git a/test/pages/send_view/send_view_test.mocks.dart b/test/pages/send_view/send_view_test.mocks.dart index fda4f4c21..ea77214b5 100644 --- a/test/pages/send_view/send_view_test.mocks.dart +++ b/test/pages/send_view/send_view_test.mocks.dart @@ -2350,6 +2350,19 @@ class MockPrefs extends _i1.Mock implements _i24.Prefs { returnValueForMissingStub: null, ); @override + bool get randomizePIN => (super.noSuchMethod( + Invocation.getter(#randomizePIN), + returnValue: false, + ) as bool); + @override + set randomizePIN(bool? randomizePIN) => super.noSuchMethod( + Invocation.setter( + #randomizePIN, + randomizePIN, + ), + returnValueForMissingStub: null, + ); + @override bool get useBiometrics => (super.noSuchMethod( Invocation.getter(#useBiometrics), returnValue: false, diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index 7a0074677..44c64d101 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -181,6 +181,19 @@ class MockPrefs extends _i1.Mock implements _i3.Prefs { returnValueForMissingStub: null, ); @override + bool get randomizePIN => (super.noSuchMethod( + Invocation.getter(#randomizePIN), + returnValue: false, + ) as bool); + @override + set randomizePIN(bool? randomizePIN) => super.noSuchMethod( + Invocation.setter( + #randomizePIN, + randomizePIN, + ), + returnValueForMissingStub: null, + ); + @override bool get useBiometrics => (super.noSuchMethod( Invocation.getter(#useBiometrics), returnValue: false, diff --git a/test/widget_tests/node_options_sheet_test.mocks.dart b/test/widget_tests/node_options_sheet_test.mocks.dart index 84f013ddb..1238cc248 100644 --- a/test/widget_tests/node_options_sheet_test.mocks.dart +++ b/test/widget_tests/node_options_sheet_test.mocks.dart @@ -419,6 +419,19 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { returnValueForMissingStub: null, ); @override + bool get randomizePIN => (super.noSuchMethod( + Invocation.getter(#randomizePIN), + returnValue: false, + ) as bool); + @override + set randomizePIN(bool? randomizePIN) => super.noSuchMethod( + Invocation.setter( + #randomizePIN, + randomizePIN, + ), + returnValueForMissingStub: null, + ); + @override bool get useBiometrics => (super.noSuchMethod( Invocation.getter(#useBiometrics), returnValue: false, diff --git a/test/widget_tests/transaction_card_test.mocks.dart b/test/widget_tests/transaction_card_test.mocks.dart index 6f025270d..0cea6059d 100644 --- a/test/widget_tests/transaction_card_test.mocks.dart +++ b/test/widget_tests/transaction_card_test.mocks.dart @@ -2282,6 +2282,19 @@ class MockPrefs extends _i1.Mock implements _i19.Prefs { returnValueForMissingStub: null, ); @override + bool get randomizePIN => (super.noSuchMethod( + Invocation.getter(#randomizePIN), + returnValue: false, + ) as bool); + @override + set randomizePIN(bool? randomizePIN) => super.noSuchMethod( + Invocation.setter( + #randomizePIN, + randomizePIN, + ), + returnValueForMissingStub: null, + ); + @override bool get useBiometrics => (super.noSuchMethod( Invocation.getter(#useBiometrics), returnValue: false, From 3a9d23d0f901d5d3b5a4ae4f9e9f01b64a66e20f Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 4 May 2023 14:52:58 -0600 Subject: [PATCH 43/65] add a (+) wallet button on mobile (per coin) wallets list --- lib/pages/wallets_view/wallets_overview.dart | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/pages/wallets_view/wallets_overview.dart b/lib/pages/wallets_view/wallets_overview.dart index 8d7f9b6ac..7fe54d0c1 100644 --- a/lib/pages/wallets_view/wallets_overview.dart +++ b/lib/pages/wallets_view/wallets_overview.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/coin_entity.dart'; import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_wallet_card.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/providers.dart'; @@ -174,6 +176,27 @@ class _EthWalletsOverviewState extends ConsumerState { "${widget.coin.prettyName} (${widget.coin.ticker}) wallets", style: STextStyles.navBarTitle(context), ), + actions: [ + AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + icon: SvgPicture.asset( + Assets.svg.plus, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + Navigator.of(context).pushNamed( + CreateOrRestoreWalletView.routeName, + arguments: CoinEntity(widget.coin), + ); + }, + ), + ), + ], ), body: SafeArea( child: Padding( From 52d50d112c98c13c8bcd41cc03e97d62608d78d3 Mon Sep 17 00:00:00 2001 From: julian Date: Thu, 4 May 2023 15:05:43 -0600 Subject: [PATCH 44/65] updated loader_and_checkmark lottie file --- assets/lottie/loader_and_checkmark.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/lottie/loader_and_checkmark.json b/assets/lottie/loader_and_checkmark.json index 76a6944d5..406f52030 100644 --- a/assets/lottie/loader_and_checkmark.json +++ b/assets/lottie/loader_and_checkmark.json @@ -1 +1 @@ -{"v":"5.10.2","fr":30,"ip":0,"op":140,"w":24,"h":24,"nm":"Loader","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Arrow","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":116,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":118,"s":[80]},{"t":130,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":118,"s":[30,30,100]},{"t":125,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.75,-3],[-1.25,3],[-4.75,-0.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":100,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":118,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":123,"s":[30]},{"t":130,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":140,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Fill","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":116,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":118,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":123,"s":[70]},{"t":130,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":118,"s":[241.821,241.821,100]},{"t":127,"s":[1511.821,1511.821,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[6.243,6.243],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":140,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Сircle green","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":73,"s":[0]},{"t":74,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.754]},"t":0,"s":[113.562,113.562,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":112,"s":[113.562,113.562,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":116,"s":[90,90,100]},{"t":130,"s":[113.562,113.562,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":87,"s":[0]},{"t":112,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":87,"s":[55]},{"t":112,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":140,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Сircle black 2 turn","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":36,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":37,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[100]},{"t":75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":0,"k":[113.562,113.562,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254901961,0.137254901961,0.137254901961,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":37,"s":[0.2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[0.2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":57,"s":[27.5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":72,"s":[99]},{"t":74,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":37,"s":[0.1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[55]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":57,"s":[73]},{"t":74,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254908681,0.137254908681,0.137254908681,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":140,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Сircle black 1 turn","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[100]},{"t":38,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":0,"k":[113.562,113.562,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254901961,0.137254901961,0.137254901961,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[0.2]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[27.5]},{"t":37,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[55]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[73]},{"t":37,"s":[99]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254908681,0.137254908681,0.137254908681,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":140,"st":0,"ct":1,"bm":0}],"markers":[{"tm":0,"cm":"{\r\n\"name\":\"marker 1\"\r\n}","dr":0},{"tm":74,"cm":"{\r\n\"name\":\"marker 2\"\r\n}","dr":0},{"tm":140,"cm":"{\r\n\"name\":\"marker 3\"\r\n}","dr":0}]} \ No newline at end of file +{"v":"5.10.2","fr":30,"ip":0,"op":130,"w":24,"h":24,"nm":"Loader","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Arrow","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":115,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":117,"s":[80]},{"t":122,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":117,"s":[30,30,100]},{"t":122,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.75,-3],[-1.25,3],[-4.75,-0.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":100,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":117,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":119,"s":[30]},{"t":122,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Fill","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":115,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":117,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":119,"s":[70]},{"t":122,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":117,"s":[241.821,241.821,100]},{"t":122,"s":[1511.821,1511.821,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[6.243,6.243],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Сircle green","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":73,"s":[0]},{"t":74,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.754]},"t":0,"s":[113.562,113.562,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":111,"s":[113.562,113.562,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":115,"s":[90,90,100]},{"t":122,"s":[113.562,113.562,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":87,"s":[0]},{"t":111,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":87,"s":[55]},{"t":111,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Сircle black 2 turn","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":36,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":37,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[100]},{"t":75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":0,"k":[113.562,113.562,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254901961,0.137254901961,0.137254901961,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":37,"s":[0.2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[0.2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":57,"s":[27.5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":72,"s":[99]},{"t":74,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":37,"s":[0.1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[55]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":57,"s":[73]},{"t":74,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254908681,0.137254908681,0.137254908681,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Сircle black 1 turn","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[100]},{"t":38,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":0,"k":[113.562,113.562,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254901961,0.137254901961,0.137254901961,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[0.2]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[27.5]},{"t":37,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[55]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[73]},{"t":37,"s":[99]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254908681,0.137254908681,0.137254908681,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0}],"markers":[{"tm":0,"cm":"{\r\n\"name\":\"marker 1\"\r\n}","dr":0},{"tm":74,"cm":"{\r\n\"name\":\"marker 2\"\r\n}","dr":0},{"tm":130,"cm":"{\r\n\"name\":\"marker 3\"\r\n}","dr":0}]} \ No newline at end of file From 107e2ba4119bc899904c7db0840da847bd9f165f Mon Sep 17 00:00:00 2001 From: Diego Salazar Date: Thu, 4 May 2023 15:18:28 -0600 Subject: [PATCH 45/65] Bump version (v1.7.5, build 166) --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index a754c2143..42be7da5e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.7.4+165 +version: 1.7.5+166 environment: sdk: ">=2.17.0 <3.0.0" From 8d7db30328a079b0988ceafe12443323e4294097 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 4 May 2023 16:17:00 -0600 Subject: [PATCH 46/65] move biometrics to appbar --- lib/pages/pinpad_views/lock_screen_view.dart | 49 +++++++++++--------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/lib/pages/pinpad_views/lock_screen_view.dart b/lib/pages/pinpad_views/lock_screen_view.dart index 316ef9b5d..a11dd4090 100644 --- a/lib/pages/pinpad_views/lock_screen_view.dart +++ b/lib/pages/pinpad_views/lock_screen_view.dart @@ -55,7 +55,6 @@ class LockscreenView extends ConsumerStatefulWidget { final VoidCallback? onSuccess; final String customKeyLabel; - @override ConsumerState createState() => _LockscreenViewState(); } @@ -223,31 +222,39 @@ class _LockscreenViewState extends ConsumerState { }, ) : Container(), + actions: [ + // check prefs and hide if user has biometrics toggle off? + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only( + right: 16.0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (ref + .read(prefsChangeNotifierProvider) + .useBiometrics == + true) + CustomTextButton( + text: "Use biometrics", + onTap: () async { + await _checkUseBiometrics(); + }, + ), + ], + ), + ), + ], + ), + ], ), body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - // check prefs and hide if user has biometrics toggle off? - Padding( - padding: const EdgeInsets.only(right: 40.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (ref.read(prefsChangeNotifierProvider).useBiometrics == - true) - CustomTextButton( - text: "Use biometrics", - onTap: () async { - await _checkUseBiometrics(); - }, - ), - ], - ), - ), - const SizedBox( - height: 55, - ), Shake( animationDuration: const Duration(milliseconds: 700), animationRange: 12, From 7c8b31dc4c7e5d7168d8c450d0a361470aa79ee2 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Thu, 4 May 2023 16:36:48 -0600 Subject: [PATCH 47/65] center lock screen PIN page --- lib/pages/pinpad_views/lock_screen_view.dart | 97 ++++++++++---------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/lib/pages/pinpad_views/lock_screen_view.dart b/lib/pages/pinpad_views/lock_screen_view.dart index a11dd4090..380717f72 100644 --- a/lib/pages/pinpad_views/lock_screen_view.dart +++ b/lib/pages/pinpad_views/lock_screen_view.dart @@ -204,55 +204,56 @@ class _LockscreenViewState extends ConsumerState { late Biometrics biometrics; Widget get _body => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: widget.showBackButton - ? AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 70)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ) - : Container(), - actions: [ - // check prefs and hide if user has biometrics toggle off? - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only( - right: 16.0, + child: SafeArea( + child: Scaffold( + extendBodyBehindAppBar: true, + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: widget.showBackButton + ? AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 70)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ) + : Container(), + actions: [ + // check prefs and hide if user has biometrics toggle off? + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only( + right: 16.0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (ref + .read(prefsChangeNotifierProvider) + .useBiometrics == + true) + CustomTextButton( + text: "Use biometrics", + onTap: () async { + await _checkUseBiometrics(); + }, + ), + ], + ), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (ref - .read(prefsChangeNotifierProvider) - .useBiometrics == - true) - CustomTextButton( - text: "Use biometrics", - onTap: () async { - await _checkUseBiometrics(); - }, - ), - ], - ), - ), - ], - ), - ], - ), - body: SafeArea( - child: Column( + ], + ), + ], + ), + body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Shake( From 4f78dfba026bdab8471c06c320e59a4246ac2b31 Mon Sep 17 00:00:00 2001 From: Diego Salazar Date: Thu, 4 May 2023 16:56:49 -0600 Subject: [PATCH 48/65] Bump version (1.7.5, build 167) --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 42be7da5e..f7d91ff98 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.7.5+166 +version: 1.7.5+167 environment: sdk: ">=2.17.0 <3.0.0" From affee53ca693411d83e9ae995fca1cb3788910aa Mon Sep 17 00:00:00 2001 From: likho Date: Fri, 5 May 2023 13:58:53 +0200 Subject: [PATCH 49/65] Add send to address for epic when sending --- .../coins/epiccash/epiccash_wallet.dart | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index a67f7e183..b44442c51 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -452,8 +452,6 @@ class EpicCashWallet extends CoinServiceAPI EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); - print("EPICBOX CONFIG HERE IS $epicboxConfig"); - // TODO determine whether it is worth sending change to a change address. dynamic message; @@ -522,6 +520,11 @@ class EpicCashWallet extends CoinServiceAPI throw BadEpicHttpAddressException(message: sendTx); } + Map txAddressInfo = {}; + txAddressInfo['from'] = await currentReceivingAddress; + txAddressInfo['to'] = txData['addresss'] as String; + await putSendToAddresses(sendTx, txAddressInfo); + Logging.instance.log("CONFIRM_RESULT_IS $sendTx", level: LogLevel.Info); final decodeData = json.decode(sendTx); @@ -1247,7 +1250,6 @@ class EpicCashWallet extends CoinServiceAPI await _secureStore.write(key: '${_walletId}_config', value: stringConfig); await _secureStore.write(key: '${_walletId}_password', value: password); - print("EPIC BOX MODEL IS ${epicboxConfig.toString()}"); await _secureStore.write( key: '${_walletId}_epicboxConfig', value: epicboxConfig.toString()); @@ -1390,7 +1392,8 @@ class EpicCashWallet extends CoinServiceAPI } } - Future putSendToAddresses(String slateMessage) async { + Future putSendToAddresses( + String slateMessage, Map txAddressInfo) async { try { var slatesToCommits = await getSlatesToCommits(); final slate0 = jsonDecode(slateMessage); @@ -1400,19 +1403,19 @@ class EpicCashWallet extends CoinServiceAPI final slateId = part1[0]['tx_slate_id']; final commitId = part2['tx']['body']['outputs'][0]['commit']; - final toFromInfoString = jsonDecode(slateMessage); - final toFromInfo = jsonDecode(toFromInfoString[1] as String); - final from = toFromInfo['from']; - final to = toFromInfo['to']; + final from = txAddressInfo['from']; + final to = txAddressInfo['to']; slatesToCommits[slateId] = { "commitId": commitId, "from": from, "to": to, }; + await epicUpdateSlatesToCommits(slatesToCommits); return true; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); + Logging.instance + .log("ERROR STORING ADDRESS $e $s", level: LogLevel.Error); return false; } } From 16ca32a882f70b73d8e153125107ff142709f105 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 5 May 2023 08:16:08 -0600 Subject: [PATCH 50/65] Don't show exchange providers until amount is entered --- lib/pages/exchange_view/exchange_form.dart | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index c4d11d162..918b22385 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -953,12 +953,20 @@ class _ExchangeFormState extends ConsumerState { onChanged: onRateTypeChanged, ), ), - Padding( - padding: EdgeInsets.only(top: isDesktop ? 20 : 12), - child: ExchangeProviderOptions( - fixedRate: rateType == ExchangeRateType.fixed, - reversed: ref.watch(efReversedProvider), - ), + AnimatedSize( + duration: const Duration(milliseconds: 300), + child: ref.watch(efSendAmountProvider) == null && + ref.watch(efReceiveAmountProvider) == null + ? const SizedBox( + height: 0, + ) + : Padding( + padding: EdgeInsets.only(top: isDesktop ? 20 : 12), + child: ExchangeProviderOptions( + fixedRate: rateType == ExchangeRateType.fixed, + reversed: ref.watch(efReversedProvider), + ), + ), ), SizedBox( height: isDesktop ? 20 : 12, From a00f226f520f8b1c8af259c3badfab2b4a41cc54 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 5 May 2023 08:20:37 -0600 Subject: [PATCH 51/65] remove auto setting 8 decimal places --- lib/providers/exchange/exchange_form_state_provider.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/providers/exchange/exchange_form_state_provider.dart b/lib/providers/exchange/exchange_form_state_provider.dart index 7828e2771..afac49de8 100644 --- a/lib/providers/exchange/exchange_form_state_provider.dart +++ b/lib/providers/exchange/exchange_form_state_provider.dart @@ -34,7 +34,7 @@ final efSendAmountStringProvider = StateProvider((ref) { if (refreshing && reversed) { return "-"; } else { - return ref.watch(efSendAmountProvider)?.toStringAsFixed(8) ?? ""; + return ref.watch(efSendAmountProvider)?.toString() ?? ""; } }); final efReceiveAmountStringProvider = StateProvider((ref) { @@ -44,7 +44,7 @@ final efReceiveAmountStringProvider = StateProvider((ref) { if (refreshing && reversed == false) { return "-"; } else { - return ref.watch(efReceiveAmountProvider)?.toStringAsFixed(8) ?? ""; + return ref.watch(efReceiveAmountProvider)?.toString() ?? ""; } }); From 0386fded7fd4840d18e72c9f4b8bda946fae0d50 Mon Sep 17 00:00:00 2001 From: likho Date: Fri, 5 May 2023 16:33:58 +0200 Subject: [PATCH 52/65] Update submodule flutterlibepiccash to epic-wallet branch epicbox-0.0.2 --- crypto_plugins/flutter_libepiccash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index b8222a95a..a31716e3a 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit b8222a95ad31885258ea341fabd9bc3a2608efce +Subproject commit a31716e3a726ae8d0c641b3d751cc0c088956b7b From 22a7763b26767f8133a147693782afdf2a3e1081 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 5 May 2023 08:56:04 -0600 Subject: [PATCH 53/65] fix: desktop trocador kyc rating info dialog --- lib/widgets/trocador_kyc_rating_info.dart | 68 ++++++++++++++++++++--- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/lib/widgets/trocador_kyc_rating_info.dart b/lib/widgets/trocador_kyc_rating_info.dart index bfd786e9c..f7d531da7 100644 --- a/lib/widgets/trocador_kyc_rating_info.dart +++ b/lib/widgets/trocador_kyc_rating_info.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_icon.dart'; import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; @@ -12,9 +15,56 @@ class TrocadorKYCRatingInfo extends StatelessWidget { @override Widget build(BuildContext context) { final small = MediaQuery.of(context).size.width <= 500; + return ConditionalParent( condition: !small, - builder: (child) => child, + builder: (child) => DesktopDialog( + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Trocador KYC Rating", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + const SizedBox( + height: 16, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: child, + ), + Padding( + padding: const EdgeInsets.all(32), + child: Row( + children: [ + const Spacer(), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Ok", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + ], + ), + ) + ], + ), + ), child: ConditionalParent( condition: small, builder: (child) { @@ -24,13 +74,15 @@ class TrocadorKYCRatingInfo extends StatelessWidget { }, child: Column( children: [ - Text( - "Trocador KYC Rating", - style: STextStyles.pageTitleH2(context), - ), - const SizedBox( - height: 16, - ), + if (small) + Text( + "Trocador KYC Rating", + style: STextStyles.pageTitleH2(context), + ), + if (small) + const SizedBox( + height: 16, + ), const _Rating( kycType: TrocadorKYCType.a, text: "Never asks for user verification.", From 1dc9add85652576927d7387e1a92a545e381a93a Mon Sep 17 00:00:00 2001 From: likho Date: Fri, 5 May 2023 17:02:00 +0200 Subject: [PATCH 54/65] Reset libepiccash to main --- crypto_plugins/flutter_libepiccash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index a31716e3a..398077d74 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit a31716e3a726ae8d0c641b3d751cc0c088956b7b +Subproject commit 398077d745bfb8e27c6fcb3fae971908566b2222 From 0ca5e46474e28953ba082bd660a69e8995c5aac6 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 5 May 2023 09:32:55 -0600 Subject: [PATCH 55/65] fix: Dart's stupid rounding --- lib/pages/exchange_view/exchange_form.dart | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 918b22385..b9cd4023f 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -5,7 +5,6 @@ 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:intl/intl.dart'; import 'package:isar/isar.dart'; import 'package:stackwallet/models/exchange/aggregate_currency.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; @@ -152,10 +151,22 @@ class _ExchangeFormState extends ConsumerState { return null; } try { - final numFromLocalised = NumberFormat.decimalPattern( - ref.read(localeServiceChangeNotifierProvider).locale) - .parse(value); - return Decimal.tryParse(numFromLocalised.toString()); + // wtf Dart????? + // This turns "99999999999999999999" into 100000000000000000000.0 + // final numFromLocalised = NumberFormat.decimalPattern( + // ref.read(localeServiceChangeNotifierProvider).locale) + // .parse(value); + // return Decimal.tryParse(numFromLocalised.toString()); + + try { + return Decimal.parse(value); + } catch (_) { + try { + return Decimal.parse(value.replaceAll(",", ".")); + } catch (_) { + rethrow; + } + } } catch (_) { return null; } From 0ab96ad6a1828380d9a5f9a940f6d534bc088608 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 5 May 2023 10:17:20 -0600 Subject: [PATCH 56/65] fix: some exchange api error propagation --- .../exchange/change_now/change_now_api.dart | 12 ++++++-- .../exchange/trocador/trocador_api.dart | 28 +++++++++++++++++-- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart index 6f050d2e2..e14b2f128 100644 --- a/lib/services/exchange/change_now/change_now_api.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -100,12 +100,18 @@ class ChangeNowAPI { body: jsonEncode(body), ); - final parsed = jsonDecode(response.body); + try { + final parsed = jsonDecode(response.body); - return parsed; + return parsed; + } catch (_) { + Logging.instance.log("ChangeNOW api failed to parse: ${response.body}", + level: LogLevel.Error); + rethrow; + } } catch (e, s) { Logging.instance - .log("_makeRequest($uri) threw: $e\n$s", level: LogLevel.Error); + .log("_makePostRequest($uri) threw: $e\n$s", level: LogLevel.Error); rethrow; } } diff --git a/lib/services/exchange/trocador/trocador_api.dart b/lib/services/exchange/trocador/trocador_api.dart index 317ebba53..d74ff2460 100644 --- a/lib/services/exchange/trocador/trocador_api.dart +++ b/lib/services/exchange/trocador/trocador_api.dart @@ -302,10 +302,32 @@ abstract class TrocadorAPI { final json = await _makeGetRequest(uri); final map = Map.from(json as Map); - return ExchangeResponse(value: TrocadorTradeNew.fromMap(map)); + try { + return ExchangeResponse(value: TrocadorTradeNew.fromMap(map)); + } catch (e, s) { + String error = map["error"] as String? ?? json.toString(); + if (error == + "trade could not be generated, some unknown error happened") { + error = + "This trade couldn't be completed. Please select another provider."; + } + + Logging.instance.log( + "_getNewTrade failed to parse response: $json\n$e\n$s", + level: LogLevel.Error, + ); + return ExchangeResponse( + exception: ExchangeException( + error, + ExchangeExceptionType.serializeResponseError, + ), + ); + } } catch (e, s) { - Logging.instance - .log("_getNewTrade exception: $e\n$s", level: LogLevel.Error); + Logging.instance.log( + "_getNewTrade exception: $e\n$s", + level: LogLevel.Error, + ); return ExchangeResponse( exception: ExchangeException( e.toString(), From 38c4f6bc12051ba7daef10aef0632f821ff01e52 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 5 May 2023 10:30:34 -0600 Subject: [PATCH 57/65] fix: desktop exchange form overflow --- .../desktop_exchange/desktop_exchange_view.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart index 36006b7c1..ab8279866 100644 --- a/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart +++ b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart @@ -150,10 +150,14 @@ class _DesktopExchangeViewState extends ConsumerState { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Expanded( - child: RoundedWhiteContainer( - padding: EdgeInsets.all(24), - child: ExchangeForm(), + Expanded( + child: ListView( + children: const [ + RoundedWhiteContainer( + padding: EdgeInsets.all(24), + child: ExchangeForm(), + ), + ], ), ), const SizedBox( From 47df2a8a7ac9c206996b5352cf734bdb03ccd902 Mon Sep 17 00:00:00 2001 From: detherminal <76167420+detherminal@users.noreply.github.com> Date: Fri, 5 May 2023 20:30:22 +0300 Subject: [PATCH 58/65] feat: use isar instead of hive --- .../advanced_views/manage_explorer_view.dart | 5 +++-- lib/utilities/block_explorers.dart | 22 +++++++------------ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart index 1eb48a757..dc84fe8c8 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart @@ -88,8 +88,9 @@ class _ManageExplorerViewState extends ConsumerState { .getPrimaryEnabledButtonStyle(context), onPressed: () { textEditingController.text = textEditingController.text.trim(); - setBlockExplorerForCoin(coin: widget.coin, url: Uri.parse(textEditingController.text)); - Navigator.of(context).pop(); + setBlockExplorerForCoin(coin: widget.coin, url: Uri.parse(textEditingController.text)).then((value) => + Navigator.of(context).pop() + ); }, child: Text( "Save", diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index 8130626b8..ebfee9db0 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -3,6 +3,8 @@ import 'dart:ffi'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import '../db/hive/db.dart'; +import '../db/isar/main_db.dart'; +import '../models/isar/models/block_explorer.dart'; Uri getDefaultBlockExplorerUrlFor({ required Coin coin, @@ -46,14 +48,10 @@ Uri getDefaultBlockExplorerUrlFor({ } } -int setBlockExplorerForCoin( +Future setBlockExplorerForCoin( {required Coin coin, required Uri url} - ) { - var ticker = coin.ticker; - DB.instance.put( - boxName: DB.boxNameAllWalletsData, - key: "${ticker}blockExplorerUrl", - value: url); + ) async { + await MainDB.instance.putTransactionBlockExplorer(TransactionBlockExplorer(ticker: coin.ticker, url: url.toString())); return 0; } @@ -61,15 +59,11 @@ Uri getBlockExplorerTransactionUrlFor({ required Coin coin, required String txid, }) { - var ticker = coin.ticker; - var url = DB.instance.get( - boxName: DB.boxNameAllWalletsData, - key: "${ticker}blockExplorerUrl", - ); + var url = MainDB.instance.getTransactionBlockExplorer(coin: coin)?.url.toString(); if (url == null) { return getDefaultBlockExplorerUrlFor(coin: coin, txid: txid); } else { - url = url.replace("%5BTXID%5D", txid); - return Uri.parse(url.toString()); + url = url.replaceAll("%5BTXID%5D", txid); + return Uri.parse(url); } } From 1a2a6720797add29d35c69346ccdb870213a40d5 Mon Sep 17 00:00:00 2001 From: Diego Salazar Date: Fri, 5 May 2023 16:17:56 -0600 Subject: [PATCH 59/65] Bump version (1.7.6, build 168) --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index f7d91ff98..b9eb4b909 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.7.5+167 +version: 1.7.6+168 environment: sdk: ">=2.17.0 <3.0.0" From 3f5cfcbfafe0c32ac23ceb95f695c1e0aa8fbeda Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 8 May 2023 08:36:05 -0600 Subject: [PATCH 60/65] clean up unused code, imports, and formatting --- .../advanced_settings_view.dart | 10 +- .../advanced_views/manage_explorer_view.dart | 143 ++++++++++-------- lib/utilities/block_explorers.dart | 30 ++-- lib/utilities/prefs.dart | 2 - lib/widgets/choose_coin_view.dart | 16 +- 5 files changed, 103 insertions(+), 98 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart index db52f39a9..9285e2f08 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart @@ -1,20 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/widgets/choose_coin_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart'; import 'package:stackwallet/pages/stack_privacy_calls.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/choose_coin_view.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:tuple/tuple.dart'; -import 'manage_explorer_view.dart'; - class AdvancedSettingsView extends StatelessWidget { const AdvancedSettingsView({ Key? key, @@ -240,7 +239,10 @@ class AdvancedSettingsView extends StatelessWidget { ), onPressed: () { Navigator.of(context).pushNamed(ChooseCoinView.routeName, - arguments: const Tuple3("Manage block explorers", "block explorer", ManageExplorerView.routeName)); + arguments: const Tuple3( + "Manage block explorers", + "block explorer", + ManageExplorerView.routeName)); }, child: Padding( padding: const EdgeInsets.symmetric( diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart index dc84fe8c8..6cf02b148 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/utilities/block_explorers.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; - -import '../../../../widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; class ManageExplorerView extends ConsumerStatefulWidget { const ManageExplorerView({ @@ -24,85 +23,95 @@ class ManageExplorerView extends ConsumerStatefulWidget { } class _ManageExplorerViewState extends ConsumerState { - - late TextEditingController textEditingController; - @override void initState() { super.initState(); - textEditingController = TextEditingController(text: getBlockExplorerTransactionUrlFor(coin: widget.coin, txid: "[TXID]").toString().replaceAll("%5BTXID%5D", "[TXID]")); + textEditingController = TextEditingController( + text: + getBlockExplorerTransactionUrlFor(coin: widget.coin, txid: "[TXID]") + .toString() + .replaceAll("%5BTXID%5D", "[TXID]")); } @override - Widget build(BuildContext context) { - return Background( - child: Scaffold( - backgroundColor: Theme.of(context).extension()! - .background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "${widget.coin.prettyName} block explorer", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - Expanded(child: Column( - children: [ - TextField( - controller: textEditingController, - decoration: const InputDecoration( - border: OutlineInputBorder(), + Widget build(BuildContext context) { + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "${widget.coin.prettyName} block explorer", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Expanded( + child: Column( + children: [ + TextField( + controller: textEditingController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Center( + child: Text( + "Edit your block explorer above. Keep in mind that " + "every block explorer has a slightly different URL " + "scheme.\n\nPaste in your block explorer of choice," + " then edit in [TXID] where the transaction ID " + "should go, and Stack Wallet will auto fill the " + "transaction ID in that place of URL.", + style: STextStyles.itemSubtitle(context), ), ), - const SizedBox(height: 8,), - RoundedWhiteContainer( - child: Center( - child: Text( - "Edit your block explorer above. Keep in mind that every block explorer has a slightly different URL scheme.\n\n" - "Paste in your block explorer of choice, then edit in [TXID] where the transaction ID should go, and Stack Wallet will auto fill the transaction ID in that place of URL.", - style: STextStyles.itemSubtitle(context), - ), - ), - ), - ], - )), - Align( - alignment: Alignment.bottomCenter, - child: ConstrainedBox(constraints: const BoxConstraints( + ), + ], + )), + Align( + alignment: Alignment.bottomCenter, + child: ConstrainedBox( + constraints: const BoxConstraints( minWidth: 480, minHeight: 70, ), - child: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonStyle(context), - onPressed: () { - textEditingController.text = textEditingController.text.trim(); - setBlockExplorerForCoin(coin: widget.coin, url: Uri.parse(textEditingController.text)).then((value) => - Navigator.of(context).pop() - ); - }, - child: Text( - "Save", - style: STextStyles.button(context), - ), + child: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + onPressed: () { + textEditingController.text = + textEditingController.text.trim(); + setBlockExplorerForCoin( + coin: widget.coin, + url: Uri.parse(textEditingController.text)) + .then((value) => Navigator.of(context).pop()); + }, + child: Text( + "Save", + style: STextStyles.button(context), ), ), - ) - ], + ), + ) + ], ), ), ), - ); - } -} \ No newline at end of file + ); + } +} diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index ebfee9db0..a5619587e 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -1,11 +1,7 @@ -import 'dart:ffi'; - +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/block_explorer.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import '../db/hive/db.dart'; -import '../db/isar/main_db.dart'; -import '../models/isar/models/block_explorer.dart'; - Uri getDefaultBlockExplorerUrlFor({ required Coin coin, required String txid, @@ -24,7 +20,7 @@ Uri getDefaultBlockExplorerUrlFor({ case Coin.dogecoinTestNet: return Uri.parse("https://chain.so/tx/DOGETEST/$txid"); case Coin.epicCash: - // TODO: Handle this case. + // TODO: Handle this case. throw UnimplementedError("missing block explorer for epic cash"); case Coin.ethereum: return Uri.parse("https://etherscan.io/tx/$txid"); @@ -48,22 +44,28 @@ Uri getDefaultBlockExplorerUrlFor({ } } -Future setBlockExplorerForCoin( - {required Coin coin, required Uri url} - ) async { - await MainDB.instance.putTransactionBlockExplorer(TransactionBlockExplorer(ticker: coin.ticker, url: url.toString())); - return 0; +/// returns internal Isar ID for the inserted object/record +Future setBlockExplorerForCoin({ + required Coin coin, + required Uri url, +}) async { + return await MainDB.instance.putTransactionBlockExplorer( + TransactionBlockExplorer( + ticker: coin.ticker, + url: url.toString(), + ), + ); } Uri getBlockExplorerTransactionUrlFor({ required Coin coin, required String txid, }) { - var url = MainDB.instance.getTransactionBlockExplorer(coin: coin)?.url.toString(); + String? url = MainDB.instance.getTransactionBlockExplorer(coin: coin)?.url; if (url == null) { return getDefaultBlockExplorerUrlFor(coin: coin, txid: txid); } else { - url = url.replaceAll("%5BTXID%5D", txid); + url = url.replaceAll("%5BTXID%5D", txid); return Uri.parse(url); } } diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index ea4720b17..973541366 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -7,8 +7,6 @@ import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; import 'package:uuid/uuid.dart'; -import 'enums/coin_enum.dart'; - class Prefs extends ChangeNotifier { Prefs._(); static final Prefs _instance = Prefs._(); diff --git a/lib/widgets/choose_coin_view.dart b/lib/widgets/choose_coin_view.dart index b2e314f7c..baced09fe 100644 --- a/lib/widgets/choose_coin_view.dart +++ b/lib/widgets/choose_coin_view.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -65,7 +64,7 @@ class _ChooseCoinViewState extends ConsumerState { }, ), title: Text( - widget.title ?? "Choose Coin", + widget.title, style: STextStyles.navBarTitle(context), ), ), @@ -80,12 +79,7 @@ class _ChooseCoinViewState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ...coins.map( - (coin) { - final count = ref - .watch(nodeServiceChangeNotifierProvider - .select((value) => value.getNodesFor(coin))) - .length; - + (coin) { return Padding( padding: const EdgeInsets.all(4), child: RoundedWhiteContainer( @@ -98,7 +92,7 @@ class _ChooseCoinViewState extends ConsumerState { ), ), materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, + MaterialTapTargetSize.shrinkWrap, onPressed: () { Navigator.of(context).pushNamed( widget.nextRouteName, @@ -121,7 +115,7 @@ class _ChooseCoinViewState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "${coin.prettyName} ${widget.coinAdditional ?? ""}", + "${coin.prettyName} ${widget.coinAdditional}", style: STextStyles.titleBold12(context), ), ], @@ -141,4 +135,4 @@ class _ChooseCoinViewState extends ConsumerState { ), ); } -} \ No newline at end of file +} From 88d0638b432a9efc86ad76099ac0f576735e3d79 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 8 May 2023 14:01:04 -0600 Subject: [PATCH 61/65] fix: ios xcode project use version from pubspec.yaml --- ios/Runner.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index af2e7bf31..4f2f20dc9 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -458,7 +458,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 102; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -512,7 +512,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.5.28; + MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)"; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -645,7 +645,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 102; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -699,7 +699,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.5.28; + MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)"; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -724,7 +724,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 102; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -778,7 +778,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.5.28; + MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)"; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; From 2cf7d6de93fac54427e43c2eeb58faea56a06525 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 8 May 2023 16:15:39 -0600 Subject: [PATCH 62/65] fix: more bch zero conf special case fixes --- .../sub_widgets/transactions_list.dart | 10 ++++++++-- .../transaction_details_view.dart | 15 ++++++++------- .../coins/bitcoincash/bitcoincash_wallet.dart | 3 ++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/pages/wallet_view/sub_widgets/transactions_list.dart b/lib/pages/wallet_view/sub_widgets/transactions_list.dart index bdfd00394..45d446d2b 100644 --- a/lib/pages/wallet_view/sub_widgets/transactions_list.dart +++ b/lib/pages/wallet_view/sub_widgets/transactions_list.dart @@ -94,7 +94,10 @@ class _TransactionsListState extends ConsumerState { TransactionCard( // this may mess with combined firo transactions key: isConfirmed - ? Key(tx.txid + tx.type.name + tx.address.value.toString()) + ? Key(tx.txid + + tx.type.name + + tx.address.value.toString() + + tx.height.toString()) : UniqueKey(), // transaction: tx, walletId: widget.walletId, @@ -191,7 +194,10 @@ class _TransactionsListState extends ConsumerState { child: TransactionCard( // this may mess with combined firo transactions key: isConfirmed - ? Key(tx.txid + tx.type.name + tx.address.value.toString()) + ? Key(tx.txid + + tx.type.name + + tx.address.value.toString() + + tx.height.toString()) : UniqueKey(), transaction: tx, walletId: widget.walletId, diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 01a9a69e1..fbaba46ca 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -112,10 +112,11 @@ class _TransactionDetailsViewState super.dispose(); } - String whatIsIt(TransactionType type, int height) { + String whatIsIt(Transaction tx, int height) { + final type = tx.type; if (coin == Coin.firo || coin == Coin.firoTestNet) { - if (_transaction.subType == TransactionSubType.mint) { - if (_transaction.isConfirmed(height, coin.requiredConfirmations)) { + if (tx.subType == TransactionSubType.mint) { + if (tx.isConfirmed(height, coin.requiredConfirmations)) { return "Minted"; } else { return "Minting"; @@ -127,13 +128,13 @@ class _TransactionDetailsViewState // if (_transaction.isMinting) { // return "Minting"; // } else - if (_transaction.isConfirmed(height, coin.requiredConfirmations)) { + if (tx.isConfirmed(height, coin.requiredConfirmations)) { return "Received"; } else { return "Receiving"; } } else if (type == TransactionType.outgoing) { - if (_transaction.isConfirmed(height, coin.requiredConfirmations)) { + if (tx.isConfirmed(height, coin.requiredConfirmations)) { return "Sent"; } else { return "Sending"; @@ -428,7 +429,7 @@ class _TransactionDetailsViewState _transaction.isCancelled ? "Cancelled" : whatIsIt( - _transaction.type, + _transaction, currentHeight, ), style: @@ -545,7 +546,7 @@ class _TransactionDetailsViewState _transaction.isCancelled ? "Cancelled" : whatIsIt( - _transaction.type, + _transaction, currentHeight, ), style: isDesktop diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index e108352b4..516af2f34 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1958,7 +1958,8 @@ class BitcoinCashWallet extends CoinServiceAPI if (storedTx == null || storedTx.address.value == null || - storedTx.height == null + storedTx.height == null || + (storedTx.height != null && storedTx.height! <= 0) // zero conf messes this up // !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS) ) { From cd176d9fa23c06d3162585cc2785a7d0d38705f3 Mon Sep 17 00:00:00 2001 From: Diego Salazar Date: Mon, 8 May 2023 16:19:19 -0600 Subject: [PATCH 63/65] Bump version (v1.6.7, build 169 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index b9eb4b909..cffb97568 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.7.6+168 +version: 1.7.7+169 environment: sdk: ">=2.17.0 <3.0.0" From bdef196a196594b49dd36e5826bfde475bf4aa43 Mon Sep 17 00:00:00 2001 From: detherminal <76167420+detherminal@users.noreply.github.com> Date: Tue, 9 May 2023 16:09:28 +0300 Subject: [PATCH 64/65] docs: fix mistakes and improve quality --- docs/building.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/building.md b/docs/building.md index 4409980d7..3f5f4415a 100644 --- a/docs/building.md +++ b/docs/building.md @@ -4,7 +4,7 @@ Here you will find instructions on how to install the necessary tools for buildi ## Prerequisites -- The only OS supported for building is Ubuntu 20.04. Advanced users may also be able to build on other Debian-based distributions like Fedora 37. +- The only OS supported for building is Ubuntu 20.04. Advanced users may also be able to build on other Debian-based distributions like Linux Mint. - Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies) - 100 GB of storage @@ -54,6 +54,7 @@ sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2- Install [Rust](https://www.rust-lang.org/tools/install) with command: ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source ~/.bashrc rustup install 1.67.1 rustup default 1.67.1 ``` From 9ed458cf912962e6a09a89ac689525ac12e8348e Mon Sep 17 00:00:00 2001 From: detherminal <76167420+detherminal@users.noreply.github.com> Date: Tue, 9 May 2023 20:28:01 +0300 Subject: [PATCH 65/65] docs: add issue templates --- .github/ISSUE_TEMPLATE/bug_report.yml | 64 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.yml | 37 +++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..a319913a8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,64 @@ +name: 🐞 Bug Report +description: File a new bug report +title: 'Bug: ' +labels: [Bug] +body: + - type: markdown + attributes: + value: ':stop_sign: _For support questions, please visit the [Discord](https://discord.gg/mRPZuXx3At), [Telegram](https://t.me/stackwallet) or [Reddit](https://www.reddit.com/r/stackwallet/) instead._' + - type: checkboxes + attributes: + label: 'Is there an existing issue for this?' + description: 'Please [search :mag: the issues](https://github.com/cypherstack/stack_wallet/issues) to check if this bug has already been reported.' + options: + - label: 'I have searched the existing issues' + required: true + - type: textarea + attributes: + label: 'Current Behavior' + description: 'Describe the problem you are experiencing. **Please do not paste your logs here.** Screenshots are welcome.' + validations: + required: true + - type: textarea + attributes: + label: 'Expected Behavior' + description: 'Describe what you expect to happen instead.' + validations: + required: true + - type: textarea + attributes: + label: 'Reproduce Steps' + description: | + Please provide a the _smallest, complete steps_ that Stack Wallet's maintainers can run to reproduce the issue ([read more about what this entails](https://stackoverflow.com/help/minimal-reproducible-example)). Failing this, any sort of reproduction steps are better than nothing! + validations: + required: true + - type: textarea + attributes: + label: 'Environment' + description: 'Please provide the following information about your environment.' + value: | + - Operating system and version: + - Device platform and version: + - Real device or emulator/simulator: + validations: + required: true + - type: input + attributes: + label: 'Logs' + description: | + Create a [Gist](https://gist.github.com) which contains your _full_ Stack Wallet logs and link it here. + + :warning: _Remember to redact or remove any sensitive information!_ + placeholder: 'https://gist.github.com/...' + validations: + required: false + - type: textarea + attributes: + label: 'Further Information' + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + validations: + required: false + - type: markdown + attributes: + value: ':stop_sign: _For support questions, please visit the [Discord](https://discord.gg/mRPZuXx3At), [Telegram](https://t.me/stackwallet) or [Reddit](https://www.reddit.com/r/stackwallet/) instead._' \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..c2c87271b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,37 @@ +name: Feature request +description: Suggest an idea for this project +title: 'FR: <title>' +labels: [Feature Request] +body: + - type: textarea + attributes: + label: 'Is Problem' + description: 'Is your feature request related to a problem? Please describe.' + value: | + A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + validations: + required: false + - type: textarea + attributes: + label: 'Solution' + description: 'Describe the solution you'd like.' + value: | + A clear and concise description of what you want to happen. + validations: + required: true + - type: textarea + attributes: + label: 'Alternatives' + description: 'Describe alternatives you've considered.' + value: | + A clear and concise description of any alternative solutions or features you've considered. + validations: + required: true + - type: textarea + attributes: + label: 'Context' + description: 'Additional context.' + value: | + Add any other context or screenshots about the feature request here. + validations: + required: false