From 6126588e60cf0118b67f40298b239c229c4277e0 Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 2 Oct 2022 13:37:11 -0600 Subject: [PATCH 01/75] WIP simpleswap and generic exchange --- .../estimated_rate_exchange_form_state.dart | 2 +- .../currency.dart | 16 + .../exchange/response_objects/pair.dart | 35 ++ .../exchange/response_objects/range.dart | 32 ++ .../exchange/response_objects/trade.dart | 141 ++++++ .../exchange/simpleswap/sp_currency.dart | 99 ++++ .../fixed_rate_pair_coin_selection_view.dart | 2 +- ...floating_rate_currency_selection_view.dart | 2 +- lib/pages/exchange_view/exchange_view.dart | 2 +- .../wallet_initiated_exchange_view.dart | 2 +- .../available_currencies_state_provider.dart | 2 +- lib/services/change_now/change_now.dart | 2 +- lib/services/exchange/exchange.dart | 46 ++ lib/services/exchange/exchange_response.dart | 24 + .../exchange/simpleswap/simpleswap_api.dart | 423 ++++++++++++++++++ .../simpleswap/simpleswap_exchange.dart | 122 +++++ ...timated_rate_exchange_form_state_test.dart | 5 +- ...d_rate_exchange_form_state_test.mocks.dart | 3 +- .../exchange/exchange_view_test.mocks.dart | 3 +- .../bitcoincash_wallet_test.mocks.dart | 356 +++++++-------- 20 files changed, 1133 insertions(+), 186 deletions(-) rename lib/models/exchange/{change_now => response_objects}/currency.dart (85%) create mode 100644 lib/models/exchange/response_objects/pair.dart create mode 100644 lib/models/exchange/response_objects/range.dart create mode 100644 lib/models/exchange/response_objects/trade.dart create mode 100644 lib/models/exchange/simpleswap/sp_currency.dart create mode 100644 lib/services/exchange/exchange.dart create mode 100644 lib/services/exchange/exchange_response.dart create mode 100644 lib/services/exchange/simpleswap/simpleswap_api.dart create mode 100644 lib/services/exchange/simpleswap/simpleswap_exchange.dart diff --git a/lib/models/exchange/estimated_rate_exchange_form_state.dart b/lib/models/exchange/estimated_rate_exchange_form_state.dart index 3161d83c19..415ce8e97b 100644 --- a/lib/models/exchange/estimated_rate_exchange_form_state.dart +++ b/lib/models/exchange/estimated_rate_exchange_form_state.dart @@ -1,7 +1,7 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:stackwallet/models/exchange/change_now/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/services/change_now/change_now.dart'; import 'package:stackwallet/utilities/logger.dart'; diff --git a/lib/models/exchange/change_now/currency.dart b/lib/models/exchange/response_objects/currency.dart similarity index 85% rename from lib/models/exchange/change_now/currency.dart rename to lib/models/exchange/response_objects/currency.dart index add8728792..c1bb33976a 100644 --- a/lib/models/exchange/change_now/currency.dart +++ b/lib/models/exchange/response_objects/currency.dart @@ -5,12 +5,18 @@ class Currency { /// Currency name final String name; + /// Currency network + final String network; + /// Currency logo url final String image; /// Indicates if a currency has an Extra ID final bool hasExternalId; + /// external id if it exists + final String? externalId; + /// Indicates if a currency is a fiat currency (EUR, USD) final bool isFiat; @@ -30,8 +36,10 @@ class Currency { Currency({ required this.ticker, required this.name, + required this.network, required this.image, required this.hasExternalId, + this.externalId, required this.isFiat, required this.featured, required this.isStable, @@ -44,8 +52,10 @@ class Currency { return Currency( ticker: json["ticker"] as String, name: json["name"] as String, + network: json["network"] as String, image: json["image"] as String, hasExternalId: json["hasExternalId"] as bool, + externalId: json["externalId"] as String?, isFiat: json["isFiat"] as bool, featured: json["featured"] as bool, isStable: json["isStable"] as bool, @@ -61,8 +71,10 @@ class Currency { final map = { "ticker": ticker, "name": name, + "network": network, "image": image, "hasExternalId": hasExternalId, + "externalId": externalId, "isFiat": isFiat, "featured": featured, "isStable": isStable, @@ -79,8 +91,10 @@ class Currency { Currency copyWith({ String? ticker, String? name, + String? network, String? image, bool? hasExternalId, + String? externalId, bool? isFiat, bool? featured, bool? isStable, @@ -90,8 +104,10 @@ class Currency { return Currency( ticker: ticker ?? this.ticker, name: name ?? this.name, + network: network ?? this.network, image: image ?? this.image, hasExternalId: hasExternalId ?? this.hasExternalId, + externalId: externalId ?? this.externalId, isFiat: isFiat ?? this.isFiat, featured: featured ?? this.featured, isStable: isStable ?? this.isStable, diff --git a/lib/models/exchange/response_objects/pair.dart b/lib/models/exchange/response_objects/pair.dart new file mode 100644 index 0000000000..cbd97140a6 --- /dev/null +++ b/lib/models/exchange/response_objects/pair.dart @@ -0,0 +1,35 @@ +class Pair { + final String from; + final String fromNetwork; + + final String to; + final String toNetwork; + + final bool fixedRate; + final bool floatingRate; + + Pair({ + required this.from, + required this.fromNetwork, + required this.to, + required this.toNetwork, + required this.fixedRate, + required this.floatingRate, + }); + + Map toJson() { + return { + "from": from, + "fromNetwork": fromNetwork, + "to": to, + "toNetwork": toNetwork, + "fixedRate": fixedRate, + "floatingRate": floatingRate, + }; + } + + @override + String toString() { + return "Pair: ${toJson()}"; + } +} diff --git a/lib/models/exchange/response_objects/range.dart b/lib/models/exchange/response_objects/range.dart new file mode 100644 index 0000000000..715a3ecce7 --- /dev/null +++ b/lib/models/exchange/response_objects/range.dart @@ -0,0 +1,32 @@ +import 'package:decimal/decimal.dart'; + +class Range { + final Decimal? min; + final Decimal? max; + + Range({this.min, this.max}); + + Range copyWith({ + Decimal? min, + Decimal? max, + }) { + return Range( + min: min ?? this.min, + max: max ?? this.max, + ); + } + + Map toMap() { + final map = { + "min": min?.toString(), + "max": max?.toString(), + }; + + return map; + } + + @override + String toString() { + return "Range: ${toMap()}"; + } +} diff --git a/lib/models/exchange/response_objects/trade.dart b/lib/models/exchange/response_objects/trade.dart new file mode 100644 index 0000000000..617621c5e6 --- /dev/null +++ b/lib/models/exchange/response_objects/trade.dart @@ -0,0 +1,141 @@ +import 'package:hive/hive.dart'; + +@HiveType(typeId: Trade.typeId) +class Trade { + static const typeId = 22; + + @HiveField(0) + final String uuid; + + @HiveField(1) + final String tradeId; + + @HiveField(2) + final String rateType; + + @HiveField(3) + final String direction; + + @HiveField(4) + final DateTime timestamp; + + @HiveField(5) + final DateTime updatedAt; + + @HiveField(6) + final String from; + + @HiveField(7) + final String fromAmount; + + @HiveField(8) + final String fromAddress; + + @HiveField(9) + final String fromNetwork; + + @HiveField(10) + final String fromExtraId; + + @HiveField(11) + final String fromTxid; + + @HiveField(12) + final String to; + + @HiveField(13) + final String toAmount; + + @HiveField(14) + final String toAddress; + + @HiveField(15) + final String toNetwork; + + @HiveField(16) + final String toExtraId; + + @HiveField(17) + final String toTxid; + + @HiveField(18) + final String refundAddress; + + @HiveField(19) + final String refundExtraId; + + @HiveField(20) + final String status; + + const Trade({ + required this.uuid, + required this.tradeId, + required this.rateType, + required this.direction, + required this.timestamp, + required this.updatedAt, + required this.from, + required this.fromAmount, + required this.fromAddress, + required this.fromNetwork, + required this.fromExtraId, + required this.fromTxid, + required this.to, + required this.toAmount, + required this.toAddress, + required this.toNetwork, + required this.toExtraId, + required this.toTxid, + required this.refundAddress, + required this.refundExtraId, + required this.status, + }); + + Trade copyWith({ + String? uuid, + String? tradeId, + String? rateType, + String? direction, + DateTime? timestamp, + DateTime? updatedAt, + String? from, + String? fromAmount, + String? fromAddress, + String? fromNetwork, + String? fromExtraId, + String? fromTxid, + String? to, + String? toAmount, + String? toAddress, + String? toNetwork, + String? toExtraId, + String? toTxid, + String? refundAddress, + String? refundExtraId, + String? status, + }) { + return Trade( + uuid: uuid ?? this.uuid, + tradeId: tradeId ?? this.tradeId, + rateType: rateType ?? this.rateType, + direction: direction ?? this.direction, + timestamp: timestamp ?? this.timestamp, + updatedAt: updatedAt ?? this.updatedAt, + from: from ?? this.from, + fromAmount: fromAmount ?? this.fromAmount, + fromAddress: fromAddress ?? this.fromAddress, + fromNetwork: fromNetwork ?? this.fromNetwork, + fromExtraId: fromExtraId ?? this.fromExtraId, + fromTxid: fromTxid ?? this.fromTxid, + to: to ?? this.to, + toAmount: toAmount ?? this.toAmount, + toAddress: toAddress ?? this.toAddress, + toNetwork: toNetwork ?? this.toNetwork, + toExtraId: toExtraId ?? this.toExtraId, + toTxid: toTxid ?? this.toTxid, + refundAddress: refundAddress ?? this.refundAddress, + refundExtraId: refundExtraId ?? this.refundExtraId, + status: status ?? this.status, + ); + } +} diff --git a/lib/models/exchange/simpleswap/sp_currency.dart b/lib/models/exchange/simpleswap/sp_currency.dart new file mode 100644 index 0000000000..5aabc20d46 --- /dev/null +++ b/lib/models/exchange/simpleswap/sp_currency.dart @@ -0,0 +1,99 @@ +import 'package:stackwallet/utilities/logger.dart'; + +class SPCurrency { + /// currency name + final String name; + + /// currency symbol + final String symbol; + + /// currency network + final String network; + + /// has this currency extra id parameter + final bool hasExtraId; + + /// name of extra id (if exists) + final String? extraId; + + /// relative url for currency icon svg + final String image; + + /// informational messages about the currency they are changing + final List warningsFrom; + + /// informational messages about the currency for which they are exchanged + final List warningsTo; + + SPCurrency({ + required this.name, + required this.symbol, + required this.network, + required this.hasExtraId, + required this.extraId, + required this.image, + required this.warningsFrom, + required this.warningsTo, + }); + + factory SPCurrency.fromJson(Map json) { + try { + return SPCurrency( + name: json["name"] as String, + symbol: json["symbol"] as String, + network: json["network"] as String? ?? "", + hasExtraId: json["has_extra_id"] as bool, + extraId: json["extra_id"] as String?, + image: json["image"] as String, + warningsFrom: json["warnings_from"] as List, + warningsTo: json["warnings_to"] as List, + ); + } catch (e, s) { + Logging.instance.log("SPCurrency.fromJson failed to parse: $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Map toJson() { + final map = { + "name": name, + "symbol": symbol, + "network": network, + "has_extra_id": hasExtraId, + "extra_id": extraId, + "image": image, + "warnings_from": warningsFrom, + "warnings_to": warningsTo, + }; + + return map; + } + + SPCurrency copyWith({ + String? name, + String? symbol, + String? network, + bool? hasExtraId, + String? extraId, + String? image, + List? warningsFrom, + List? warningsTo, + }) { + return SPCurrency( + name: name ?? this.name, + symbol: symbol ?? this.symbol, + network: network ?? this.network, + hasExtraId: hasExtraId ?? this.hasExtraId, + extraId: extraId ?? this.extraId, + image: image ?? this.image, + warningsFrom: warningsFrom ?? this.warningsFrom, + warningsTo: warningsTo ?? this.warningsTo, + ); + } + + @override + String toString() { + return "SPCurrency: ${toJson()}"; + } +} diff --git a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart index 82a8ba0ea0..b3fe0c3ccd 100644 --- a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart +++ b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/change_now/currency.dart'; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; diff --git a/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart index 28ed9accbc..7c3b935b76 100644 --- a/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart +++ b/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/change_now/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart index 5e2be20b33..9281e8a2fe 100644 --- a/lib/pages/exchange_view/exchange_view.dart +++ b/lib/pages/exchange_view/exchange_view.dart @@ -8,9 +8,9 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; -import 'package:stackwallet/models/exchange/change_now/currency.dart'; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart'; diff --git a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart index 7db0493b58..79c24a3ba0 100644 --- a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart +++ b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart @@ -7,9 +7,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; -import 'package:stackwallet/models/exchange/change_now/currency.dart'; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart'; diff --git a/lib/providers/exchange/available_currencies_state_provider.dart b/lib/providers/exchange/available_currencies_state_provider.dart index 5b8395201c..e47fab8d6b 100644 --- a/lib/providers/exchange/available_currencies_state_provider.dart +++ b/lib/providers/exchange/available_currencies_state_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/change_now/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; final availableChangeNowCurrenciesStateProvider = StateProvider>((ref) => []); diff --git a/lib/services/change_now/change_now.dart b/lib/services/change_now/change_now.dart index 4b8dc5810e..c566ff8f13 100644 --- a/lib/services/change_now/change_now.dart +++ b/lib/services/change_now/change_now.dart @@ -7,11 +7,11 @@ import 'package:stackwallet/external_api_keys.dart'; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; import 'package:stackwallet/models/exchange/change_now/change_now_response.dart'; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; -import 'package:stackwallet/models/exchange/change_now/currency.dart'; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/utilities/logger.dart'; class ChangeNow { diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart new file mode 100644 index 0000000000..03b11445ff --- /dev/null +++ b/lib/services/exchange/exchange.dart @@ -0,0 +1,46 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; + +abstract class Exchange { + // + + Future>> getAllCurrencies(bool fixedRate); + + Future>> getPairsFor( + String currency, + bool fixedRate, + ); + + Future>> getAllPairs(bool fixedRate); + + Future> getTrade(String tradeId); + + Future>> getTrades(); + + Future> getMinMaxExchangeAmounts( + String from, + String to, + bool fixedRate, + ); + + Future> getEstimate( + String from, + String to, + Decimal amount, + bool fixedRate, + ); + + Future> createTrade({ + required String from, + required String to, + required bool fixedRate, + required Decimal amount, + required String addressTo, + required String addressRefund, + required String refundExtraId, + }); +} diff --git a/lib/services/exchange/exchange_response.dart b/lib/services/exchange/exchange_response.dart new file mode 100644 index 0000000000..59441fb9bd --- /dev/null +++ b/lib/services/exchange/exchange_response.dart @@ -0,0 +1,24 @@ +enum ExchangeExceptionType { generic, serializeResponseError } + +class ExchangeException implements Exception { + String errorMessage; + ExchangeExceptionType type; + ExchangeException(this.errorMessage, this.type); + + @override + String toString() { + return errorMessage; + } +} + +class ExchangeResponse { + late final T? value; + late final ExchangeException? exception; + + ExchangeResponse({this.value, this.exception}); + + @override + String toString() { + return "{error: $exception, value: $value}"; + } +} diff --git a/lib/services/exchange/simpleswap/simpleswap_api.dart b/lib/services/exchange/simpleswap/simpleswap_api.dart new file mode 100644 index 0000000000..26f02e70e5 --- /dev/null +++ b/lib/services/exchange/simpleswap/simpleswap_api.dart @@ -0,0 +1,423 @@ +import 'dart:convert'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; +import 'package:stackwallet/external_api_keys.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/models/exchange/simpleswap/sp_currency.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:tuple/tuple.dart'; +import 'package:uuid/uuid.dart'; + +class SimpleSwapAPI { + static const String scheme = "https"; + static const String authority = "api.simpleswap.io"; + + SimpleSwapAPI._(); + static final SimpleSwapAPI _instance = SimpleSwapAPI._(); + static SimpleSwapAPI get instance => _instance; + + /// set this to override using standard http client. Useful for testing + http.Client? client; + + Uri _buildUri(String path, Map? params) { + return Uri.https(authority, path, params); + } + + Future _makeGetRequest(Uri uri) async { + final client = this.client ?? http.Client(); + try { + final response = await client.get( + uri, + ); + + final parsed = jsonDecode(response.body); + + return parsed; + } catch (e, s) { + Logging.instance + .log("_makeRequest($uri) threw: $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + Future _makePostRequest( + Uri uri, + Map body, + ) async { + final client = this.client ?? http.Client(); + try { + final response = await client.post( + uri, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(body), + ); + + if (response.statusCode == 200) { + final parsed = jsonDecode(response.body); + return parsed; + } + + throw Exception("response: ${response.body}"); + } catch (e, s) { + Logging.instance + .log("_makeRequest($uri) threw: $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + Future> createNewExchange({ + required bool isFixedRate, + required String currencyFrom, + required String currencyTo, + required String addressTo, + required String userRefundAddress, + required String userRefundExtraId, + required String amount, + String? extraIdTo, + String? apiKey, + }) async { + Map body = { + "fixed": isFixedRate.toString(), + "currency_from": currencyFrom, + "currency_to": currencyTo, + "addressTo": addressTo, + "userRefundAddress": userRefundAddress, + "userRefundExtraId": userRefundExtraId, + "amount": amount, + }; + + final uri = + _buildUri("/create_exchange", {"api_key": apiKey ?? kSimpleSwapApiKey}); + + try { + final jsonObject = await _makePostRequest(uri, body); + print("================================"); + print(jsonObject); + print("================================"); + + final json = Map.from(jsonObject as Map); + final trade = Trade( + uuid: const Uuid().v1(), + tradeId: json["id"] as String, + rateType: json["type"] as String, + direction: "direct", + timestamp: DateTime.parse(json["timestamp"] as String), + updatedAt: DateTime.parse(json["updated_at"] as String), + from: json["currency_from"] as String, + fromAmount: json["amount_from"] as String, + fromAddress: json["address_from"] as String, + fromNetwork: "", + fromExtraId: json["extra_id_from"] as String, + fromTxid: json["tx_from"] as String, + to: json["currency_to"] as String, + toAmount: json["amount_to"] as String, + toAddress: json["address_to"] as String, + toNetwork: "", + toExtraId: json["extra_id_to"] as String, + toTxid: json["tx_to"] as String, + refundAddress: json["user_refund_address"] as String, + refundExtraId: json["user_refund_extra_id"] as String, + status: json["status"] as String, + ); + return ExchangeResponse(value: trade, exception: null); + } catch (e, s) { + Logging.instance.log("getAvailableCurrencies exception: $e\n$s", + level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + value: null, + ); + } + } + + Future>> getAllCurrencies({ + String? apiKey, + required bool fixedRate, + }) async { + final uri = _buildUri( + "/get_all_currencies", {"api_key": apiKey ?? kSimpleSwapApiKey}); + + try { + final jsonArray = await _makeGetRequest(uri); + + return await compute(_parseAvailableCurrenciesJson, jsonArray as List); + } catch (e, s) { + Logging.instance.log("getAvailableCurrencies exception: $e\n$s", + level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + ExchangeResponse> _parseAvailableCurrenciesJson( + List jsonArray) { + try { + List currencies = []; + + for (final json in jsonArray) { + try { + currencies + .add(SPCurrency.fromJson(Map.from(json as Map))); + } catch (_) { + return ExchangeResponse( + exception: ExchangeException("Failed to serialize $json", + ExchangeExceptionType.serializeResponseError)); + } + } + + return ExchangeResponse(value: currencies); + } catch (e, s) { + Logging.instance.log("_parseAvailableCurrenciesJson exception: $e\n$s", + level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future> getCurrency({ + required String symbol, + String? apiKey, + }) async { + final uri = _buildUri( + "/get_currency", + { + "api_key": apiKey ?? kSimpleSwapApiKey, + "symbol": symbol, + }, + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + return ExchangeResponse( + value: SPCurrency.fromJson( + Map.from(jsonObject as Map))); + } catch (e, s) { + Logging.instance + .log("getCurrency exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + /// returns a map where the key currency symbol is a valid pair with any of + /// the symbols in its value list + Future>> getAllPairs({ + required bool isFixedRate, + String? apiKey, + }) async { + final uri = _buildUri( + "/get_all_pairs", + { + "api_key": apiKey ?? kSimpleSwapApiKey, + "fixed": isFixedRate.toString(), + }, + ); + + try { + final jsonObject = await _makeGetRequest(uri); + final result = await compute( + _parseAvailablePairsJson, + Tuple2(jsonObject as Map, isFixedRate), + ); + return result; + } catch (e, s) { + Logging.instance + .log("getAllPairs exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + ExchangeResponse> _parseAvailablePairsJson( + Tuple2, bool> args, + ) { + try { + List pairs = []; + + for (final entry in args.item1.entries) { + try { + final from = entry.key as String; + for (final to in entry.value as List) { + pairs.add( + Pair( + from: from, + fromNetwork: "", + to: to as String, + toNetwork: "", + fixedRate: args.item2, + floatingRate: !args.item2, + ), + ); + } + } catch (_) { + return ExchangeResponse( + exception: ExchangeException("Failed to serialize $json", + ExchangeExceptionType.serializeResponseError)); + } + } + + return ExchangeResponse(value: pairs); + } catch (e, s) { + Logging.instance.log("_parseAvailableCurrenciesJson exception: $e\n$s", + level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + /// returns the estimated amount as a string + Future> getEstimated({ + required bool isFixedRate, + required String currencyFrom, + required String currencyTo, + required String amount, + String? apiKey, + }) async { + final uri = _buildUri( + "/get_estimated", + { + "api_key": apiKey ?? kSimpleSwapApiKey, + "fixed": isFixedRate.toString(), + "currency_from": currencyFrom, + "currency_to": currencyTo, + "amount": amount, + }, + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + return ExchangeResponse(value: jsonObject as String); + } catch (e, s) { + Logging.instance + .log("getEstimated exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + /// returns the exchange for the given id + Future> getExchange({ + required String exchangeId, + String? apiKey, + }) async { + final uri = _buildUri( + "/get_exchange", + { + "api_key": apiKey ?? kSimpleSwapApiKey, + "id": exchangeId, + }, + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + final json = Map.from(jsonObject as Map); + final trade = Trade( + uuid: const Uuid().v1(), + tradeId: json["id"] as String, + rateType: json["type"] as String, + direction: "direct", + timestamp: DateTime.parse(json["timestamp"] as String), + updatedAt: DateTime.parse(json["updated_at"] as String), + from: json["currency_from"] as String, + fromAmount: json["amount_from"] as String, + fromAddress: json["address_from"] as String, + fromNetwork: "", + fromExtraId: json["extra_id_from"] as String, + fromTxid: json["tx_from"] as String, + to: json["currency_to"] as String, + toAmount: json["amount_to"] as String, + toAddress: json["address_to"] as String, + toNetwork: "", + toExtraId: json["extra_id_to"] as String, + toTxid: json["tx_to"] as String, + refundAddress: json["user_refund_address"] as String, + refundExtraId: json["user_refund_extra_id"] as String, + status: json["status"] as String, + ); + + return ExchangeResponse(value: trade); + } catch (e, s) { + Logging.instance + .log("getExchange exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + /// returns the minimal exchange amount + Future> getRange({ + required bool isFixedRate, + required String currencyFrom, + required String currencyTo, + String? apiKey, + }) async { + final uri = _buildUri( + "/get_ranges", + { + "api_key": apiKey ?? kSimpleSwapApiKey, + "fixed": isFixedRate.toString(), + "currency_from": currencyFrom, + "currency_to": currencyTo, + }, + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + final json = Map.from(jsonObject as Map); + return ExchangeResponse( + value: Range( + max: Decimal.tryParse(json["max"] as String? ?? ""), + min: Decimal.tryParse(json["min"] as String? ?? ""), + ), + ); + } catch (e, s) { + Logging.instance.log("getRange exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } +} diff --git a/lib/services/exchange/simpleswap/simpleswap_exchange.dart b/lib/services/exchange/simpleswap/simpleswap_exchange.dart new file mode 100644 index 0000000000..18e8f41813 --- /dev/null +++ b/lib/services/exchange/simpleswap/simpleswap_exchange.dart @@ -0,0 +1,122 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/simpleswap/simpleswap_api.dart'; + +class SimpleSwapExchange extends Exchange { + @override + Future> createTrade({ + required String from, + required String to, + required bool fixedRate, + required Decimal amount, + required String addressTo, + required String addressRefund, + required String refundExtraId, + }) async { + return await SimpleSwapAPI.instance.createNewExchange( + isFixedRate: fixedRate, + currencyFrom: from, + currencyTo: to, + addressTo: addressTo, + userRefundAddress: addressRefund, + userRefundExtraId: refundExtraId, + amount: amount.toString(), + ); + } + + @override + Future>> getAllCurrencies( + bool fixedRate, + ) async { + final response = + await SimpleSwapAPI.instance.getAllCurrencies(fixedRate: fixedRate); + if (response.value != null) { + final List currencies = response.value! + .map((e) => Currency( + ticker: e.symbol, + name: e.name, + network: e.network, + image: e.image, + hasExternalId: e.hasExtraId, + externalId: e.extraId, + isFiat: false, + featured: false, + isStable: false, + supportsFixedRate: fixedRate, + )) + .toList(); + return ExchangeResponse>( + value: currencies, + exception: response.exception, + ); + } + + return ExchangeResponse>( + value: null, + exception: response.exception, + ); + } + + @override + Future>> getAllPairs(bool fixedRate) async { + return await SimpleSwapAPI.instance.getAllPairs(isFixedRate: fixedRate); + } + + @override + Future> getEstimate( + String from, + String to, + Decimal amount, + bool fixedRate, + ) async { + final response = await SimpleSwapAPI.instance.getEstimated( + isFixedRate: fixedRate, + currencyFrom: from, + currencyTo: to, + amount: amount.toString(), + ); + + return ExchangeResponse( + value: Decimal.tryParse(response.value ?? ""), + exception: response.exception, + ); + } + + @override + Future> getMinMaxExchangeAmounts( + String from, + String to, + bool fixedRate, + ) async { + return await SimpleSwapAPI.instance.getRange( + isFixedRate: fixedRate, + currencyFrom: from, + currencyTo: to, + ); + } + + @override + Future>> getPairsFor( + String currency, + bool fixedRate, + ) async { + // return await SimpleSwapAPI.instance.ge + throw UnimplementedError(); + } + + @override + Future> getTrade(String tradeId) async { + return await SimpleSwapAPI.instance.getExchange(exchangeId: tradeId); + } + + @override + Future>> getTrades() async { + // TODO: implement getTrades + throw UnimplementedError(); + } +} diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.dart index 3c6c869e59..fd10027800 100644 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.dart +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.dart @@ -3,9 +3,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:stackwallet/models/exchange/change_now/change_now_response.dart'; -import 'package:stackwallet/models/exchange/change_now/currency.dart'; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/estimated_rate_exchange_form_state.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/services/change_now/change_now.dart'; import 'estimated_rate_exchange_form_state_test.mocks.dart'; @@ -21,6 +21,7 @@ void main() { featured: false, isStable: true, supportsFixedRate: true, + network: '', ); final currencyB = Currency( ticker: "xmr", @@ -31,6 +32,7 @@ void main() { featured: false, isStable: true, supportsFixedRate: true, + network: '', ); final currencyC = Currency( ticker: "firo", @@ -41,6 +43,7 @@ void main() { featured: false, isStable: true, supportsFixedRate: true, + network: '', ); test("EstimatedRateExchangeFormState constructor", () async { diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart index 2e496569fe..632702c526 100644 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart @@ -13,7 +13,6 @@ import 'package:stackwallet/models/exchange/change_now/change_now_response.dart' as _i2; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' as _i9; -import 'package:stackwallet/models/exchange/change_now/currency.dart' as _i6; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart' as _i8; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' @@ -22,6 +21,8 @@ import 'package:stackwallet/models/exchange/change_now/exchange_transaction_stat as _i12; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' as _i10; +import 'package:stackwallet/models/exchange/response_objects/currency.dart' + as _i6; import 'package:stackwallet/services/change_now/change_now.dart' as _i3; // ignore_for_file: type=lint diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index 0295bfaedc..b3c6798f6d 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -14,7 +14,6 @@ import 'package:stackwallet/models/exchange/change_now/change_now_response.dart' as _i2; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' as _i17; -import 'package:stackwallet/models/exchange/change_now/currency.dart' as _i14; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart' as _i16; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' @@ -23,6 +22,8 @@ import 'package:stackwallet/models/exchange/change_now/exchange_transaction_stat as _i19; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' as _i18; +import 'package:stackwallet/models/exchange/response_objects/currency.dart' + as _i14; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart' as _i5; import 'package:stackwallet/services/change_now/change_now.dart' as _i12; diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart index fa38e1f9e4..f24f8be4c0 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart @@ -2,18 +2,18 @@ // in stackwallet/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart. // Do not manually edit this file. -import 'dart:async' as _i7; +import 'dart:async' as _i6; -import 'package:decimal/decimal.dart' as _i4; -import 'package:http/http.dart' as _i3; +import 'package:decimal/decimal.dart' as _i2; +import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i5; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i6; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5; import 'package:stackwallet/services/price.dart' as _i9; import 'package:stackwallet/services/transaction_notification_tracker.dart' as _i11; import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; -import 'package:stackwallet/utilities/prefs.dart' as _i2; +import 'package:stackwallet/utilities/prefs.dart' as _i3; import 'package:tuple/tuple.dart' as _i10; // ignore_for_file: type=lint @@ -26,145 +26,22 @@ import 'package:tuple/tuple.dart' as _i10; // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types -class _FakePrefs_0 extends _i1.Fake implements _i2.Prefs {} +class _FakeDecimal_0 extends _i1.Fake implements _i2.Decimal {} -class _FakeClient_1 extends _i1.Fake implements _i3.Client {} +class _FakePrefs_1 extends _i1.Fake implements _i3.Prefs {} -class _FakeDecimal_2 extends _i1.Fake implements _i4.Decimal {} +class _FakeClient_2 extends _i1.Fake implements _i4.Client {} -/// A class which mocks [CachedElectrumX]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockCachedElectrumX extends _i1.Mock implements _i5.CachedElectrumX { - MockCachedElectrumX() { - _i1.throwOnMissingStub(this); - } - - @override - String get server => - (super.noSuchMethod(Invocation.getter(#server), returnValue: '') - as String); - @override - int get port => - (super.noSuchMethod(Invocation.getter(#port), returnValue: 0) as int); - @override - bool get useSSL => - (super.noSuchMethod(Invocation.getter(#useSSL), returnValue: false) - as bool); - @override - _i2.Prefs get prefs => (super.noSuchMethod(Invocation.getter(#prefs), - returnValue: _FakePrefs_0()) as _i2.Prefs); - @override - List<_i6.ElectrumXNode> get failovers => - (super.noSuchMethod(Invocation.getter(#failovers), - returnValue: <_i6.ElectrumXNode>[]) as List<_i6.ElectrumXNode>); - @override - _i7.Future> getAnonymitySet( - {String? groupId, String? blockhash = r'', _i8.Coin? coin}) => - (super.noSuchMethod( - Invocation.method(#getAnonymitySet, [], - {#groupId: groupId, #blockhash: blockhash, #coin: coin}), - returnValue: - Future>.value({})) - as _i7.Future>); - @override - _i7.Future> getTransaction( - {String? txHash, _i8.Coin? coin, bool? verbose = true}) => - (super.noSuchMethod( - Invocation.method(#getTransaction, [], - {#txHash: txHash, #coin: coin, #verbose: verbose}), - returnValue: - Future>.value({})) - as _i7.Future>); - @override - _i7.Future> getUsedCoinSerials( - {_i8.Coin? coin, int? startNumber = 0}) => - (super.noSuchMethod( - Invocation.method(#getUsedCoinSerials, [], - {#coin: coin, #startNumber: startNumber}), - returnValue: Future>.value([])) - as _i7.Future>); - @override - _i7.Future clearSharedTransactionCache({_i8.Coin? coin}) => - (super.noSuchMethod( - Invocation.method(#clearSharedTransactionCache, [], {#coin: coin}), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i7.Future); -} - -/// A class which mocks [PriceAPI]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPriceAPI extends _i1.Mock implements _i9.PriceAPI { - MockPriceAPI() { - _i1.throwOnMissingStub(this); - } - - @override - _i3.Client get client => (super.noSuchMethod(Invocation.getter(#client), - returnValue: _FakeClient_1()) as _i3.Client); - @override - void resetLastCalledToForceNextCallToUpdateCache() => super.noSuchMethod( - Invocation.method(#resetLastCalledToForceNextCallToUpdateCache, []), - returnValueForMissingStub: null); - @override - _i7.Future>> - getPricesAnd24hChange({String? baseCurrency}) => (super.noSuchMethod( - Invocation.method( - #getPricesAnd24hChange, [], {#baseCurrency: baseCurrency}), - returnValue: - Future>>.value( - <_i8.Coin, _i10.Tuple2<_i4.Decimal, double>>{})) - as _i7.Future>>); -} - -/// A class which mocks [TransactionNotificationTracker]. +/// A class which mocks [ElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockTransactionNotificationTracker extends _i1.Mock - implements _i11.TransactionNotificationTracker { - MockTransactionNotificationTracker() { +class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { + MockElectrumX() { _i1.throwOnMissingStub(this); } @override - String get walletId => - (super.noSuchMethod(Invocation.getter(#walletId), returnValue: '') - as String); - @override - List get pendings => - (super.noSuchMethod(Invocation.getter(#pendings), returnValue: []) - as List); - @override - List get confirmeds => (super - .noSuchMethod(Invocation.getter(#confirmeds), returnValue: []) - as List); - @override - bool wasNotifiedPending(String? txid) => - (super.noSuchMethod(Invocation.method(#wasNotifiedPending, [txid]), - returnValue: false) as bool); - @override - _i7.Future addNotifiedPending(String? txid) => - (super.noSuchMethod(Invocation.method(#addNotifiedPending, [txid]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i7.Future); - @override - bool wasNotifiedConfirmed(String? txid) => - (super.noSuchMethod(Invocation.method(#wasNotifiedConfirmed, [txid]), - returnValue: false) as bool); - @override - _i7.Future addNotifiedConfirmed(String? txid) => - (super.noSuchMethod(Invocation.method(#addNotifiedConfirmed, [txid]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i7.Future); -} - -/// A class which mocks [ElectrumX]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockElectrumX extends _i1.Mock implements _i6.ElectrumX { - @override - set failovers(List<_i6.ElectrumXNode>? _failovers) => + set failovers(List<_i5.ElectrumXNode>? _failovers) => super.noSuchMethod(Invocation.setter(#failovers, _failovers), returnValueForMissingStub: null); @override @@ -186,7 +63,7 @@ class MockElectrumX extends _i1.Mock implements _i6.ElectrumX { (super.noSuchMethod(Invocation.getter(#useSSL), returnValue: false) as bool); @override - _i7.Future request( + _i6.Future request( {String? command, List? args = const [], Duration? connectionTimeout = const Duration(seconds: 60), @@ -200,9 +77,9 @@ class MockElectrumX extends _i1.Mock implements _i6.ElectrumX { #requestID: requestID, #retries: retries }), - returnValue: Future.value()) as _i7.Future); + returnValue: Future.value()) as _i6.Future); @override - _i7.Future>> batchRequest( + _i6.Future>> batchRequest( {String? command, Map>? args, Duration? connectionTimeout = const Duration(seconds: 60), @@ -216,86 +93,86 @@ class MockElectrumX extends _i1.Mock implements _i6.ElectrumX { }), returnValue: Future>>.value( >[])) - as _i7.Future>>); + as _i6.Future>>); @override - _i7.Future ping({String? requestID, int? retryCount = 1}) => + _i6.Future ping({String? requestID, int? retryCount = 1}) => (super.noSuchMethod( Invocation.method( #ping, [], {#requestID: requestID, #retryCount: retryCount}), - returnValue: Future.value(false)) as _i7.Future); + returnValue: Future.value(false)) as _i6.Future); @override - _i7.Future> getBlockHeadTip({String? requestID}) => + _i6.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method(#getBlockHeadTip, [], {#requestID: requestID}), returnValue: Future>.value({})) - as _i7.Future>); + as _i6.Future>); @override - _i7.Future> getServerFeatures({String? requestID}) => + _i6.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method(#getServerFeatures, [], {#requestID: requestID}), returnValue: - Future>.value({})) as _i7 + Future>.value({})) as _i6 .Future>); @override - _i7.Future broadcastTransaction({String? rawTx, String? requestID}) => + _i6.Future broadcastTransaction({String? rawTx, String? requestID}) => (super.noSuchMethod( Invocation.method(#broadcastTransaction, [], {#rawTx: rawTx, #requestID: requestID}), - returnValue: Future.value('')) as _i7.Future); + returnValue: Future.value('')) as _i6.Future); @override - _i7.Future> getBalance( + _i6.Future> getBalance( {String? scripthash, String? requestID}) => (super.noSuchMethod( Invocation.method(#getBalance, [], {#scripthash: scripthash, #requestID: requestID}), returnValue: Future>.value({})) - as _i7.Future>); + as _i6.Future>); @override - _i7.Future>> getHistory( + _i6.Future>> getHistory( {String? scripthash, String? requestID}) => (super.noSuchMethod( Invocation.method(#getHistory, [], {#scripthash: scripthash, #requestID: requestID}), returnValue: Future>>.value( >[])) - as _i7.Future>>); + as _i6.Future>>); @override - _i7.Future>>> getBatchHistory( + _i6.Future>>> getBatchHistory( {Map>? args}) => (super.noSuchMethod( Invocation.method(#getBatchHistory, [], {#args: args}), returnValue: Future>>>.value( - >>{})) as _i7 + >>{})) as _i6 .Future>>>); @override - _i7.Future>> getUTXOs( + _i6.Future>> getUTXOs( {String? scripthash, String? requestID}) => (super.noSuchMethod( Invocation.method( #getUTXOs, [], {#scripthash: scripthash, #requestID: requestID}), returnValue: Future>>.value( - >[])) as _i7 + >[])) as _i6 .Future>>); @override - _i7.Future>>> getBatchUTXOs( + _i6.Future>>> getBatchUTXOs( {Map>? args}) => (super.noSuchMethod(Invocation.method(#getBatchUTXOs, [], {#args: args}), returnValue: Future>>>.value( - >>{})) as _i7 + >>{})) as _i6 .Future>>>); @override - _i7.Future> getTransaction( + _i6.Future> getTransaction( {String? txHash, bool? verbose = true, String? requestID}) => (super.noSuchMethod( Invocation.method(#getTransaction, [], {#txHash: txHash, #verbose: verbose, #requestID: requestID}), returnValue: Future>.value({})) - as _i7.Future>); + as _i6.Future>); @override - _i7.Future> getAnonymitySet( + _i6.Future> getAnonymitySet( {String? groupId = r'1', String? blockhash = r'', String? requestID}) => @@ -307,42 +184,169 @@ class MockElectrumX extends _i1.Mock implements _i6.ElectrumX { }), returnValue: Future>.value({})) - as _i7.Future>); + as _i6.Future>); @override - _i7.Future getMintData({dynamic mints, String? requestID}) => + _i6.Future getMintData({dynamic mints, String? requestID}) => (super.noSuchMethod( Invocation.method( #getMintData, [], {#mints: mints, #requestID: requestID}), - returnValue: Future.value()) as _i7.Future); + returnValue: Future.value()) as _i6.Future); @override - _i7.Future> getUsedCoinSerials( + _i6.Future> getUsedCoinSerials( {String? requestID, int? startNumber}) => (super.noSuchMethod( Invocation.method(#getUsedCoinSerials, [], {#requestID: requestID, #startNumber: startNumber}), returnValue: Future>.value({})) - as _i7.Future>); + as _i6.Future>); @override - _i7.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( + _i6.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method(#getLatestCoinId, [], {#requestID: requestID}), - returnValue: Future.value(0)) as _i7.Future); + returnValue: Future.value(0)) as _i6.Future); @override - _i7.Future> getFeeRate({String? requestID}) => (super + _i6.Future> getFeeRate({String? requestID}) => (super .noSuchMethod(Invocation.method(#getFeeRate, [], {#requestID: requestID}), returnValue: - Future>.value({})) as _i7 + Future>.value({})) as _i6 .Future>); @override - _i7.Future<_i4.Decimal> estimateFee({String? requestID, int? blocks}) => + _i6.Future<_i2.Decimal> estimateFee({String? requestID, int? blocks}) => (super.noSuchMethod( Invocation.method( #estimateFee, [], {#requestID: requestID, #blocks: blocks}), - returnValue: Future<_i4.Decimal>.value(_FakeDecimal_2())) - as _i7.Future<_i4.Decimal>); + returnValue: Future<_i2.Decimal>.value(_FakeDecimal_0())) + as _i6.Future<_i2.Decimal>); @override - _i7.Future<_i4.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i6.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method(#relayFee, [], {#requestID: requestID}), - returnValue: Future<_i4.Decimal>.value(_FakeDecimal_2())) - as _i7.Future<_i4.Decimal>); + returnValue: Future<_i2.Decimal>.value(_FakeDecimal_0())) + as _i6.Future<_i2.Decimal>); +} + +/// A class which mocks [CachedElectrumX]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { + MockCachedElectrumX() { + _i1.throwOnMissingStub(this); + } + + @override + String get server => + (super.noSuchMethod(Invocation.getter(#server), returnValue: '') + as String); + @override + int get port => + (super.noSuchMethod(Invocation.getter(#port), returnValue: 0) as int); + @override + bool get useSSL => + (super.noSuchMethod(Invocation.getter(#useSSL), returnValue: false) + as bool); + @override + _i3.Prefs get prefs => (super.noSuchMethod(Invocation.getter(#prefs), + returnValue: _FakePrefs_1()) as _i3.Prefs); + @override + List<_i5.ElectrumXNode> get failovers => + (super.noSuchMethod(Invocation.getter(#failovers), + returnValue: <_i5.ElectrumXNode>[]) as List<_i5.ElectrumXNode>); + @override + _i6.Future> getAnonymitySet( + {String? groupId, String? blockhash = r'', _i8.Coin? coin}) => + (super.noSuchMethod( + Invocation.method(#getAnonymitySet, [], + {#groupId: groupId, #blockhash: blockhash, #coin: coin}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future> getTransaction( + {String? txHash, _i8.Coin? coin, bool? verbose = true}) => + (super.noSuchMethod( + Invocation.method(#getTransaction, [], + {#txHash: txHash, #coin: coin, #verbose: verbose}), + returnValue: + Future>.value({})) + as _i6.Future>); + @override + _i6.Future> getUsedCoinSerials( + {_i8.Coin? coin, int? startNumber = 0}) => + (super.noSuchMethod( + Invocation.method(#getUsedCoinSerials, [], + {#coin: coin, #startNumber: startNumber}), + returnValue: Future>.value([])) + as _i6.Future>); + @override + _i6.Future clearSharedTransactionCache({_i8.Coin? coin}) => + (super.noSuchMethod( + Invocation.method(#clearSharedTransactionCache, [], {#coin: coin}), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); +} + +/// A class which mocks [PriceAPI]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPriceAPI extends _i1.Mock implements _i9.PriceAPI { + MockPriceAPI() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Client get client => (super.noSuchMethod(Invocation.getter(#client), + returnValue: _FakeClient_2()) as _i4.Client); + @override + void resetLastCalledToForceNextCallToUpdateCache() => super.noSuchMethod( + Invocation.method(#resetLastCalledToForceNextCallToUpdateCache, []), + returnValueForMissingStub: null); + @override + _i6.Future>> + getPricesAnd24hChange({String? baseCurrency}) => (super.noSuchMethod( + Invocation.method( + #getPricesAnd24hChange, [], {#baseCurrency: baseCurrency}), + returnValue: + Future>>.value( + <_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>{})) + as _i6.Future>>); +} + +/// A class which mocks [TransactionNotificationTracker]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTransactionNotificationTracker extends _i1.Mock + implements _i11.TransactionNotificationTracker { + MockTransactionNotificationTracker() { + _i1.throwOnMissingStub(this); + } + + @override + String get walletId => + (super.noSuchMethod(Invocation.getter(#walletId), returnValue: '') + as String); + @override + List get pendings => + (super.noSuchMethod(Invocation.getter(#pendings), returnValue: []) + as List); + @override + List get confirmeds => (super + .noSuchMethod(Invocation.getter(#confirmeds), returnValue: []) + as List); + @override + bool wasNotifiedPending(String? txid) => + (super.noSuchMethod(Invocation.method(#wasNotifiedPending, [txid]), + returnValue: false) as bool); + @override + _i6.Future addNotifiedPending(String? txid) => + (super.noSuchMethod(Invocation.method(#addNotifiedPending, [txid]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); + @override + bool wasNotifiedConfirmed(String? txid) => + (super.noSuchMethod(Invocation.method(#wasNotifiedConfirmed, [txid]), + returnValue: false) as bool); + @override + _i6.Future addNotifiedConfirmed(String? txid) => + (super.noSuchMethod(Invocation.method(#addNotifiedConfirmed, [txid]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i6.Future); } From 65cedbac02f035a266fa37b80d73512d3d25e250 Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 2 Oct 2022 14:02:10 -0600 Subject: [PATCH 02/75] simpleswap impl --- .../exchange/response_objects/trade.dart | 31 +++++++++++++++++++ .../exchange/simpleswap/simpleswap_api.dart | 16 +++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/lib/models/exchange/response_objects/trade.dart b/lib/models/exchange/response_objects/trade.dart index 617621c5e6..89a5b47931 100644 --- a/lib/models/exchange/response_objects/trade.dart +++ b/lib/models/exchange/response_objects/trade.dart @@ -138,4 +138,35 @@ class Trade { status: status ?? this.status, ); } + + Map toMap() { + return { + "uuid,": uuid, + "tradeId,": tradeId, + "rateType,": rateType, + "direction,": direction, + "timestamp,": timestamp, + "updatedAt,": updatedAt, + "from,": from, + "fromAmount,": fromAmount, + "fromAddress,": fromAddress, + "fromNetwork,": fromNetwork, + "fromExtraId,": fromExtraId, + "fromTxid,": fromTxid, + "to,": to, + "toAmount,": toAmount, + "toAddress,": toAddress, + "toNetwork,": toNetwork, + "toExtraId,": toExtraId, + "toTxid,": toTxid, + "refundAddress,": refundAddress, + "refundExtraId,": refundExtraId, + "status,": status, + }; + } + + @override + String toString() { + return toMap().toString(); + } } diff --git a/lib/services/exchange/simpleswap/simpleswap_api.dart b/lib/services/exchange/simpleswap/simpleswap_api.dart index 26f02e70e5..ae0a926fbd 100644 --- a/lib/services/exchange/simpleswap/simpleswap_api.dart +++ b/lib/services/exchange/simpleswap/simpleswap_api.dart @@ -36,6 +36,7 @@ class SimpleSwapAPI { ); final parsed = jsonDecode(response.body); + print("PARSED: $parsed"); return parsed; } catch (e, s) { @@ -81,14 +82,14 @@ class SimpleSwapAPI { String? extraIdTo, String? apiKey, }) async { - Map body = { - "fixed": isFixedRate.toString(), + Map body = { + "fixed": isFixedRate, "currency_from": currencyFrom, "currency_to": currencyTo, "addressTo": addressTo, "userRefundAddress": userRefundAddress, "userRefundExtraId": userRefundExtraId, - "amount": amount, + "amount": double.parse(amount), }; final uri = @@ -118,7 +119,7 @@ class SimpleSwapAPI { toAmount: json["amount_to"] as String, toAddress: json["address_to"] as String, toNetwork: "", - toExtraId: json["extra_id_to"] as String, + toExtraId: json["extra_id_to"] as String? ?? "", toTxid: json["tx_to"] as String, refundAddress: json["user_refund_address"] as String, refundExtraId: json["user_refund_extra_id"] as String, @@ -346,13 +347,14 @@ class SimpleSwapAPI { final jsonObject = await _makeGetRequest(uri); final json = Map.from(jsonObject as Map); + final ts = DateTime.parse(json["timestamp"] as String); final trade = Trade( uuid: const Uuid().v1(), tradeId: json["id"] as String, rateType: json["type"] as String, direction: "direct", - timestamp: DateTime.parse(json["timestamp"] as String), - updatedAt: DateTime.parse(json["updated_at"] as String), + timestamp: ts, + updatedAt: DateTime.tryParse(json["updated_at"] as String? ?? "") ?? ts, from: json["currency_from"] as String, fromAmount: json["amount_from"] as String, fromAddress: json["address_from"] as String, @@ -363,7 +365,7 @@ class SimpleSwapAPI { toAmount: json["amount_to"] as String, toAddress: json["address_to"] as String, toNetwork: "", - toExtraId: json["extra_id_to"] as String, + toExtraId: json["extra_id_to"] as String? ?? "", toTxid: json["tx_to"] as String, refundAddress: json["user_refund_address"] as String, refundExtraId: json["user_refund_extra_id"] as String, From cdcda481bf81c87286a0ae1bd3064eb052f43305 Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 2 Oct 2022 14:46:50 -0600 Subject: [PATCH 03/75] changenow trade to generic trade db migration --- lib/hive/db.dart | 4 + .../change_now/exchange_transaction.dart | 2 + .../exchange/response_objects/trade.dart | 141 +++++++++--------- .../exchange/simpleswap/simpleswap_api.dart | 48 +++--- lib/utilities/db_version_migration.dart | 68 ++++++--- 5 files changed, 148 insertions(+), 115 deletions(-) diff --git a/lib/hive/db.dart b/lib/hive/db.dart index c8e148923c..062d029a50 100644 --- a/lib/hive/db.dart +++ b/lib/hive/db.dart @@ -4,6 +4,7 @@ import 'package:cw_core/wallet_info.dart' as xmr; import 'package:hive/hive.dart'; import 'package:mutex/mutex.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/notification_model.dart'; import 'package:stackwallet/models/trade_wallet_lookup.dart'; @@ -22,6 +23,7 @@ class DB { "watchedTxNotificationModels"; static const String boxNameWatchedTrades = "watchedTradesNotificationModels"; static const String boxNameTrades = "exchangeTransactionsBox"; + static const String boxNameTradesV2 = "exchangeTradesBox"; static const String boxNameTradeNotes = "tradeNotesBox"; static const String boxNameTradeLookup = "tradeToTxidLookUpBox"; static const String boxNameFavoriteWallets = "favoriteWallets"; @@ -48,6 +50,7 @@ class DB { late final Box _boxWatchedTransactions; late final Box _boxWatchedTrades; late final Box _boxTrades; + late final Box _boxTradesV2; late final Box _boxTradeNotes; late final Box _boxFavoriteWallets; late final Box _walletInfoSource; @@ -125,6 +128,7 @@ class DB { _boxWatchedTrades = await Hive.openBox(boxNameWatchedTrades); _boxTrades = await Hive.openBox(boxNameTrades); + _boxTradesV2 = await Hive.openBox(boxNameTradesV2); _boxTradeNotes = await Hive.openBox(boxNameTradeNotes); _boxTradeLookup = await Hive.openBox(boxNameTradeLookup); diff --git a/lib/models/exchange/change_now/exchange_transaction.dart b/lib/models/exchange/change_now/exchange_transaction.dart index 0ea153876b..233c242c92 100644 --- a/lib/models/exchange/change_now/exchange_transaction.dart +++ b/lib/models/exchange/change_now/exchange_transaction.dart @@ -5,6 +5,8 @@ import 'package:uuid/uuid.dart'; part '../../type_adaptors/exchange_transaction.g.dart'; +@Deprecated( + "Do not use. Migrated to Trade in db_version_migration to hive_data_version 2") // @HiveType(typeId: 13) class ExchangeTransaction { /// You can use it to get transaction status at the Transaction status API endpoint diff --git a/lib/models/exchange/response_objects/trade.dart b/lib/models/exchange/response_objects/trade.dart index 89a5b47931..b8b9ad8e7f 100644 --- a/lib/models/exchange/response_objects/trade.dart +++ b/lib/models/exchange/response_objects/trade.dart @@ -23,40 +23,40 @@ class Trade { final DateTime updatedAt; @HiveField(6) - final String from; + final String payInCurrency; @HiveField(7) - final String fromAmount; + final String payInAmount; @HiveField(8) - final String fromAddress; + final String payInAddress; @HiveField(9) - final String fromNetwork; + final String payInNetwork; @HiveField(10) - final String fromExtraId; + final String payInExtraId; @HiveField(11) - final String fromTxid; + final String payInTxid; @HiveField(12) - final String to; + final String payOutCurrency; @HiveField(13) - final String toAmount; + final String payOutAmount; @HiveField(14) - final String toAddress; + final String payOutAddress; @HiveField(15) - final String toNetwork; + final String payOutNetwork; @HiveField(16) - final String toExtraId; + final String payOutExtraId; @HiveField(17) - final String toTxid; + final String payOutTxid; @HiveField(18) final String refundAddress; @@ -74,65 +74,64 @@ class Trade { required this.direction, required this.timestamp, required this.updatedAt, - required this.from, - required this.fromAmount, - required this.fromAddress, - required this.fromNetwork, - required this.fromExtraId, - required this.fromTxid, - required this.to, - required this.toAmount, - required this.toAddress, - required this.toNetwork, - required this.toExtraId, - required this.toTxid, + required this.payInCurrency, + required this.payInAmount, + required this.payInAddress, + required this.payInNetwork, + required this.payInExtraId, + required this.payInTxid, + required this.payOutCurrency, + required this.payOutAmount, + required this.payOutAddress, + required this.payOutNetwork, + required this.payOutExtraId, + required this.payOutTxid, required this.refundAddress, required this.refundExtraId, required this.status, }); Trade copyWith({ - String? uuid, String? tradeId, String? rateType, String? direction, DateTime? timestamp, DateTime? updatedAt, - String? from, - String? fromAmount, - String? fromAddress, - String? fromNetwork, - String? fromExtraId, - String? fromTxid, - String? to, - String? toAmount, - String? toAddress, - String? toNetwork, - String? toExtraId, - String? toTxid, + String? payInCurrency, + String? payInAmount, + String? payInAddress, + String? payInNetwork, + String? payInExtraId, + String? payInTxid, + String? payOutCurrency, + String? payOutAmount, + String? payOutAddress, + String? payOutNetwork, + String? payOutExtraId, + String? payOutTxid, String? refundAddress, String? refundExtraId, String? status, }) { return Trade( - uuid: uuid ?? this.uuid, + uuid: uuid, tradeId: tradeId ?? this.tradeId, rateType: rateType ?? this.rateType, direction: direction ?? this.direction, timestamp: timestamp ?? this.timestamp, updatedAt: updatedAt ?? this.updatedAt, - from: from ?? this.from, - fromAmount: fromAmount ?? this.fromAmount, - fromAddress: fromAddress ?? this.fromAddress, - fromNetwork: fromNetwork ?? this.fromNetwork, - fromExtraId: fromExtraId ?? this.fromExtraId, - fromTxid: fromTxid ?? this.fromTxid, - to: to ?? this.to, - toAmount: toAmount ?? this.toAmount, - toAddress: toAddress ?? this.toAddress, - toNetwork: toNetwork ?? this.toNetwork, - toExtraId: toExtraId ?? this.toExtraId, - toTxid: toTxid ?? this.toTxid, + payInCurrency: payInCurrency ?? this.payInCurrency, + payInAmount: payInAmount ?? this.payInAmount, + payInAddress: payInAddress ?? this.payInAddress, + payInNetwork: payInNetwork ?? this.payInNetwork, + payInExtraId: payInExtraId ?? this.payInExtraId, + payInTxid: payInTxid ?? this.payInTxid, + payOutCurrency: payOutCurrency ?? this.payOutCurrency, + payOutAmount: payOutAmount ?? this.payOutAmount, + payOutAddress: payOutAddress ?? this.payOutAddress, + payOutNetwork: payOutNetwork ?? this.payOutNetwork, + payOutExtraId: payOutExtraId ?? this.payOutExtraId, + payOutTxid: payOutTxid ?? this.payOutTxid, refundAddress: refundAddress ?? this.refundAddress, refundExtraId: refundExtraId ?? this.refundExtraId, status: status ?? this.status, @@ -141,27 +140,27 @@ class Trade { Map toMap() { return { - "uuid,": uuid, - "tradeId,": tradeId, - "rateType,": rateType, - "direction,": direction, - "timestamp,": timestamp, - "updatedAt,": updatedAt, - "from,": from, - "fromAmount,": fromAmount, - "fromAddress,": fromAddress, - "fromNetwork,": fromNetwork, - "fromExtraId,": fromExtraId, - "fromTxid,": fromTxid, - "to,": to, - "toAmount,": toAmount, - "toAddress,": toAddress, - "toNetwork,": toNetwork, - "toExtraId,": toExtraId, - "toTxid,": toTxid, - "refundAddress,": refundAddress, - "refundExtraId,": refundExtraId, - "status,": status, + "uuid": uuid, + "tradeId": tradeId, + "rateType": rateType, + "direction": direction, + "timestamp": timestamp, + "updatedAt": updatedAt, + "payInCurrency": payInCurrency, + "payInAmount": payInAmount, + "payInAddress": payInAddress, + "payInNetwork": payInNetwork, + "payInExtraId": payInExtraId, + "payInTxid": payInTxid, + "payOutCurrency": payOutCurrency, + "payOutAmount": payOutAmount, + "payOutAddress": payOutAddress, + "payOutNetwork": payOutNetwork, + "payOutExtraId": payOutExtraId, + "payOutTxid": payOutTxid, + "refundAddress": refundAddress, + "refundExtraId": refundExtraId, + "status": status, }; } diff --git a/lib/services/exchange/simpleswap/simpleswap_api.dart b/lib/services/exchange/simpleswap/simpleswap_api.dart index ae0a926fbd..3465765c94 100644 --- a/lib/services/exchange/simpleswap/simpleswap_api.dart +++ b/lib/services/exchange/simpleswap/simpleswap_api.dart @@ -109,18 +109,18 @@ class SimpleSwapAPI { direction: "direct", timestamp: DateTime.parse(json["timestamp"] as String), updatedAt: DateTime.parse(json["updated_at"] as String), - from: json["currency_from"] as String, - fromAmount: json["amount_from"] as String, - fromAddress: json["address_from"] as String, - fromNetwork: "", - fromExtraId: json["extra_id_from"] as String, - fromTxid: json["tx_from"] as String, - to: json["currency_to"] as String, - toAmount: json["amount_to"] as String, - toAddress: json["address_to"] as String, - toNetwork: "", - toExtraId: json["extra_id_to"] as String? ?? "", - toTxid: json["tx_to"] as String, + payInCurrency: json["currency_from"] as String, + payInAmount: json["amount_from"] as String, + payInAddress: json["address_from"] as String, + payInNetwork: "", + payInExtraId: json["extra_id_payIn"] as String, + payInTxid: json["tx_from"] as String, + payOutCurrency: json["currency_to"] as String, + payOutAmount: json["amount_to"] as String, + payOutAddress: json["address_to"] as String, + payOutNetwork: "", + payOutExtraId: json["extra_id_to"] as String? ?? "", + payOutTxid: json["tx_to"] as String, refundAddress: json["user_refund_address"] as String, refundExtraId: json["user_refund_extra_id"] as String, status: json["status"] as String, @@ -355,18 +355,18 @@ class SimpleSwapAPI { direction: "direct", timestamp: ts, updatedAt: DateTime.tryParse(json["updated_at"] as String? ?? "") ?? ts, - from: json["currency_from"] as String, - fromAmount: json["amount_from"] as String, - fromAddress: json["address_from"] as String, - fromNetwork: "", - fromExtraId: json["extra_id_from"] as String, - fromTxid: json["tx_from"] as String, - to: json["currency_to"] as String, - toAmount: json["amount_to"] as String, - toAddress: json["address_to"] as String, - toNetwork: "", - toExtraId: json["extra_id_to"] as String? ?? "", - toTxid: json["tx_to"] as String, + payInCurrency: json["currency_from"] as String, + payInAmount: json["amount_from"] as String, + payInAddress: json["address_from"] as String, + payInNetwork: "", + payInExtraId: json["extra_id_payIn"] as String, + payInTxid: json["tx_from"] as String, + payOutCurrency: json["currency_to"] as String, + payOutAmount: json["amount_to"] as String, + payOutAddress: json["address_to"] as String, + payOutNetwork: "", + payOutExtraId: json["extra_id_to"] as String? ?? "", + payOutTxid: json["tx_to"] as String, refundAddress: json["user_refund_address"] as String, refundExtraId: json["user_refund_extra_id"] as String, status: json["status"] as String, diff --git a/lib/utilities/db_version_migration.dart b/lib/utilities/db_version_migration.dart index e3867ecc44..db12b0f377 100644 --- a/lib/utilities/db_version_migration.dart +++ b/lib/utilities/db_version_migration.dart @@ -2,6 +2,8 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/lelantus_coin.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/services/node_service.dart'; @@ -114,26 +116,52 @@ class DbVersionMigrator { // try to continue migrating return await migrate(1); - // case 1: - // await Hive.openBox(DB.boxNameAllWalletsData); - // final walletsService = WalletsService(); - // final walletInfoList = await walletsService.walletNames; - // for (final walletInfo in walletInfoList.values) { - // if (walletInfo.coin == Coin.firo) { - // await Hive.openBox(walletInfo.walletId); - // await DB.instance.delete( - // key: "latest_tx_model", boxName: walletInfo.walletId); - // await DB.instance.delete( - // key: "latest_lelantus_tx_model", boxName: walletInfo.walletId); - // } - // } - // - // // update version - // await DB.instance.put( - // boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 2); - // - // // try to continue migrating - // return await migrate(2); + case 1: + await Hive.openBox(DB.boxNameTrades); + await Hive.openBox(DB.boxNameTradesV2); + final trades = + DB.instance.values(boxName: DB.boxNameTrades); + + for (final old in trades) { + if (old.statusObject != null) { + final trade = Trade( + uuid: old.uuid, + tradeId: old.id, + rateType: "", + direction: "direct", + timestamp: old.date, + updatedAt: DateTime.tryParse(old.statusObject?.updatedAt ?? "") ?? + old.date, + payInCurrency: old.fromCurrency, + payInAmount: old.statusObject!.expectedSendAmountDecimal, + payInAddress: old.payinAddress, + payInNetwork: "", + payInExtraId: old.payinExtraId, + payInTxid: "", + payOutCurrency: old.toCurrency, + payOutAmount: old.amount, + payOutAddress: old.payoutAddress, + payOutNetwork: "", + payOutExtraId: old.payoutExtraId, + payOutTxid: "", + refundAddress: old.refundAddress, + refundExtraId: old.refundExtraId, + status: old.statusObject!.status.name, + ); + await DB.instance.put( + boxName: DB.boxNameTradesV2, + key: trade.uuid, + value: trade, + ); + } + } + + // update version + await DB.instance.put( + boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 2); + + // try to continue migrating + return await migrate(2); default: // finally return From 9af457d1c69cc9013047259b541736aabc201be0 Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 2 Oct 2022 14:53:53 -0600 Subject: [PATCH 04/75] refactor changenow api --- .../change_now/change_now_response.dart | 24 -- .../estimated_rate_exchange_form_state.dart | 8 +- .../fixed_rate_exchange_form_state.dart | 5 +- .../exchange_step_views/step_3_view.dart | 4 +- lib/pages/home_view/home_view.dart | 2 +- lib/pages/wallet_view/wallet_view.dart | 2 +- .../exchange/change_now_provider.dart | 5 +- .../change_now/change_now_api.dart} | 218 +++++++++--------- .../change_now/change_now_exchange.dart | 79 +++++++ .../change_now_loading_service.dart | 0 lib/services/notifications_service.dart | 6 +- ...timated_rate_exchange_form_state_test.dart | 16 +- ...d_rate_exchange_form_state_test.mocks.dart | 137 ++++++----- .../exchange/exchange_view_test.dart | 4 +- .../exchange/exchange_view_test.mocks.dart | 136 +++++------ test/services/change_now/change_now_test.dart | 188 +++++++-------- 16 files changed, 447 insertions(+), 387 deletions(-) delete mode 100644 lib/models/exchange/change_now/change_now_response.dart rename lib/services/{change_now/change_now.dart => exchange/change_now/change_now_api.dart} (77%) create mode 100644 lib/services/exchange/change_now/change_now_exchange.dart rename lib/services/{ => exchange}/change_now/change_now_loading_service.dart (100%) diff --git a/lib/models/exchange/change_now/change_now_response.dart b/lib/models/exchange/change_now/change_now_response.dart deleted file mode 100644 index a45a8d665e..0000000000 --- a/lib/models/exchange/change_now/change_now_response.dart +++ /dev/null @@ -1,24 +0,0 @@ -enum ChangeNowExceptionType { generic, serializeResponseError } - -class ChangeNowException implements Exception { - String errorMessage; - ChangeNowExceptionType type; - ChangeNowException(this.errorMessage, this.type); - - @override - String toString() { - return errorMessage; - } -} - -class ChangeNowResponse { - late final T? value; - late final ChangeNowException? exception; - - ChangeNowResponse({this.value, this.exception}); - - @override - String toString() { - return "{ error: $exception, value: $value }"; - } -} diff --git a/lib/models/exchange/estimated_rate_exchange_form_state.dart b/lib/models/exchange/estimated_rate_exchange_form_state.dart index 415ce8e97b..aed815de8e 100644 --- a/lib/models/exchange/estimated_rate_exchange_form_state.dart +++ b/lib/models/exchange/estimated_rate_exchange_form_state.dart @@ -2,12 +2,12 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -import 'package:stackwallet/services/change_now/change_now.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/utilities/logger.dart'; class EstimatedRateExchangeFormState extends ChangeNotifier { /// used in testing to inject mock - ChangeNow? cnTesting; + ChangeNowAPI? cnTesting; Decimal? _fromAmount; Decimal? _toAmount; @@ -195,7 +195,7 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { required Currency to, }) async { final response = - await (cnTesting ?? ChangeNow.instance).getEstimatedExchangeAmount( + await (cnTesting ?? ChangeNowAPI.instance).getEstimatedExchangeAmount( fromTicker: from.ticker, toTicker: to.ticker, fromAmount: fromAmount, @@ -234,7 +234,7 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { required Currency from, required Currency to, }) async { - final response = await (cnTesting ?? ChangeNow.instance) + final response = await (cnTesting ?? ChangeNowAPI.instance) .getMinimalExchangeAmount(fromTicker: from.ticker, toTicker: to.ticker); if (response.value != null) { diff --git a/lib/models/exchange/fixed_rate_exchange_form_state.dart b/lib/models/exchange/fixed_rate_exchange_form_state.dart index b751931584..1ae6fac96a 100644 --- a/lib/models/exchange/fixed_rate_exchange_form_state.dart +++ b/lib/models/exchange/fixed_rate_exchange_form_state.dart @@ -2,7 +2,7 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/cupertino.dart'; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; -import 'package:stackwallet/services/change_now/change_now.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/utilities/logger.dart'; class FixedRateExchangeFormState extends ChangeNotifier { @@ -153,7 +153,8 @@ class FixedRateExchangeFormState extends ChangeNotifier { } if (amount != null && market != null && amount > Decimal.zero) { - final response = await ChangeNow.instance.getEstimatedExchangeAmountV2( + final response = + await ChangeNowAPI.instance.getEstimatedExchangeAmountV2( fromTicker: market!.from, toTicker: market!.to, fromOrTo: direction, diff --git a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart index a7b34571b1..0716a9d3b5 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/change_now/change_now_response.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_4_view.dart'; @@ -10,6 +9,7 @@ import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet. import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; import 'package:stackwallet/providers/exchange/change_now_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/notifications_api.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; @@ -243,7 +243,7 @@ class _Step3ViewState extends ConsumerState { ), ); - ChangeNowResponse + ExchangeResponse response; if (model.rateType == ExchangeRateType.estimated) { diff --git a/lib/pages/home_view/home_view.dart b/lib/pages/home_view/home_view.dart index b561207ab9..2503d05dc2 100644 --- a/lib/pages/home_view/home_view.dart +++ b/lib/pages/home_view/home_view.dart @@ -13,7 +13,7 @@ import 'package:stackwallet/pages/wallets_view/wallets_view.dart'; import 'package:stackwallet/providers/global/notifications_provider.dart'; import 'package:stackwallet/providers/ui/home_view_index_provider.dart'; import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; -import 'package:stackwallet/services/change_now/change_now_loading_service.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_loading_service.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 95dfc4a981..1a1918557e 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -26,12 +26,12 @@ import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart'; -import 'package:stackwallet/services/change_now/change_now_loading_service.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_loading_service.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; diff --git a/lib/providers/exchange/change_now_provider.dart b/lib/providers/exchange/change_now_provider.dart index 759692c137..858ddc6647 100644 --- a/lib/providers/exchange/change_now_provider.dart +++ b/lib/providers/exchange/change_now_provider.dart @@ -1,4 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/services/change_now/change_now.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; -final changeNowProvider = Provider((ref) => ChangeNow.instance); +final changeNowProvider = + Provider((ref) => ChangeNowAPI.instance); diff --git a/lib/services/change_now/change_now.dart b/lib/services/exchange/change_now/change_now_api.dart similarity index 77% rename from lib/services/change_now/change_now.dart rename to lib/services/exchange/change_now/change_now_api.dart index c566ff8f13..e88114aa5e 100644 --- a/lib/services/change_now/change_now.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -5,24 +5,24 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:stackwallet/external_api_keys.dart'; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; -import 'package:stackwallet/models/exchange/change_now/change_now_response.dart'; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/utilities/logger.dart'; -class ChangeNow { +class ChangeNowAPI { static const String scheme = "https"; static const String authority = "api.changenow.io"; static const String apiVersion = "/v1"; static const String apiVersionV2 = "/v2"; - ChangeNow._(); - static final ChangeNow _instance = ChangeNow._(); - static ChangeNow get instance => _instance; + ChangeNowAPI._(); + static final ChangeNowAPI _instance = ChangeNowAPI._(); + static ChangeNowAPI get instance => _instance; /// set this to override using standard http client. Useful for testing http.Client? client; @@ -100,7 +100,7 @@ class ChangeNow { /// /// Set [active] to true to return only active currencies. /// Set [fixedRate] to true to return only currencies available on a fixed-rate flow. - Future>> getAvailableCurrencies({ + Future>> getAvailableCurrencies({ bool? fixedRate, bool? active, }) async { @@ -129,26 +129,26 @@ class ChangeNow { } catch (e, s) { Logging.instance.log("getAvailableCurrencies exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( "Error: $jsonArray", - ChangeNowExceptionType.serializeResponseError, + ExchangeExceptionType.serializeResponseError, ), ); } } catch (e, s) { Logging.instance.log("getAvailableCurrencies exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( e.toString(), - ChangeNowExceptionType.generic, + ExchangeExceptionType.generic, ), ); } } - ChangeNowResponse> _parseAvailableCurrenciesJson( + ExchangeResponse> _parseAvailableCurrenciesJson( List jsonArray) { try { List currencies = []; @@ -158,13 +158,13 @@ class ChangeNow { currencies .add(Currency.fromJson(Map.from(json as Map))); } catch (_) { - return ChangeNowResponse( - exception: ChangeNowException("Failed to serialize $json", - ChangeNowExceptionType.serializeResponseError)); + return ExchangeResponse( + exception: ExchangeException("Failed to serialize $json", + ExchangeExceptionType.serializeResponseError)); } } - return ChangeNowResponse(value: currencies); + return ExchangeResponse(value: currencies); } catch (_) { rethrow; } @@ -175,7 +175,7 @@ class ChangeNow { /// /// Required [ticker] to fetch paired currencies for. /// Set [fixedRate] to true to return only currencies available on a fixed-rate flow. - Future>> getPairedCurrencies({ + Future>> getPairedCurrencies({ required String ticker, bool? fixedRate, }) async { @@ -199,10 +199,10 @@ class ChangeNow { currencies .add(Currency.fromJson(Map.from(json as Map))); } catch (_) { - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( "Failed to serialize $json", - ChangeNowExceptionType.serializeResponseError, + ExchangeExceptionType.serializeResponseError, ), ); } @@ -210,18 +210,18 @@ class ChangeNow { } catch (e, s) { Logging.instance.log("getPairedCurrencies exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException("Error: $jsonArray", - ChangeNowExceptionType.serializeResponseError)); + return ExchangeResponse( + exception: ExchangeException("Error: $jsonArray", + ExchangeExceptionType.serializeResponseError)); } - return ChangeNowResponse(value: currencies); + return ExchangeResponse(value: currencies); } catch (e, s) { Logging.instance .log("getPairedCurrencies exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( e.toString(), - ChangeNowExceptionType.generic, + ExchangeExceptionType.generic, ), ); } @@ -230,7 +230,7 @@ class ChangeNow { /// The API endpoint returns minimal payment amount required to make /// an exchange of [fromTicker] to [toTicker]. /// If you try to exchange less, the transaction will most likely fail. - Future> getMinimalExchangeAmount({ + Future> getMinimalExchangeAmount({ required String fromTicker, required String toTicker, String? apiKey, @@ -245,22 +245,22 @@ class ChangeNow { try { final value = Decimal.parse(json["minAmount"].toString()); - return ChangeNowResponse(value: value); + return ExchangeResponse(value: value); } catch (_) { - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( "Failed to serialize $json", - ChangeNowExceptionType.serializeResponseError, + ExchangeExceptionType.serializeResponseError, ), ); } } catch (e, s) { Logging.instance.log("getMinimalExchangeAmount exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( e.toString(), - ChangeNowExceptionType.generic, + ExchangeExceptionType.generic, ), ); } @@ -268,8 +268,7 @@ class ChangeNow { /// Get estimated amount of [toTicker] cryptocurrency to receive /// for [fromAmount] of [fromTicker] - Future> - getEstimatedExchangeAmount({ + Future> getEstimatedExchangeAmount({ required String fromTicker, required String toTicker, required Decimal fromAmount, @@ -289,22 +288,22 @@ class ChangeNow { try { final value = EstimatedExchangeAmount.fromJson( Map.from(json as Map)); - return ChangeNowResponse(value: value); + return ExchangeResponse(value: value); } catch (_) { - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( "Failed to serialize $json", - ChangeNowExceptionType.serializeResponseError, + ExchangeExceptionType.serializeResponseError, ), ); } } catch (e, s) { Logging.instance.log("getEstimatedExchangeAmount exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( e.toString(), - ChangeNowExceptionType.generic, + ExchangeExceptionType.generic, ), ); } @@ -313,7 +312,7 @@ class ChangeNow { // old v1 version /// This API endpoint returns fixed-rate estimated exchange amount of /// [toTicker] cryptocurrency to receive for [fromAmount] of [fromTicker] - // Future> + // Future> // getEstimatedFixedRateExchangeAmount({ // required String fromTicker, // required String toTicker, @@ -342,12 +341,12 @@ class ChangeNow { // try { // final value = EstimatedExchangeAmount.fromJson( // Map.from(json as Map)); - // return ChangeNowResponse(value: value); + // return ExchangeResponse(value: value); // } catch (_) { - // return ChangeNowResponse( - // exception: ChangeNowException( + // return ExchangeResponse( + // exception: ExchangeException( // "Failed to serialize $json", - // ChangeNowExceptionType.serializeResponseError, + // ExchangeExceptionType.serializeResponseError, // ), // ); // } @@ -355,10 +354,10 @@ class ChangeNow { // Logging.instance.log( // "getEstimatedFixedRateExchangeAmount exception: $e\n$s", // level: LogLevel.Error); - // return ChangeNowResponse( - // exception: ChangeNowException( + // return ExchangeResponse( + // exception: ExchangeException( // e.toString(), - // ChangeNowExceptionType.generic, + // ExchangeExceptionType.generic, // ), // ); // } @@ -366,7 +365,7 @@ class ChangeNow { /// Get estimated amount of [toTicker] cryptocurrency to receive /// for [fromAmount] of [fromTicker] - Future> getEstimatedExchangeAmountV2({ + Future> getEstimatedExchangeAmountV2({ required String fromTicker, required String toTicker, required CNEstimateType fromOrTo, @@ -413,22 +412,22 @@ class ChangeNow { try { final value = CNExchangeEstimate.fromJson(Map.from(json as Map)); - return ChangeNowResponse(value: value); + return ExchangeResponse(value: value); } catch (_) { - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( "Failed to serialize $json", - ChangeNowExceptionType.serializeResponseError, + ExchangeExceptionType.serializeResponseError, ), ); } } catch (e, s) { Logging.instance.log("getEstimatedExchangeAmountV2 exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( e.toString(), - ChangeNowExceptionType.generic, + ExchangeExceptionType.generic, ), ); } @@ -438,8 +437,7 @@ class ChangeNow { /// fixed-rate flow. Some currencies get enabled or disabled from time to /// time and the market info gets updates, so make sure to refresh the list /// occasionally. One time per minute is sufficient. - Future>> - getAvailableFixedRateMarkets({ + Future>> getAvailableFixedRateMarkets({ String? apiKey, }) async { final uri = _buildUri( @@ -456,26 +454,26 @@ class ChangeNow { } catch (e, s) { Logging.instance.log("getAvailableFixedRateMarkets exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( "Error: $jsonArray", - ChangeNowExceptionType.serializeResponseError, + ExchangeExceptionType.serializeResponseError, ), ); } } catch (e, s) { Logging.instance.log("getAvailableFixedRateMarkets exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( e.toString(), - ChangeNowExceptionType.generic, + ExchangeExceptionType.generic, ), ); } } - ChangeNowResponse> _parseFixedRateMarketsJson( + ExchangeResponse> _parseFixedRateMarketsJson( List jsonArray) { try { List markets = []; @@ -484,12 +482,12 @@ class ChangeNow { markets.add( FixedRateMarket.fromJson(Map.from(json as Map))); } catch (_) { - return ChangeNowResponse( - exception: ChangeNowException("Failed to serialize $json", - ChangeNowExceptionType.serializeResponseError)); + return ExchangeResponse( + exception: ExchangeException("Failed to serialize $json", + ExchangeExceptionType.serializeResponseError)); } } - return ChangeNowResponse(value: markets); + return ExchangeResponse(value: markets); } catch (_) { rethrow; } @@ -497,7 +495,7 @@ class ChangeNow { /// The API endpoint creates a transaction, generates an address for /// sending funds and returns transaction attributes. - Future> + Future> createStandardExchangeTransaction({ required String fromTicker, required String toTicker, @@ -535,12 +533,12 @@ class ChangeNow { try { final value = ExchangeTransaction.fromJson( Map.from(json as Map)); - return ChangeNowResponse(value: value); + return ExchangeResponse(value: value); } catch (_) { - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( "Failed to serialize $json", - ChangeNowExceptionType.serializeResponseError, + ExchangeExceptionType.serializeResponseError, ), ); } @@ -548,10 +546,10 @@ class ChangeNow { Logging.instance.log( "createStandardExchangeTransaction exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( e.toString(), - ChangeNowExceptionType.generic, + ExchangeExceptionType.generic, ), ); } @@ -559,7 +557,7 @@ class ChangeNow { /// The API endpoint creates a transaction, generates an address for /// sending funds and returns transaction attributes. - Future> + Future> createFixedRateExchangeTransaction({ required String fromTicker, required String toTicker, @@ -600,12 +598,12 @@ class ChangeNow { try { final value = ExchangeTransaction.fromJson( Map.from(json as Map)); - return ChangeNowResponse(value: value); + return ExchangeResponse(value: value); } catch (_) { - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( "Failed to serialize $json", - ChangeNowExceptionType.serializeResponseError, + ExchangeExceptionType.serializeResponseError, ), ); } @@ -613,16 +611,16 @@ class ChangeNow { Logging.instance.log( "createFixedRateExchangeTransaction exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( e.toString(), - ChangeNowExceptionType.generic, + ExchangeExceptionType.generic, ), ); } } - Future> getTransactionStatus({ + Future> getTransactionStatus({ required String id, String? apiKey, }) async { @@ -636,28 +634,28 @@ class ChangeNow { try { final value = ExchangeTransactionStatus.fromJson( Map.from(json as Map)); - return ChangeNowResponse(value: value); + return ExchangeResponse(value: value); } catch (_) { - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( "Failed to serialize $json", - ChangeNowExceptionType.serializeResponseError, + ExchangeExceptionType.serializeResponseError, ), ); } } catch (e, s) { Logging.instance .log("getTransactionStatus exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( e.toString(), - ChangeNowExceptionType.generic, + ExchangeExceptionType.generic, ), ); } } - Future>> + Future>> getAvailableFloatingRatePairs({ bool includePartners = false, }) async { @@ -675,26 +673,26 @@ class ChangeNow { } catch (e, s) { Logging.instance.log("getAvailableFloatingRatePairs exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( "Error: $jsonArray", - ChangeNowExceptionType.serializeResponseError, + ExchangeExceptionType.serializeResponseError, ), ); } } catch (e, s) { Logging.instance.log("getAvailableFloatingRatePairs exception: $e\n$s", level: LogLevel.Error); - return ChangeNowResponse( - exception: ChangeNowException( + return ExchangeResponse( + exception: ExchangeException( e.toString(), - ChangeNowExceptionType.generic, + ExchangeExceptionType.generic, ), ); } } - ChangeNowResponse> + ExchangeResponse> _parseAvailableFloatingRatePairsJson(List jsonArray) { try { List pairs = []; @@ -704,12 +702,12 @@ class ChangeNow { pairs.add(AvailableFloatingRatePair( fromTicker: stringPair[0], toTicker: stringPair[1])); } catch (_) { - return ChangeNowResponse( - exception: ChangeNowException("Failed to serialize $json", - ChangeNowExceptionType.serializeResponseError)); + return ExchangeResponse( + exception: ExchangeException("Failed to serialize $json", + ExchangeExceptionType.serializeResponseError)); } } - return ChangeNowResponse(value: pairs); + return ExchangeResponse(value: pairs); } catch (_) { rethrow; } diff --git a/lib/services/exchange/change_now/change_now_exchange.dart b/lib/services/exchange/change_now/change_now_exchange.dart new file mode 100644 index 0000000000..5072db5283 --- /dev/null +++ b/lib/services/exchange/change_now/change_now_exchange.dart @@ -0,0 +1,79 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; + +class ChangeNowExchange extends Exchange { + @override + Future> createTrade({ + required String from, + required String to, + required bool fixedRate, + required Decimal amount, + required String addressTo, + required String addressRefund, + required String refundExtraId, + }) async { + // TODO: implement createTrade + throw UnimplementedError(); + } + + @override + Future>> getAllCurrencies( + bool fixedRate, + ) async { + // TODO: implement getAllCurrencies + throw UnimplementedError(); + } + + @override + Future>> getAllPairs(bool fixedRate) async { + // TODO: implement getAllPairs + throw UnimplementedError(); + } + + @override + Future> getEstimate( + String from, + String to, + Decimal amount, + bool fixedRate, + ) async { + // TODO: implement getEstimate + throw UnimplementedError(); + } + + @override + Future> getMinMaxExchangeAmounts( + String from, + String to, + bool fixedRate, + ) async { + // TODO: implement getMinMaxExchangeAmounts + throw UnimplementedError(); + } + + @override + Future>> getPairsFor( + String currency, + bool fixedRate, + ) async { + // TODO: implement getPairsFor + throw UnimplementedError(); + } + + @override + Future> getTrade(String tradeId) async { + // TODO: implement getTrade + throw UnimplementedError(); + } + + @override + Future>> getTrades() async { + // TODO: implement getTrades + throw UnimplementedError(); + } +} diff --git a/lib/services/change_now/change_now_loading_service.dart b/lib/services/exchange/change_now/change_now_loading_service.dart similarity index 100% rename from lib/services/change_now/change_now_loading_service.dart rename to lib/services/exchange/change_now/change_now_loading_service.dart diff --git a/lib/services/notifications_service.dart b/lib/services/notifications_service.dart index 977d046a2f..aea849bdb4 100644 --- a/lib/services/notifications_service.dart +++ b/lib/services/notifications_service.dart @@ -5,7 +5,7 @@ import 'package:stackwallet/electrumx_rpc/electrumx.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; import 'package:stackwallet/models/notification_model.dart'; -import 'package:stackwallet/services/change_now/change_now.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/notifications_api.dart'; import 'package:stackwallet/services/trade_service.dart'; @@ -17,7 +17,7 @@ class NotificationsService extends ChangeNotifier { late NodeService nodeService; late TradesService tradesService; late Prefs prefs; - late ChangeNow changeNow; + late ChangeNowAPI changeNow; NotificationsService._(); static final NotificationsService _instance = NotificationsService._(); @@ -27,7 +27,7 @@ class NotificationsService extends ChangeNotifier { required NodeService nodeService, required TradesService tradesService, required Prefs prefs, - required ChangeNow changeNow, + required ChangeNowAPI changeNow, }) async { this.nodeService = nodeService; this.tradesService = tradesService; diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.dart index fd10027800..f36cab9758 100644 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.dart +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.dart @@ -2,15 +2,15 @@ import 'package:decimal/decimal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:stackwallet/models/exchange/change_now/change_now_response.dart'; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/estimated_rate_exchange_form_state.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -import 'package:stackwallet/services/change_now/change_now.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'estimated_rate_exchange_form_state_test.mocks.dart'; -@GenerateMocks([ChangeNow]) +@GenerateMocks([ChangeNowAPI]) void main() { final currencyA = Currency( ticker: "btc", @@ -98,7 +98,7 @@ void main() { state.cnTesting = cn; when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) - .thenAnswer((_) async => ChangeNowResponse(value: Decimal.fromInt(42))); + .thenAnswer((_) async => ExchangeResponse(value: Decimal.fromInt(42))); await state.updateFrom(currencyA, true); await state.updateTo(currencyB, true); @@ -125,7 +125,7 @@ void main() { state.cnTesting = cn; when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) - .thenAnswer((_) async => ChangeNowResponse()); + .thenAnswer((_) async => ExchangeResponse()); await state.updateFrom(currencyA, true); await state.updateTo(currencyB, true); @@ -152,7 +152,7 @@ void main() { state.cnTesting = cn; when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) - .thenAnswer((_) async => ChangeNowResponse(value: Decimal.fromInt(42))); + .thenAnswer((_) async => ExchangeResponse(value: Decimal.fromInt(42))); await state.updateFrom(currencyA, true); await state.setFromAmountAndCalculateToAmount(Decimal.parse("10.10"), true); @@ -180,12 +180,12 @@ void main() { state.cnTesting = cn; when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) - .thenAnswer((_) async => ChangeNowResponse(value: Decimal.fromInt(42))); + .thenAnswer((_) async => ExchangeResponse(value: Decimal.fromInt(42))); when(cn.getEstimatedExchangeAmount( fromTicker: "btc", toTicker: "xmr", fromAmount: Decimal.parse("110.10"))) - .thenAnswer((_) async => ChangeNowResponse( + .thenAnswer((_) async => ExchangeResponse( value: EstimatedExchangeAmount( transactionSpeedForecast: '10-60', rateId: 'some rate id', diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart index 632702c526..cceac1f624 100644 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart @@ -9,8 +9,6 @@ import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart' as _i13; -import 'package:stackwallet/models/exchange/change_now/change_now_response.dart' - as _i2; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' as _i9; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart' @@ -23,7 +21,9 @@ import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' as _i10; import 'package:stackwallet/models/exchange/response_objects/currency.dart' as _i6; -import 'package:stackwallet/services/change_now/change_now.dart' as _i3; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart' + as _i3; +import 'package:stackwallet/services/exchange/exchange_response.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -36,12 +36,12 @@ import 'package:stackwallet/services/change_now/change_now.dart' as _i3; // ignore_for_file: camel_case_types class _FakeChangeNowResponse_0 extends _i1.Fake - implements _i2.ChangeNowResponse {} + implements _i2.ExchangeResponse {} /// A class which mocks [ChangeNow]. /// /// See the documentation for Mockito's code generation for more information. -class MockChangeNow extends _i1.Mock implements _i3.ChangeNow { +class MockChangeNow extends _i1.Mock implements _i3.ChangeNowAPI { MockChangeNow() { _i1.throwOnMissingStub(this); } @@ -51,25 +51,25 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNow { super.noSuchMethod(Invocation.setter(#client, _client), returnValueForMissingStub: null); @override - _i5.Future<_i2.ChangeNowResponse>> getAvailableCurrencies( + _i5.Future<_i2.ExchangeResponse>> getAvailableCurrencies( {bool? fixedRate, bool? active}) => (super.noSuchMethod( Invocation.method(#getAvailableCurrencies, [], {#fixedRate: fixedRate, #active: active}), - returnValue: Future<_i2.ChangeNowResponse>>.value( + returnValue: Future<_i2.ExchangeResponse>>.value( _FakeChangeNowResponse_0>())) as _i5 - .Future<_i2.ChangeNowResponse>>); + .Future<_i2.ExchangeResponse>>); @override - _i5.Future<_i2.ChangeNowResponse>> getPairedCurrencies( + _i5.Future<_i2.ExchangeResponse>> getPairedCurrencies( {String? ticker, bool? fixedRate}) => (super.noSuchMethod( Invocation.method(#getPairedCurrencies, [], {#ticker: ticker, #fixedRate: fixedRate}), - returnValue: Future<_i2.ChangeNowResponse>>.value( + returnValue: Future<_i2.ExchangeResponse>>.value( _FakeChangeNowResponse_0>())) as _i5 - .Future<_i2.ChangeNowResponse>>); + .Future<_i2.ExchangeResponse>>); @override - _i5.Future<_i2.ChangeNowResponse<_i7.Decimal>> getMinimalExchangeAmount( + _i5.Future<_i2.ExchangeResponse<_i7.Decimal>> getMinimalExchangeAmount( {String? fromTicker, String? toTicker, String? apiKey}) => (super.noSuchMethod( Invocation.method(#getMinimalExchangeAmount, [], { @@ -77,11 +77,11 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNow { #toTicker: toTicker, #apiKey: apiKey }), - returnValue: Future<_i2.ChangeNowResponse<_i7.Decimal>>.value( + returnValue: Future<_i2.ExchangeResponse<_i7.Decimal>>.value( _FakeChangeNowResponse_0<_i7.Decimal>())) - as _i5.Future<_i2.ChangeNowResponse<_i7.Decimal>>); + as _i5.Future<_i2.ExchangeResponse<_i7.Decimal>>); @override - _i5.Future<_i2.ChangeNowResponse<_i8.EstimatedExchangeAmount>> + _i5.Future<_i2.ExchangeResponse<_i8.EstimatedExchangeAmount>> getEstimatedExchangeAmount( {String? fromTicker, String? toTicker, @@ -95,13 +95,12 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNow { #apiKey: apiKey }), returnValue: Future< - _i2.ChangeNowResponse< + _i2.ExchangeResponse< _i8.EstimatedExchangeAmount>>.value( _FakeChangeNowResponse_0<_i8.EstimatedExchangeAmount>())) - as _i5 - .Future<_i2.ChangeNowResponse<_i8.EstimatedExchangeAmount>>); + as _i5.Future<_i2.ExchangeResponse<_i8.EstimatedExchangeAmount>>); @override - _i5.Future<_i2.ChangeNowResponse<_i9.CNExchangeEstimate>> + _i5.Future<_i2.ExchangeResponse<_i9.CNExchangeEstimate>> getEstimatedExchangeAmountV2( {String? fromTicker, String? toTicker, @@ -123,20 +122,20 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNow { #apiKey: apiKey }), returnValue: Future< - _i2.ChangeNowResponse<_i9.CNExchangeEstimate>>.value( + _i2.ExchangeResponse<_i9.CNExchangeEstimate>>.value( _FakeChangeNowResponse_0<_i9.CNExchangeEstimate>())) - as _i5.Future<_i2.ChangeNowResponse<_i9.CNExchangeEstimate>>); + as _i5.Future<_i2.ExchangeResponse<_i9.CNExchangeEstimate>>); @override - _i5.Future<_i2.ChangeNowResponse>> + _i5.Future<_i2.ExchangeResponse>> getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod( - Invocation.method( - #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), - returnValue: - Future<_i2.ChangeNowResponse>>.value( - _FakeChangeNowResponse_0>())) as _i5 - .Future<_i2.ChangeNowResponse>>); + Invocation.method( + #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), + returnValue: + Future<_i2.ExchangeResponse>>.value( + _FakeChangeNowResponse_0>())) + as _i5.Future<_i2.ExchangeResponse>>); @override - _i5.Future<_i2.ChangeNowResponse<_i11.ExchangeTransaction>> + _i5.Future<_i2.ExchangeResponse<_i11.ExchangeTransaction>> createStandardExchangeTransaction( {String? fromTicker, String? toTicker, @@ -149,24 +148,24 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNow { String? refundExtraId = r'', String? apiKey}) => (super.noSuchMethod( - Invocation.method(#createStandardExchangeTransaction, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #receivingAddress: receivingAddress, - #amount: amount, - #extraId: extraId, - #userId: userId, - #contactEmail: contactEmail, - #refundAddress: refundAddress, - #refundExtraId: refundExtraId, - #apiKey: apiKey - }), - returnValue: Future< - _i2.ChangeNowResponse<_i11.ExchangeTransaction>>.value( - _FakeChangeNowResponse_0<_i11.ExchangeTransaction>())) as _i5 - .Future<_i2.ChangeNowResponse<_i11.ExchangeTransaction>>); + Invocation.method(#createStandardExchangeTransaction, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #receivingAddress: receivingAddress, + #amount: amount, + #extraId: extraId, + #userId: userId, + #contactEmail: contactEmail, + #refundAddress: refundAddress, + #refundExtraId: refundExtraId, + #apiKey: apiKey + }), + returnValue: Future< + _i2.ExchangeResponse<_i11.ExchangeTransaction>>.value( + _FakeChangeNowResponse_0<_i11.ExchangeTransaction>())) + as _i5.Future<_i2.ExchangeResponse<_i11.ExchangeTransaction>>); @override - _i5.Future<_i2.ChangeNowResponse<_i11.ExchangeTransaction>> + _i5.Future<_i2.ExchangeResponse<_i11.ExchangeTransaction>> createFixedRateExchangeTransaction( {String? fromTicker, String? toTicker, @@ -180,40 +179,40 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNow { String? refundExtraId = r'', String? apiKey}) => (super.noSuchMethod( - Invocation.method(#createFixedRateExchangeTransaction, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #receivingAddress: receivingAddress, - #amount: amount, - #rateId: rateId, - #extraId: extraId, - #userId: userId, - #contactEmail: contactEmail, - #refundAddress: refundAddress, - #refundExtraId: refundExtraId, - #apiKey: apiKey - }), - returnValue: Future< - _i2.ChangeNowResponse<_i11.ExchangeTransaction>>.value( - _FakeChangeNowResponse_0<_i11.ExchangeTransaction>())) as _i5 - .Future<_i2.ChangeNowResponse<_i11.ExchangeTransaction>>); + Invocation.method(#createFixedRateExchangeTransaction, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #receivingAddress: receivingAddress, + #amount: amount, + #rateId: rateId, + #extraId: extraId, + #userId: userId, + #contactEmail: contactEmail, + #refundAddress: refundAddress, + #refundExtraId: refundExtraId, + #apiKey: apiKey + }), + returnValue: Future< + _i2.ExchangeResponse<_i11.ExchangeTransaction>>.value( + _FakeChangeNowResponse_0<_i11.ExchangeTransaction>())) + as _i5.Future<_i2.ExchangeResponse<_i11.ExchangeTransaction>>); @override - _i5.Future<_i2.ChangeNowResponse<_i12.ExchangeTransactionStatus>> + _i5.Future<_i2.ExchangeResponse<_i12.ExchangeTransactionStatus>> getTransactionStatus({String? id, String? apiKey}) => (super.noSuchMethod( Invocation.method( #getTransactionStatus, [], {#id: id, #apiKey: apiKey}), returnValue: - Future<_i2.ChangeNowResponse<_i12.ExchangeTransactionStatus>>.value( + Future<_i2.ExchangeResponse<_i12.ExchangeTransactionStatus>>.value( _FakeChangeNowResponse_0<_i12.ExchangeTransactionStatus>())) as _i5 - .Future<_i2.ChangeNowResponse<_i12.ExchangeTransactionStatus>>); + .Future<_i2.ExchangeResponse<_i12.ExchangeTransactionStatus>>); @override - _i5.Future<_i2.ChangeNowResponse>> + _i5.Future<_i2.ExchangeResponse>> getAvailableFloatingRatePairs({bool? includePartners = false}) => (super .noSuchMethod( Invocation.method(#getAvailableFloatingRatePairs, [], {#includePartners: includePartners}), returnValue: - Future<_i2.ChangeNowResponse>>.value( + Future<_i2.ExchangeResponse>>.value( _FakeChangeNowResponse_0>())) as _i5 - .Future<_i2.ChangeNowResponse>>); + .Future<_i2.ExchangeResponse>>); } diff --git a/test/screen_tests/exchange/exchange_view_test.dart b/test/screen_tests/exchange/exchange_view_test.dart index 1db2ac5a84..aa13e055f9 100644 --- a/test/screen_tests/exchange/exchange_view_test.dart +++ b/test/screen_tests/exchange/exchange_view_test.dart @@ -1,10 +1,10 @@ import 'package:mockito/annotations.dart'; -import 'package:stackwallet/services/change_now/change_now.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/services/trade_notes_service.dart'; import 'package:stackwallet/services/trade_service.dart'; import 'package:stackwallet/utilities/prefs.dart'; -@GenerateMocks([Prefs, TradesService, TradeNotesService, ChangeNow]) +@GenerateMocks([Prefs, TradesService, TradeNotesService, ChangeNowAPI]) void main() { // testWidgets("ExchangeView builds correctly with no trade history", // (widgetTester) async { diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index b3c6798f6d..d39df96e1e 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -10,8 +10,6 @@ import 'package:http/http.dart' as _i13; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart' as _i20; -import 'package:stackwallet/models/exchange/change_now/change_now_response.dart' - as _i2; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' as _i17; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart' @@ -26,7 +24,9 @@ import 'package:stackwallet/models/exchange/response_objects/currency.dart' as _i14; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart' as _i5; -import 'package:stackwallet/services/change_now/change_now.dart' as _i12; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart' + as _i12; +import 'package:stackwallet/services/exchange/exchange_response.dart' as _i2; import 'package:stackwallet/services/trade_notes_service.dart' as _i11; import 'package:stackwallet/services/trade_service.dart' as _i9; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i6; @@ -44,7 +44,7 @@ import 'package:stackwallet/utilities/prefs.dart' as _i3; // ignore_for_file: camel_case_types class _FakeChangeNowResponse_0 extends _i1.Fake - implements _i2.ChangeNowResponse {} + implements _i2.ExchangeResponse {} /// A class which mocks [Prefs]. /// @@ -351,7 +351,7 @@ class MockTradeNotesService extends _i1.Mock implements _i11.TradeNotesService { /// A class which mocks [ChangeNow]. /// /// See the documentation for Mockito's code generation for more information. -class MockChangeNow extends _i1.Mock implements _i12.ChangeNow { +class MockChangeNow extends _i1.Mock implements _i12.ChangeNowAPI { MockChangeNow() { _i1.throwOnMissingStub(this); } @@ -361,25 +361,25 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNow { super.noSuchMethod(Invocation.setter(#client, _client), returnValueForMissingStub: null); @override - _i7.Future<_i2.ChangeNowResponse>> getAvailableCurrencies( + _i7.Future<_i2.ExchangeResponse>> getAvailableCurrencies( {bool? fixedRate, bool? active}) => (super.noSuchMethod( Invocation.method(#getAvailableCurrencies, [], {#fixedRate: fixedRate, #active: active}), - returnValue: Future<_i2.ChangeNowResponse>>.value( + returnValue: Future<_i2.ExchangeResponse>>.value( _FakeChangeNowResponse_0>())) as _i7 - .Future<_i2.ChangeNowResponse>>); + .Future<_i2.ExchangeResponse>>); @override - _i7.Future<_i2.ChangeNowResponse>> getPairedCurrencies( + _i7.Future<_i2.ExchangeResponse>> getPairedCurrencies( {String? ticker, bool? fixedRate}) => (super.noSuchMethod( Invocation.method(#getPairedCurrencies, [], {#ticker: ticker, #fixedRate: fixedRate}), - returnValue: Future<_i2.ChangeNowResponse>>.value( + returnValue: Future<_i2.ExchangeResponse>>.value( _FakeChangeNowResponse_0>())) as _i7 - .Future<_i2.ChangeNowResponse>>); + .Future<_i2.ExchangeResponse>>); @override - _i7.Future<_i2.ChangeNowResponse<_i15.Decimal>> getMinimalExchangeAmount( + _i7.Future<_i2.ExchangeResponse<_i15.Decimal>> getMinimalExchangeAmount( {String? fromTicker, String? toTicker, String? apiKey}) => (super.noSuchMethod( Invocation.method(#getMinimalExchangeAmount, [], { @@ -387,11 +387,11 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNow { #toTicker: toTicker, #apiKey: apiKey }), - returnValue: Future<_i2.ChangeNowResponse<_i15.Decimal>>.value( + returnValue: Future<_i2.ExchangeResponse<_i15.Decimal>>.value( _FakeChangeNowResponse_0<_i15.Decimal>())) - as _i7.Future<_i2.ChangeNowResponse<_i15.Decimal>>); + as _i7.Future<_i2.ExchangeResponse<_i15.Decimal>>); @override - _i7.Future<_i2.ChangeNowResponse<_i16.EstimatedExchangeAmount>> + _i7.Future<_i2.ExchangeResponse<_i16.EstimatedExchangeAmount>> getEstimatedExchangeAmount( {String? fromTicker, String? toTicker, @@ -405,13 +405,13 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNow { #apiKey: apiKey }), returnValue: Future< - _i2.ChangeNowResponse< + _i2.ExchangeResponse< _i16.EstimatedExchangeAmount>>.value( _FakeChangeNowResponse_0<_i16.EstimatedExchangeAmount>())) as _i7 - .Future<_i2.ChangeNowResponse<_i16.EstimatedExchangeAmount>>); + .Future<_i2.ExchangeResponse<_i16.EstimatedExchangeAmount>>); @override - _i7.Future<_i2.ChangeNowResponse<_i17.CNExchangeEstimate>> + _i7.Future<_i2.ExchangeResponse<_i17.CNExchangeEstimate>> getEstimatedExchangeAmountV2( {String? fromTicker, String? toTicker, @@ -433,20 +433,20 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNow { #apiKey: apiKey }), returnValue: Future< - _i2.ChangeNowResponse<_i17.CNExchangeEstimate>>.value( + _i2.ExchangeResponse<_i17.CNExchangeEstimate>>.value( _FakeChangeNowResponse_0<_i17.CNExchangeEstimate>())) - as _i7.Future<_i2.ChangeNowResponse<_i17.CNExchangeEstimate>>); + as _i7.Future<_i2.ExchangeResponse<_i17.CNExchangeEstimate>>); @override - _i7.Future<_i2.ChangeNowResponse>> + _i7.Future<_i2.ExchangeResponse>> getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod( - Invocation.method( - #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), - returnValue: - Future<_i2.ChangeNowResponse>>.value( - _FakeChangeNowResponse_0>())) as _i7 - .Future<_i2.ChangeNowResponse>>); + Invocation.method( + #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), + returnValue: + Future<_i2.ExchangeResponse>>.value( + _FakeChangeNowResponse_0>())) + as _i7.Future<_i2.ExchangeResponse>>); @override - _i7.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>> + _i7.Future<_i2.ExchangeResponse<_i10.ExchangeTransaction>> createStandardExchangeTransaction( {String? fromTicker, String? toTicker, @@ -459,24 +459,24 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNow { String? refundExtraId = r'', String? apiKey}) => (super.noSuchMethod( - Invocation.method(#createStandardExchangeTransaction, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #receivingAddress: receivingAddress, - #amount: amount, - #extraId: extraId, - #userId: userId, - #contactEmail: contactEmail, - #refundAddress: refundAddress, - #refundExtraId: refundExtraId, - #apiKey: apiKey - }), - returnValue: Future< - _i2.ChangeNowResponse<_i10.ExchangeTransaction>>.value( - _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i7 - .Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>); + Invocation.method(#createStandardExchangeTransaction, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #receivingAddress: receivingAddress, + #amount: amount, + #extraId: extraId, + #userId: userId, + #contactEmail: contactEmail, + #refundAddress: refundAddress, + #refundExtraId: refundExtraId, + #apiKey: apiKey + }), + returnValue: Future< + _i2.ExchangeResponse<_i10.ExchangeTransaction>>.value( + _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) + as _i7.Future<_i2.ExchangeResponse<_i10.ExchangeTransaction>>); @override - _i7.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>> + _i7.Future<_i2.ExchangeResponse<_i10.ExchangeTransaction>> createFixedRateExchangeTransaction( {String? fromTicker, String? toTicker, @@ -490,40 +490,40 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNow { String? refundExtraId = r'', String? apiKey}) => (super.noSuchMethod( - Invocation.method(#createFixedRateExchangeTransaction, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #receivingAddress: receivingAddress, - #amount: amount, - #rateId: rateId, - #extraId: extraId, - #userId: userId, - #contactEmail: contactEmail, - #refundAddress: refundAddress, - #refundExtraId: refundExtraId, - #apiKey: apiKey - }), - returnValue: Future< - _i2.ChangeNowResponse<_i10.ExchangeTransaction>>.value( - _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i7 - .Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>); + Invocation.method(#createFixedRateExchangeTransaction, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #receivingAddress: receivingAddress, + #amount: amount, + #rateId: rateId, + #extraId: extraId, + #userId: userId, + #contactEmail: contactEmail, + #refundAddress: refundAddress, + #refundExtraId: refundExtraId, + #apiKey: apiKey + }), + returnValue: Future< + _i2.ExchangeResponse<_i10.ExchangeTransaction>>.value( + _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) + as _i7.Future<_i2.ExchangeResponse<_i10.ExchangeTransaction>>); @override - _i7.Future<_i2.ChangeNowResponse<_i19.ExchangeTransactionStatus>> + _i7.Future<_i2.ExchangeResponse<_i19.ExchangeTransactionStatus>> getTransactionStatus({String? id, String? apiKey}) => (super.noSuchMethod( Invocation.method( #getTransactionStatus, [], {#id: id, #apiKey: apiKey}), returnValue: - Future<_i2.ChangeNowResponse<_i19.ExchangeTransactionStatus>>.value( + Future<_i2.ExchangeResponse<_i19.ExchangeTransactionStatus>>.value( _FakeChangeNowResponse_0<_i19.ExchangeTransactionStatus>())) as _i7 - .Future<_i2.ChangeNowResponse<_i19.ExchangeTransactionStatus>>); + .Future<_i2.ExchangeResponse<_i19.ExchangeTransactionStatus>>); @override - _i7.Future<_i2.ChangeNowResponse>> + _i7.Future<_i2.ExchangeResponse>> getAvailableFloatingRatePairs({bool? includePartners = false}) => (super .noSuchMethod( Invocation.method(#getAvailableFloatingRatePairs, [], {#includePartners: includePartners}), returnValue: - Future<_i2.ChangeNowResponse>>.value( + Future<_i2.ExchangeResponse>>.value( _FakeChangeNowResponse_0>())) as _i7 - .Future<_i2.ChangeNowResponse>>); + .Future<_i2.ExchangeResponse>>); } diff --git a/test/services/change_now/change_now_test.dart b/test/services/change_now/change_now_test.dart index a5b9272988..2447f47447 100644 --- a/test/services/change_now/change_now_test.dart +++ b/test/services/change_now/change_now_test.dart @@ -6,11 +6,11 @@ import 'package:http/http.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; -import 'package:stackwallet/models/exchange/change_now/change_now_response.dart'; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; -import 'package:stackwallet/services/change_now/change_now.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'change_now_sample_data.dart'; import 'change_now_test.mocks.dart'; @@ -20,7 +20,7 @@ void main() { group("getAvailableCurrencies", () { test("getAvailableCurrencies succeeds without options", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse("https://api.ChangeNow.io/v1/currencies"), @@ -28,7 +28,7 @@ void main() { )).thenAnswer((realInvocation) async => Response(jsonEncode(availableCurrenciesJSON), 200)); - final result = await ChangeNow.instance.getAvailableCurrencies(); + final result = await ChangeNowAPI.instance.getAvailableCurrencies(); expect(result.exception, null); expect(result.value == null, false); @@ -37,7 +37,7 @@ void main() { test("getAvailableCurrencies succeeds with active option", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse("https://api.ChangeNow.io/v1/currencies?active=true"), @@ -46,7 +46,7 @@ void main() { Response(jsonEncode(availableCurrenciesJSONActive), 200)); final result = - await ChangeNow.instance.getAvailableCurrencies(active: true); + await ChangeNowAPI.instance.getAvailableCurrencies(active: true); expect(result.exception, null); expect(result.value == null, false); @@ -55,7 +55,7 @@ void main() { test("getAvailableCurrencies succeeds with fixedRate option", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse("https://api.ChangeNow.io/v1/currencies?fixedRate=true"), @@ -64,7 +64,7 @@ void main() { Response(jsonEncode(availableCurrenciesJSONFixedRate), 200)); final result = - await ChangeNow.instance.getAvailableCurrencies(fixedRate: true); + await ChangeNowAPI.instance.getAvailableCurrencies(fixedRate: true); expect(result.exception, null); expect(result.value == null, false); @@ -74,7 +74,7 @@ void main() { test("getAvailableCurrencies succeeds with fixedRate and active options", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -83,7 +83,7 @@ void main() { )).thenAnswer((realInvocation) async => Response(jsonEncode(availableCurrenciesJSONActiveFixedRate), 200)); - final result = await ChangeNow.instance + final result = await ChangeNowAPI.instance .getAvailableCurrencies(active: true, fixedRate: true); expect(result.exception, null); @@ -95,7 +95,7 @@ void main() { "getAvailableCurrencies fails with ChangeNowExceptionType.serializeResponseError", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse("https://api.ChangeNow.io/v1/currencies"), @@ -103,25 +103,25 @@ void main() { )).thenAnswer((realInvocation) async => Response('{"some unexpected": "but valid json data"}', 200)); - final result = await ChangeNow.instance.getAvailableCurrencies(); + final result = await ChangeNowAPI.instance.getAvailableCurrencies(); - expect(result.exception!.type, - ChangeNowExceptionType.serializeResponseError); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); test("getAvailableCurrencies fails for any other reason", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse("https://api.ChangeNow.io/v1/currencies"), headers: {'Content-Type': 'application/json'}, )).thenAnswer((realInvocation) async => Response("", 400)); - final result = await ChangeNow.instance.getAvailableCurrencies(); + final result = await ChangeNowAPI.instance.getAvailableCurrencies(); - expect(result.exception!.type, ChangeNowExceptionType.generic); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); }); @@ -129,7 +129,7 @@ void main() { group("getPairedCurrencies", () { test("getPairedCurrencies succeeds without fixedRate option", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse("https://api.ChangeNow.io/v1/currencies-to/XMR"), @@ -138,7 +138,7 @@ void main() { Response(jsonEncode(getPairedCurrenciesJSON), 200)); final result = - await ChangeNow.instance.getPairedCurrencies(ticker: "XMR"); + await ChangeNowAPI.instance.getPairedCurrencies(ticker: "XMR"); expect(result.exception, null); expect(result.value == null, false); @@ -147,7 +147,7 @@ void main() { test("getPairedCurrencies succeeds with fixedRate option", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -156,7 +156,7 @@ void main() { )).thenAnswer((realInvocation) async => Response(jsonEncode(getPairedCurrenciesJSONFixedRate), 200)); - final result = await ChangeNow.instance + final result = await ChangeNowAPI.instance .getPairedCurrencies(ticker: "XMR", fixedRate: true); expect(result.exception, null); @@ -168,7 +168,7 @@ void main() { "getPairedCurrencies fails with ChangeNowExceptionType.serializeResponseError A", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse("https://api.ChangeNow.io/v1/currencies-to/XMR"), @@ -177,26 +177,26 @@ void main() { Response('[{"some unexpected": "but valid json data"}]', 200)); final result = - await ChangeNow.instance.getPairedCurrencies(ticker: "XMR"); + await ChangeNowAPI.instance.getPairedCurrencies(ticker: "XMR"); - expect(result.exception!.type, - ChangeNowExceptionType.serializeResponseError); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); test("getPairedCurrencies fails for any other reason", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse("https://api.ChangeNow.io/v1/currencies"), headers: {'Content-Type': 'application/json'}, )).thenAnswer((realInvocation) async => Response("", 400)); - final result = await ChangeNow.instance + final result = await ChangeNowAPI.instance .getPairedCurrencies(ticker: "XMR", fixedRate: true); - expect(result.exception!.type, ChangeNowExceptionType.generic); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); }); @@ -204,7 +204,7 @@ void main() { group("getMinimalExchangeAmount", () { test("getMinimalExchangeAmount succeeds", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -213,7 +213,7 @@ void main() { )).thenAnswer( (realInvocation) async => Response('{"minAmount": 42}', 200)); - final result = await ChangeNow.instance.getMinimalExchangeAmount( + final result = await ChangeNowAPI.instance.getMinimalExchangeAmount( fromTicker: "xmr", toTicker: "btc", apiKey: "testAPIKEY", @@ -228,7 +228,7 @@ void main() { "getMinimalExchangeAmount fails with ChangeNowExceptionType.serializeResponseError", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -236,20 +236,20 @@ void main() { headers: {'Content-Type': 'application/json'}, )).thenAnswer((realInvocation) async => Response('{"error": 42}', 200)); - final result = await ChangeNow.instance.getMinimalExchangeAmount( + final result = await ChangeNowAPI.instance.getMinimalExchangeAmount( fromTicker: "xmr", toTicker: "btc", apiKey: "testAPIKEY", ); - expect(result.exception!.type, - ChangeNowExceptionType.serializeResponseError); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); test("getMinimalExchangeAmount fails for any other reason", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -257,13 +257,13 @@ void main() { headers: {'Content-Type': 'application/json'}, )).thenAnswer((realInvocation) async => Response('', 400)); - final result = await ChangeNow.instance.getMinimalExchangeAmount( + final result = await ChangeNowAPI.instance.getMinimalExchangeAmount( fromTicker: "xmr", toTicker: "btc", apiKey: "testAPIKEY", ); - expect(result.exception!.type, ChangeNowExceptionType.generic); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); }); @@ -271,7 +271,7 @@ void main() { group("getEstimatedExchangeAmount", () { test("getEstimatedExchangeAmount succeeds", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -281,7 +281,7 @@ void main() { '{"estimatedAmount": 58.4142873, "transactionSpeedForecast": "10-60", "warningMessage": null}', 200)); - final result = await ChangeNow.instance.getEstimatedExchangeAmount( + final result = await ChangeNowAPI.instance.getEstimatedExchangeAmount( fromTicker: "xmr", toTicker: "btc", fromAmount: Decimal.fromInt(42), @@ -297,7 +297,7 @@ void main() { "getEstimatedExchangeAmount fails with ChangeNowExceptionType.serializeResponseError", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -305,21 +305,21 @@ void main() { headers: {'Content-Type': 'application/json'}, )).thenAnswer((realInvocation) async => Response('{"error": 42}', 200)); - final result = await ChangeNow.instance.getEstimatedExchangeAmount( + final result = await ChangeNowAPI.instance.getEstimatedExchangeAmount( fromTicker: "xmr", toTicker: "btc", fromAmount: Decimal.fromInt(42), apiKey: "testAPIKEY", ); - expect(result.exception!.type, - ChangeNowExceptionType.serializeResponseError); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); test("getEstimatedExchangeAmount fails for any other reason", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -327,14 +327,14 @@ void main() { headers: {'Content-Type': 'application/json'}, )).thenAnswer((realInvocation) async => Response('', 400)); - final result = await ChangeNow.instance.getEstimatedExchangeAmount( + final result = await ChangeNowAPI.instance.getEstimatedExchangeAmount( fromTicker: "xmr", toTicker: "btc", fromAmount: Decimal.fromInt(42), apiKey: "testAPIKEY", ); - expect(result.exception!.type, ChangeNowExceptionType.generic); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); }); @@ -417,7 +417,7 @@ void main() { group("getAvailableFixedRateMarkets", () { test("getAvailableFixedRateMarkets succeeds", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -426,7 +426,7 @@ void main() { )).thenAnswer((realInvocation) async => Response(jsonEncode(fixedRateMarketsJSON), 200)); - final result = await ChangeNow.instance.getAvailableFixedRateMarkets( + final result = await ChangeNowAPI.instance.getAvailableFixedRateMarkets( apiKey: "testAPIKEY", ); @@ -439,7 +439,7 @@ void main() { "getAvailableFixedRateMarkets fails with ChangeNowExceptionType.serializeResponseError", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -447,18 +447,18 @@ void main() { headers: {'Content-Type': 'application/json'}, )).thenAnswer((realInvocation) async => Response('{"error": 42}', 200)); - final result = await ChangeNow.instance.getAvailableFixedRateMarkets( + final result = await ChangeNowAPI.instance.getAvailableFixedRateMarkets( apiKey: "testAPIKEY", ); - expect(result.exception!.type, - ChangeNowExceptionType.serializeResponseError); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); test("getAvailableFixedRateMarkets fails for any other reason", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -466,11 +466,11 @@ void main() { headers: {'Content-Type': 'application/json'}, )).thenAnswer((realInvocation) async => Response('', 400)); - final result = await ChangeNow.instance.getAvailableFixedRateMarkets( + final result = await ChangeNowAPI.instance.getAvailableFixedRateMarkets( apiKey: "testAPIKEY", ); - expect(result.exception!.type, ChangeNowExceptionType.generic); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); }); @@ -478,7 +478,7 @@ void main() { group("createStandardExchangeTransaction", () { test("createStandardExchangeTransaction succeeds", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.post( Uri.parse("https://api.ChangeNow.io/v1/transactions/testAPIKEY"), @@ -489,7 +489,8 @@ void main() { )).thenAnswer((realInvocation) async => Response(jsonEncode(createStandardTransactionResponse), 200)); - final result = await ChangeNow.instance.createStandardExchangeTransaction( + final result = + await ChangeNowAPI.instance.createStandardExchangeTransaction( fromTicker: "xmr", toTicker: "btc", receivingAddress: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", @@ -508,7 +509,7 @@ void main() { "createStandardExchangeTransaction fails with ChangeNowExceptionType.serializeResponseError", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.post( Uri.parse("https://api.ChangeNow.io/v1/transactions/testAPIKEY"), @@ -518,7 +519,8 @@ void main() { encoding: null, )).thenAnswer((realInvocation) async => Response('{"error": 42}', 200)); - final result = await ChangeNow.instance.createStandardExchangeTransaction( + final result = + await ChangeNowAPI.instance.createStandardExchangeTransaction( fromTicker: "xmr", toTicker: "btc", receivingAddress: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", @@ -528,15 +530,15 @@ void main() { apiKey: "testAPIKEY", ); - expect(result.exception!.type, - ChangeNowExceptionType.serializeResponseError); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); test("createStandardExchangeTransaction fails for any other reason", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.post( Uri.parse("https://api.ChangeNow.io/v1/transactions/testAPIKEY"), @@ -546,7 +548,8 @@ void main() { encoding: null, )).thenAnswer((realInvocation) async => Response('', 400)); - final result = await ChangeNow.instance.createStandardExchangeTransaction( + final result = + await ChangeNowAPI.instance.createStandardExchangeTransaction( fromTicker: "xmr", toTicker: "btc", receivingAddress: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", @@ -556,7 +559,7 @@ void main() { apiKey: "testAPIKEY", ); - expect(result.exception!.type, ChangeNowExceptionType.generic); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); }); @@ -564,7 +567,7 @@ void main() { group("createFixedRateExchangeTransaction", () { test("createFixedRateExchangeTransaction succeeds", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.post( Uri.parse( @@ -578,7 +581,7 @@ void main() { 200)); final result = - await ChangeNow.instance.createFixedRateExchangeTransaction( + await ChangeNowAPI.instance.createFixedRateExchangeTransaction( fromTicker: "btc", toTicker: "eth", receivingAddress: "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", @@ -597,7 +600,7 @@ void main() { "createFixedRateExchangeTransaction fails with ChangeNowExceptionType.serializeResponseError", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.post( Uri.parse( @@ -610,7 +613,7 @@ void main() { Response('{"id": "a5c73e2603f40d","amount": 62.9737711}', 200)); final result = - await ChangeNow.instance.createFixedRateExchangeTransaction( + await ChangeNowAPI.instance.createFixedRateExchangeTransaction( fromTicker: "btc", toTicker: "eth", receivingAddress: "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", @@ -620,15 +623,15 @@ void main() { rateId: '', ); - expect(result.exception!.type, - ChangeNowExceptionType.serializeResponseError); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); test("createFixedRateExchangeTransaction fails for any other reason", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.post( Uri.parse( @@ -640,7 +643,7 @@ void main() { )).thenAnswer((realInvocation) async => Response('', 400)); final result = - await ChangeNow.instance.createFixedRateExchangeTransaction( + await ChangeNowAPI.instance.createFixedRateExchangeTransaction( fromTicker: "xmr", toTicker: "btc", receivingAddress: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", @@ -651,7 +654,7 @@ void main() { rateId: '', ); - expect(result.exception!.type, ChangeNowExceptionType.generic); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); }); @@ -659,7 +662,7 @@ void main() { group("getTransactionStatus", () { test("getTransactionStatus succeeds", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -669,7 +672,7 @@ void main() { '{"status": "waiting", "payinAddress": "32Ge2ci26rj1sRGw2NjiQa9L7Xvxtgzhrj", "payoutAddress": "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", "fromCurrency": "btc", "toCurrency": "eth", "id": "50727663e5d9a4", "updatedAt": "2019-08-22T14:47:49.943Z", "expectedSendAmount": 1, "expectedReceiveAmount": 52.31667, "createdAt": "2019-08-22T14:47:49.943Z", "isPartner": false}', 200)); - final result = await ChangeNow.instance.getTransactionStatus( + final result = await ChangeNowAPI.instance.getTransactionStatus( id: "47F87eDB1675566DAfF5EC886", apiKey: "testAPIKEY", ); @@ -683,7 +686,7 @@ void main() { "getTransactionStatus fails with ChangeNowExceptionType.serializeResponseError", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -691,19 +694,19 @@ void main() { headers: {'Content-Type': 'application/json'}, )).thenAnswer((realInvocation) async => Response('{"error": 42}', 200)); - final result = await ChangeNow.instance.getTransactionStatus( + final result = await ChangeNowAPI.instance.getTransactionStatus( id: "47F87eDB1675566DAfF5EC886", apiKey: "testAPIKEY", ); - expect(result.exception!.type, - ChangeNowExceptionType.serializeResponseError); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); test("getTransactionStatus fails for any other reason", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -711,12 +714,12 @@ void main() { headers: {'Content-Type': 'application/json'}, )).thenAnswer((realInvocation) async => Response('', 400)); - final result = await ChangeNow.instance.getTransactionStatus( + final result = await ChangeNowAPI.instance.getTransactionStatus( id: "47F87eDB1675566DAfF5EC886", apiKey: "testAPIKEY", ); - expect(result.exception!.type, ChangeNowExceptionType.generic); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); }); @@ -724,7 +727,7 @@ void main() { group("getAvailableFloatingRatePairs", () { test("getAvailableFloatingRatePairs succeeds", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -733,7 +736,8 @@ void main() { )).thenAnswer((realInvocation) async => Response('["btc_xmr","btc_firo","btc_doge","eth_ltc"]', 200)); - final result = await ChangeNow.instance.getAvailableFloatingRatePairs(); + final result = + await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); expect(result.exception, null); expect(result.value == null, false); @@ -744,7 +748,7 @@ void main() { "getAvailableFloatingRatePairs fails with ChangeNowExceptionType.serializeResponseError", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -752,16 +756,17 @@ void main() { headers: {'Content-Type': 'application/json'}, )).thenAnswer((realInvocation) async => Response('{"error": 42}', 200)); - final result = await ChangeNow.instance.getAvailableFloatingRatePairs(); + final result = + await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); - expect(result.exception!.type, - ChangeNowExceptionType.serializeResponseError); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); test("getAvailableFloatingRatePairs fails for any other reason", () async { final client = MockClient(); - ChangeNow.instance.client = client; + ChangeNowAPI.instance.client = client; when(client.get( Uri.parse( @@ -769,9 +774,10 @@ void main() { headers: {'Content-Type': 'application/json'}, )).thenAnswer((realInvocation) async => Response('', 400)); - final result = await ChangeNow.instance.getAvailableFloatingRatePairs(); + final result = + await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); - expect(result.exception!.type, ChangeNowExceptionType.generic); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); }); From 807379f11293302f07c72ade7a83f7f5a9df20ac Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 2 Oct 2022 15:48:43 -0600 Subject: [PATCH 05/75] generic changenow impl --- .../exchange/change_now/change_now_api.dart | 94 +++++++++++++++ .../change_now/change_now_exchange.dart | 113 ++++++++++++++++-- lib/services/exchange/exchange.dart | 4 +- .../exchange/simpleswap/simpleswap_api.dart | 3 +- .../simpleswap/simpleswap_exchange.dart | 11 +- 5 files changed, 213 insertions(+), 12 deletions(-) diff --git a/lib/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart index e88114aa5e..993d9a08ee 100644 --- a/lib/services/exchange/change_now/change_now_api.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -11,6 +11,7 @@ import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -266,6 +267,46 @@ class ChangeNowAPI { } } + /// The API endpoint returns minimal payment amount and maximum payment amount + /// required to make an exchange. If you try to exchange less than minimum or + /// more than maximum, the transaction will most likely fail. Any pair of + /// assets has minimum amount and some of pairs have maximum amount. + Future> getRange({ + required String fromTicker, + required String toTicker, + required bool isFixedRate, + String? apiKey, + }) async { + Map? params = {"api_key": apiKey ?? kChangeNowApiKey}; + + final uri = _buildUri( + "/exchange-range${isFixedRate ? "/fixed-rate" : ""}/${fromTicker}_$toTicker", + params); + + try { + final jsonObject = await _makeGetRequest(uri); + + final json = Map.from(jsonObject as Map); + return ExchangeResponse( + value: Range( + max: Decimal.tryParse(json["maxAmount"] as String? ?? ""), + min: Decimal.tryParse(json["minAmount"] as String? ?? ""), + ), + ); + } catch (e, s) { + Logging.instance.log( + "getRange exception: $e\n$s", + level: LogLevel.Error, + ); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + /// Get estimated amount of [toTicker] cryptocurrency to receive /// for [fromAmount] of [fromTicker] Future> getEstimatedExchangeAmount({ @@ -309,6 +350,59 @@ class ChangeNowAPI { } } + /// Get estimated amount of [toTicker] cryptocurrency to receive + /// for [fromAmount] of [fromTicker] + Future> + getEstimatedExchangeAmountFixedRate({ + required String fromTicker, + required String toTicker, + required Decimal fromAmount, + required bool reversed, + String? apiKey, + }) async { + Map params = {"api_key": apiKey ?? kChangeNowApiKey}; + + late final Uri uri; + if (reversed) { + uri = _buildUri( + "/exchange-deposit/fixed-rate/${fromAmount.toString()}/${fromTicker}_$toTicker", + params, + ); + } else { + uri = _buildUri( + "/exchange-amount/fixed-rate/${fromAmount.toString()}/${fromTicker}_$toTicker", + params, + ); + } + + try { + // simple json object is expected here + final json = await _makeGetRequest(uri); + + try { + final value = EstimatedExchangeAmount.fromJson( + Map.from(json as Map)); + return ExchangeResponse(value: value); + } catch (_) { + return ExchangeResponse( + exception: ExchangeException( + "Failed to serialize $json", + ExchangeExceptionType.serializeResponseError, + ), + ); + } + } catch (e, s) { + Logging.instance.log("getEstimatedExchangeAmount exception: $e\n$s", + level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + // old v1 version /// This API endpoint returns fixed-rate estimated exchange amount of /// [toTicker] cryptocurrency to receive for [fromAmount] of [fromTicker] diff --git a/lib/services/exchange/change_now/change_now_exchange.dart b/lib/services/exchange/change_now/change_now_exchange.dart index 5072db5283..4220be17a0 100644 --- a/lib/services/exchange/change_now/change_now_exchange.dart +++ b/lib/services/exchange/change_now/change_now_exchange.dart @@ -1,10 +1,13 @@ import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:uuid/uuid.dart'; class ChangeNowExchange extends Exchange { @override @@ -25,8 +28,10 @@ class ChangeNowExchange extends Exchange { Future>> getAllCurrencies( bool fixedRate, ) async { - // TODO: implement getAllCurrencies - throw UnimplementedError(); + return await ChangeNowAPI.instance.getAvailableCurrencies( + fixedRate: fixedRate ? true : null, + active: true, + ); } @override @@ -41,19 +46,41 @@ class ChangeNowExchange extends Exchange { String to, Decimal amount, bool fixedRate, + bool reversed, ) async { - // TODO: implement getEstimate - throw UnimplementedError(); + late final ExchangeResponse response; + if (fixedRate) { + response = + await ChangeNowAPI.instance.getEstimatedExchangeAmountFixedRate( + fromTicker: from, + toTicker: to, + fromAmount: amount, + reversed: reversed, + ); + } else { + response = await ChangeNowAPI.instance.getEstimatedExchangeAmount( + fromTicker: from, + toTicker: to, + fromAmount: amount, + ); + } + if (response.exception != null) { + return ExchangeResponse(exception: response.exception); + } + return ExchangeResponse(value: response.value?.estimatedAmount); } @override - Future> getMinMaxExchangeAmounts( + Future> getRange( String from, String to, bool fixedRate, ) async { - // TODO: implement getMinMaxExchangeAmounts - throw UnimplementedError(); + return await ChangeNowAPI.instance.getRange( + fromTicker: from, + toTicker: to, + isFixedRate: fixedRate, + ); } @override @@ -67,8 +94,76 @@ class ChangeNowExchange extends Exchange { @override Future> getTrade(String tradeId) async { - // TODO: implement getTrade - throw UnimplementedError(); + final response = + await ChangeNowAPI.instance.getTransactionStatus(id: tradeId); + if (response.exception != null) { + return ExchangeResponse(exception: response.exception); + } + final t = response.value!; + final timestamp = DateTime.tryParse(t.createdAt) ?? DateTime.now(); + + final trade = Trade( + uuid: const Uuid().v1(), + tradeId: tradeId, + rateType: "", + direction: "", + timestamp: timestamp, + updatedAt: DateTime.tryParse(t.updatedAt) ?? timestamp, + payInCurrency: t.fromCurrency, + payInAmount: t.expectedSendAmountDecimal, + payInAddress: t.payinAddress, + payInNetwork: "", + payInExtraId: t.payinExtraId, + payInTxid: "", + payOutCurrency: t.toCurrency, + payOutAmount: t.expectedReceiveAmountDecimal, + payOutAddress: t.payoutAddress, + payOutNetwork: "", + payOutExtraId: t.payoutExtraId, + payOutTxid: "", + refundAddress: t.refundAddress, + refundExtraId: t.refundExtraId, + status: t.status.name, + ); + + return ExchangeResponse(value: trade); + } + + @override + Future> updateTrade(Trade trade) async { + final response = + await ChangeNowAPI.instance.getTransactionStatus(id: trade.tradeId); + if (response.exception != null) { + return ExchangeResponse(exception: response.exception); + } + final t = response.value!; + final timestamp = DateTime.tryParse(t.createdAt) ?? DateTime.now(); + + final _trade = Trade( + uuid: trade.uuid, + tradeId: trade.tradeId, + rateType: "", + direction: "", + timestamp: timestamp, + updatedAt: DateTime.tryParse(t.updatedAt) ?? timestamp, + payInCurrency: t.fromCurrency, + payInAmount: t.expectedSendAmountDecimal, + payInAddress: t.payinAddress, + payInNetwork: "", + payInExtraId: t.payinExtraId, + payInTxid: "", + payOutCurrency: t.toCurrency, + payOutAmount: t.expectedReceiveAmountDecimal, + payOutAddress: t.payoutAddress, + payOutNetwork: "", + payOutExtraId: t.payoutExtraId, + payOutTxid: "", + refundAddress: t.refundAddress, + refundExtraId: t.refundExtraId, + status: t.status.name, + ); + + return ExchangeResponse(value: _trade); } @override diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart index 03b11445ff..e629b0e0df 100644 --- a/lib/services/exchange/exchange.dart +++ b/lib/services/exchange/exchange.dart @@ -18,10 +18,11 @@ abstract class Exchange { Future>> getAllPairs(bool fixedRate); Future> getTrade(String tradeId); + Future> updateTrade(Trade trade); Future>> getTrades(); - Future> getMinMaxExchangeAmounts( + Future> getRange( String from, String to, bool fixedRate, @@ -32,6 +33,7 @@ abstract class Exchange { String to, Decimal amount, bool fixedRate, + bool reversed, ); Future> createTrade({ diff --git a/lib/services/exchange/simpleswap/simpleswap_api.dart b/lib/services/exchange/simpleswap/simpleswap_api.dart index 3465765c94..944f9a5206 100644 --- a/lib/services/exchange/simpleswap/simpleswap_api.dart +++ b/lib/services/exchange/simpleswap/simpleswap_api.dart @@ -334,6 +334,7 @@ class SimpleSwapAPI { Future> getExchange({ required String exchangeId, String? apiKey, + Trade? oldTrade, }) async { final uri = _buildUri( "/get_exchange", @@ -349,7 +350,7 @@ class SimpleSwapAPI { final json = Map.from(jsonObject as Map); final ts = DateTime.parse(json["timestamp"] as String); final trade = Trade( - uuid: const Uuid().v1(), + uuid: oldTrade?.uuid ?? const Uuid().v1(), tradeId: json["id"] as String, rateType: json["type"] as String, direction: "direct", diff --git a/lib/services/exchange/simpleswap/simpleswap_exchange.dart b/lib/services/exchange/simpleswap/simpleswap_exchange.dart index 18e8f41813..ca45317ea6 100644 --- a/lib/services/exchange/simpleswap/simpleswap_exchange.dart +++ b/lib/services/exchange/simpleswap/simpleswap_exchange.dart @@ -73,6 +73,7 @@ class SimpleSwapExchange extends Exchange { String to, Decimal amount, bool fixedRate, + bool reversed, ) async { final response = await SimpleSwapAPI.instance.getEstimated( isFixedRate: fixedRate, @@ -88,7 +89,7 @@ class SimpleSwapExchange extends Exchange { } @override - Future> getMinMaxExchangeAmounts( + Future> getRange( String from, String to, bool fixedRate, @@ -114,6 +115,14 @@ class SimpleSwapExchange extends Exchange { return await SimpleSwapAPI.instance.getExchange(exchangeId: tradeId); } + @override + Future> updateTrade(Trade trade) async { + return await SimpleSwapAPI.instance.getExchange( + exchangeId: trade.tradeId, + oldTrade: trade, + ); + } + @override Future>> getTrades() async { // TODO: implement getTrades From fd0070411da2717bf403b1c65722d1014fc25571 Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 2 Oct 2022 18:49:12 -0600 Subject: [PATCH 06/75] Currency parse fix --- lib/models/exchange/response_objects/currency.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/exchange/response_objects/currency.dart b/lib/models/exchange/response_objects/currency.dart index c1bb33976a..0850f9a38c 100644 --- a/lib/models/exchange/response_objects/currency.dart +++ b/lib/models/exchange/response_objects/currency.dart @@ -52,7 +52,7 @@ class Currency { return Currency( ticker: json["ticker"] as String, name: json["name"] as String, - network: json["network"] as String, + network: json["network"] as String? ?? "", image: json["image"] as String, hasExternalId: json["hasExternalId"] as bool, externalId: json["externalId"] as String?, From 58deb850f2ae1381661ff06cd1ee1576ba40a14a Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 2 Oct 2022 18:54:35 -0600 Subject: [PATCH 07/75] move from the changenow specific ExchangeTransaction model to Using the generic Trade model --- lib/main.dart | 10 +- lib/models/exchange/incomplete_exchange.dart | 4 +- .../exchange/response_objects/trade.dart | 73 ++++++++++- .../confirm_change_now_send.dart | 10 +- .../exchange_step_views/step_3_view.dart | 63 +++------ .../exchange_step_views/step_4_view.dart | 34 +++-- lib/pages/exchange_view/exchange_view.dart | 121 +++++++++--------- lib/pages/exchange_view/send_from_view.dart | 12 +- .../exchange_view/trade_details_view.dart | 110 ++++++++-------- .../wallet_initiated_exchange_view.dart | 121 +++++++++--------- .../sub_widgets/home_view_button_bar.dart | 7 +- .../helpers/restore_create_backup.dart | 40 +++++- .../sub_widgets/transactions_list.dart | 11 +- .../exchange/change_now_provider.dart | 6 +- lib/route_generator.dart | 4 +- .../change_now/change_now_exchange.dart | 72 +++++++++-- .../change_now_loading_service.dart | 8 +- lib/services/exchange/exchange.dart | 4 +- .../exchange/simpleswap/simpleswap_api.dart | 4 + .../simpleswap/simpleswap_exchange.dart | 8 ++ lib/services/notifications_service.dart | 80 ++++++------ lib/services/trade_service.dart | 26 ++-- lib/utilities/theme/stack_colors.dart | 34 +++-- lib/widgets/trade_card.dart | 14 +- 24 files changed, 508 insertions(+), 368 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 0be26c39ec..f18c2fa498 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,6 +18,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/isar/models/log.dart'; import 'package:stackwallet/models/models.dart'; import 'package:stackwallet/models/node_model.dart'; @@ -33,7 +34,6 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; -import 'package:stackwallet/providers/exchange/change_now_provider.dart'; import 'package:stackwallet/providers/exchange/changenow_initial_load_status.dart'; import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart'; import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart'; @@ -46,6 +46,7 @@ import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/color_theme_provider.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/debug_service.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/services/locale_service.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/notifications_api.dart'; @@ -221,7 +222,6 @@ class _MaterialAppWithThemeState extends ConsumerState nodeService: _nodeService, tradesService: _tradesService, prefs: _prefs, - changeNow: ref.read(changeNowProvider), ); await _prefs.init(); ref.read(priceAnd24hChangeNotifierProvider).start(true); @@ -258,9 +258,9 @@ class _MaterialAppWithThemeState extends ConsumerState .isNotEmpty) { return; } - final response = await ref.read(changeNowProvider).getAvailableCurrencies(); + final response = await ChangeNowAPI.instance.getAvailableCurrencies(); final response2 = - await ref.read(changeNowProvider).getAvailableFloatingRatePairs(); + await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); if (response.value != null) { ref.read(availableChangeNowCurrenciesStateProvider.state).state = response.value!; @@ -312,7 +312,7 @@ class _MaterialAppWithThemeState extends ConsumerState } final response3 = - await ref.read(changeNowProvider).getAvailableFixedRateMarkets(); + await ChangeNowAPI.instance.getAvailableFixedRateMarkets(); if (response3.value != null) { ref.read(fixedRateMarketPairsStateProvider.state).state = response3.value!; diff --git a/lib/models/exchange/incomplete_exchange.dart b/lib/models/exchange/incomplete_exchange.dart index cd80e402f2..7d71fde2df 100644 --- a/lib/models/exchange/incomplete_exchange.dart +++ b/lib/models/exchange/incomplete_exchange.dart @@ -1,5 +1,5 @@ import 'package:decimal/decimal.dart'; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; class IncompleteExchangeModel { @@ -18,7 +18,7 @@ class IncompleteExchangeModel { String? rateId; - ExchangeTransaction? trade; + Trade? trade; IncompleteExchangeModel({ required this.sendTicker, diff --git a/lib/models/exchange/response_objects/trade.dart b/lib/models/exchange/response_objects/trade.dart index b8b9ad8e7f..c1b494097f 100644 --- a/lib/models/exchange/response_objects/trade.dart +++ b/lib/models/exchange/response_objects/trade.dart @@ -1,4 +1,8 @@ import 'package:hive/hive.dart'; +import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; + +part 'trade.g.dart'; @HiveType(typeId: Trade.typeId) class Trade { @@ -67,6 +71,9 @@ class Trade { @HiveField(20) final String status; + @HiveField(21) + final String exchangeName; + const Trade({ required this.uuid, required this.tradeId, @@ -89,6 +96,7 @@ class Trade { required this.refundAddress, required this.refundExtraId, required this.status, + required this.exchangeName, }); Trade copyWith({ @@ -112,6 +120,7 @@ class Trade { String? refundAddress, String? refundExtraId, String? status, + String? exchangeName, }) { return Trade( uuid: uuid, @@ -135,17 +144,18 @@ class Trade { refundAddress: refundAddress ?? this.refundAddress, refundExtraId: refundExtraId ?? this.refundExtraId, status: status ?? this.status, + exchangeName: exchangeName ?? this.exchangeName, ); } - Map toMap() { + Map toMap() { return { "uuid": uuid, "tradeId": tradeId, "rateType": rateType, "direction": direction, - "timestamp": timestamp, - "updatedAt": updatedAt, + "timestamp": timestamp.toIso8601String(), + "updatedAt": updatedAt.toIso8601String(), "payInCurrency": payInCurrency, "payInAmount": payInAmount, "payInAddress": payInAddress, @@ -161,9 +171,66 @@ class Trade { "refundAddress": refundAddress, "refundExtraId": refundExtraId, "status": status, + "exchangeName": exchangeName, }; } + factory Trade.fromMap(Map map) { + return Trade( + uuid: map["uuid"] as String, + tradeId: map["tradeId"] as String, + rateType: map["rateType"] as String, + direction: map["direction"] as String, + timestamp: DateTime.parse(map["timestamp"] as String), + updatedAt: DateTime.parse(map["updatedAt"] as String), + payInCurrency: map["payInCurrency"] as String, + payInAmount: map["payInAmount"] as String, + payInAddress: map["payInAddress"] as String, + payInNetwork: map["payInNetwork"] as String, + payInExtraId: map["payInExtraId"] as String, + payInTxid: map["payInTxid"] as String, + payOutCurrency: map["payOutCurrency"] as String, + payOutAmount: map["payOutAmount"] as String, + payOutAddress: map["payOutAddress"] as String, + payOutNetwork: map["payOutNetwork"] as String, + payOutExtraId: map["payOutExtraId"] as String, + payOutTxid: map["payOutTxid"] as String, + refundAddress: map["refundAddress"] as String, + refundExtraId: map["refundExtraId"] as String, + status: map["status"] as String, + exchangeName: map["exchangeName"] as String, + ); + } + + factory Trade.fromExchangeTransaction(ExchangeTransaction exTx) { + return Trade( + uuid: exTx.uuid, + tradeId: exTx.id, + rateType: "", + direction: "direct", + timestamp: exTx.date, + updatedAt: DateTime.tryParse(exTx.statusObject!.updatedAt) ?? exTx.date, + payInCurrency: exTx.fromCurrency, + payInAmount: exTx.statusObject!.amountSendDecimal.isEmpty + ? exTx.statusObject!.expectedSendAmountDecimal + : exTx.statusObject!.amountSendDecimal, + payInAddress: exTx.payinAddress, + payInNetwork: "", + payInExtraId: exTx.payinExtraId, + payInTxid: exTx.statusObject!.payinHash, + payOutCurrency: exTx.toCurrency, + payOutAmount: exTx.amount, + payOutAddress: exTx.payoutAddress, + payOutNetwork: "", + payOutExtraId: exTx.payoutExtraId, + payOutTxid: exTx.statusObject!.payoutHash, + refundAddress: exTx.refundAddress, + refundExtraId: exTx.refundExtraId, + status: exTx.statusObject!.status.name, + exchangeName: ChangeNowExchange.exchangeName, + ); + } + @override String toString() { return toMap().toString(); diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index 3ddf8932aa..cf1c7f0150 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/trade_wallet_lookup.dart'; import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/sending_transaction_dialog.dart'; @@ -34,7 +34,7 @@ class ConfirmChangeNowSendView extends ConsumerStatefulWidget { final Map transactionInfo; final String walletId; final String routeOnSuccessName; - final ExchangeTransaction trade; + final Trade trade; @override ConsumerState createState() => @@ -46,7 +46,7 @@ class _ConfirmChangeNowSendViewState late final Map transactionInfo; late final String walletId; late final String routeOnSuccessName; - late final ExchangeTransaction trade; + late final Trade trade; Future _attemptSend(BuildContext context) async { unawaited(showDialog( @@ -75,7 +75,7 @@ class _ConfirmChangeNowSendViewState tradeWalletLookup: TradeWalletLookup( uuid: const Uuid().v1(), txid: txid, - tradeId: trade.id, + tradeId: trade.tradeId, walletIds: [walletId], ), ); @@ -309,7 +309,7 @@ class _ConfirmChangeNowSendViewState style: STextStyles.smallMed12(context), ), Text( - trade.id, + trade.tradeId, style: STextStyles.itemSubtitle12(context), textAlign: TextAlign.right, ), diff --git a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart index 0716a9d3b5..20b6740ad7 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_4_view.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; @@ -243,33 +243,19 @@ class _Step3ViewState extends ConsumerState { ), ); - ExchangeResponse - response; - if (model.rateType == - ExchangeRateType.estimated) { - response = await ref - .read(changeNowProvider) - .createStandardExchangeTransaction( - fromTicker: model.sendTicker, - toTicker: model.receiveTicker, - receivingAddress: - model.recipientAddress!, - amount: model.sendAmount, - refundAddress: model.refundAddress!, - ); - } else { - response = await ref - .read(changeNowProvider) - .createFixedRateExchangeTransaction( - fromTicker: model.sendTicker, - toTicker: model.receiveTicker, - receivingAddress: - model.recipientAddress!, - amount: model.sendAmount, - refundAddress: model.refundAddress!, - rateId: model.rateId!, - ); - } + final ExchangeResponse response = + await ref + .read(changeNowProvider) + .createTrade( + from: model.sendTicker, + to: model.receiveTicker, + fixedRate: model.rateType != + ExchangeRateType.estimated, + amount: model.sendAmount, + addressTo: model.recipientAddress!, + addressRefund: model.refundAddress!, + refundExtraId: "", + ); if (response.value == null) { if (mounted) { @@ -293,20 +279,9 @@ class _Step3ViewState extends ConsumerState { shouldNotifyListeners: true, ); - final statusResponse = await ref - .read(changeNowProvider) - .getTransactionStatus( - id: response.value!.id); - - String status = "Waiting"; - if (statusResponse.value != null) { - status = statusResponse.value!.status.name; - } + String status = response.value!.status; - model.trade = response.value!.copyWith( - statusString: status, - statusObject: statusResponse.value!, - ); + model.trade = response.value!; // extra info if status is waiting if (status == "Waiting") { @@ -318,12 +293,12 @@ class _Step3ViewState extends ConsumerState { } unawaited(NotificationApi.showNotification( - changeNowId: model.trade!.id, + changeNowId: model.trade!.tradeId, title: status, - body: "Trade ID ${model.trade!.id}", + body: "Trade ID ${model.trade!.tradeId}", walletId: "", iconAssetName: Assets.svg.arrowRotate, - date: model.trade!.date, + date: model.trade!.timestamp, shouldWatchForUpdates: true, coinName: "coinName", )); diff --git a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart index 222ccfc082..45f67c152e 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart @@ -5,7 +5,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:qr_flutter/qr_flutter.dart'; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/confirm_change_now_send.dart'; @@ -51,7 +50,6 @@ class _Step4ViewState extends ConsumerState { late final ClipboardInterface clipboard; String _statusString = "New"; - ChangeNowTransactionStatus _status = ChangeNowTransactionStatus.New; Timer? _statusTimer; @@ -69,13 +67,11 @@ class _Step4ViewState extends ConsumerState { } Future _updateStatus() async { - final statusResponse = await ref - .read(changeNowProvider) - .getTransactionStatus(id: model.trade!.id); + final statusResponse = + await ref.read(changeNowProvider).updateTrade(model.trade!); String status = "Waiting"; if (statusResponse.value != null) { - _status = statusResponse.value!.status; - status = _status.name; + status = statusResponse.value!.status; } // extra info if status is waiting @@ -112,7 +108,7 @@ class _Step4ViewState extends ConsumerState { @override Widget build(BuildContext context) { final bool isWalletCoin = - _isWalletCoinAndHasWallet(model.trade!.fromCurrency, ref); + _isWalletCoinAndHasWallet(model.trade!.payInCurrency, ref); return Scaffold( backgroundColor: Theme.of(context).extension()!.background, appBar: AppBar( @@ -272,7 +268,7 @@ class _Step4ViewState extends ConsumerState { GestureDetector( onTap: () async { final data = ClipboardData( - text: model.trade!.payinAddress); + text: model.trade!.payInAddress); await clipboard.setData(data); unawaited(showFloatingFlushBar( type: FlushBarType.info, @@ -305,7 +301,7 @@ class _Step4ViewState extends ConsumerState { height: 4, ), Text( - model.trade!.payinAddress, + model.trade!.payInAddress, style: STextStyles.itemSubtitle12(context), ), ], @@ -325,7 +321,7 @@ class _Step4ViewState extends ConsumerState { Row( children: [ Text( - model.trade!.id, + model.trade!.tradeId, style: STextStyles.itemSubtitle12(context), ), const SizedBox( @@ -333,8 +329,8 @@ class _Step4ViewState extends ConsumerState { ), GestureDetector( onTap: () async { - final data = - ClipboardData(text: model.trade!.id); + final data = ClipboardData( + text: model.trade!.tradeId); await clipboard.setData(data); unawaited(showFloatingFlushBar( type: FlushBarType.info, @@ -372,7 +368,7 @@ class _Step4ViewState extends ConsumerState { STextStyles.itemSubtitle(context).copyWith( color: Theme.of(context) .extension()! - .colorForStatus(_status), + .colorForStatus(_statusString), ), ), ], @@ -408,7 +404,7 @@ class _Step4ViewState extends ConsumerState { child: QrImage( // TODO: grab coin uri scheme from somewhere // data: "${coin.uriScheme}:$receivingAddress", - data: model.trade!.payinAddress, + data: model.trade!.payInAddress, size: MediaQuery.of(context) .size .width / @@ -496,7 +492,7 @@ class _Step4ViewState extends ConsumerState { Format.decimalAmountToSatoshis( model.sendAmount); final address = - model.trade!.payinAddress; + model.trade!.payInAddress; try { bool wasCancelled = false; @@ -534,7 +530,7 @@ class _Step4ViewState extends ConsumerState { } txData["note"] = - "${model.trade!.fromCurrency.toUpperCase()}/${model.trade!.toCurrency.toUpperCase()} exchange"; + "${model.trade!.payInCurrency.toUpperCase()}/${model.trade!.payOutCurrency.toUpperCase()} exchange"; txData["address"] = address; if (mounted) { @@ -611,10 +607,10 @@ class _Step4ViewState extends ConsumerState { coin: coinFromTickerCaseInsensitive( model.trade! - .fromCurrency), + .payInCurrency), amount: model.sendAmount, address: - model.trade!.payinAddress, + model.trade!.payInAddress, trade: model.trade!, ); }, diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart index 30a895088f..dd0b3ad1ee 100644 --- a/lib/pages/exchange_view/exchange_view.dart +++ b/lib/pages/exchange_view/exchange_view.dart @@ -7,7 +7,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; -import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; @@ -1255,10 +1254,12 @@ class _ExchangeViewState extends ConsumerState { final response = await ref .read(changeNowProvider) - .getEstimatedExchangeAmount( - fromTicker: fromTicker, - toTicker: toTicker, - fromAmount: sendAmount, + .getEstimate( + fromTicker, + toTicker, + sendAmount, + false, + false, ); if (response.value == null) { @@ -1275,16 +1276,14 @@ class _ExchangeViewState extends ConsumerState { } String rate = - "1 ${fromTicker.toUpperCase()} ~${(response.value!.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + "1 ${fromTicker.toUpperCase()} ~${(response.value! / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; final model = IncompleteExchangeModel( sendTicker: fromTicker.toUpperCase(), receiveTicker: toTicker.toUpperCase(), rateInfo: rate, sendAmount: sendAmount, - receiveAmount: - response.value!.estimatedAmount, - rateId: response.value!.rateId, + receiveAmount: response.value!, rateType: rateType, ); @@ -1321,12 +1320,12 @@ class _ExchangeViewState extends ConsumerState { final response = await ref .read(changeNowProvider) - .getEstimatedExchangeAmountV2( - fromTicker: fromTicker, - toTicker: toTicker, - fromOrTo: CNEstimateType.direct, - amount: sendAmount, - flow: CNFlowType.fixedRate, + .getEstimate( + fromTicker, + toTicker, + sendAmount, + true, + false, ); bool? shouldCancel; @@ -1342,50 +1341,51 @@ class _ExchangeViewState extends ConsumerState { ), )); return; - } else if (response.value!.warningMessage != - null && - response - .value!.warningMessage!.isNotEmpty) { - shouldCancel = await showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: - "Failed to update trade estimate", - message: - "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Cancel", - style: STextStyles.itemSubtitle12( - context), - ), - onPressed: () { - // notify return to cancel - Navigator.of(context).pop(true); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor( - context), - child: Text( - "Attempt", - style: STextStyles.button(context), - ), - onPressed: () { - // continue and try to attempt trade - Navigator.of(context).pop(false); - }, - ), - ), - ); } + // else if (response.value!.warningMessage != + // null && + // response + // .value!.warningMessage!.isNotEmpty) { + // shouldCancel = await showDialog( + // context: context, + // barrierDismissible: true, + // builder: (_) => StackDialog( + // title: + // "Failed to update trade estimate", + // message: + // "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?", + // leftButton: TextButton( + // style: Theme.of(context) + // .extension()! + // .getSecondaryEnabledButtonColor( + // context), + // child: Text( + // "Cancel", + // style: STextStyles.itemSubtitle12( + // context), + // ), + // onPressed: () { + // // notify return to cancel + // Navigator.of(context).pop(true); + // }, + // ), + // rightButton: TextButton( + // style: Theme.of(context) + // .extension()! + // .getPrimaryEnabledButtonColor( + // context), + // child: Text( + // "Attempt", + // style: STextStyles.button(context), + // ), + // onPressed: () { + // // continue and try to attempt trade + // Navigator.of(context).pop(false); + // }, + // ), + // ), + // ); + // } if (shouldCancel is bool && shouldCancel) { return; @@ -1399,8 +1399,7 @@ class _ExchangeViewState extends ConsumerState { receiveTicker: toTicker, rateInfo: rate, sendAmount: sendAmount, - receiveAmount: response.value!.toAmount, - rateId: response.value!.rateId, + receiveAmount: response.value!, rateType: rateType, ); @@ -1490,7 +1489,7 @@ class _ExchangeViewState extends ConsumerState { key: Key("tradeCard_${trades[index].uuid}"), trade: trades[index], onTap: () async { - final String tradeId = trades[index].id; + final String tradeId = trades[index].tradeId; final lookup = ref .read(tradeSentFromStackLookupProvider) diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart index 74a1f62d42..51c420c829 100644 --- a/lib/pages/exchange_view/send_from_view.dart +++ b/lib/pages/exchange_view/send_from_view.dart @@ -4,7 +4,7 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/pages/exchange_view/confirm_change_now_send.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; @@ -36,7 +36,7 @@ class SendFromView extends ConsumerStatefulWidget { final Coin coin; final Decimal amount; final String address; - final ExchangeTransaction trade; + final Trade trade; @override ConsumerState createState() => _SendFromViewState(); @@ -46,7 +46,7 @@ class _SendFromViewState extends ConsumerState { late final Coin coin; late final Decimal amount; late final String address; - late final ExchangeTransaction trade; + late final Trade trade; String formatAmount(Decimal amount, Coin coin) { switch (coin) { @@ -148,7 +148,7 @@ class SendFromCard extends ConsumerStatefulWidget { final String walletId; final Decimal amount; final String address; - final ExchangeTransaction trade; + final Trade trade; @override ConsumerState createState() => _SendFromCardState(); @@ -158,7 +158,7 @@ class _SendFromCardState extends ConsumerState { late final String walletId; late final Decimal amount; late final String address; - late final ExchangeTransaction trade; + late final Trade trade; @override void initState() { @@ -230,7 +230,7 @@ class _SendFromCardState extends ConsumerState { } txData["note"] = - "${trade.fromCurrency.toUpperCase()}/${trade.toCurrency.toUpperCase()} exchange"; + "${trade.payInCurrency.toUpperCase()}/${trade.payOutCurrency.toUpperCase()} exchange"; txData["address"] = address; if (mounted) { diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index ae44faeef9..5570757a6e 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -82,18 +82,15 @@ class _TradeDetailsViewState extends ConsumerState { final trade = ref .read(tradesServiceProvider) .trades - .firstWhere((e) => e.id == tradeId); + .firstWhere((e) => e.tradeId == tradeId); - if (mounted && trade.statusObject == null || - trade.statusObject!.amountSendDecimal.isEmpty) { - final status = await ref - .read(changeNowProvider) - .getTransactionStatus(id: trade.id); + if (mounted) { + final response = await ref.read(changeNowProvider).updateTrade(trade); - if (mounted && status.value != null) { - await ref.read(tradesServiceProvider).edit( - trade: trade.copyWith(statusObject: status.value), - shouldNotifyListeners: true); + if (mounted && response.value != null) { + await ref + .read(tradesServiceProvider) + .edit(trade: response.value!, shouldNotifyListeners: true); } } }); @@ -132,23 +129,29 @@ class _TradeDetailsViewState extends ConsumerState { final bool sentFromStack = transactionIfSentFromStack != null && walletId != null; - final trade = ref.watch(tradesServiceProvider - .select((value) => value.trades.firstWhere((e) => e.id == tradeId))); + final trade = ref.watch(tradesServiceProvider.select( + (value) => value.trades.firstWhere((e) => e.tradeId == tradeId))); final bool hasTx = sentFromStack || - !(trade.statusObject?.status == ChangeNowTransactionStatus.New || - trade.statusObject?.status == ChangeNowTransactionStatus.Waiting || - trade.statusObject?.status == ChangeNowTransactionStatus.Refunded || - trade.statusObject?.status == ChangeNowTransactionStatus.Failed); + !(trade.status == "New" || + trade.status == "new" || + trade.status == "Waiting" || + trade.status == "waiting" || + trade.status == "Refunded" || + trade.status == "refunded" || + trade.status == "Closed" || + trade.status == "closed" || + trade.status == "Expired" || + trade.status == "expired" || + trade.status == "Failed" || + trade.status == "failed"); debugPrint("sentFromStack: $sentFromStack"); debugPrint("hasTx: $hasTx"); debugPrint("trade: ${trade.toString()}"); - final sendAmount = Decimal.tryParse( - trade.statusObject?.amountSendDecimal ?? "") ?? - Decimal.tryParse(trade.statusObject?.expectedSendAmountDecimal ?? "") ?? - Decimal.parse("-1"); + final sendAmount = + Decimal.tryParse(trade.payInAmount) ?? Decimal.parse("-1"); return Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -180,7 +183,7 @@ class _TradeDetailsViewState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ SelectableText( - "${trade.fromCurrency.toUpperCase()} → ${trade.toCurrency.toUpperCase()}", + "${trade.payInCurrency.toUpperCase()} → ${trade.payOutCurrency.toUpperCase()}", style: STextStyles.titleBold12(context), ), const SizedBox( @@ -190,7 +193,7 @@ class _TradeDetailsViewState extends ConsumerState { "${Format.localizedStringAsFixed(value: sendAmount, locale: ref.watch( localeServiceChangeNotifierProvider .select((value) => value.locale), - ), decimalPlaces: trade.fromCurrency.toLowerCase() == "xmr" ? 12 : 8)} ${trade.fromCurrency.toUpperCase()}", + ), decimalPlaces: trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8)} ${trade.payInCurrency.toUpperCase()}", style: STextStyles.itemSubtitle(context), ), ], @@ -203,9 +206,7 @@ class _TradeDetailsViewState extends ConsumerState { ), child: Center( child: SvgPicture.asset( - _fetchIconAssetForStatus( - trade.statusObject?.status.name ?? - trade.statusString), + _fetchIconAssetForStatus(trade.status), width: 32, height: 32, ), @@ -229,15 +230,11 @@ class _TradeDetailsViewState extends ConsumerState { height: 4, ), SelectableText( - trade.statusObject?.status.name ?? trade.statusString, + trade.status, style: STextStyles.itemSubtitle(context).copyWith( - color: trade.statusObject != null - ? Theme.of(context) - .extension()! - .colorForStatus(trade.statusObject!.status) - : Theme.of(context) - .extension()! - .accentColorDark, + color: Theme.of(context) + .extension()! + .colorForStatus(trade.status), ), ), // ), @@ -258,8 +255,8 @@ class _TradeDetailsViewState extends ConsumerState { text: TextSpan( text: "You must send at least ${sendAmount.toStringAsFixed( - trade.fromCurrency.toLowerCase() == "xmr" ? 12 : 8, - )} ${trade.fromCurrency.toUpperCase()}. ", + trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, + )} ${trade.payInCurrency.toUpperCase()}. ", style: STextStyles.label700(context).copyWith( color: Theme.of(context) .extension()! @@ -269,10 +266,10 @@ class _TradeDetailsViewState extends ConsumerState { TextSpan( text: "If you send less than ${sendAmount.toStringAsFixed( - trade.fromCurrency.toLowerCase() == "xmr" + trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8, - )} ${trade.fromCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.", + )} ${trade.payInCurrency.toUpperCase()}, your transaction may not be converted and it may not be refunded.", style: STextStyles.label(context).copyWith( color: Theme.of(context) .extension()! @@ -308,7 +305,7 @@ class _TradeDetailsViewState extends ConsumerState { GestureDetector( onTap: () { final Coin coin = coinFromTickerCaseInsensitive( - trade.fromCurrency); + trade.payInCurrency); Navigator.of(context).pushNamed( TransactionDetailsView.routeName, @@ -341,7 +338,7 @@ class _TradeDetailsViewState extends ConsumerState { height: 4, ), SelectableText( - trade.payinAddress, + trade.payInAddress, style: STextStyles.itemSubtitle12(context), ), ], @@ -360,12 +357,12 @@ class _TradeDetailsViewState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "Send ${trade.fromCurrency.toUpperCase()} to this address", + "Send ${trade.payInCurrency.toUpperCase()} to this address", style: STextStyles.itemSubtitle(context), ), GestureDetector( onTap: () async { - final address = trade.payinAddress; + final address = trade.payInAddress; await Clipboard.setData( ClipboardData( text: address, @@ -403,7 +400,7 @@ class _TradeDetailsViewState extends ConsumerState { height: 4, ), SelectableText( - trade.payinAddress, + trade.payInAddress, style: STextStyles.itemSubtitle12(context), ), const SizedBox( @@ -425,7 +422,7 @@ class _TradeDetailsViewState extends ConsumerState { children: [ Center( child: Text( - "Send ${trade.fromCurrency.toUpperCase()} to this address", + "Send ${trade.payInCurrency.toUpperCase()} to this address", style: STextStyles.pageTitleH2(context), ), @@ -440,7 +437,7 @@ class _TradeDetailsViewState extends ConsumerState { width: width + 20, height: width + 20, child: QrImage( - data: trade.payinAddress, + data: trade.payInAddress, size: width, backgroundColor: Theme.of( context) @@ -658,7 +655,7 @@ class _TradeDetailsViewState extends ConsumerState { // child: SelectableText( Format.extractDateFrom( - trade.date.millisecondsSinceEpoch ~/ 1000), + trade.timestamp.millisecondsSinceEpoch ~/ 1000), style: STextStyles.itemSubtitle12(context), ), // ), @@ -704,7 +701,7 @@ class _TradeDetailsViewState extends ConsumerState { Row( children: [ Text( - trade.id, + trade.tradeId, style: STextStyles.itemSubtitle12(context), ), const SizedBox( @@ -712,7 +709,7 @@ class _TradeDetailsViewState extends ConsumerState { ), GestureDetector( onTap: () async { - final data = ClipboardData(text: trade.id); + final data = ClipboardData(text: trade.tradeId); await clipboard.setData(data); unawaited(showFloatingFlushBar( type: FlushBarType.info, @@ -750,14 +747,14 @@ class _TradeDetailsViewState extends ConsumerState { GestureDetector( onTap: () { final url = - "https://changenow.io/exchange/txs/${trade.id}"; + "https://changenow.io/exchange/txs/${trade.tradeId}"; launchUrl( Uri.parse(url), mode: LaunchMode.externalApplication, ); }, child: Text( - "https://changenow.io/exchange/txs/${trade.id}", + "https://changenow.io/exchange/txs/${trade.tradeId}", style: STextStyles.link2(context), ), ), @@ -767,20 +764,19 @@ class _TradeDetailsViewState extends ConsumerState { const SizedBox( height: 12, ), - if (isStackCoin(trade.fromCurrency) && - trade.statusObject != null && - (trade.statusObject!.status == - ChangeNowTransactionStatus.New || - trade.statusObject!.status == - ChangeNowTransactionStatus.Waiting)) + if (isStackCoin(trade.payInCurrency) && + (trade.status == "New" || + trade.status == "new" || + trade.status == "waiting" || + trade.status == "Waiting")) SecondaryButton( label: "Send from Stack", onPressed: () { final amount = sendAmount; - final address = trade.payinAddress; + final address = trade.payInAddress; final coin = - coinFromTickerCaseInsensitive(trade.fromCurrency); + coinFromTickerCaseInsensitive(trade.payInCurrency); Navigator.of(context).pushNamed( SendFromView.routeName, diff --git a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart index 0b4670c122..6652af32b5 100644 --- a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart +++ b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart @@ -6,7 +6,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; -import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; @@ -1409,10 +1408,12 @@ class _WalletInitiatedExchangeViewState final response = await ref .read(changeNowProvider) - .getEstimatedExchangeAmount( - fromTicker: fromTicker, - toTicker: toTicker, - fromAmount: sendAmount, + .getEstimate( + fromTicker, + toTicker, + sendAmount, + false, + false, ); if (response.value == null) { @@ -1430,16 +1431,14 @@ class _WalletInitiatedExchangeViewState } String rate = - "1 ${fromTicker.toUpperCase()} ~${(response.value!.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + "1 ${fromTicker.toUpperCase()} ~${(response.value! / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; final model = IncompleteExchangeModel( sendTicker: fromTicker.toUpperCase(), receiveTicker: toTicker.toUpperCase(), rateInfo: rate, sendAmount: sendAmount, - receiveAmount: - response.value!.estimatedAmount, - rateId: response.value!.rateId, + receiveAmount: response.value!, rateType: rateType, ); @@ -1476,12 +1475,12 @@ class _WalletInitiatedExchangeViewState final response = await ref .read(changeNowProvider) - .getEstimatedExchangeAmountV2( - fromTicker: fromTicker, - toTicker: toTicker, - fromOrTo: CNEstimateType.direct, - amount: sendAmount, - flow: CNFlowType.fixedRate, + .getEstimate( + fromTicker, + toTicker, + sendAmount, + true, + false, ); bool? shouldCancel; @@ -1498,51 +1497,52 @@ class _WalletInitiatedExchangeViewState ), )); return; - } else if (response.value!.warningMessage != - null && - response.value!.warningMessage! - .isNotEmpty) { - shouldCancel = await showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: - "Failed to update trade estimate", - message: - "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Cancel", - style: STextStyles.itemSubtitle12( - context), - ), - onPressed: () { - // notify return to cancel - Navigator.of(context).pop(true); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor( - context), - child: Text( - "Attempt", - style: - STextStyles.button(context), - ), - onPressed: () { - // continue and try to attempt trade - Navigator.of(context).pop(false); - }, - ), - ), - ); } + // else if (response.value!.warningMessage != + // null && + // response.value!.warningMessage! + // .isNotEmpty) { + // shouldCancel = await showDialog( + // context: context, + // barrierDismissible: true, + // builder: (_) => StackDialog( + // title: + // "Failed to update trade estimate", + // message: + // "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?", + // leftButton: TextButton( + // style: Theme.of(context) + // .extension()! + // .getSecondaryEnabledButtonColor( + // context), + // child: Text( + // "Cancel", + // style: STextStyles.itemSubtitle12( + // context), + // ), + // onPressed: () { + // // notify return to cancel + // Navigator.of(context).pop(true); + // }, + // ), + // rightButton: TextButton( + // style: Theme.of(context) + // .extension()! + // .getPrimaryEnabledButtonColor( + // context), + // child: Text( + // "Attempt", + // style: + // STextStyles.button(context), + // ), + // onPressed: () { + // // continue and try to attempt trade + // Navigator.of(context).pop(false); + // }, + // ), + // ), + // ); + // } if (shouldCancel is bool && shouldCancel) { return; @@ -1556,8 +1556,7 @@ class _WalletInitiatedExchangeViewState receiveTicker: toTicker, rateInfo: rate, sendAmount: sendAmount, - receiveAmount: response.value!.toAmount, - rateId: response.value!.rateId, + receiveAmount: response.value!, rateType: rateType, ); diff --git a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart index 7541b166f1..3563df5502 100644 --- a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart +++ b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart @@ -10,6 +10,7 @@ import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provi import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart'; import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -43,9 +44,9 @@ class _HomeViewButtonBarState extends ConsumerState { BuildContext context, WidgetRef ref, ) async { - final response = await ref.read(changeNowProvider).getAvailableCurrencies(); + final response = await ref.read(changeNowProvider).getAllCurrencies(false); final response2 = - await ref.read(changeNowProvider).getAvailableFloatingRatePairs(); + await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); if (response.value != null && response2.value != null) { ref.read(availableChangeNowCurrenciesStateProvider.state).state = response.value!; @@ -90,7 +91,7 @@ class _HomeViewButtonBarState extends ConsumerState { WidgetRef ref, ) async { final response3 = - await ref.read(changeNowProvider).getAvailableFixedRateMarkets(); + await ChangeNowAPI.instance.getAvailableFixedRateMarkets(); if (response3.value != null) { ref.read(fixedRateMarketPairsStateProvider.state).state = diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index 721141cb82..dac0b37d66 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -11,6 +11,7 @@ import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/contact.dart'; import 'package:stackwallet/models/contact_address_entry.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/stack_restoring_ui_state.dart'; import 'package:stackwallet/models/trade_wallet_lookup.dart'; @@ -1026,8 +1027,7 @@ abstract class SWB { // trade existed before attempted restore so we don't delete it, only // revert data to pre restore state await tradesService.edit( - trade: ExchangeTransaction.fromJson( - tradeData as Map), + trade: Trade.fromMap(tradeData as Map), shouldNotifyListeners: true); } else { // trade did not exist before so we delete it @@ -1048,7 +1048,7 @@ abstract class SWB { } } else { // grab all trade IDs of (reverted to pre state) trades - final idsToKeep = tradesService.trades.map((e) => e.id); + final idsToKeep = tradesService.trades.map((e) => e.tradeId); // delete all notes that don't correspond to an id that we have for (final noteEntry in currentNotes.entries) { @@ -1189,16 +1189,44 @@ abstract class SWB { ) async { final tradesService = TradesService(); for (int i = 0; i < trades.length - 1; i++) { + ExchangeTransaction? exTx; + try { + exTx = ExchangeTransaction.fromJson(trades[i] as Map); + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + } + + Trade trade; + if (exTx != null) { + trade = Trade.fromExchangeTransaction(exTx); + } else { + trade = Trade.fromMap(trades[i] as Map); + } + await tradesService.add( - trade: ExchangeTransaction.fromJson(trades[i] as Map), + trade: trade, shouldNotifyListeners: false, ); } // only call notifyListeners on last one added if (trades.isNotEmpty) { + ExchangeTransaction? exTx; + try { + exTx = + ExchangeTransaction.fromJson(trades.last as Map); + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + } + + Trade trade; + if (exTx != null) { + trade = Trade.fromExchangeTransaction(exTx); + } else { + trade = Trade.fromMap(trades.last as Map); + } + await tradesService.add( - trade: - ExchangeTransaction.fromJson(trades.last as Map), + trade: trade, shouldNotifyListeners: true, ); } diff --git a/lib/pages/wallet_view/sub_widgets/transactions_list.dart b/lib/pages/wallet_view/sub_widgets/transactions_list.dart index f95e759978..2246882ab4 100644 --- a/lib/pages/wallet_view/sub_widgets/transactions_list.dart +++ b/lib/pages/wallet_view/sub_widgets/transactions_list.dart @@ -3,7 +3,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart'; +import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -13,9 +15,6 @@ import 'package:stackwallet/widgets/trade_card.dart'; import 'package:stackwallet/widgets/transaction_card.dart'; import 'package:tuple/tuple.dart'; -import '../../../providers/global/trades_service_provider.dart'; -import '../../exchange_view/trade_details_view.dart'; - class TransactionsList extends ConsumerStatefulWidget { const TransactionsList({ Key? key, @@ -135,9 +134,7 @@ class _TransactionsListState extends ConsumerState { .read(tradesServiceProvider) .trades .where((e) => - e.statusObject != null && - (e.statusObject!.payinHash == tx.txid || - e.statusObject!.payoutHash == tx.txid)); + e.payInTxid == tx.txid || e.payOutTxid == tx.txid); if (tx.txType == "Sent" && matchingTrades.isNotEmpty) { final trade = matchingTrades.first; return Container( @@ -164,7 +161,7 @@ class _TransactionsListState extends ConsumerState { Navigator.of(context).pushNamed( TradeDetailsView.routeName, arguments: Tuple4( - trade.id, + trade.tradeId, tx, widget.walletId, ref.read(managerProvider).walletName, diff --git a/lib/providers/exchange/change_now_provider.dart b/lib/providers/exchange/change_now_provider.dart index 858ddc6647..8493680f4d 100644 --- a/lib/providers/exchange/change_now_provider.dart +++ b/lib/providers/exchange/change_now_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; -final changeNowProvider = - Provider((ref) => ChangeNowAPI.instance); +final changeNowProvider = Provider((ref) => ChangeNowExchange()); diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 3681881713..a47c223e95 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -3,8 +3,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/contact_address_entry.dart'; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/paymint/transactions_model.dart'; import 'package:stackwallet/models/send_view_auto_fill_data.dart'; import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart'; @@ -898,7 +898,7 @@ class RouteGenerator { return _routeError("${settings.name} invalid args: ${args.toString()}"); case SendFromView.routeName: - if (args is Tuple4) { + if (args is Tuple4) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => SendFromView( diff --git a/lib/services/exchange/change_now/change_now_exchange.dart b/lib/services/exchange/change_now/change_now_exchange.dart index 4220be17a0..ce5182e44e 100644 --- a/lib/services/exchange/change_now/change_now_exchange.dart +++ b/lib/services/exchange/change_now/change_now_exchange.dart @@ -1,5 +1,6 @@ import 'package:decimal/decimal.dart'; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; +import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; @@ -10,6 +11,11 @@ import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:uuid/uuid.dart'; class ChangeNowExchange extends Exchange { + static const exchangeName = "ChangeNOW"; + + @override + String get name => exchangeName; + @override Future> createTrade({ required String from, @@ -17,11 +23,51 @@ class ChangeNowExchange extends Exchange { required bool fixedRate, required Decimal amount, required String addressTo, + String? extraId, required String addressRefund, required String refundExtraId, + String? rateId, }) async { - // TODO: implement createTrade - throw UnimplementedError(); + late final ExchangeResponse response; + if (fixedRate) { + response = await ChangeNowAPI.instance.createFixedRateExchangeTransaction( + fromTicker: from, + toTicker: to, + receivingAddress: addressTo, + amount: amount, + rateId: rateId!, + extraId: extraId ?? "", + refundAddress: addressRefund, + refundExtraId: refundExtraId, + ); + } else { + response = await ChangeNowAPI.instance.createStandardExchangeTransaction( + fromTicker: from, + toTicker: to, + receivingAddress: addressTo, + amount: amount, + extraId: extraId ?? "", + refundAddress: addressRefund, + refundExtraId: refundExtraId, + ); + } + if (response.exception != null) { + return ExchangeResponse(exception: response.exception); + } + + final statusResponse = await ChangeNowAPI.instance + .getTransactionStatus(id: response.value!.id); + if (statusResponse.exception != null) { + return ExchangeResponse(exception: statusResponse.exception); + } + + return ExchangeResponse( + value: Trade.fromExchangeTransaction( + response.value!.copyWith( + statusObject: statusResponse.value!, + ), + ), + ); } @override @@ -124,6 +170,7 @@ class ChangeNowExchange extends Exchange { refundAddress: t.refundAddress, refundExtraId: t.refundExtraId, status: t.status.name, + exchangeName: ChangeNowExchange.exchangeName, ); return ExchangeResponse(value: trade); @@ -142,25 +189,30 @@ class ChangeNowExchange extends Exchange { final _trade = Trade( uuid: trade.uuid, tradeId: trade.tradeId, - rateType: "", - direction: "", + rateType: trade.rateType, + direction: trade.direction, timestamp: timestamp, updatedAt: DateTime.tryParse(t.updatedAt) ?? timestamp, payInCurrency: t.fromCurrency, - payInAmount: t.expectedSendAmountDecimal, + payInAmount: t.amountSendDecimal.isEmpty + ? t.expectedSendAmountDecimal + : t.amountSendDecimal, payInAddress: t.payinAddress, - payInNetwork: "", + payInNetwork: trade.payInNetwork, payInExtraId: t.payinExtraId, - payInTxid: "", + payInTxid: t.payinHash, payOutCurrency: t.toCurrency, - payOutAmount: t.expectedReceiveAmountDecimal, + payOutAmount: t.amountReceiveDecimal.isEmpty + ? t.expectedReceiveAmountDecimal + : t.amountReceiveDecimal, payOutAddress: t.payoutAddress, - payOutNetwork: "", + payOutNetwork: trade.payOutNetwork, payOutExtraId: t.payoutExtraId, - payOutTxid: "", + payOutTxid: t.payoutHash, refundAddress: t.refundAddress, refundExtraId: t.refundExtraId, status: t.status.name, + exchangeName: ChangeNowExchange.exchangeName, ); return ExchangeResponse(value: _trade); diff --git a/lib/services/exchange/change_now/change_now_loading_service.dart b/lib/services/exchange/change_now/change_now_loading_service.dart index 51cd12d21a..d4412d864b 100644 --- a/lib/services/exchange/change_now/change_now_loading_service.dart +++ b/lib/services/exchange/change_now/change_now_loading_service.dart @@ -1,11 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; -import 'package:stackwallet/providers/exchange/change_now_provider.dart'; import 'package:stackwallet/providers/exchange/changenow_initial_load_status.dart'; import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart'; import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart'; import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -33,7 +33,7 @@ class ChangeNowLoadingService { ChangeNowLoadStatus.loading; final response3 = - await ref.read(changeNowProvider).getAvailableFixedRateMarkets(); + await ChangeNowAPI.instance.getAvailableFixedRateMarkets(); if (response3.value != null) { ref.read(fixedRateMarketPairsStateProvider.state).state = response3.value!; @@ -81,9 +81,9 @@ class ChangeNowLoadingService { ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = ChangeNowLoadStatus.loading; - final response = await ref.read(changeNowProvider).getAvailableCurrencies(); + final response = await ChangeNowAPI.instance.getAvailableCurrencies(); final response2 = - await ref.read(changeNowProvider).getAvailableFloatingRatePairs(); + await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); if (response.value != null) { ref.read(availableChangeNowCurrenciesStateProvider.state).state = response.value!; diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart index e629b0e0df..8771fffd55 100644 --- a/lib/services/exchange/exchange.dart +++ b/lib/services/exchange/exchange.dart @@ -6,7 +6,7 @@ import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; abstract class Exchange { - // + String get name; Future>> getAllCurrencies(bool fixedRate); @@ -42,7 +42,9 @@ abstract class Exchange { required bool fixedRate, required Decimal amount, required String addressTo, + String? extraId, required String addressRefund, required String refundExtraId, + String? rateId, }); } diff --git a/lib/services/exchange/simpleswap/simpleswap_api.dart b/lib/services/exchange/simpleswap/simpleswap_api.dart index 944f9a5206..2df5d16a1a 100644 --- a/lib/services/exchange/simpleswap/simpleswap_api.dart +++ b/lib/services/exchange/simpleswap/simpleswap_api.dart @@ -9,6 +9,7 @@ import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/exchange/simpleswap/sp_currency.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; @@ -90,6 +91,7 @@ class SimpleSwapAPI { "userRefundAddress": userRefundAddress, "userRefundExtraId": userRefundExtraId, "amount": double.parse(amount), + "extraIdTo": extraIdTo, }; final uri = @@ -124,6 +126,7 @@ class SimpleSwapAPI { refundAddress: json["user_refund_address"] as String, refundExtraId: json["user_refund_extra_id"] as String, status: json["status"] as String, + exchangeName: SimpleSwapExchange.exchangeName, ); return ExchangeResponse(value: trade, exception: null); } catch (e, s) { @@ -371,6 +374,7 @@ class SimpleSwapAPI { refundAddress: json["user_refund_address"] as String, refundExtraId: json["user_refund_extra_id"] as String, status: json["status"] as String, + exchangeName: SimpleSwapExchange.exchangeName, ); return ExchangeResponse(value: trade); diff --git a/lib/services/exchange/simpleswap/simpleswap_exchange.dart b/lib/services/exchange/simpleswap/simpleswap_exchange.dart index ca45317ea6..e8e79b06da 100644 --- a/lib/services/exchange/simpleswap/simpleswap_exchange.dart +++ b/lib/services/exchange/simpleswap/simpleswap_exchange.dart @@ -8,6 +8,11 @@ import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_api.dart'; class SimpleSwapExchange extends Exchange { + static const exchangeName = "SimpleSwap"; + + @override + String get name => exchangeName; + @override Future> createTrade({ required String from, @@ -15,8 +20,10 @@ class SimpleSwapExchange extends Exchange { required bool fixedRate, required Decimal amount, required String addressTo, + String? extraId, required String addressRefund, required String refundExtraId, + String? rateId, }) async { return await SimpleSwapAPI.instance.createNewExchange( isFixedRate: fixedRate, @@ -26,6 +33,7 @@ class SimpleSwapExchange extends Exchange { userRefundAddress: addressRefund, userRefundExtraId: refundExtraId, amount: amount.toString(), + extraIdTo: extraId, ); } diff --git a/lib/services/notifications_service.dart b/lib/services/notifications_service.dart index aea849bdb4..8ade3e9696 100644 --- a/lib/services/notifications_service.dart +++ b/lib/services/notifications_service.dart @@ -3,9 +3,11 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; import 'package:stackwallet/hive/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/notification_model.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/notifications_api.dart'; import 'package:stackwallet/services/trade_service.dart'; @@ -17,7 +19,6 @@ class NotificationsService extends ChangeNotifier { late NodeService nodeService; late TradesService tradesService; late Prefs prefs; - late ChangeNowAPI changeNow; NotificationsService._(); static final NotificationsService _instance = NotificationsService._(); @@ -27,12 +28,10 @@ class NotificationsService extends ChangeNotifier { required NodeService nodeService, required TradesService tradesService, required Prefs prefs, - required ChangeNowAPI changeNow, }) async { this.nodeService = nodeService; this.tradesService = tradesService; this.prefs = prefs; - this.changeNow = changeNow; } // watched transactions @@ -184,33 +183,52 @@ class NotificationsService extends ChangeNotifier { for (final notification in _watchedChangeNowTradeNotifications) { final id = notification.changeNowId!; - final result = await changeNow.getTransactionStatus(id: id); + final trades = + tradesService.trades.where((element) => element.tradeId == id); - ChangeNowTransactionStatus? status = result.value?.status; + if (trades.isEmpty) { + return; + } + final oldTrade = trades.first; + late final ExchangeResponse response; + switch (oldTrade.exchangeName) { + case SimpleSwapExchange.exchangeName: + response = await SimpleSwapExchange().updateTrade(oldTrade); + break; + case ChangeNowExchange.exchangeName: + response = await ChangeNowExchange().updateTrade(oldTrade); + break; + default: + return; + } + + if (response.value == null) { + return; + } + + final trade = response.value!; // only update if status has changed - if (status != null && status.name != notification.title) { + if (trade.status != notification.title) { bool shouldWatchForUpdates = true; // TODO: make sure we set shouldWatchForUpdates to correct value here - switch (status) { - case ChangeNowTransactionStatus.New: - case ChangeNowTransactionStatus.Waiting: - case ChangeNowTransactionStatus.Confirming: - case ChangeNowTransactionStatus.Exchanging: - case ChangeNowTransactionStatus.Verifying: - case ChangeNowTransactionStatus.Sending: - shouldWatchForUpdates = true; - break; - - case ChangeNowTransactionStatus.Finished: - case ChangeNowTransactionStatus.Failed: - case ChangeNowTransactionStatus.Refunded: + switch (trade.status) { + case "Refunded": + case "refunded": + case "Failed": + case "failed": + case "closed": + case "expired": + case "Finished": + case "finished": shouldWatchForUpdates = false; break; + default: + shouldWatchForUpdates = true; } final updatedNotification = notification.copyWith( - title: status.name, + title: trade.status, shouldWatchForUpdates: shouldWatchForUpdates, ); @@ -220,23 +238,11 @@ class NotificationsService extends ChangeNotifier { } // replaces the current notification with the updated one - add(updatedNotification, true); + unawaited(add(updatedNotification, true)); // update the trade in db - if (result.value != null) { - // fetch matching trade from db - final trade = tradesService.trades - .firstWhere((element) => element.id == result.value!.id); - - // update status - final updatedTrade = trade.copyWith( - statusObject: result.value!, - statusString: result.value!.status.name, - ); - - // over write trade stored in db with updated version - tradesService.add(trade: updatedTrade, shouldNotifyListeners: true); - } + // over write trade stored in db with updated version + await tradesService.edit(trade: trade, shouldNotifyListeners: true); } } } diff --git a/lib/services/trade_service.dart b/lib/services/trade_service.dart index 1f38c06816..0ec6d17a61 100644 --- a/lib/services/trade_service.dart +++ b/lib/services/trade_service.dart @@ -1,22 +1,22 @@ import 'package:flutter/cupertino.dart'; import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; class TradesService extends ChangeNotifier { - List get trades { - final list = - DB.instance.values(boxName: DB.boxNameTrades); + List get trades { + final list = DB.instance.values(boxName: DB.boxNameTradesV2); list.sort((a, b) => - b.date.millisecondsSinceEpoch - a.date.millisecondsSinceEpoch); + b.timestamp.millisecondsSinceEpoch - + a.timestamp.millisecondsSinceEpoch); return list; } Future add({ - required ExchangeTransaction trade, + required Trade trade, required bool shouldNotifyListeners, }) async { - await DB.instance.put( - boxName: DB.boxNameTrades, key: trade.uuid, value: trade); + await DB.instance + .put(boxName: DB.boxNameTradesV2, key: trade.uuid, value: trade); if (shouldNotifyListeners) { notifyListeners(); @@ -24,11 +24,10 @@ class TradesService extends ChangeNotifier { } Future edit({ - required ExchangeTransaction trade, + required Trade trade, required bool shouldNotifyListeners, }) async { - if (DB.instance.get( - boxName: DB.boxNameTrades, key: trade.uuid) == + if (DB.instance.get(boxName: DB.boxNameTradesV2, key: trade.uuid) == null) { throw Exception("Attempted to edit a trade that does not exist in Hive!"); } @@ -38,7 +37,7 @@ class TradesService extends ChangeNotifier { } Future delete({ - required ExchangeTransaction trade, + required Trade trade, required bool shouldNotifyListeners, }) async { await deleteByUuid( @@ -49,8 +48,7 @@ class TradesService extends ChangeNotifier { required String uuid, required bool shouldNotifyListeners, }) async { - await DB.instance - .delete(boxName: DB.boxNameTrades, key: uuid); + await DB.instance.delete(boxName: DB.boxNameTradesV2, key: uuid); if (shouldNotifyListeners) { notifyListeners(); diff --git a/lib/utilities/theme/stack_colors.dart b/lib/utilities/theme/stack_colors.dart index c6ee28892a..9dd34809b9 100644 --- a/lib/utilities/theme/stack_colors.dart +++ b/lib/utilities/theme/stack_colors.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/theme/color_theme.dart'; @@ -1435,21 +1434,34 @@ class StackColors extends ThemeExtension { blurRadius: 4, ); - Color colorForStatus(ChangeNowTransactionStatus status) { + Color colorForStatus(String status) { switch (status) { - case ChangeNowTransactionStatus.New: - case ChangeNowTransactionStatus.Waiting: - case ChangeNowTransactionStatus.Confirming: - case ChangeNowTransactionStatus.Exchanging: - case ChangeNowTransactionStatus.Sending: - case ChangeNowTransactionStatus.Verifying: + case "New": + case "new": + case "Waiting": + case "waiting": + case "Confirming": + case "confirming": + case "Exchanging": + case "exchanging": + case "Sending": + case "sending": + case "Verifying": + case "verifying": return const Color(0xFFD3A90F); - case ChangeNowTransactionStatus.Finished: + case "Finished": + case "finished": return accentColorGreen; - case ChangeNowTransactionStatus.Failed: + case "Failed": + case "failed": + case "closed": + case "expired": return accentColorRed; - case ChangeNowTransactionStatus.Refunded: + case "Refunded": + case "refunded": return textSubtitle2; + default: + return const Color(0xFFD3A90F); } } diff --git a/lib/widgets/trade_card.dart b/lib/widgets/trade_card.dart index ab58e22934..ba07b95767 100644 --- a/lib/widgets/trade_card.dart +++ b/lib/widgets/trade_card.dart @@ -2,8 +2,8 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -16,7 +16,7 @@ class TradeCard extends ConsumerWidget { required this.onTap, }) : super(key: key); - final ExchangeTransaction trade; + final Trade trade; final VoidCallback onTap; String _fetchIconAssetForStatus(String statusString, BuildContext context) { @@ -62,7 +62,7 @@ class TradeCard extends ConsumerWidget { child: Center( child: SvgPicture.asset( _fetchIconAssetForStatus( - trade.statusObject?.status.name ?? trade.statusString, + trade.status, context, ), width: 32, @@ -80,11 +80,11 @@ class TradeCard extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "${trade.fromCurrency.toUpperCase()} → ${trade.toCurrency.toUpperCase()}", + "${trade.payInCurrency.toUpperCase()} → ${trade.payOutCurrency.toUpperCase()}", style: STextStyles.itemSubtitle12(context), ), Text( - "${Decimal.tryParse(trade.statusObject?.amountSendDecimal ?? "") ?? "..."} ${trade.fromCurrency.toUpperCase()}", + "${Decimal.tryParse(trade.payInAmount) ?? "..."} ${trade.payInCurrency.toUpperCase()}", style: STextStyles.itemSubtitle12(context), ), ], @@ -96,12 +96,12 @@ class TradeCard extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "ChangeNOW", + trade.exchangeName, style: STextStyles.label(context), ), Text( Format.extractDateFrom( - trade.date.millisecondsSinceEpoch ~/ 1000), + trade.timestamp.millisecondsSinceEpoch ~/ 1000), style: STextStyles.label(context), ), ], From 649ebd1987e53b0c2b45712445c6df2ad288f52b Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 2 Oct 2022 18:55:06 -0600 Subject: [PATCH 08/75] clean up db migrate --- lib/utilities/constants.dart | 2 +- lib/utilities/db_version_migration.dart | 33 ++++++------------------- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 69c1444c32..4eb69d7e17 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -36,7 +36,7 @@ abstract class Constants { // Enable Logger.print statements static const bool disableLogger = false; - static const int currentHiveDbVersion = 1; + static const int currentHiveDbVersion = 2; static List possibleLengthsForCoin(Coin coin) { final List values = []; diff --git a/lib/utilities/db_version_migration.dart b/lib/utilities/db_version_migration.dart index db12b0f377..e88a0cafb8 100644 --- a/lib/utilities/db_version_migration.dart +++ b/lib/utilities/db_version_migration.dart @@ -21,6 +21,10 @@ class DbVersionMigrator { FlutterSecureStorage(), ), }) async { + Logging.instance.log( + "Running migrate fromVersion $fromVersion", + level: LogLevel.Warning, + ); switch (fromVersion) { case 0: await Hive.openBox(DB.boxNameAllWalletsData); @@ -117,37 +121,14 @@ class DbVersionMigrator { return await migrate(1); case 1: - await Hive.openBox(DB.boxNameTrades); - await Hive.openBox(DB.boxNameTradesV2); + await Hive.openBox(DB.boxNameTrades); + await Hive.openBox(DB.boxNameTradesV2); final trades = DB.instance.values(boxName: DB.boxNameTrades); for (final old in trades) { if (old.statusObject != null) { - final trade = Trade( - uuid: old.uuid, - tradeId: old.id, - rateType: "", - direction: "direct", - timestamp: old.date, - updatedAt: DateTime.tryParse(old.statusObject?.updatedAt ?? "") ?? - old.date, - payInCurrency: old.fromCurrency, - payInAmount: old.statusObject!.expectedSendAmountDecimal, - payInAddress: old.payinAddress, - payInNetwork: "", - payInExtraId: old.payinExtraId, - payInTxid: "", - payOutCurrency: old.toCurrency, - payOutAmount: old.amount, - payOutAddress: old.payoutAddress, - payOutNetwork: "", - payOutExtraId: old.payoutExtraId, - payOutTxid: "", - refundAddress: old.refundAddress, - refundExtraId: old.refundExtraId, - status: old.statusObject!.status.name, - ); + final trade = Trade.fromExchangeTransaction(old); await DB.instance.put( boxName: DB.boxNameTradesV2, key: trade.uuid, From f2eaba178c8eee11a7d2b80f164ddc2aef5120a5 Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 2 Oct 2022 18:55:35 -0600 Subject: [PATCH 09/75] update tests and generated files --- .../exchange/response_objects/trade.g.dart | 104 ++++++++++++ ...timated_rate_exchange_form_state_test.dart | 8 +- ...d_rate_exchange_form_state_test.mocks.dart | 130 +++++++++------ .../exchange/exchange_view_test.mocks.dart | 149 +++++++++++------- 4 files changed, 285 insertions(+), 106 deletions(-) create mode 100644 lib/models/exchange/response_objects/trade.g.dart diff --git a/lib/models/exchange/response_objects/trade.g.dart b/lib/models/exchange/response_objects/trade.g.dart new file mode 100644 index 0000000000..12d77bd836 --- /dev/null +++ b/lib/models/exchange/response_objects/trade.g.dart @@ -0,0 +1,104 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'trade.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class TradeAdapter extends TypeAdapter { + @override + final int typeId = 22; + + @override + Trade read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return Trade( + uuid: fields[0] as String, + tradeId: fields[1] as String, + rateType: fields[2] as String, + direction: fields[3] as String, + timestamp: fields[4] as DateTime, + updatedAt: fields[5] as DateTime, + payInCurrency: fields[6] as String, + payInAmount: fields[7] as String, + payInAddress: fields[8] as String, + payInNetwork: fields[9] as String, + payInExtraId: fields[10] as String, + payInTxid: fields[11] as String, + payOutCurrency: fields[12] as String, + payOutAmount: fields[13] as String, + payOutAddress: fields[14] as String, + payOutNetwork: fields[15] as String, + payOutExtraId: fields[16] as String, + payOutTxid: fields[17] as String, + refundAddress: fields[18] as String, + refundExtraId: fields[19] as String, + status: fields[20] as String, + exchangeName: fields[21] as String, + ); + } + + @override + void write(BinaryWriter writer, Trade obj) { + writer + ..writeByte(22) + ..writeByte(0) + ..write(obj.uuid) + ..writeByte(1) + ..write(obj.tradeId) + ..writeByte(2) + ..write(obj.rateType) + ..writeByte(3) + ..write(obj.direction) + ..writeByte(4) + ..write(obj.timestamp) + ..writeByte(5) + ..write(obj.updatedAt) + ..writeByte(6) + ..write(obj.payInCurrency) + ..writeByte(7) + ..write(obj.payInAmount) + ..writeByte(8) + ..write(obj.payInAddress) + ..writeByte(9) + ..write(obj.payInNetwork) + ..writeByte(10) + ..write(obj.payInExtraId) + ..writeByte(11) + ..write(obj.payInTxid) + ..writeByte(12) + ..write(obj.payOutCurrency) + ..writeByte(13) + ..write(obj.payOutAmount) + ..writeByte(14) + ..write(obj.payOutAddress) + ..writeByte(15) + ..write(obj.payOutNetwork) + ..writeByte(16) + ..write(obj.payOutExtraId) + ..writeByte(17) + ..write(obj.payOutTxid) + ..writeByte(18) + ..write(obj.refundAddress) + ..writeByte(19) + ..write(obj.refundExtraId) + ..writeByte(20) + ..write(obj.status) + ..writeByte(21) + ..write(obj.exchangeName); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is TradeAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.dart index f36cab9758..6386d533fb 100644 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.dart +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.dart @@ -92,7 +92,7 @@ void main() { test( "updateTo after updateFrom where amounts are null and getMinimalExchangeAmount succeeds", () async { - final cn = MockChangeNow(); + final cn = MockChangeNowAPI(); final state = EstimatedRateExchangeFormState(); state.cnTesting = cn; @@ -119,7 +119,7 @@ void main() { test( "updateTo after updateFrom where amounts are null and getMinimalExchangeAmount fails", () async { - final cn = MockChangeNow(); + final cn = MockChangeNowAPI(); final state = EstimatedRateExchangeFormState(); state.cnTesting = cn; @@ -146,7 +146,7 @@ void main() { test( "updateTo after updateFrom and setFromAmountAndCalculateToAmount where fromAmount is less than the minimum required exchange amount", () async { - final cn = MockChangeNow(); + final cn = MockChangeNowAPI(); final state = EstimatedRateExchangeFormState(); state.cnTesting = cn; @@ -174,7 +174,7 @@ void main() { test( "updateTo after updateFrom and setFromAmountAndCalculateToAmount where fromAmount is greater than the minimum required exchange amount", () async { - final cn = MockChangeNow(); + final cn = MockChangeNowAPI(); final state = EstimatedRateExchangeFormState(); state.cnTesting = cn; diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart index cceac1f624..450b9891b2 100644 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart @@ -8,19 +8,20 @@ import 'package:decimal/decimal.dart' as _i7; import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart' - as _i13; + as _i14; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' - as _i9; + as _i10; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart' - as _i8; + as _i9; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' - as _i11; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' as _i12; +import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' + as _i13; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' - as _i10; + as _i11; import 'package:stackwallet/models/exchange/response_objects/currency.dart' as _i6; +import 'package:stackwallet/models/exchange/response_objects/range.dart' as _i8; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart' as _i3; import 'package:stackwallet/services/exchange/exchange_response.dart' as _i2; @@ -35,14 +36,14 @@ import 'package:stackwallet/services/exchange/exchange_response.dart' as _i2; // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types -class _FakeChangeNowResponse_0 extends _i1.Fake +class _FakeExchangeResponse_0 extends _i1.Fake implements _i2.ExchangeResponse {} -/// A class which mocks [ChangeNow]. +/// A class which mocks [ChangeNowAPI]. /// /// See the documentation for Mockito's code generation for more information. -class MockChangeNow extends _i1.Mock implements _i3.ChangeNowAPI { - MockChangeNow() { +class MockChangeNowAPI extends _i1.Mock implements _i3.ChangeNowAPI { + MockChangeNowAPI() { _i1.throwOnMissingStub(this); } @@ -57,7 +58,7 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNowAPI { Invocation.method(#getAvailableCurrencies, [], {#fixedRate: fixedRate, #active: active}), returnValue: Future<_i2.ExchangeResponse>>.value( - _FakeChangeNowResponse_0>())) as _i5 + _FakeExchangeResponse_0>())) as _i5 .Future<_i2.ExchangeResponse>>); @override _i5.Future<_i2.ExchangeResponse>> getPairedCurrencies( @@ -66,7 +67,7 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNowAPI { Invocation.method(#getPairedCurrencies, [], {#ticker: ticker, #fixedRate: fixedRate}), returnValue: Future<_i2.ExchangeResponse>>.value( - _FakeChangeNowResponse_0>())) as _i5 + _FakeExchangeResponse_0>())) as _i5 .Future<_i2.ExchangeResponse>>); @override _i5.Future<_i2.ExchangeResponse<_i7.Decimal>> getMinimalExchangeAmount( @@ -78,10 +79,26 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNowAPI { #apiKey: apiKey }), returnValue: Future<_i2.ExchangeResponse<_i7.Decimal>>.value( - _FakeChangeNowResponse_0<_i7.Decimal>())) + _FakeExchangeResponse_0<_i7.Decimal>())) as _i5.Future<_i2.ExchangeResponse<_i7.Decimal>>); @override - _i5.Future<_i2.ExchangeResponse<_i8.EstimatedExchangeAmount>> + _i5.Future<_i2.ExchangeResponse<_i8.Range>> getRange( + {String? fromTicker, + String? toTicker, + bool? isFixedRate, + String? apiKey}) => + (super.noSuchMethod( + Invocation.method(#getRange, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #isFixedRate: isFixedRate, + #apiKey: apiKey + }), + returnValue: Future<_i2.ExchangeResponse<_i8.Range>>.value( + _FakeExchangeResponse_0<_i8.Range>())) + as _i5.Future<_i2.ExchangeResponse<_i8.Range>>); + @override + _i5.Future<_i2.ExchangeResponse<_i9.EstimatedExchangeAmount>> getEstimatedExchangeAmount( {String? fromTicker, String? toTicker, @@ -96,19 +113,40 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNowAPI { }), returnValue: Future< _i2.ExchangeResponse< - _i8.EstimatedExchangeAmount>>.value( - _FakeChangeNowResponse_0<_i8.EstimatedExchangeAmount>())) - as _i5.Future<_i2.ExchangeResponse<_i8.EstimatedExchangeAmount>>); + _i9.EstimatedExchangeAmount>>.value( + _FakeExchangeResponse_0<_i9.EstimatedExchangeAmount>())) + as _i5.Future<_i2.ExchangeResponse<_i9.EstimatedExchangeAmount>>); @override - _i5.Future<_i2.ExchangeResponse<_i9.CNExchangeEstimate>> + _i5.Future<_i2.ExchangeResponse<_i9.EstimatedExchangeAmount>> + getEstimatedExchangeAmountFixedRate( + {String? fromTicker, + String? toTicker, + _i7.Decimal? fromAmount, + bool? reversed, + String? apiKey}) => + (super.noSuchMethod( + Invocation.method(#getEstimatedExchangeAmountFixedRate, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #fromAmount: fromAmount, + #reversed: reversed, + #apiKey: apiKey + }), + returnValue: Future< + _i2.ExchangeResponse< + _i9.EstimatedExchangeAmount>>.value( + _FakeExchangeResponse_0<_i9.EstimatedExchangeAmount>())) + as _i5.Future<_i2.ExchangeResponse<_i9.EstimatedExchangeAmount>>); + @override + _i5.Future<_i2.ExchangeResponse<_i10.CNExchangeEstimate>> getEstimatedExchangeAmountV2( {String? fromTicker, String? toTicker, - _i9.CNEstimateType? fromOrTo, + _i10.CNEstimateType? fromOrTo, _i7.Decimal? amount, String? fromNetwork, String? toNetwork, - _i9.CNFlowType? flow = _i9.CNFlowType.standard, + _i10.CNFlowType? flow = _i10.CNFlowType.standard, String? apiKey}) => (super.noSuchMethod( Invocation.method(#getEstimatedExchangeAmountV2, [], { @@ -122,20 +160,20 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNowAPI { #apiKey: apiKey }), returnValue: Future< - _i2.ExchangeResponse<_i9.CNExchangeEstimate>>.value( - _FakeChangeNowResponse_0<_i9.CNExchangeEstimate>())) - as _i5.Future<_i2.ExchangeResponse<_i9.CNExchangeEstimate>>); + _i2.ExchangeResponse<_i10.CNExchangeEstimate>>.value( + _FakeExchangeResponse_0<_i10.CNExchangeEstimate>())) + as _i5.Future<_i2.ExchangeResponse<_i10.CNExchangeEstimate>>); @override - _i5.Future<_i2.ExchangeResponse>> + _i5.Future<_i2.ExchangeResponse>> getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod( - Invocation.method( - #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), - returnValue: - Future<_i2.ExchangeResponse>>.value( - _FakeChangeNowResponse_0>())) - as _i5.Future<_i2.ExchangeResponse>>); + Invocation.method( + #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), + returnValue: + Future<_i2.ExchangeResponse>>.value( + _FakeExchangeResponse_0>())) as _i5 + .Future<_i2.ExchangeResponse>>); @override - _i5.Future<_i2.ExchangeResponse<_i11.ExchangeTransaction>> + _i5.Future<_i2.ExchangeResponse<_i12.ExchangeTransaction>> createStandardExchangeTransaction( {String? fromTicker, String? toTicker, @@ -161,11 +199,11 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNowAPI { #apiKey: apiKey }), returnValue: Future< - _i2.ExchangeResponse<_i11.ExchangeTransaction>>.value( - _FakeChangeNowResponse_0<_i11.ExchangeTransaction>())) - as _i5.Future<_i2.ExchangeResponse<_i11.ExchangeTransaction>>); + _i2.ExchangeResponse<_i12.ExchangeTransaction>>.value( + _FakeExchangeResponse_0<_i12.ExchangeTransaction>())) + as _i5.Future<_i2.ExchangeResponse<_i12.ExchangeTransaction>>); @override - _i5.Future<_i2.ExchangeResponse<_i11.ExchangeTransaction>> + _i5.Future<_i2.ExchangeResponse<_i12.ExchangeTransaction>> createFixedRateExchangeTransaction( {String? fromTicker, String? toTicker, @@ -193,26 +231,26 @@ class MockChangeNow extends _i1.Mock implements _i3.ChangeNowAPI { #apiKey: apiKey }), returnValue: Future< - _i2.ExchangeResponse<_i11.ExchangeTransaction>>.value( - _FakeChangeNowResponse_0<_i11.ExchangeTransaction>())) - as _i5.Future<_i2.ExchangeResponse<_i11.ExchangeTransaction>>); + _i2.ExchangeResponse<_i12.ExchangeTransaction>>.value( + _FakeExchangeResponse_0<_i12.ExchangeTransaction>())) + as _i5.Future<_i2.ExchangeResponse<_i12.ExchangeTransaction>>); @override - _i5.Future<_i2.ExchangeResponse<_i12.ExchangeTransactionStatus>> + _i5.Future<_i2.ExchangeResponse<_i13.ExchangeTransactionStatus>> getTransactionStatus({String? id, String? apiKey}) => (super.noSuchMethod( Invocation.method( #getTransactionStatus, [], {#id: id, #apiKey: apiKey}), returnValue: - Future<_i2.ExchangeResponse<_i12.ExchangeTransactionStatus>>.value( - _FakeChangeNowResponse_0<_i12.ExchangeTransactionStatus>())) as _i5 - .Future<_i2.ExchangeResponse<_i12.ExchangeTransactionStatus>>); + Future<_i2.ExchangeResponse<_i13.ExchangeTransactionStatus>>.value( + _FakeExchangeResponse_0<_i13.ExchangeTransactionStatus>())) as _i5 + .Future<_i2.ExchangeResponse<_i13.ExchangeTransactionStatus>>); @override - _i5.Future<_i2.ExchangeResponse>> + _i5.Future<_i2.ExchangeResponse>> getAvailableFloatingRatePairs({bool? includePartners = false}) => (super .noSuchMethod( Invocation.method(#getAvailableFloatingRatePairs, [], {#includePartners: includePartners}), returnValue: - Future<_i2.ExchangeResponse>>.value( - _FakeChangeNowResponse_0>())) as _i5 - .Future<_i2.ExchangeResponse>>); + Future<_i2.ExchangeResponse>>.value( + _FakeExchangeResponse_0>())) as _i5 + .Future<_i2.ExchangeResponse>>); } diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index d39df96e1e..2665436fb6 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -9,19 +9,23 @@ import 'package:decimal/decimal.dart' as _i15; import 'package:http/http.dart' as _i13; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart' - as _i20; + as _i22; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' - as _i17; + as _i18; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart' - as _i16; + as _i17; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' - as _i10; + as _i20; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' - as _i19; + as _i21; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' - as _i18; + as _i19; import 'package:stackwallet/models/exchange/response_objects/currency.dart' as _i14; +import 'package:stackwallet/models/exchange/response_objects/range.dart' + as _i16; +import 'package:stackwallet/models/exchange/response_objects/trade.dart' + as _i10; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart' as _i5; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart' @@ -43,7 +47,7 @@ import 'package:stackwallet/utilities/prefs.dart' as _i3; // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types -class _FakeChangeNowResponse_0 extends _i1.Fake +class _FakeExchangeResponse_0 extends _i1.Fake implements _i2.ExchangeResponse {} /// A class which mocks [Prefs]. @@ -246,33 +250,28 @@ class MockTradesService extends _i1.Mock implements _i9.TradesService { } @override - List<_i10.ExchangeTransaction> get trades => - (super.noSuchMethod(Invocation.getter(#trades), - returnValue: <_i10.ExchangeTransaction>[]) - as List<_i10.ExchangeTransaction>); + List<_i10.Trade> get trades => (super.noSuchMethod(Invocation.getter(#trades), + returnValue: <_i10.Trade>[]) as List<_i10.Trade>); @override bool get hasListeners => (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool); @override - _i7.Future add( - {_i10.ExchangeTransaction? trade, bool? shouldNotifyListeners}) => + _i7.Future add({_i10.Trade? trade, bool? shouldNotifyListeners}) => (super.noSuchMethod( Invocation.method(#add, [], {#trade: trade, #shouldNotifyListeners: shouldNotifyListeners}), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i7.Future); @override - _i7.Future edit( - {_i10.ExchangeTransaction? trade, bool? shouldNotifyListeners}) => + _i7.Future edit({_i10.Trade? trade, bool? shouldNotifyListeners}) => (super.noSuchMethod( Invocation.method(#edit, [], {#trade: trade, #shouldNotifyListeners: shouldNotifyListeners}), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i7.Future); @override - _i7.Future delete( - {_i10.ExchangeTransaction? trade, bool? shouldNotifyListeners}) => + _i7.Future delete({_i10.Trade? trade, bool? shouldNotifyListeners}) => (super.noSuchMethod( Invocation.method(#delete, [], {#trade: trade, #shouldNotifyListeners: shouldNotifyListeners}), @@ -348,11 +347,11 @@ class MockTradeNotesService extends _i1.Mock implements _i11.TradeNotesService { returnValueForMissingStub: null); } -/// A class which mocks [ChangeNow]. +/// A class which mocks [ChangeNowAPI]. /// /// See the documentation for Mockito's code generation for more information. -class MockChangeNow extends _i1.Mock implements _i12.ChangeNowAPI { - MockChangeNow() { +class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { + MockChangeNowAPI() { _i1.throwOnMissingStub(this); } @@ -367,7 +366,7 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNowAPI { Invocation.method(#getAvailableCurrencies, [], {#fixedRate: fixedRate, #active: active}), returnValue: Future<_i2.ExchangeResponse>>.value( - _FakeChangeNowResponse_0>())) as _i7 + _FakeExchangeResponse_0>())) as _i7 .Future<_i2.ExchangeResponse>>); @override _i7.Future<_i2.ExchangeResponse>> getPairedCurrencies( @@ -376,7 +375,7 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNowAPI { Invocation.method(#getPairedCurrencies, [], {#ticker: ticker, #fixedRate: fixedRate}), returnValue: Future<_i2.ExchangeResponse>>.value( - _FakeChangeNowResponse_0>())) as _i7 + _FakeExchangeResponse_0>())) as _i7 .Future<_i2.ExchangeResponse>>); @override _i7.Future<_i2.ExchangeResponse<_i15.Decimal>> getMinimalExchangeAmount( @@ -388,10 +387,26 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNowAPI { #apiKey: apiKey }), returnValue: Future<_i2.ExchangeResponse<_i15.Decimal>>.value( - _FakeChangeNowResponse_0<_i15.Decimal>())) + _FakeExchangeResponse_0<_i15.Decimal>())) as _i7.Future<_i2.ExchangeResponse<_i15.Decimal>>); @override - _i7.Future<_i2.ExchangeResponse<_i16.EstimatedExchangeAmount>> + _i7.Future<_i2.ExchangeResponse<_i16.Range>> getRange( + {String? fromTicker, + String? toTicker, + bool? isFixedRate, + String? apiKey}) => + (super.noSuchMethod( + Invocation.method(#getRange, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #isFixedRate: isFixedRate, + #apiKey: apiKey + }), + returnValue: Future<_i2.ExchangeResponse<_i16.Range>>.value( + _FakeExchangeResponse_0<_i16.Range>())) + as _i7.Future<_i2.ExchangeResponse<_i16.Range>>); + @override + _i7.Future<_i2.ExchangeResponse<_i17.EstimatedExchangeAmount>> getEstimatedExchangeAmount( {String? fromTicker, String? toTicker, @@ -406,20 +421,42 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNowAPI { }), returnValue: Future< _i2.ExchangeResponse< - _i16.EstimatedExchangeAmount>>.value( - _FakeChangeNowResponse_0<_i16.EstimatedExchangeAmount>())) + _i17.EstimatedExchangeAmount>>.value( + _FakeExchangeResponse_0<_i17.EstimatedExchangeAmount>())) as _i7 - .Future<_i2.ExchangeResponse<_i16.EstimatedExchangeAmount>>); + .Future<_i2.ExchangeResponse<_i17.EstimatedExchangeAmount>>); @override - _i7.Future<_i2.ExchangeResponse<_i17.CNExchangeEstimate>> + _i7.Future<_i2.ExchangeResponse<_i17.EstimatedExchangeAmount>> + getEstimatedExchangeAmountFixedRate( + {String? fromTicker, + String? toTicker, + _i15.Decimal? fromAmount, + bool? reversed, + String? apiKey}) => + (super.noSuchMethod( + Invocation.method(#getEstimatedExchangeAmountFixedRate, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #fromAmount: fromAmount, + #reversed: reversed, + #apiKey: apiKey + }), + returnValue: Future< + _i2.ExchangeResponse< + _i17.EstimatedExchangeAmount>>.value( + _FakeExchangeResponse_0<_i17.EstimatedExchangeAmount>())) + as _i7 + .Future<_i2.ExchangeResponse<_i17.EstimatedExchangeAmount>>); + @override + _i7.Future<_i2.ExchangeResponse<_i18.CNExchangeEstimate>> getEstimatedExchangeAmountV2( {String? fromTicker, String? toTicker, - _i17.CNEstimateType? fromOrTo, + _i18.CNEstimateType? fromOrTo, _i15.Decimal? amount, String? fromNetwork, String? toNetwork, - _i17.CNFlowType? flow = _i17.CNFlowType.standard, + _i18.CNFlowType? flow = _i18.CNFlowType.standard, String? apiKey}) => (super.noSuchMethod( Invocation.method(#getEstimatedExchangeAmountV2, [], { @@ -433,20 +470,20 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNowAPI { #apiKey: apiKey }), returnValue: Future< - _i2.ExchangeResponse<_i17.CNExchangeEstimate>>.value( - _FakeChangeNowResponse_0<_i17.CNExchangeEstimate>())) - as _i7.Future<_i2.ExchangeResponse<_i17.CNExchangeEstimate>>); + _i2.ExchangeResponse<_i18.CNExchangeEstimate>>.value( + _FakeExchangeResponse_0<_i18.CNExchangeEstimate>())) + as _i7.Future<_i2.ExchangeResponse<_i18.CNExchangeEstimate>>); @override - _i7.Future<_i2.ExchangeResponse>> + _i7.Future<_i2.ExchangeResponse>> getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod( - Invocation.method( - #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), - returnValue: - Future<_i2.ExchangeResponse>>.value( - _FakeChangeNowResponse_0>())) - as _i7.Future<_i2.ExchangeResponse>>); + Invocation.method( + #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), + returnValue: + Future<_i2.ExchangeResponse>>.value( + _FakeExchangeResponse_0>())) as _i7 + .Future<_i2.ExchangeResponse>>); @override - _i7.Future<_i2.ExchangeResponse<_i10.ExchangeTransaction>> + _i7.Future<_i2.ExchangeResponse<_i20.ExchangeTransaction>> createStandardExchangeTransaction( {String? fromTicker, String? toTicker, @@ -472,11 +509,11 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNowAPI { #apiKey: apiKey }), returnValue: Future< - _i2.ExchangeResponse<_i10.ExchangeTransaction>>.value( - _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) - as _i7.Future<_i2.ExchangeResponse<_i10.ExchangeTransaction>>); + _i2.ExchangeResponse<_i20.ExchangeTransaction>>.value( + _FakeExchangeResponse_0<_i20.ExchangeTransaction>())) + as _i7.Future<_i2.ExchangeResponse<_i20.ExchangeTransaction>>); @override - _i7.Future<_i2.ExchangeResponse<_i10.ExchangeTransaction>> + _i7.Future<_i2.ExchangeResponse<_i20.ExchangeTransaction>> createFixedRateExchangeTransaction( {String? fromTicker, String? toTicker, @@ -504,26 +541,26 @@ class MockChangeNow extends _i1.Mock implements _i12.ChangeNowAPI { #apiKey: apiKey }), returnValue: Future< - _i2.ExchangeResponse<_i10.ExchangeTransaction>>.value( - _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) - as _i7.Future<_i2.ExchangeResponse<_i10.ExchangeTransaction>>); + _i2.ExchangeResponse<_i20.ExchangeTransaction>>.value( + _FakeExchangeResponse_0<_i20.ExchangeTransaction>())) + as _i7.Future<_i2.ExchangeResponse<_i20.ExchangeTransaction>>); @override - _i7.Future<_i2.ExchangeResponse<_i19.ExchangeTransactionStatus>> + _i7.Future<_i2.ExchangeResponse<_i21.ExchangeTransactionStatus>> getTransactionStatus({String? id, String? apiKey}) => (super.noSuchMethod( Invocation.method( #getTransactionStatus, [], {#id: id, #apiKey: apiKey}), returnValue: - Future<_i2.ExchangeResponse<_i19.ExchangeTransactionStatus>>.value( - _FakeChangeNowResponse_0<_i19.ExchangeTransactionStatus>())) as _i7 - .Future<_i2.ExchangeResponse<_i19.ExchangeTransactionStatus>>); + Future<_i2.ExchangeResponse<_i21.ExchangeTransactionStatus>>.value( + _FakeExchangeResponse_0<_i21.ExchangeTransactionStatus>())) as _i7 + .Future<_i2.ExchangeResponse<_i21.ExchangeTransactionStatus>>); @override - _i7.Future<_i2.ExchangeResponse>> + _i7.Future<_i2.ExchangeResponse>> getAvailableFloatingRatePairs({bool? includePartners = false}) => (super .noSuchMethod( Invocation.method(#getAvailableFloatingRatePairs, [], {#includePartners: includePartners}), returnValue: - Future<_i2.ExchangeResponse>>.value( - _FakeChangeNowResponse_0>())) as _i7 - .Future<_i2.ExchangeResponse>>); + Future<_i2.ExchangeResponse>>.value( + _FakeExchangeResponse_0>())) as _i7 + .Future<_i2.ExchangeResponse>>); } From ee186a8669f2961d14830721bca6149f68f90573 Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 2 Oct 2022 18:55:54 -0600 Subject: [PATCH 10/75] register TradeAdapter --- lib/main.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index f18c2fa498..40f5321fca 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -121,6 +121,8 @@ void main() async { Hive.registerAdapter(ExchangeTransactionAdapter()); Hive.registerAdapter(ExchangeTransactionStatusAdapter()); + Hive.registerAdapter(TradeAdapter()); + // reference lookup data adapter Hive.registerAdapter(TradeWalletLookupAdapter()); From 078259ec0f8b0537d802e81b4b7a1ab934ffca46 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 3 Oct 2022 07:59:59 -0600 Subject: [PATCH 11/75] cn trade hash fix --- lib/services/exchange/change_now/change_now_exchange.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/exchange/change_now/change_now_exchange.dart b/lib/services/exchange/change_now/change_now_exchange.dart index ce5182e44e..5ff68de4a3 100644 --- a/lib/services/exchange/change_now/change_now_exchange.dart +++ b/lib/services/exchange/change_now/change_now_exchange.dart @@ -160,13 +160,13 @@ class ChangeNowExchange extends Exchange { payInAddress: t.payinAddress, payInNetwork: "", payInExtraId: t.payinExtraId, - payInTxid: "", + payInTxid: t.payinHash, payOutCurrency: t.toCurrency, payOutAmount: t.expectedReceiveAmountDecimal, payOutAddress: t.payoutAddress, payOutNetwork: "", payOutExtraId: t.payoutExtraId, - payOutTxid: "", + payOutTxid: t.payoutHash, refundAddress: t.refundAddress, refundExtraId: t.refundExtraId, status: t.status.name, From c182557b30ab827b4017d35b8003137a7dd69756 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 3 Oct 2022 09:41:53 -0600 Subject: [PATCH 12/75] generic market pair --- .../available_floating_rate_pair.dart | 26 ----------- .../exchange/response_objects/pair.dart | 46 +++++++++++++++++-- lib/pages/exchange_view/exchange_view.dart | 20 ++++---- .../wallet_initiated_exchange_view.dart | 20 ++++---- ...le_floating_rate_pairs_state_provider.dart | 5 +- .../exchange/change_now/change_now_api.dart | 23 ++++++---- ...d_rate_exchange_form_state_test.mocks.dart | 16 +++---- .../exchange/exchange_view_test.mocks.dart | 16 +++---- test/services/change_now/change_now_test.dart | 4 +- 9 files changed, 95 insertions(+), 81 deletions(-) delete mode 100644 lib/models/exchange/change_now/available_floating_rate_pair.dart diff --git a/lib/models/exchange/change_now/available_floating_rate_pair.dart b/lib/models/exchange/change_now/available_floating_rate_pair.dart deleted file mode 100644 index d30b9817e1..0000000000 --- a/lib/models/exchange/change_now/available_floating_rate_pair.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; - -class AvailableFloatingRatePair { - final String fromTicker; - final String toTicker; - - AvailableFloatingRatePair({ - required this.fromTicker, - required this.toTicker, - }); - - @override - bool operator ==(other) { - return other is AvailableFloatingRatePair && - fromTicker == other.fromTicker && - toTicker == other.toTicker; - } - - @override - int get hashCode => hashValues(fromTicker, toTicker); - - @override - String toString() { - return "${fromTicker}_$toTicker"; - } -} diff --git a/lib/models/exchange/response_objects/pair.dart b/lib/models/exchange/response_objects/pair.dart index cbd97140a6..1f12acf08f 100644 --- a/lib/models/exchange/response_objects/pair.dart +++ b/lib/models/exchange/response_objects/pair.dart @@ -1,3 +1,7 @@ +import 'dart:ui'; + +import 'package:stackwallet/utilities/logger.dart'; + class Pair { final String from; final String fromNetwork; @@ -17,7 +21,23 @@ class Pair { required this.floatingRate, }); - Map toJson() { + factory Pair.fromMap(Map map) { + try { + return Pair( + from: map["from"] as String, + fromNetwork: map["fromNetwork"] as String, + to: map["to"] as String, + toNetwork: map["toNetwork"] as String, + fixedRate: map["fixedRate"] as bool, + floatingRate: map["floatingRate"] as bool, + ); + } catch (e, s) { + Logging.instance.log("Pair.fromMap(): $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + Map toMap() { return { "from": from, "fromNetwork": fromNetwork, @@ -29,7 +49,25 @@ class Pair { } @override - String toString() { - return "Pair: ${toJson()}"; - } + bool operator ==(other) => + other is Pair && + from == other.from && + fromNetwork == other.fromNetwork && + to == other.to && + toNetwork == other.toNetwork && + fixedRate == other.fixedRate && + floatingRate == other.floatingRate; + + @override + int get hashCode => hashValues( + from, + fromNetwork, + to, + toNetwork, + fixedRate, + floatingRate, + ); + + @override + String toString() => "Pair: ${toMap()}"; } diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart index dd0b3ad1ee..dad79d3c1c 100644 --- a/lib/pages/exchange_view/exchange_view.dart +++ b/lib/pages/exchange_view/exchange_view.dart @@ -6,10 +6,10 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart'; @@ -112,7 +112,7 @@ class _ExchangeViewState extends ConsumerState { _sendFocusNode.unfocus(); _receiveFocusNode.unfocus(); - List availablePairs = []; + List availablePairs = []; if (fromTicker.isEmpty || fromTicker == "-" || excludedTicker.isEmpty || @@ -123,23 +123,23 @@ class _ExchangeViewState extends ConsumerState { availablePairs = ref .read(availableFloatingRatePairsStateProvider.state) .state - .where((e) => e.fromTicker == excludedTicker) + .where((e) => e.from == excludedTicker) .toList(growable: false); } else { availablePairs = ref .read(availableFloatingRatePairsStateProvider.state) .state - .where((e) => e.toTicker == excludedTicker) + .where((e) => e.to == excludedTicker) .toList(growable: false); } final List tickers = currencies.where((e) { if (excludedTicker == fromTicker) { return e.ticker != excludedTicker && - availablePairs.where((e2) => e2.toTicker == e.ticker).isNotEmpty; + availablePairs.where((e2) => e2.to == e.ticker).isNotEmpty; } else { return e.ticker != excludedTicker && - availablePairs.where((e2) => e2.fromTicker == e.ticker).isNotEmpty; + availablePairs.where((e2) => e2.from == e.ticker).isNotEmpty; } }).toList(growable: false); @@ -1054,8 +1054,8 @@ class _ExchangeViewState extends ConsumerState { .state) .state .where((e) => - e.toTicker == toTicker && - e.fromTicker == fromTicker); + e.to == toTicker && + e.from == fromTicker); if (available.isNotEmpty) { final availableCurrencies = ref .read( @@ -1223,8 +1223,8 @@ class _ExchangeViewState extends ConsumerState { .state) .state; for (final pair in availableFloatingPairs) { - if (pair.fromTicker == fromTicker && - pair.toTicker == toTicker) { + if (pair.from == fromTicker && + pair.to == toTicker) { isAvailable = true; break; } diff --git a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart index 6652af32b5..5aca1b957e 100644 --- a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart +++ b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart @@ -5,10 +5,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart'; @@ -127,7 +127,7 @@ class _WalletInitiatedExchangeViewState _sendFocusNode.unfocus(); _receiveFocusNode.unfocus(); - List availablePairs = []; + List availablePairs = []; if (fromTicker.isEmpty || fromTicker == "-" || excludedTicker.isEmpty || @@ -138,23 +138,23 @@ class _WalletInitiatedExchangeViewState availablePairs = ref .read(availableFloatingRatePairsStateProvider.state) .state - .where((e) => e.fromTicker == excludedTicker) + .where((e) => e.from == excludedTicker) .toList(growable: false); } else { availablePairs = ref .read(availableFloatingRatePairsStateProvider.state) .state - .where((e) => e.toTicker == excludedTicker) + .where((e) => e.to == excludedTicker) .toList(growable: false); } final List tickers = currencies.where((e) { if (excludedTicker == fromTicker) { return e.ticker != excludedTicker && - availablePairs.where((e2) => e2.toTicker == e.ticker).isNotEmpty; + availablePairs.where((e2) => e2.to == e.ticker).isNotEmpty; } else { return e.ticker != excludedTicker && - availablePairs.where((e2) => e2.fromTicker == e.ticker).isNotEmpty; + availablePairs.where((e2) => e2.from == e.ticker).isNotEmpty; } }).toList(growable: false); @@ -1169,8 +1169,8 @@ class _WalletInitiatedExchangeViewState .state) .state .where((e) => - e.toTicker == toTicker && - e.fromTicker == fromTicker); + e.to == toTicker && + e.from == fromTicker); if (available.isNotEmpty) { final availableCurrencies = ref .read( @@ -1381,8 +1381,8 @@ class _WalletInitiatedExchangeViewState .state) .state; for (final pair in availableFloatingPairs) { - if (pair.fromTicker == fromTicker && - pair.toTicker == toTicker) { + if (pair.from == fromTicker && + pair.to == toTicker) { isAvailable = true; break; } diff --git a/lib/providers/exchange/available_floating_rate_pairs_state_provider.dart b/lib/providers/exchange/available_floating_rate_pairs_state_provider.dart index a157b0727d..e65f367b51 100644 --- a/lib/providers/exchange/available_floating_rate_pairs_state_provider.dart +++ b/lib/providers/exchange/available_floating_rate_pairs_state_provider.dart @@ -1,6 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; final availableFloatingRatePairsStateProvider = - StateProvider>( - (ref) => []); + StateProvider>((ref) => []); diff --git a/lib/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart index 993d9a08ee..bf5cde890c 100644 --- a/lib/services/exchange/change_now/change_now_api.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -4,13 +4,13 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:stackwallet/external_api_keys.dart'; -import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -749,8 +749,7 @@ class ChangeNowAPI { } } - Future>> - getAvailableFloatingRatePairs({ + Future>> getAvailableFloatingRatePairs({ bool includePartners = false, }) async { final uri = _buildUri("/market-info/available-pairs", @@ -786,15 +785,23 @@ class ChangeNowAPI { } } - ExchangeResponse> - _parseAvailableFloatingRatePairsJson(List jsonArray) { + ExchangeResponse> _parseAvailableFloatingRatePairsJson( + List jsonArray) { try { - List pairs = []; + List pairs = []; for (final json in jsonArray) { try { final List stringPair = (json as String).split("_"); - pairs.add(AvailableFloatingRatePair( - fromTicker: stringPair[0], toTicker: stringPair[1])); + pairs.add( + Pair( + from: stringPair[0], + to: stringPair[1], + fromNetwork: "", + toNetwork: "", + fixedRate: false, + floatingRate: true, + ), + ); } catch (_) { return ExchangeResponse( exception: ExchangeException("Failed to serialize $json", diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart index 450b9891b2..94ec0ad5d4 100644 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart @@ -7,8 +7,6 @@ import 'dart:async' as _i5; import 'package:decimal/decimal.dart' as _i7; import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart' - as _i14; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' as _i10; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart' @@ -21,6 +19,7 @@ import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' as _i11; import 'package:stackwallet/models/exchange/response_objects/currency.dart' as _i6; +import 'package:stackwallet/models/exchange/response_objects/pair.dart' as _i14; import 'package:stackwallet/models/exchange/response_objects/range.dart' as _i8; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart' as _i3; @@ -244,13 +243,12 @@ class MockChangeNowAPI extends _i1.Mock implements _i3.ChangeNowAPI { _FakeExchangeResponse_0<_i13.ExchangeTransactionStatus>())) as _i5 .Future<_i2.ExchangeResponse<_i13.ExchangeTransactionStatus>>); @override - _i5.Future<_i2.ExchangeResponse>> - getAvailableFloatingRatePairs({bool? includePartners = false}) => (super - .noSuchMethod( + _i5.Future<_i2.ExchangeResponse>> + getAvailableFloatingRatePairs({bool? includePartners = false}) => + (super.noSuchMethod( Invocation.method(#getAvailableFloatingRatePairs, [], {#includePartners: includePartners}), - returnValue: - Future<_i2.ExchangeResponse>>.value( - _FakeExchangeResponse_0>())) as _i5 - .Future<_i2.ExchangeResponse>>); + returnValue: Future<_i2.ExchangeResponse>>.value( + _FakeExchangeResponse_0>())) as _i5 + .Future<_i2.ExchangeResponse>>); } diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index 2665436fb6..c0151e028d 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -8,8 +8,6 @@ import 'dart:ui' as _i8; import 'package:decimal/decimal.dart' as _i15; import 'package:http/http.dart' as _i13; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart' - as _i22; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' as _i18; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart' @@ -22,6 +20,7 @@ import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' as _i19; import 'package:stackwallet/models/exchange/response_objects/currency.dart' as _i14; +import 'package:stackwallet/models/exchange/response_objects/pair.dart' as _i22; import 'package:stackwallet/models/exchange/response_objects/range.dart' as _i16; import 'package:stackwallet/models/exchange/response_objects/trade.dart' @@ -554,13 +553,12 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { _FakeExchangeResponse_0<_i21.ExchangeTransactionStatus>())) as _i7 .Future<_i2.ExchangeResponse<_i21.ExchangeTransactionStatus>>); @override - _i7.Future<_i2.ExchangeResponse>> - getAvailableFloatingRatePairs({bool? includePartners = false}) => (super - .noSuchMethod( + _i7.Future<_i2.ExchangeResponse>> + getAvailableFloatingRatePairs({bool? includePartners = false}) => + (super.noSuchMethod( Invocation.method(#getAvailableFloatingRatePairs, [], {#includePartners: includePartners}), - returnValue: - Future<_i2.ExchangeResponse>>.value( - _FakeExchangeResponse_0>())) as _i7 - .Future<_i2.ExchangeResponse>>); + returnValue: Future<_i2.ExchangeResponse>>.value( + _FakeExchangeResponse_0>())) as _i7 + .Future<_i2.ExchangeResponse>>); } diff --git a/test/services/change_now/change_now_test.dart b/test/services/change_now/change_now_test.dart index 2447f47447..e9e0132459 100644 --- a/test/services/change_now/change_now_test.dart +++ b/test/services/change_now/change_now_test.dart @@ -5,10 +5,10 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; @@ -741,7 +741,7 @@ void main() { expect(result.exception, null); expect(result.value == null, false); - expect(result.value, isA>()); + expect(result.value, isA>()); }); test( From 670c32aeafc6b47a632d8081ce58769e30b9f4f9 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 3 Oct 2022 10:30:50 -0600 Subject: [PATCH 13/75] generic rate estimate --- .../fixed_rate_exchange_form_state.dart | 2 +- .../exchange/response_objects/estimate.dart | 46 ++++++++ .../fixed_rate_market.dart | 23 ++-- .../fixed_rate_pair_coin_selection_view.dart | 2 +- lib/pages/exchange_view/exchange_view.dart | 99 ++++++++--------- .../wallet_initiated_exchange_view.dart | 101 +++++++++--------- .../fixed_rate_market_pairs_provider.dart | 2 +- .../exchange/change_now/change_now_api.dart | 30 ++++-- .../change_now/change_now_exchange.dart | 11 +- lib/services/exchange/exchange.dart | 3 +- .../exchange/simpleswap/simpleswap_api.dart | 77 ++++++++++++- .../simpleswap/simpleswap_exchange.dart | 15 ++- ...timated_rate_exchange_form_state_test.dart | 7 +- ...d_rate_exchange_form_state_test.mocks.dart | 51 ++++----- .../exchange/exchange_view_test.mocks.dart | 67 ++++++------ 15 files changed, 335 insertions(+), 201 deletions(-) create mode 100644 lib/models/exchange/response_objects/estimate.dart rename lib/models/exchange/{change_now => response_objects}/fixed_rate_market.dart (77%) diff --git a/lib/models/exchange/fixed_rate_exchange_form_state.dart b/lib/models/exchange/fixed_rate_exchange_form_state.dart index 1ae6fac96a..65104ef8b0 100644 --- a/lib/models/exchange/fixed_rate_exchange_form_state.dart +++ b/lib/models/exchange/fixed_rate_exchange_form_state.dart @@ -1,7 +1,7 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/cupertino.dart'; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; -import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/utilities/logger.dart'; diff --git a/lib/models/exchange/response_objects/estimate.dart b/lib/models/exchange/response_objects/estimate.dart new file mode 100644 index 0000000000..7df490079f --- /dev/null +++ b/lib/models/exchange/response_objects/estimate.dart @@ -0,0 +1,46 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/utilities/logger.dart'; + +class Estimate { + final Decimal estimatedAmount; + final bool fixedRate; + final bool reversed; + final String? warningMessage; + final String? rateId; + + Estimate({ + required this.estimatedAmount, + required this.fixedRate, + required this.reversed, + this.warningMessage, + this.rateId, + }); + + factory Estimate.fromMap(Map map) { + try { + return Estimate( + estimatedAmount: Decimal.parse(map["estimatedAmount"] as String), + fixedRate: map["fixedRate"] as bool, + reversed: map["reversed"] as bool, + warningMessage: map["warningMessage"] as String?, + rateId: map["rateId"] as String?, + ); + } catch (e, s) { + Logging.instance.log("Estimate.fromMap(): $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + Map toMap() { + return { + "estimatedAmount": estimatedAmount.toString(), + "fixedRate": fixedRate, + "reversed": reversed, + "warningMessage": warningMessage, + "rateId": rateId, + }; + } + + @override + String toString() => "Estimate: ${toMap()}"; +} diff --git a/lib/models/exchange/change_now/fixed_rate_market.dart b/lib/models/exchange/response_objects/fixed_rate_market.dart similarity index 77% rename from lib/models/exchange/change_now/fixed_rate_market.dart rename to lib/models/exchange/response_objects/fixed_rate_market.dart index 9b8b2db808..7e2b363c90 100644 --- a/lib/models/exchange/change_now/fixed_rate_market.dart +++ b/lib/models/exchange/response_objects/fixed_rate_market.dart @@ -1,4 +1,5 @@ import 'package:decimal/decimal.dart'; +import 'package:stackwallet/utilities/logger.dart'; class FixedRateMarket { /// Currency ticker @@ -20,7 +21,7 @@ class FixedRateMarket { /// Network fee for transferring funds between wallets, it should /// be deducted from the result. - final Decimal minerFee; + final Decimal? minerFee; FixedRateMarket({ required this.from, @@ -31,7 +32,7 @@ class FixedRateMarket { required this.minerFee, }); - factory FixedRateMarket.fromJson(Map json) { + factory FixedRateMarket.fromMap(Map json) { try { return FixedRateMarket( from: json["from"] as String, @@ -39,15 +40,19 @@ class FixedRateMarket { min: Decimal.parse(json["min"].toString()), max: Decimal.parse(json["max"].toString()), rate: Decimal.parse(json["rate"].toString()), - minerFee: Decimal.parse(json["minerFee"].toString()), + minerFee: Decimal.tryParse(json["minerFee"].toString()), + ); + } catch (e, s) { + Logging.instance.log( + "FixedRateMarket.fromMap(): $e\n$s", + level: LogLevel.Error, ); - } catch (e) { rethrow; } } - Map toJson() { - final map = { + Map toMap() { + return { "from": from, "to": to, "min": min, @@ -55,8 +60,6 @@ class FixedRateMarket { "rate": rate, "minerFee": minerFee, }; - - return map; } FixedRateMarket copyWith({ @@ -78,7 +81,5 @@ class FixedRateMarket { } @override - String toString() { - return "FixedRateMarket: ${toJson()}"; - } + String toString() => "FixedRateMarket: ${toMap()}"; } diff --git a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart index b3fe0c3ccd..d7577e9604 100644 --- a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart +++ b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart index dad79d3c1c..e9319071d8 100644 --- a/lib/pages/exchange_view/exchange_view.dart +++ b/lib/pages/exchange_view/exchange_view.dart @@ -6,9 +6,9 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart'; @@ -1276,15 +1276,17 @@ class _ExchangeViewState extends ConsumerState { } String rate = - "1 ${fromTicker.toUpperCase()} ~${(response.value! / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + "1 ${fromTicker.toUpperCase()} ~${(response.value!.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; final model = IncompleteExchangeModel( sendTicker: fromTicker.toUpperCase(), receiveTicker: toTicker.toUpperCase(), rateInfo: rate, sendAmount: sendAmount, - receiveAmount: response.value!, + receiveAmount: + response.value!.estimatedAmount, rateType: rateType, + rateId: response.value!.rateId, ); if (mounted) { @@ -1341,51 +1343,50 @@ class _ExchangeViewState extends ConsumerState { ), )); return; + } else if (response.value!.warningMessage != + null && + response + .value!.warningMessage!.isNotEmpty) { + shouldCancel = await showDialog( + context: context, + barrierDismissible: true, + builder: (_) => StackDialog( + title: + "Failed to update trade estimate", + message: + "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor( + context), + child: Text( + "Cancel", + style: STextStyles.itemSubtitle12( + context), + ), + onPressed: () { + // notify return to cancel + Navigator.of(context).pop(true); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor( + context), + child: Text( + "Attempt", + style: STextStyles.button(context), + ), + onPressed: () { + // continue and try to attempt trade + Navigator.of(context).pop(false); + }, + ), + ), + ); } - // else if (response.value!.warningMessage != - // null && - // response - // .value!.warningMessage!.isNotEmpty) { - // shouldCancel = await showDialog( - // context: context, - // barrierDismissible: true, - // builder: (_) => StackDialog( - // title: - // "Failed to update trade estimate", - // message: - // "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?", - // leftButton: TextButton( - // style: Theme.of(context) - // .extension()! - // .getSecondaryEnabledButtonColor( - // context), - // child: Text( - // "Cancel", - // style: STextStyles.itemSubtitle12( - // context), - // ), - // onPressed: () { - // // notify return to cancel - // Navigator.of(context).pop(true); - // }, - // ), - // rightButton: TextButton( - // style: Theme.of(context) - // .extension()! - // .getPrimaryEnabledButtonColor( - // context), - // child: Text( - // "Attempt", - // style: STextStyles.button(context), - // ), - // onPressed: () { - // // continue and try to attempt trade - // Navigator.of(context).pop(false); - // }, - // ), - // ), - // ); - // } if (shouldCancel is bool && shouldCancel) { return; @@ -1399,8 +1400,10 @@ class _ExchangeViewState extends ConsumerState { receiveTicker: toTicker, rateInfo: rate, sendAmount: sendAmount, - receiveAmount: response.value!, + receiveAmount: + response.value!.estimatedAmount, rateType: rateType, + rateId: response.value!.rateId, ); if (mounted) { diff --git a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart index 5aca1b957e..dc7c4f2dd8 100644 --- a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart +++ b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart @@ -5,9 +5,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart'; @@ -1431,15 +1431,17 @@ class _WalletInitiatedExchangeViewState } String rate = - "1 ${fromTicker.toUpperCase()} ~${(response.value! / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + "1 ${fromTicker.toUpperCase()} ~${(response.value!.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; final model = IncompleteExchangeModel( sendTicker: fromTicker.toUpperCase(), receiveTicker: toTicker.toUpperCase(), rateInfo: rate, sendAmount: sendAmount, - receiveAmount: response.value!, + receiveAmount: + response.value!.estimatedAmount, rateType: rateType, + rateId: response.value!.rateId, ); if (mounted) { @@ -1497,52 +1499,51 @@ class _WalletInitiatedExchangeViewState ), )); return; + } else if (response.value!.warningMessage != + null && + response.value!.warningMessage! + .isNotEmpty) { + shouldCancel = await showDialog( + context: context, + barrierDismissible: true, + builder: (_) => StackDialog( + title: + "Failed to update trade estimate", + message: + "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor( + context), + child: Text( + "Cancel", + style: STextStyles.itemSubtitle12( + context), + ), + onPressed: () { + // notify return to cancel + Navigator.of(context).pop(true); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor( + context), + child: Text( + "Attempt", + style: + STextStyles.button(context), + ), + onPressed: () { + // continue and try to attempt trade + Navigator.of(context).pop(false); + }, + ), + ), + ); } - // else if (response.value!.warningMessage != - // null && - // response.value!.warningMessage! - // .isNotEmpty) { - // shouldCancel = await showDialog( - // context: context, - // barrierDismissible: true, - // builder: (_) => StackDialog( - // title: - // "Failed to update trade estimate", - // message: - // "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?", - // leftButton: TextButton( - // style: Theme.of(context) - // .extension()! - // .getSecondaryEnabledButtonColor( - // context), - // child: Text( - // "Cancel", - // style: STextStyles.itemSubtitle12( - // context), - // ), - // onPressed: () { - // // notify return to cancel - // Navigator.of(context).pop(true); - // }, - // ), - // rightButton: TextButton( - // style: Theme.of(context) - // .extension()! - // .getPrimaryEnabledButtonColor( - // context), - // child: Text( - // "Attempt", - // style: - // STextStyles.button(context), - // ), - // onPressed: () { - // // continue and try to attempt trade - // Navigator.of(context).pop(false); - // }, - // ), - // ), - // ); - // } if (shouldCancel is bool && shouldCancel) { return; @@ -1556,8 +1557,10 @@ class _WalletInitiatedExchangeViewState receiveTicker: toTicker, rateInfo: rate, sendAmount: sendAmount, - receiveAmount: response.value!, + receiveAmount: + response.value!.estimatedAmount, rateType: rateType, + rateId: response.value!.rateId, ); if (mounted) { diff --git a/lib/providers/exchange/fixed_rate_market_pairs_provider.dart b/lib/providers/exchange/fixed_rate_market_pairs_provider.dart index 019714ea98..1b2cbdaca7 100644 --- a/lib/providers/exchange/fixed_rate_market_pairs_provider.dart +++ b/lib/providers/exchange/fixed_rate_market_pairs_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; final fixedRateMarketPairsStateProvider = StateProvider>((ref) => []); diff --git a/lib/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart index bf5cde890c..7c7f679748 100644 --- a/lib/services/exchange/change_now/change_now_api.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -8,8 +8,9 @@ import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; -import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; @@ -309,7 +310,7 @@ class ChangeNowAPI { /// Get estimated amount of [toTicker] cryptocurrency to receive /// for [fromAmount] of [fromTicker] - Future> getEstimatedExchangeAmount({ + Future> getEstimatedExchangeAmount({ required String fromTicker, required String toTicker, required Decimal fromAmount, @@ -329,7 +330,15 @@ class ChangeNowAPI { try { final value = EstimatedExchangeAmount.fromJson( Map.from(json as Map)); - return ExchangeResponse(value: value); + return ExchangeResponse( + value: Estimate( + estimatedAmount: value.estimatedAmount, + fixedRate: false, + reversed: false, + rateId: value.rateId, + warningMessage: value.warningMessage, + ), + ); } catch (_) { return ExchangeResponse( exception: ExchangeException( @@ -352,8 +361,7 @@ class ChangeNowAPI { /// Get estimated amount of [toTicker] cryptocurrency to receive /// for [fromAmount] of [fromTicker] - Future> - getEstimatedExchangeAmountFixedRate({ + Future> getEstimatedExchangeAmountFixedRate({ required String fromTicker, required String toTicker, required Decimal fromAmount, @@ -382,7 +390,15 @@ class ChangeNowAPI { try { final value = EstimatedExchangeAmount.fromJson( Map.from(json as Map)); - return ExchangeResponse(value: value); + return ExchangeResponse( + value: Estimate( + estimatedAmount: value.estimatedAmount, + fixedRate: true, + reversed: reversed, + rateId: value.rateId, + warningMessage: value.warningMessage, + ), + ); } catch (_) { return ExchangeResponse( exception: ExchangeException( @@ -574,7 +590,7 @@ class ChangeNowAPI { for (final json in jsonArray) { try { markets.add( - FixedRateMarket.fromJson(Map.from(json as Map))); + FixedRateMarket.fromMap(Map.from(json as Map))); } catch (_) { return ExchangeResponse( exception: ExchangeException("Failed to serialize $json", diff --git a/lib/services/exchange/change_now/change_now_exchange.dart b/lib/services/exchange/change_now/change_now_exchange.dart index 5ff68de4a3..ba6ce3f0fd 100644 --- a/lib/services/exchange/change_now/change_now_exchange.dart +++ b/lib/services/exchange/change_now/change_now_exchange.dart @@ -1,7 +1,7 @@ import 'package:decimal/decimal.dart'; -import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; @@ -87,14 +87,14 @@ class ChangeNowExchange extends Exchange { } @override - Future> getEstimate( + Future> getEstimate( String from, String to, Decimal amount, bool fixedRate, bool reversed, ) async { - late final ExchangeResponse response; + late final ExchangeResponse response; if (fixedRate) { response = await ChangeNowAPI.instance.getEstimatedExchangeAmountFixedRate( @@ -110,10 +110,7 @@ class ChangeNowExchange extends Exchange { fromAmount: amount, ); } - if (response.exception != null) { - return ExchangeResponse(exception: response.exception); - } - return ExchangeResponse(value: response.value?.estimatedAmount); + return response; } @override diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart index 8771fffd55..c3210cddc3 100644 --- a/lib/services/exchange/exchange.dart +++ b/lib/services/exchange/exchange.dart @@ -1,5 +1,6 @@ import 'package:decimal/decimal.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; @@ -28,7 +29,7 @@ abstract class Exchange { bool fixedRate, ); - Future> getEstimate( + Future> getEstimate( String from, String to, Decimal amount, diff --git a/lib/services/exchange/simpleswap/simpleswap_api.dart b/lib/services/exchange/simpleswap/simpleswap_api.dart index 2df5d16a1a..4c78820113 100644 --- a/lib/services/exchange/simpleswap/simpleswap_api.dart +++ b/lib/services/exchange/simpleswap/simpleswap_api.dart @@ -4,6 +4,7 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:stackwallet/external_api_keys.dart'; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; @@ -37,7 +38,6 @@ class SimpleSwapAPI { ); final parsed = jsonDecode(response.body); - print("PARSED: $parsed"); return parsed; } catch (e, s) { @@ -99,9 +99,6 @@ class SimpleSwapAPI { try { final jsonObject = await _makePostRequest(uri, body); - print("================================"); - print(jsonObject); - print("================================"); final json = Map.from(jsonObject as Map); final trade = Trade( @@ -427,4 +424,76 @@ class SimpleSwapAPI { ); } } + + Future>> getFixedRateMarketInfo({ + String? apiKey, + }) async { + final uri = _buildUri( + "/get_market_info", + null, + // { + // "api_key": apiKey ?? kSimpleSwapApiKey, + // "fixed": isFixedRate.toString(), + // "currency_from": currencyFrom, + // "currency_to": currencyTo, + // }, + ); + + try { + final jsonArray = await _makeGetRequest(uri); + + try { + final result = await compute( + _parseFixedRateMarketsJson, + jsonArray as List, + ); + return result; + } catch (e, s) { + Logging.instance.log("getAvailableFixedRateMarkets exception: $e\n$s", + level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + "Error: $jsonArray", + ExchangeExceptionType.serializeResponseError, + ), + ); + } + } catch (e, s) { + Logging.instance.log("getAvailableFixedRateMarkets exception: $e\n$s", + level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + ExchangeResponse> _parseFixedRateMarketsJson( + List jsonArray) { + try { + final List markets = []; + for (final json in jsonArray) { + try { + final map = Map.from(json as Map); + markets.add(FixedRateMarket( + from: map["currency_from"] as String, + to: map["currency_to"] as String, + min: Decimal.parse(map["min"] as String), + max: Decimal.parse(map["max"] as String), + rate: Decimal.parse(map["rate"] as String), + minerFee: null, + )); + } catch (_) { + return ExchangeResponse( + exception: ExchangeException("Failed to serialize $json", + ExchangeExceptionType.serializeResponseError)); + } + } + return ExchangeResponse(value: markets); + } catch (_) { + rethrow; + } + } } diff --git a/lib/services/exchange/simpleswap/simpleswap_exchange.dart b/lib/services/exchange/simpleswap/simpleswap_exchange.dart index e8e79b06da..1fbfa4f3c9 100644 --- a/lib/services/exchange/simpleswap/simpleswap_exchange.dart +++ b/lib/services/exchange/simpleswap/simpleswap_exchange.dart @@ -1,5 +1,6 @@ import 'package:decimal/decimal.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; @@ -76,7 +77,7 @@ class SimpleSwapExchange extends Exchange { } @override - Future> getEstimate( + Future> getEstimate( String from, String to, Decimal amount, @@ -89,10 +90,18 @@ class SimpleSwapExchange extends Exchange { currencyTo: to, amount: amount.toString(), ); + if (response.exception != null) { + return ExchangeResponse( + exception: response.exception, + ); + } return ExchangeResponse( - value: Decimal.tryParse(response.value ?? ""), - exception: response.exception, + value: Estimate( + estimatedAmount: Decimal.parse(response.value!), + fixedRate: fixedRate, + reversed: reversed, + ), ); } diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.dart index 6386d533fb..21af573eb7 100644 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.dart +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.dart @@ -2,9 +2,9 @@ import 'package:decimal/decimal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/estimated_rate_exchange_form_state.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; @@ -186,8 +186,9 @@ void main() { toTicker: "xmr", fromAmount: Decimal.parse("110.10"))) .thenAnswer((_) async => ExchangeResponse( - value: EstimatedExchangeAmount( - transactionSpeedForecast: '10-60', + value: Estimate( + reversed: false, + fixedRate: false, rateId: 'some rate id', warningMessage: '', estimatedAmount: Decimal.parse("302.002348"), diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart index 94ec0ad5d4..82a7eceb75 100644 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart @@ -9,16 +9,16 @@ import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' as _i10; -import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart' - as _i9; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' as _i12; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' as _i13; -import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' - as _i11; import 'package:stackwallet/models/exchange/response_objects/currency.dart' as _i6; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart' + as _i9; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart' + as _i11; import 'package:stackwallet/models/exchange/response_objects/pair.dart' as _i14; import 'package:stackwallet/models/exchange/response_objects/range.dart' as _i8; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart' @@ -97,26 +97,23 @@ class MockChangeNowAPI extends _i1.Mock implements _i3.ChangeNowAPI { _FakeExchangeResponse_0<_i8.Range>())) as _i5.Future<_i2.ExchangeResponse<_i8.Range>>); @override - _i5.Future<_i2.ExchangeResponse<_i9.EstimatedExchangeAmount>> - getEstimatedExchangeAmount( - {String? fromTicker, - String? toTicker, - _i7.Decimal? fromAmount, - String? apiKey}) => - (super.noSuchMethod( - Invocation.method(#getEstimatedExchangeAmount, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #fromAmount: fromAmount, - #apiKey: apiKey - }), - returnValue: Future< - _i2.ExchangeResponse< - _i9.EstimatedExchangeAmount>>.value( - _FakeExchangeResponse_0<_i9.EstimatedExchangeAmount>())) - as _i5.Future<_i2.ExchangeResponse<_i9.EstimatedExchangeAmount>>); + _i5.Future<_i2.ExchangeResponse<_i9.Estimate>> getEstimatedExchangeAmount( + {String? fromTicker, + String? toTicker, + _i7.Decimal? fromAmount, + String? apiKey}) => + (super.noSuchMethod( + Invocation.method(#getEstimatedExchangeAmount, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #fromAmount: fromAmount, + #apiKey: apiKey + }), + returnValue: Future<_i2.ExchangeResponse<_i9.Estimate>>.value( + _FakeExchangeResponse_0<_i9.Estimate>())) + as _i5.Future<_i2.ExchangeResponse<_i9.Estimate>>); @override - _i5.Future<_i2.ExchangeResponse<_i9.EstimatedExchangeAmount>> + _i5.Future<_i2.ExchangeResponse<_i9.Estimate>> getEstimatedExchangeAmountFixedRate( {String? fromTicker, String? toTicker, @@ -131,11 +128,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i3.ChangeNowAPI { #reversed: reversed, #apiKey: apiKey }), - returnValue: Future< - _i2.ExchangeResponse< - _i9.EstimatedExchangeAmount>>.value( - _FakeExchangeResponse_0<_i9.EstimatedExchangeAmount>())) - as _i5.Future<_i2.ExchangeResponse<_i9.EstimatedExchangeAmount>>); + returnValue: Future<_i2.ExchangeResponse<_i9.Estimate>>.value( + _FakeExchangeResponse_0<_i9.Estimate>())) + as _i5.Future<_i2.ExchangeResponse<_i9.Estimate>>); @override _i5.Future<_i2.ExchangeResponse<_i10.CNExchangeEstimate>> getEstimatedExchangeAmountV2( diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index c0151e028d..da09726457 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -10,16 +10,16 @@ import 'package:http/http.dart' as _i13; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' as _i18; -import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart' - as _i17; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' as _i20; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' as _i21; -import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' - as _i19; import 'package:stackwallet/models/exchange/response_objects/currency.dart' as _i14; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart' + as _i17; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart' + as _i19; import 'package:stackwallet/models/exchange/response_objects/pair.dart' as _i22; import 'package:stackwallet/models/exchange/response_objects/range.dart' as _i16; @@ -405,27 +405,23 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { _FakeExchangeResponse_0<_i16.Range>())) as _i7.Future<_i2.ExchangeResponse<_i16.Range>>); @override - _i7.Future<_i2.ExchangeResponse<_i17.EstimatedExchangeAmount>> - getEstimatedExchangeAmount( - {String? fromTicker, - String? toTicker, - _i15.Decimal? fromAmount, - String? apiKey}) => - (super.noSuchMethod( - Invocation.method(#getEstimatedExchangeAmount, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #fromAmount: fromAmount, - #apiKey: apiKey - }), - returnValue: Future< - _i2.ExchangeResponse< - _i17.EstimatedExchangeAmount>>.value( - _FakeExchangeResponse_0<_i17.EstimatedExchangeAmount>())) - as _i7 - .Future<_i2.ExchangeResponse<_i17.EstimatedExchangeAmount>>); + _i7.Future<_i2.ExchangeResponse<_i17.Estimate>> getEstimatedExchangeAmount( + {String? fromTicker, + String? toTicker, + _i15.Decimal? fromAmount, + String? apiKey}) => + (super.noSuchMethod( + Invocation.method(#getEstimatedExchangeAmount, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #fromAmount: fromAmount, + #apiKey: apiKey + }), + returnValue: Future<_i2.ExchangeResponse<_i17.Estimate>>.value( + _FakeExchangeResponse_0<_i17.Estimate>())) + as _i7.Future<_i2.ExchangeResponse<_i17.Estimate>>); @override - _i7.Future<_i2.ExchangeResponse<_i17.EstimatedExchangeAmount>> + _i7.Future<_i2.ExchangeResponse<_i17.Estimate>> getEstimatedExchangeAmountFixedRate( {String? fromTicker, String? toTicker, @@ -433,19 +429,16 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { bool? reversed, String? apiKey}) => (super.noSuchMethod( - Invocation.method(#getEstimatedExchangeAmountFixedRate, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #fromAmount: fromAmount, - #reversed: reversed, - #apiKey: apiKey - }), - returnValue: Future< - _i2.ExchangeResponse< - _i17.EstimatedExchangeAmount>>.value( - _FakeExchangeResponse_0<_i17.EstimatedExchangeAmount>())) - as _i7 - .Future<_i2.ExchangeResponse<_i17.EstimatedExchangeAmount>>); + Invocation.method(#getEstimatedExchangeAmountFixedRate, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #fromAmount: fromAmount, + #reversed: reversed, + #apiKey: apiKey + }), + returnValue: Future<_i2.ExchangeResponse<_i17.Estimate>>.value( + _FakeExchangeResponse_0<_i17.Estimate>())) as _i7 + .Future<_i2.ExchangeResponse<_i17.Estimate>>); @override _i7.Future<_i2.ExchangeResponse<_i18.CNExchangeEstimate>> getEstimatedExchangeAmountV2( From 6d9399146082ac86091be98f7b1d0ca5b11d98f5 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 3 Oct 2022 12:24:37 -0600 Subject: [PATCH 14/75] exchange icons --- .../svg/exchange_icons/change_now_logo_1.svg | 25 +++++++++++++++++++ assets/svg/exchange_icons/simpleswap-icon.svg | 5 ++++ lib/utilities/assets.dart | 8 ++++++ pubspec.yaml | 3 +++ 4 files changed, 41 insertions(+) create mode 100644 assets/svg/exchange_icons/change_now_logo_1.svg create mode 100644 assets/svg/exchange_icons/simpleswap-icon.svg diff --git a/assets/svg/exchange_icons/change_now_logo_1.svg b/assets/svg/exchange_icons/change_now_logo_1.svg new file mode 100644 index 0000000000..323488e529 --- /dev/null +++ b/assets/svg/exchange_icons/change_now_logo_1.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/exchange_icons/simpleswap-icon.svg b/assets/svg/exchange_icons/simpleswap-icon.svg new file mode 100644 index 0000000000..b3986ef2f2 --- /dev/null +++ b/assets/svg/exchange_icons/simpleswap-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 94558e6235..376273fdc0 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -7,6 +7,7 @@ abstract class Assets { static const png = _PNG(); static const lottie = _ANIMATIONS(); static const socials = _SOCIALS(); + static const exchange = _EXCHANGE(); } class _SOCIALS { @@ -18,6 +19,13 @@ class _SOCIALS { String get telegram => "assets/svg/socials/telegram-brands.svg"; } +class _EXCHANGE { + const _EXCHANGE(); + + String get changeNow => "assets/svg/exchange_icons/change_now_logo_1.svg"; + String get simpleSwap => "assets/svg/exchange_icons/simpleswap-icon.svg"; +} + class _SVG { const _SVG(); diff --git a/pubspec.yaml b/pubspec.yaml index 3d0ae5a4b5..47358cc584 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -312,6 +312,9 @@ flutter: - assets/svg/exchange-3.svg - assets/svg/message-question-1.svg - assets/svg/drd-icon.svg + # exchange icons + - assets/svg/exchange_icons/change_now_logo_1.svg + - assets/svg/exchange_icons/simpleswap-icon.svg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. From 6b21078cc86d7425308b545b95bde9cc213f78bc Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 3 Oct 2022 12:24:57 -0600 Subject: [PATCH 15/75] extract exchange form from view --- lib/pages/exchange_view/exchange_form.dart | 1210 ++++++++++++++ lib/pages/exchange_view/exchange_view.dart | 1394 +---------------- .../exchange_provider_options.dart | 203 +++ .../sub_widgets/rate_type_toggle.dart | 132 ++ .../wallet_initiated_exchange_view.dart | 3 - 5 files changed, 1551 insertions(+), 1391 deletions(-) create mode 100644 lib/pages/exchange_view/exchange_form.dart create mode 100644 lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart create mode 100644 lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart new file mode 100644 index 0000000000..644863e1b2 --- /dev/null +++ b/lib/pages/exchange_view/exchange_form.dart @@ -0,0 +1,1210 @@ +import 'dart:async'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart'; +import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart'; +import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_1_view.dart'; +import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_options.dart'; +import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; +import 'package:stackwallet/pages/exchange_view/sub_widgets/rate_type_toggle.dart'; +import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; +import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; +import 'package:stackwallet/providers/exchange/change_now_provider.dart'; +import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart'; +import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; +import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart'; +import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_loading_overlay.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class ExchangeForm extends ConsumerStatefulWidget { + const ExchangeForm({Key? key}) : super(key: key); + + @override + ConsumerState createState() => _ExchangeFormState(); +} + +class _ExchangeFormState extends ConsumerState { + late final TextEditingController _sendController; + late final TextEditingController _receiveController; + final FocusNode _sendFocusNode = FocusNode(); + final FocusNode _receiveFocusNode = FocusNode(); + + bool _swapLock = false; + bool _reversed = false; + + void sendFieldOnChanged(String value) async { + final newFromAmount = Decimal.tryParse(value); + final isEstimated = + ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated; + if (newFromAmount != null) { + if (isEstimated) { + await ref + .read(estimatedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount(newFromAmount, false); + } else { + await ref + .read(fixedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount(newFromAmount, false); + } + } else { + if (isEstimated) { + await ref + .read(estimatedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount(Decimal.zero, false); + } else { + await ref + .read(fixedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount(Decimal.zero, false); + } + _receiveController.text = isEstimated ? "-" : ""; + } + } + + void selectSendCurrency() async { + if (ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) { + await _showFloatingRateSelectionSheet( + currencies: + ref.read(availableChangeNowCurrenciesStateProvider.state).state, + excludedTicker: + ref.read(estimatedRateExchangeFormProvider).to?.ticker ?? "-", + fromTicker: + ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? "-", + onSelected: (from) => ref + .read(estimatedRateExchangeFormProvider) + .updateFrom(from, true)); + } else { + final toTicker = ref.read(fixedRateExchangeFormProvider).market?.to ?? ""; + final fromTicker = + ref.read(fixedRateExchangeFormProvider).market?.from ?? ""; + await _showFixedRateSelectionSheet( + excludedTicker: toTicker, + fromTicker: fromTicker, + onSelected: (selectedFromTicker) async { + try { + final market = ref + .read(fixedRateMarketPairsStateProvider.state) + .state + .firstWhere( + (e) => e.to == toTicker && e.from == selectedFromTicker, + ); + + await ref + .read(fixedRateExchangeFormProvider) + .updateMarket(market, true); + } catch (e) { + unawaited(showDialog( + context: context, + builder: (_) => const StackDialog( + title: "Fixed rate market error", + message: "Could not find the specified fixed rate trade pair", + ), + )); + return; + } + }, + ); + } + } + + void selectReceiveCurrency() async { + if (ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) { + await _showFloatingRateSelectionSheet( + currencies: + ref.read(availableChangeNowCurrenciesStateProvider.state).state, + excludedTicker: + ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? "", + fromTicker: + ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? "", + onSelected: (to) => + ref.read(estimatedRateExchangeFormProvider).updateTo(to, true)); + } else { + final fromTicker = + ref.read(fixedRateExchangeFormProvider).market?.from ?? ""; + await _showFixedRateSelectionSheet( + excludedTicker: fromTicker, + fromTicker: fromTicker, + onSelected: (selectedToTicker) async { + try { + final market = ref + .read(fixedRateMarketPairsStateProvider.state) + .state + .firstWhere( + (e) => e.to == selectedToTicker && e.from == fromTicker, + ); + + await ref + .read(fixedRateExchangeFormProvider) + .updateMarket(market, true); + } catch (e) { + unawaited(showDialog( + context: context, + builder: (_) => const StackDialog( + title: "Fixed rate market error", + message: "Could not find the specified fixed rate trade pair", + ), + )); + return; + } + }, + ); + } + } + + void receiveFieldOnChanged(String value) async { + final newToAmount = Decimal.tryParse(value); + if (newToAmount != null) { + if (ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) { + // await ref + // .read(estimatedRateExchangeFormProvider) + // .setToAmountAndCalculateFromAmount( + // newToAmount, false); + } else { + await ref + .read(fixedRateExchangeFormProvider) + .setToAmountAndCalculateFromAmount(newToAmount, false); + } + } else { + if (ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) { + // await ref + // .read(estimatedRateExchangeFormProvider) + // .setToAmountAndCalculateFromAmount( + // Decimal.zero, false); + } else { + await ref + .read(fixedRateExchangeFormProvider) + .setToAmountAndCalculateFromAmount(Decimal.zero, false); + } + _sendController.text = ""; + } + } + + Future _swap() async { + _swapLock = true; + _sendFocusNode.unfocus(); + _receiveFocusNode.unfocus(); + + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => WillPopScope( + onWillPop: () async => false, + child: Container( + color: Theme.of(context) + .extension()! + .overlay + .withOpacity(0.6), + child: const CustomLoadingOverlay( + message: "Updating exchange rate", + eventBus: null, + ), + ), + ), + ), + ); + + if (ref.watch(prefsChangeNotifierProvider + .select((pref) => pref.exchangeRateType)) == + ExchangeRateType.estimated) { + await ref.read(estimatedRateExchangeFormProvider).swap(); + } else { + final from = ref.read(fixedRateExchangeFormProvider).market?.from; + final to = ref.read(fixedRateExchangeFormProvider).market?.to; + + if (to != null && from != null) { + final markets = ref + .read(fixedRateMarketPairsStateProvider.state) + .state + .where((e) => e.from == to && e.to == from); + + if (markets.isNotEmpty) { + await ref.read(fixedRateExchangeFormProvider).swap(markets.first); + } + } + } + if (mounted) { + Navigator.of(context).pop(); + } + _swapLock = false; + } + + Future _showFloatingRateSelectionSheet({ + required List currencies, + required String excludedTicker, + required String fromTicker, + required void Function(Currency) onSelected, + }) async { + _sendFocusNode.unfocus(); + _receiveFocusNode.unfocus(); + + List availablePairs = []; + if (fromTicker.isEmpty || + fromTicker == "-" || + excludedTicker.isEmpty || + excludedTicker == "-") { + availablePairs = + ref.read(availableFloatingRatePairsStateProvider.state).state; + } else if (excludedTicker == fromTicker) { + availablePairs = ref + .read(availableFloatingRatePairsStateProvider.state) + .state + .where((e) => e.from == excludedTicker) + .toList(growable: false); + } else { + availablePairs = ref + .read(availableFloatingRatePairsStateProvider.state) + .state + .where((e) => e.to == excludedTicker) + .toList(growable: false); + } + + final List tickers = currencies.where((e) { + if (excludedTicker == fromTicker) { + return e.ticker != excludedTicker && + availablePairs.where((e2) => e2.to == e.ticker).isNotEmpty; + } else { + return e.ticker != excludedTicker && + availablePairs.where((e2) => e2.from == e.ticker).isNotEmpty; + } + }).toList(growable: false); + + final result = await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => FloatingRateCurrencySelectionView( + currencies: tickers, + ), + ), + ); + + if (mounted && result is Currency) { + onSelected(result); + } + } + + String? _fetchIconUrlFromTickerForFixedRateFlow(String? ticker) { + if (ticker == null) return null; + + final possibleCurrencies = ref + .read(availableChangeNowCurrenciesStateProvider.state) + .state + .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase()); + + for (final currency in possibleCurrencies) { + if (currency.image.isNotEmpty) { + return currency.image; + } + } + + return null; + } + + Future _showFixedRateSelectionSheet({ + required String excludedTicker, + required String fromTicker, + required void Function(String) onSelected, + }) async { + _sendFocusNode.unfocus(); + _receiveFocusNode.unfocus(); + + List marketsThatPairWithExcludedTicker = []; + + if (excludedTicker == "" || + excludedTicker == "-" || + fromTicker == "" || + fromTicker == "-") { + marketsThatPairWithExcludedTicker = + ref.read(fixedRateMarketPairsStateProvider.state).state; + } else if (excludedTicker == fromTicker) { + marketsThatPairWithExcludedTicker = ref + .read(fixedRateMarketPairsStateProvider.state) + .state + .where((e) => e.from == excludedTicker && e.to != excludedTicker) + .toList(growable: false); + } else { + marketsThatPairWithExcludedTicker = ref + .read(fixedRateMarketPairsStateProvider.state) + .state + .where((e) => e.to == excludedTicker && e.from != excludedTicker) + .toList(growable: false); + } + + final result = await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => FixedRateMarketPairCoinSelectionView( + markets: marketsThatPairWithExcludedTicker, + currencies: + ref.read(availableChangeNowCurrenciesStateProvider.state).state, + isFrom: excludedTicker != fromTicker, + ), + ), + ); + + if (mounted && result is String) { + onSelected(result); + } + } + + void onRateTypeChanged(ExchangeRateType rateType) async { + _receiveFocusNode.unfocus(); + _sendFocusNode.unfocus(); + switch (rateType) { + case ExchangeRateType.estimated: + final market = ref.read(fixedRateExchangeFormProvider).market; + final fromTicker = market?.from ?? ""; + final toTicker = market?.to ?? ""; + if (!(fromTicker.isEmpty || + toTicker.isEmpty || + toTicker == "-" || + fromTicker == "-")) { + final available = ref + .read(availableFloatingRatePairsStateProvider.state) + .state + .where((e) => e.to == toTicker && e.from == fromTicker); + if (available.isNotEmpty) { + final availableCurrencies = ref + .read(availableChangeNowCurrenciesStateProvider.state) + .state + .where((e) => e.ticker == fromTicker || e.ticker == toTicker); + if (availableCurrencies.length > 1) { + final from = + availableCurrencies.firstWhere((e) => e.ticker == fromTicker); + final to = + availableCurrencies.firstWhere((e) => e.ticker == toTicker); + + final newFromAmount = Decimal.tryParse(_sendController.text); + if (newFromAmount != null) { + await ref + .read(estimatedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount(newFromAmount, false); + } else { + await ref + .read(estimatedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount(Decimal.zero, false); + + _receiveController.text = ""; + } + + await ref + .read(estimatedRateExchangeFormProvider) + .updateTo(to, false); + await ref + .read(estimatedRateExchangeFormProvider) + .updateFrom(from, true); + return; + } + } + } + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: + "Estimated rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last estimated rate pair.", + context: context, + )); + break; + case ExchangeRateType.fixed: + final fromTicker = + ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? ""; + final toTicker = + ref.read(estimatedRateExchangeFormProvider).to?.ticker ?? ""; + if (!(fromTicker.isEmpty || + toTicker.isEmpty || + toTicker == "-" || + fromTicker == "-")) { + FixedRateMarket? market; + try { + market = ref + .read(fixedRateMarketPairsStateProvider.state) + .state + .firstWhere((e) => e.from == fromTicker && e.to == toTicker); + } catch (_) { + market = null; + } + + final newFromAmount = Decimal.tryParse(_sendController.text); + if (newFromAmount != null) { + await ref + .read(fixedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount(newFromAmount, false); + } else { + await ref + .read(fixedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount(Decimal.zero, false); + + _receiveController.text = ""; + } + + await ref + .read(fixedRateExchangeFormProvider) + .updateMarket(market, false); + await ref + .read(fixedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount( + Decimal.tryParse(_sendController.text) ?? Decimal.zero, + true, + ); + return; + } + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: + "Fixed rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last fixed rate pair.", + context: context, + )); + break; + } + } + + void onExchangePressed() async { + if (ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) { + final fromTicker = + ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? ""; + final toTicker = + ref.read(estimatedRateExchangeFormProvider).to?.ticker ?? ""; + + bool isAvailable = false; + final availableFloatingPairs = + ref.read(availableFloatingRatePairsStateProvider.state).state; + for (final pair in availableFloatingPairs) { + if (pair.from == fromTicker && pair.to == toTicker) { + isAvailable = true; + break; + } + } + + if (!isAvailable) { + unawaited(showDialog( + context: context, + barrierDismissible: true, + builder: (_) => StackDialog( + title: "Selected trade pair unavailable", + message: + "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", + ), + )); + return; + } + + final sendAmount = Decimal.parse( + ref.read(estimatedRateExchangeFormProvider).fromAmountString); + + final rateType = ref.read(prefsChangeNotifierProvider).exchangeRateType; + + final response = await ref.read(changeNowProvider).getEstimate( + fromTicker, + toTicker, + sendAmount, + false, + false, + ); + + if (response.value == null) { + unawaited(showDialog( + context: context, + barrierDismissible: true, + builder: (_) => StackDialog( + title: "Failed to update trade estimate", + message: response.exception?.toString(), + ), + )); + return; + } + + String rate = + "1 ${fromTicker.toUpperCase()} ~${(response.value!.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + + final model = IncompleteExchangeModel( + sendTicker: fromTicker.toUpperCase(), + receiveTicker: toTicker.toUpperCase(), + rateInfo: rate, + sendAmount: sendAmount, + receiveAmount: response.value!.estimatedAmount, + rateType: rateType, + rateId: response.value!.rateId, + ); + + if (mounted) { + ref.read(exchangeSendFromWalletIdStateProvider.state).state = null; + unawaited(Navigator.of(context).pushNamed( + Step1View.routeName, + arguments: model, + )); + } + } else { + final fromTicker = + ref.read(fixedRateExchangeFormProvider).market?.from ?? ""; + final toTicker = ref.read(fixedRateExchangeFormProvider).market?.to ?? ""; + + final sendAmount = Decimal.parse( + ref.read(fixedRateExchangeFormProvider).fromAmountString); + + final rateType = ref.read(prefsChangeNotifierProvider).exchangeRateType; + + final response = await ref.read(changeNowProvider).getEstimate( + fromTicker, + toTicker, + sendAmount, + true, + false, + ); + + bool? shouldCancel; + + if (response.value == null) { + unawaited(showDialog( + context: context, + barrierDismissible: true, + builder: (_) => StackDialog( + title: "Failed to update trade estimate", + message: response.exception?.toString(), + ), + )); + return; + } else if (response.value!.warningMessage != null && + response.value!.warningMessage!.isNotEmpty) { + shouldCancel = await showDialog( + context: context, + barrierDismissible: true, + builder: (_) => StackDialog( + title: "Failed to update trade estimate", + message: + "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Cancel", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + // notify return to cancel + Navigator.of(context).pop(true); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Attempt", + style: STextStyles.button(context), + ), + onPressed: () { + // continue and try to attempt trade + Navigator.of(context).pop(false); + }, + ), + ), + ); + } + + if (shouldCancel is bool && shouldCancel) { + return; + } + + String rate = + "1 ${fromTicker.toUpperCase()} ~${ref.read(fixedRateExchangeFormProvider).rate!.toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + + final model = IncompleteExchangeModel( + sendTicker: fromTicker, + receiveTicker: toTicker, + rateInfo: rate, + sendAmount: sendAmount, + receiveAmount: response.value!.estimatedAmount, + rateType: rateType, + rateId: response.value!.rateId, + ); + + if (mounted) { + ref.read(exchangeSendFromWalletIdStateProvider.state).state = null; + unawaited(Navigator.of(context).pushNamed( + Step1View.routeName, + arguments: model, + )); + } + } + } + + @override + void initState() { + _sendController = TextEditingController(); + _receiveController = TextEditingController(); + + final isEstimated = + ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated; + _sendController.text = isEstimated + ? ref.read(estimatedRateExchangeFormProvider).fromAmountString + : ref.read(fixedRateExchangeFormProvider).fromAmountString; + _receiveController.text = isEstimated + ? "-" //ref.read(estimatedRateExchangeFormProvider).toAmountString + : ref.read(fixedRateExchangeFormProvider).toAmountString; + + _sendFocusNode.addListener(() async { + if (!_sendFocusNode.hasFocus) { + final newFromAmount = Decimal.tryParse(_sendController.text); + if (newFromAmount != null) { + if (ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) { + await ref + .read(estimatedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount(newFromAmount, true); + } else { + await ref + .read(fixedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount(newFromAmount, true); + } + } else { + if (ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) { + await ref + .read(estimatedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount(Decimal.zero, true); + } else { + await ref + .read(fixedRateExchangeFormProvider) + .setFromAmountAndCalculateToAmount(Decimal.zero, true); + } + _receiveController.text = + ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated + ? "-" + : ""; + } + } + }); + _receiveFocusNode.addListener(() async { + if (!_receiveFocusNode.hasFocus) { + final newToAmount = Decimal.tryParse(_receiveController.text); + if (newToAmount != null) { + if (ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) { + // await ref + // .read(estimatedRateExchangeFormProvider) + // .setToAmountAndCalculateFromAmount(newToAmount, true); + } else { + await ref + .read(fixedRateExchangeFormProvider) + .setToAmountAndCalculateFromAmount(newToAmount, true); + } + } else { + if (ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) { + // await ref + // .read(estimatedRateExchangeFormProvider) + // .setToAmountAndCalculateFromAmount(Decimal.zero, true); + } else { + await ref + .read(fixedRateExchangeFormProvider) + .setToAmountAndCalculateFromAmount(Decimal.zero, true); + } + _sendController.text = ""; + } + } + }); + + super.initState(); + } + + @override + void dispose() { + _receiveController.dispose(); + _sendController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + final isEstimated = ref.watch(prefsChangeNotifierProvider + .select((pref) => pref.exchangeRateType)) == + ExchangeRateType.estimated; + + ref.listen( + isEstimated + ? estimatedRateExchangeFormProvider + .select((value) => value.toAmountString) + : fixedRateExchangeFormProvider.select( + (value) => value.toAmountString), (previous, String next) { + if (!_receiveFocusNode.hasFocus) { + _receiveController.text = isEstimated && next.isEmpty ? "-" : next; + debugPrint("RECEIVE AMOUNT LISTENER ACTIVATED"); + if (_swapLock) { + _sendController.text = isEstimated + ? ref.read(estimatedRateExchangeFormProvider).fromAmountString + : ref.read(fixedRateExchangeFormProvider).fromAmountString; + } + } + }); + ref.listen( + isEstimated + ? estimatedRateExchangeFormProvider + .select((value) => value.fromAmountString) + : fixedRateExchangeFormProvider.select( + (value) => value.fromAmountString), (previous, String next) { + if (!_sendFocusNode.hasFocus) { + _sendController.text = next; + debugPrint("SEND AMOUNT LISTENER ACTIVATED"); + if (_swapLock) { + _receiveController.text = isEstimated + ? ref + .read(estimatedRateExchangeFormProvider) + .toAmountString + .isEmpty + ? "-" + : ref.read(estimatedRateExchangeFormProvider).toAmountString + : ref.read(fixedRateExchangeFormProvider).toAmountString; + } + } + }); + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "You will send", + style: STextStyles.itemSubtitle(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ), + ), + const SizedBox( + height: 4, + ), + TextFormField( + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + focusNode: _sendFocusNode, + controller: _sendController, + textAlign: TextAlign.right, + onTap: () { + if (_sendController.text == "-") { + _sendController.text = ""; + } + }, + onChanged: sendFieldOnChanged, + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + inputFormatters: [ + // regex to validate a crypto amount with 8 decimal places + TextInputFormatter.withFunction((oldValue, newValue) => + RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') + .hasMatch(newValue.text) + ? newValue + : oldValue), + ], + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + top: 12, + right: 12, + ), + hintText: "0", + hintStyle: STextStyles.fieldLabel(context).copyWith( + fontSize: 14, + ), + prefixIcon: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: GestureDetector( + onTap: selectSendCurrency, + child: Container( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Container( + width: 18, + height: 18, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + ), + child: Builder( + builder: (context) { + String? image; + if (ref.watch(prefsChangeNotifierProvider.select( + (value) => value.exchangeRateType)) == + ExchangeRateType.estimated) { + image = ref + .watch(estimatedRateExchangeFormProvider + .select((value) => value.from)) + ?.image; + } else { + image = _fetchIconUrlFromTickerForFixedRateFlow( + ref.watch( + fixedRateExchangeFormProvider.select( + (value) => value.market?.from))); + } + if (image != null && image.isNotEmpty) { + return Center( + child: SvgPicture.network( + image, + height: 18, + placeholderBuilder: (_) => Container( + width: 18, + height: 18, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular( + 18, + ), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular( + 18, + ), + child: const LoadingIndicator(), + ), + ), + ), + ); + } else { + return Container( + width: 18, + height: 18, + decoration: BoxDecoration( + // color: Theme.of(context).extension()!.accentColorDark + borderRadius: BorderRadius.circular(18), + ), + child: SvgPicture.asset( + Assets.svg.circleQuestion, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + ), + ); + } + }, + ), + ), + const SizedBox( + width: 6, + ), + Text( + ref.watch(prefsChangeNotifierProvider.select( + (value) => value.exchangeRateType)) == + ExchangeRateType.estimated + ? ref.watch(estimatedRateExchangeFormProvider + .select((value) => + value.from?.ticker.toUpperCase())) ?? + "-" + : ref.watch(fixedRateExchangeFormProvider.select( + (value) => + value.market?.from.toUpperCase())) ?? + "-", + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + const SizedBox( + width: 6, + ), + SvgPicture.asset( + Assets.svg.chevronDown, + width: 5, + height: 2.5, + color: Theme.of(context) + .extension()! + .textDark, + ), + ], + ), + ), + ), + ), + ), + ), + ), + const SizedBox( + height: 4, + ), + Stack( + children: [ + Positioned.fill( + child: Align( + alignment: Alignment.bottomLeft, + child: Text( + "You will receive", + style: STextStyles.itemSubtitle(context).copyWith( + color: + Theme.of(context).extension()!.textDark3, + ), + ), + ), + ), + Center( + child: Column( + children: [ + const SizedBox( + height: 6, + ), + GestureDetector( + onTap: () async { + await _swap(); + }, + child: Padding( + padding: const EdgeInsets.all(4), + child: SvgPicture.asset( + Assets.svg.swap, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + const SizedBox( + height: 6, + ), + ], + ), + ), + Positioned.fill( + child: Align( + alignment: Alignment.topRight, + child: Text( + ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated + ? ref.watch(estimatedRateExchangeFormProvider + .select((value) => value.minimumSendWarning)) + : ref.watch(fixedRateExchangeFormProvider + .select((value) => value.sendAmountWarning)), + style: STextStyles.errorSmall(context), + ), + ), + ), + ], + ), + const SizedBox( + height: 4, + ), + TextFormField( + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + focusNode: _receiveFocusNode, + controller: _receiveController, + readOnly: ref.watch(prefsChangeNotifierProvider + .select((value) => value.exchangeRateType)) == + ExchangeRateType.estimated, + onTap: () { + if (!(ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) && + _receiveController.text == "-") { + _receiveController.text = ""; + } + }, + onChanged: receiveFieldOnChanged, + textAlign: TextAlign.right, + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + inputFormatters: [ + // regex to validate a crypto amount with 8 decimal places + TextInputFormatter.withFunction((oldValue, newValue) => + RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') + .hasMatch(newValue.text) + ? newValue + : oldValue), + ], + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + top: 12, + right: 12, + ), + hintText: "0", + hintStyle: STextStyles.fieldLabel(context).copyWith( + fontSize: 14, + ), + prefixIcon: FittedBox( + fit: BoxFit.scaleDown, + child: GestureDetector( + onTap: selectReceiveCurrency, + child: Container( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Container( + width: 18, + height: 18, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(18), + ), + child: Builder( + builder: (context) { + String? image; + if (ref.watch(prefsChangeNotifierProvider.select( + (value) => value.exchangeRateType)) == + ExchangeRateType.estimated) { + image = ref + .watch(estimatedRateExchangeFormProvider + .select((value) => value.to)) + ?.image; + } else { + image = _fetchIconUrlFromTickerForFixedRateFlow( + ref.watch(fixedRateExchangeFormProvider + .select((value) => value.market?.to))); + } + if (image != null && image.isNotEmpty) { + return Center( + child: SvgPicture.network( + image, + height: 18, + placeholderBuilder: (_) => Container( + width: 18, + height: 18, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + borderRadius: BorderRadius.circular(18), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular( + 18, + ), + child: const LoadingIndicator(), + ), + ), + ), + ); + } else { + return Container( + width: 18, + height: 18, + decoration: BoxDecoration( + // color: Theme.of(context).extension()!.accentColorDark + borderRadius: BorderRadius.circular(18), + ), + child: SvgPicture.asset( + Assets.svg.circleQuestion, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + ), + ); + } + }, + ), + ), + const SizedBox( + width: 6, + ), + Text( + ref.watch(prefsChangeNotifierProvider.select( + (value) => value.exchangeRateType)) == + ExchangeRateType.estimated + ? ref.watch(estimatedRateExchangeFormProvider + .select((value) => + value.to?.ticker.toUpperCase())) ?? + "-" + : ref.watch(fixedRateExchangeFormProvider.select( + (value) => + value.market?.to.toUpperCase())) ?? + "-", + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + const SizedBox( + width: 6, + ), + SvgPicture.asset( + Assets.svg.chevronDown, + width: 5, + height: 2.5, + color: Theme.of(context) + .extension()! + .textDark, + ), + ], + ), + ), + ), + ), + ), + ), + ), + const SizedBox( + height: 12, + ), + RateTypeToggle( + onChanged: onRateTypeChanged, + ), + const SizedBox( + height: 8, + ), + ExchangeProviderOptions( + fromAmount: null, + toAmount: null, + fixedRate: ref.watch(prefsChangeNotifierProvider + .select((value) => value.exchangeRateType)) == + ExchangeRateType.fixed, + reversed: _reversed, + ), + const SizedBox( + height: 12, + ), + PrimaryButton( + enabled: ((ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) + ? ref.watch(estimatedRateExchangeFormProvider + .select((value) => value.canExchange)) + : ref.watch(fixedRateExchangeFormProvider + .select((value) => value.canExchange))), + onPressed: ((ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) + ? ref.watch(estimatedRateExchangeFormProvider + .select((value) => value.canExchange)) + : ref.watch(fixedRateExchangeFormProvider + .select((value) => value.canExchange))) + ? onExchangePressed + : null, + label: "Exchange", + ) + ], + ); + } +} diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart index e9319071d8..0b0ff9eda0 100644 --- a/lib/pages/exchange_view/exchange_view.dart +++ b/lib/pages/exchange_view/exchange_view.dart @@ -1,39 +1,22 @@ import 'dart:async'; -import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; -import 'package:stackwallet/models/exchange/response_objects/pair.dart'; -import 'package:stackwallet/notifications/show_flush_bar.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_1_view.dart'; +import 'package:stackwallet/pages/exchange_view/exchange_form.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; -import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; -import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; -import 'package:stackwallet/providers/exchange/change_now_provider.dart'; import 'package:stackwallet/providers/exchange/changenow_initial_load_status.dart'; import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart'; -import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart'; -import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/custom_loading_overlay.dart'; -import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/trade_card.dart'; import 'package:tuple/tuple.dart'; @@ -48,317 +31,20 @@ class ExchangeView extends ConsumerStatefulWidget { } class _ExchangeViewState extends ConsumerState { - late final TextEditingController _sendController; - late final TextEditingController _receiveController; - - bool _swapLock = false; - - Future _swap() async { - _swapLock = true; - _sendFocusNode.unfocus(); - _receiveFocusNode.unfocus(); - - unawaited( - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => WillPopScope( - onWillPop: () async => false, - child: Container( - color: Theme.of(context) - .extension()! - .overlay - .withOpacity(0.6), - child: const CustomLoadingOverlay( - message: "Updating exchange rate", - eventBus: null, - ), - ), - ), - ), - ); - - if (ref.watch(prefsChangeNotifierProvider - .select((pref) => pref.exchangeRateType)) == - ExchangeRateType.estimated) { - await ref.read(estimatedRateExchangeFormProvider).swap(); - } else { - final from = ref.read(fixedRateExchangeFormProvider).market?.from; - final to = ref.read(fixedRateExchangeFormProvider).market?.to; - - if (to != null && from != null) { - final markets = ref - .read(fixedRateMarketPairsStateProvider.state) - .state - .where((e) => e.from == to && e.to == from); - - if (markets.isNotEmpty) { - await ref.read(fixedRateExchangeFormProvider).swap(markets.first); - } - } - } - if (mounted) { - Navigator.of(context).pop(); - } - _swapLock = false; - } - - Future _showFloatingRateSelectionSheet({ - required List currencies, - required String excludedTicker, - required String fromTicker, - required void Function(Currency) onSelected, - }) async { - _sendFocusNode.unfocus(); - _receiveFocusNode.unfocus(); - - List availablePairs = []; - if (fromTicker.isEmpty || - fromTicker == "-" || - excludedTicker.isEmpty || - excludedTicker == "-") { - availablePairs = - ref.read(availableFloatingRatePairsStateProvider.state).state; - } else if (excludedTicker == fromTicker) { - availablePairs = ref - .read(availableFloatingRatePairsStateProvider.state) - .state - .where((e) => e.from == excludedTicker) - .toList(growable: false); - } else { - availablePairs = ref - .read(availableFloatingRatePairsStateProvider.state) - .state - .where((e) => e.to == excludedTicker) - .toList(growable: false); - } - - final List tickers = currencies.where((e) { - if (excludedTicker == fromTicker) { - return e.ticker != excludedTicker && - availablePairs.where((e2) => e2.to == e.ticker).isNotEmpty; - } else { - return e.ticker != excludedTicker && - availablePairs.where((e2) => e2.from == e.ticker).isNotEmpty; - } - }).toList(growable: false); - - final result = await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => FloatingRateCurrencySelectionView( - currencies: tickers, - ), - ), - ); - - if (mounted && result is Currency) { - onSelected(result); - } - } - - String? _fetchIconUrlFromTickerForFixedRateFlow(String? ticker) { - if (ticker == null) return null; - - final possibleCurrencies = ref - .read(availableChangeNowCurrenciesStateProvider.state) - .state - .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase()); - - for (final currency in possibleCurrencies) { - if (currency.image.isNotEmpty) { - return currency.image; - } - } - - return null; - } - - Future _showFixedRateSelectionSheet({ - required String excludedTicker, - required String fromTicker, - required void Function(String) onSelected, - }) async { - _sendFocusNode.unfocus(); - _receiveFocusNode.unfocus(); - - List marketsThatPairWithExcludedTicker = []; - - if (excludedTicker == "" || - excludedTicker == "-" || - fromTicker == "" || - fromTicker == "-") { - marketsThatPairWithExcludedTicker = - ref.read(fixedRateMarketPairsStateProvider.state).state; - } else if (excludedTicker == fromTicker) { - marketsThatPairWithExcludedTicker = ref - .read(fixedRateMarketPairsStateProvider.state) - .state - .where((e) => e.from == excludedTicker && e.to != excludedTicker) - .toList(growable: false); - } else { - marketsThatPairWithExcludedTicker = ref - .read(fixedRateMarketPairsStateProvider.state) - .state - .where((e) => e.to == excludedTicker && e.from != excludedTicker) - .toList(growable: false); - } - - final result = await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => FixedRateMarketPairCoinSelectionView( - markets: marketsThatPairWithExcludedTicker, - currencies: - ref.read(availableChangeNowCurrenciesStateProvider.state).state, - isFrom: excludedTicker != fromTicker, - ), - ), - ); - - if (mounted && result is String) { - onSelected(result); - } - } - @override void initState() { - _sendController = TextEditingController(); - _receiveController = TextEditingController(); - - final isEstimated = - ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated; - _sendController.text = isEstimated - ? ref.read(estimatedRateExchangeFormProvider).fromAmountString - : ref.read(fixedRateExchangeFormProvider).fromAmountString; - _receiveController.text = isEstimated - ? "-" //ref.read(estimatedRateExchangeFormProvider).toAmountString - : ref.read(fixedRateExchangeFormProvider).toAmountString; - - _sendFocusNode.addListener(() async { - if (!_sendFocusNode.hasFocus) { - final newFromAmount = Decimal.tryParse(_sendController.text); - if (newFromAmount != null) { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(newFromAmount, true); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(newFromAmount, true); - } - } else { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(Decimal.zero, true); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(Decimal.zero, true); - } - _receiveController.text = - ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated - ? "-" - : ""; - } - } - }); - _receiveFocusNode.addListener(() async { - if (!_receiveFocusNode.hasFocus) { - final newToAmount = Decimal.tryParse(_receiveController.text); - if (newToAmount != null) { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - // await ref - // .read(estimatedRateExchangeFormProvider) - // .setToAmountAndCalculateFromAmount(newToAmount, true); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount(newToAmount, true); - } - } else { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - // await ref - // .read(estimatedRateExchangeFormProvider) - // .setToAmountAndCalculateFromAmount(Decimal.zero, true); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount(Decimal.zero, true); - } - _sendController.text = ""; - } - } - }); - super.initState(); } @override void dispose() { - _receiveController.dispose(); - _sendController.dispose(); super.dispose(); } - String calcSend = ""; - String calcReceive = ""; - - final FocusNode _sendFocusNode = FocusNode(); - final FocusNode _receiveFocusNode = FocusNode(); - @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - final isEstimated = ref.watch(prefsChangeNotifierProvider - .select((pref) => pref.exchangeRateType)) == - ExchangeRateType.estimated; - - ref.listen( - isEstimated - ? estimatedRateExchangeFormProvider - .select((value) => value.toAmountString) - : fixedRateExchangeFormProvider.select( - (value) => value.toAmountString), (previous, String next) { - if (!_receiveFocusNode.hasFocus) { - _receiveController.text = isEstimated && next.isEmpty ? "-" : next; - debugPrint("RECEIVE AMOUNT LISTENER ACTIVATED"); - if (_swapLock) { - _sendController.text = isEstimated - ? ref.read(estimatedRateExchangeFormProvider).fromAmountString - : ref.read(fixedRateExchangeFormProvider).fromAmountString; - } - } - }); - ref.listen( - isEstimated - ? estimatedRateExchangeFormProvider - .select((value) => value.fromAmountString) - : fixedRateExchangeFormProvider.select( - (value) => value.fromAmountString), (previous, String next) { - if (!_sendFocusNode.hasFocus) { - _sendController.text = next; - debugPrint("SEND AMOUNT LISTENER ACTIVATED"); - if (_swapLock) { - _receiveController.text = isEstimated - ? ref - .read(estimatedRateExchangeFormProvider) - .toAmountString - .isEmpty - ? "-" - : ref.read(estimatedRateExchangeFormProvider).toAmountString - : ref.read(fixedRateExchangeFormProvider).toAmountString; - } - } - }); - return SafeArea( child: NestedScrollView( floatHeaderSlivers: true, @@ -366,1079 +52,10 @@ class _ExchangeViewState extends ConsumerState { return [ SliverOverlapAbsorber( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverToBoxAdapter( + sliver: const SliverToBoxAdapter( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox( - height: 20, - ), - Text( - "You will send", - style: STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark3, - ), - ), - const SizedBox( - height: 4, - ), - TextFormField( - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - focusNode: _sendFocusNode, - controller: _sendController, - textAlign: TextAlign.right, - onTap: () { - if (_sendController.text == "-") { - _sendController.text = ""; - } - }, - onChanged: (value) async { - final newFromAmount = Decimal.tryParse(value); - if (newFromAmount != null) { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - newFromAmount, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - newFromAmount, false); - } - } else { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - Decimal.zero, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - Decimal.zero, false); - } - _receiveController.text = isEstimated ? "-" : ""; - } - }, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - inputFormatters: [ - // regex to validate a crypto amount with 8 decimal places - TextInputFormatter.withFunction((oldValue, - newValue) => - RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') - .hasMatch(newValue.text) - ? newValue - : oldValue), - ], - decoration: InputDecoration( - contentPadding: const EdgeInsets.only( - top: 12, - right: 12, - ), - hintText: "0", - hintStyle: STextStyles.fieldLabel(context).copyWith( - fontSize: 14, - ), - prefixIcon: FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: GestureDetector( - onTap: () async { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - await _showFloatingRateSelectionSheet( - currencies: ref - .read( - availableChangeNowCurrenciesStateProvider - .state) - .state, - excludedTicker: ref - .read( - estimatedRateExchangeFormProvider) - .to - ?.ticker ?? - "-", - fromTicker: ref - .read( - estimatedRateExchangeFormProvider) - .from - ?.ticker ?? - "-", - onSelected: (from) => ref - .read( - estimatedRateExchangeFormProvider) - .updateFrom(from, true)); - } else { - final toTicker = ref - .read(fixedRateExchangeFormProvider) - .market - ?.to ?? - ""; - final fromTicker = ref - .read(fixedRateExchangeFormProvider) - .market - ?.from ?? - ""; - await _showFixedRateSelectionSheet( - excludedTicker: toTicker, - fromTicker: fromTicker, - onSelected: (selectedFromTicker) async { - try { - final market = ref - .read( - fixedRateMarketPairsStateProvider - .state) - .state - .firstWhere( - (e) => - e.to == toTicker && - e.from == selectedFromTicker, - ); - - await ref - .read(fixedRateExchangeFormProvider) - .updateMarket(market, true); - } catch (e) { - unawaited(showDialog( - context: context, - builder: (_) => const StackDialog( - title: "Fixed rate market error", - message: - "Could not find the specified fixed rate trade pair", - ), - )); - return; - } - }, - ); - } - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Container( - width: 18, - height: 18, - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(18), - ), - child: Builder( - builder: (context) { - String? image; - if (ref.watch( - prefsChangeNotifierProvider - .select((value) => value - .exchangeRateType)) == - ExchangeRateType.estimated) { - image = ref - .watch( - estimatedRateExchangeFormProvider - .select((value) => - value.from)) - ?.image; - } else { - image = _fetchIconUrlFromTickerForFixedRateFlow( - ref.watch( - fixedRateExchangeFormProvider - .select((value) => - value.market - ?.from))); - } - if (image != null && - image.isNotEmpty) { - return Center( - child: SvgPicture.network( - image, - height: 18, - placeholderBuilder: (_) => - Container( - width: 18, - height: 18, - decoration: BoxDecoration( - color: Theme.of(context) - .extension< - StackColors>()! - .textFieldDefaultBG, - borderRadius: - BorderRadius.circular( - 18, - ), - ), - child: ClipRRect( - borderRadius: - BorderRadius.circular( - 18, - ), - child: - const LoadingIndicator(), - ), - ), - ), - ); - } else { - return Container( - width: 18, - height: 18, - decoration: BoxDecoration( - // color: Theme.of(context).extension()!.accentColorDark - borderRadius: - BorderRadius.circular(18), - ), - child: SvgPicture.asset( - Assets.svg.circleQuestion, - width: 18, - height: 18, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - ), - ); - } - }, - ), - ), - const SizedBox( - width: 6, - ), - Text( - isEstimated - ? ref.watch( - estimatedRateExchangeFormProvider - .select((value) => value - .from?.ticker - .toUpperCase())) ?? - "-" - : ref.watch( - fixedRateExchangeFormProvider - .select((value) => value - .market?.from - .toUpperCase())) ?? - "-", - style: STextStyles.smallMed14(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - const SizedBox( - width: 6, - ), - SvgPicture.asset( - Assets.svg.chevronDown, - width: 5, - height: 2.5, - color: Theme.of(context) - .extension()! - .textDark, - ), - ], - ), - ), - ), - ), - ), - ), - ), - const SizedBox( - height: 4, - ), - Stack( - children: [ - Positioned.fill( - child: Align( - alignment: Alignment.bottomLeft, - child: Text( - "You will receive", - style: - STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark3, - ), - ), - ), - ), - Center( - child: Column( - children: [ - const SizedBox( - height: 6, - ), - GestureDetector( - onTap: () async { - await _swap(); - }, - child: Padding( - padding: const EdgeInsets.all(4), - child: SvgPicture.asset( - Assets.svg.swap, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - ), - const SizedBox( - height: 6, - ), - ], - ), - ), - Positioned.fill( - child: Align( - alignment: Alignment.topRight, - child: Text( - isEstimated - ? ref.watch( - estimatedRateExchangeFormProvider - .select((value) => - value.minimumSendWarning)) - : ref.watch(fixedRateExchangeFormProvider - .select((value) => - value.sendAmountWarning)), - style: STextStyles.errorSmall(context), - ), - ), - ), - ], - ), - const SizedBox( - height: 4, - ), - TextFormField( - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - focusNode: _receiveFocusNode, - controller: _receiveController, - readOnly: ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated, - onTap: () { - if (!isEstimated && _receiveController.text == "-") { - _receiveController.text = ""; - } - }, - onChanged: (value) async { - final newToAmount = Decimal.tryParse(value); - if (newToAmount != null) { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - // await ref - // .read(estimatedRateExchangeFormProvider) - // .setToAmountAndCalculateFromAmount( - // newToAmount, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount( - newToAmount, false); - } - } else { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - // await ref - // .read(estimatedRateExchangeFormProvider) - // .setToAmountAndCalculateFromAmount( - // Decimal.zero, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount( - Decimal.zero, false); - } - _sendController.text = ""; - } - }, - textAlign: TextAlign.right, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - inputFormatters: [ - // regex to validate a crypto amount with 8 decimal places - TextInputFormatter.withFunction((oldValue, - newValue) => - RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') - .hasMatch(newValue.text) - ? newValue - : oldValue), - ], - decoration: InputDecoration( - contentPadding: const EdgeInsets.only( - top: 12, - right: 12, - ), - hintText: "0", - hintStyle: STextStyles.fieldLabel(context).copyWith( - fontSize: 14, - ), - prefixIcon: FittedBox( - fit: BoxFit.scaleDown, - child: GestureDetector( - onTap: () async { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - await _showFloatingRateSelectionSheet( - currencies: ref - .read( - availableChangeNowCurrenciesStateProvider - .state) - .state, - excludedTicker: ref - .read( - estimatedRateExchangeFormProvider) - .from - ?.ticker ?? - "", - fromTicker: ref - .read( - estimatedRateExchangeFormProvider) - .from - ?.ticker ?? - "", - onSelected: (to) => ref - .read( - estimatedRateExchangeFormProvider) - .updateTo(to, true)); - } else { - final fromTicker = ref - .read(fixedRateExchangeFormProvider) - .market - ?.from ?? - ""; - await _showFixedRateSelectionSheet( - excludedTicker: fromTicker, - fromTicker: fromTicker, - onSelected: (selectedToTicker) async { - try { - final market = ref - .read( - fixedRateMarketPairsStateProvider - .state) - .state - .firstWhere( - (e) => - e.to == selectedToTicker && - e.from == fromTicker, - ); - - await ref - .read(fixedRateExchangeFormProvider) - .updateMarket(market, true); - } catch (e) { - unawaited(showDialog( - context: context, - builder: (_) => const StackDialog( - title: "Fixed rate market error", - message: - "Could not find the specified fixed rate trade pair", - ), - )); - return; - } - }, - ); - } - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Container( - width: 18, - height: 18, - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(18), - ), - child: Builder( - builder: (context) { - String? image; - if (ref.watch( - prefsChangeNotifierProvider - .select((value) => value - .exchangeRateType)) == - ExchangeRateType.estimated) { - image = ref - .watch( - estimatedRateExchangeFormProvider - .select((value) => - value.to)) - ?.image; - } else { - image = _fetchIconUrlFromTickerForFixedRateFlow( - ref.watch( - fixedRateExchangeFormProvider - .select((value) => - value.market - ?.to))); - } - if (image != null && - image.isNotEmpty) { - return Center( - child: SvgPicture.network( - image, - height: 18, - placeholderBuilder: (_) => - Container( - width: 18, - height: 18, - decoration: BoxDecoration( - color: Theme.of(context) - .extension< - StackColors>()! - .textFieldDefaultBG, - borderRadius: - BorderRadius.circular( - 18), - ), - child: ClipRRect( - borderRadius: - BorderRadius.circular( - 18, - ), - child: - const LoadingIndicator(), - ), - ), - ), - ); - } else { - return Container( - width: 18, - height: 18, - decoration: BoxDecoration( - // color: Theme.of(context).extension()!.accentColorDark - borderRadius: - BorderRadius.circular(18), - ), - child: SvgPicture.asset( - Assets.svg.circleQuestion, - width: 18, - height: 18, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - ), - ); - } - }, - ), - ), - const SizedBox( - width: 6, - ), - Text( - isEstimated - ? ref.watch( - estimatedRateExchangeFormProvider - .select((value) => value - .to?.ticker - .toUpperCase())) ?? - "-" - : ref.watch( - fixedRateExchangeFormProvider - .select((value) => value - .market?.to - .toUpperCase())) ?? - "-", - style: STextStyles.smallMed14(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - const SizedBox( - width: 6, - ), - SvgPicture.asset( - Assets.svg.chevronDown, - width: 5, - height: 2.5, - color: Theme.of(context) - .extension()! - .textDark, - ), - ], - ), - ), - ), - ), - ), - ), - ), - // if (ref - // .watch(exchangeFormSateProvider - // .select((value) => value.minimumReceiveWarning)) - // .isNotEmpty) - // SizedBox( - // height: 4, - // ), - // - // if (ref - // .watch(exchangeFormSateProvider - // .select((value) => value.minimumReceiveWarning)) - // .isNotEmpty) - // Row( - // children: [ - // Spacer(), - // Text( - // ref.watch(exchangeFormSateProvider.select( - // (value) => value.minimumReceiveWarning)), - // style: STextStyles.errorSmall(context), - // ), - // ], - // ), - - const SizedBox( - height: 12, - ), - RateInfo( - onChanged: (rateType) async { - _receiveFocusNode.unfocus(); - _sendFocusNode.unfocus(); - switch (rateType) { - case ExchangeRateType.estimated: - final market = ref - .read(fixedRateExchangeFormProvider) - .market; - final fromTicker = market?.from ?? ""; - final toTicker = market?.to ?? ""; - if (!(fromTicker.isEmpty || - toTicker.isEmpty || - toTicker == "-" || - fromTicker == "-")) { - final available = ref - .read( - availableFloatingRatePairsStateProvider - .state) - .state - .where((e) => - e.to == toTicker && - e.from == fromTicker); - if (available.isNotEmpty) { - final availableCurrencies = ref - .read( - availableChangeNowCurrenciesStateProvider - .state) - .state - .where((e) => - e.ticker == fromTicker || - e.ticker == toTicker); - if (availableCurrencies.length > 1) { - final from = availableCurrencies.firstWhere( - (e) => e.ticker == fromTicker); - final to = availableCurrencies.firstWhere( - (e) => e.ticker == toTicker); - - final newFromAmount = - Decimal.tryParse(_sendController.text); - if (newFromAmount != null) { - await ref - .read( - estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - newFromAmount, false); - } else { - await ref - .read( - estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - Decimal.zero, false); - - _receiveController.text = ""; - } - - await ref - .read(estimatedRateExchangeFormProvider) - .updateTo(to, false); - await ref - .read(estimatedRateExchangeFormProvider) - .updateFrom(from, true); - return; - } - } - } - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Estimated rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last estimated rate pair.", - context: context, - )); - break; - case ExchangeRateType.fixed: - final fromTicker = ref - .read(estimatedRateExchangeFormProvider) - .from - ?.ticker ?? - ""; - final toTicker = ref - .read(estimatedRateExchangeFormProvider) - .to - ?.ticker ?? - ""; - if (!(fromTicker.isEmpty || - toTicker.isEmpty || - toTicker == "-" || - fromTicker == "-")) { - FixedRateMarket? market; - try { - market = ref - .read(fixedRateMarketPairsStateProvider - .state) - .state - .firstWhere((e) => - e.from == fromTicker && - e.to == toTicker); - } catch (_) { - market = null; - } - - final newFromAmount = - Decimal.tryParse(_sendController.text); - if (newFromAmount != null) { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - newFromAmount, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - Decimal.zero, false); - - _receiveController.text = ""; - } - - await ref - .read(fixedRateExchangeFormProvider) - .updateMarket(market, false); - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - Decimal.tryParse(_sendController.text) ?? - Decimal.zero, - true, - ); - return; - } - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Fixed rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last fixed rate pair.", - context: context, - )); - break; - } - }, - ), - const SizedBox( - height: 12, - ), - TextButton( - style: ((ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) - ? ref.watch(estimatedRateExchangeFormProvider - .select((value) => value.canExchange)) - : ref.watch(fixedRateExchangeFormProvider - .select((value) => value.canExchange))) - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonColor(context), - onPressed: ((ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) - ? ref.watch(estimatedRateExchangeFormProvider - .select((value) => value.canExchange)) - : ref.watch(fixedRateExchangeFormProvider - .select((value) => value.canExchange))) - ? () async { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - final fromTicker = ref - .read( - estimatedRateExchangeFormProvider) - .from - ?.ticker ?? - ""; - final toTicker = ref - .read( - estimatedRateExchangeFormProvider) - .to - ?.ticker ?? - ""; - - bool isAvailable = false; - final availableFloatingPairs = ref - .read( - availableFloatingRatePairsStateProvider - .state) - .state; - for (final pair in availableFloatingPairs) { - if (pair.from == fromTicker && - pair.to == toTicker) { - isAvailable = true; - break; - } - } - - if (!isAvailable) { - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: - "Selected trade pair unavailable", - message: - "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", - ), - )); - return; - } - - final sendAmount = Decimal.parse(ref - .read(estimatedRateExchangeFormProvider) - .fromAmountString); - - final rateType = ref - .read(prefsChangeNotifierProvider) - .exchangeRateType; - - final response = await ref - .read(changeNowProvider) - .getEstimate( - fromTicker, - toTicker, - sendAmount, - false, - false, - ); - - if (response.value == null) { - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: - "Failed to update trade estimate", - message: response.exception?.toString(), - ), - )); - return; - } - - String rate = - "1 ${fromTicker.toUpperCase()} ~${(response.value!.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; - - final model = IncompleteExchangeModel( - sendTicker: fromTicker.toUpperCase(), - receiveTicker: toTicker.toUpperCase(), - rateInfo: rate, - sendAmount: sendAmount, - receiveAmount: - response.value!.estimatedAmount, - rateType: rateType, - rateId: response.value!.rateId, - ); - - if (mounted) { - ref - .read( - exchangeSendFromWalletIdStateProvider - .state) - .state = null; - unawaited(Navigator.of(context).pushNamed( - Step1View.routeName, - arguments: model, - )); - } - } else { - final fromTicker = ref - .read(fixedRateExchangeFormProvider) - .market - ?.from ?? - ""; - final toTicker = ref - .read(fixedRateExchangeFormProvider) - .market - ?.to ?? - ""; - - final sendAmount = Decimal.parse(ref - .read(fixedRateExchangeFormProvider) - .fromAmountString); - - final rateType = ref - .read(prefsChangeNotifierProvider) - .exchangeRateType; - - final response = await ref - .read(changeNowProvider) - .getEstimate( - fromTicker, - toTicker, - sendAmount, - true, - false, - ); - - bool? shouldCancel; - - if (response.value == null) { - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: - "Failed to update trade estimate", - message: response.exception?.toString(), - ), - )); - return; - } else if (response.value!.warningMessage != - null && - response - .value!.warningMessage!.isNotEmpty) { - shouldCancel = await showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: - "Failed to update trade estimate", - message: - "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Cancel", - style: STextStyles.itemSubtitle12( - context), - ), - onPressed: () { - // notify return to cancel - Navigator.of(context).pop(true); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor( - context), - child: Text( - "Attempt", - style: STextStyles.button(context), - ), - onPressed: () { - // continue and try to attempt trade - Navigator.of(context).pop(false); - }, - ), - ), - ); - } - - if (shouldCancel is bool && shouldCancel) { - return; - } - - String rate = - "1 ${fromTicker.toUpperCase()} ~${ref.read(fixedRateExchangeFormProvider).rate!.toStringAsFixed(8)} ${toTicker.toUpperCase()}"; - - final model = IncompleteExchangeModel( - sendTicker: fromTicker, - receiveTicker: toTicker, - rateInfo: rate, - sendAmount: sendAmount, - receiveAmount: - response.value!.estimatedAmount, - rateType: rateType, - rateId: response.value!.rateId, - ); - - if (mounted) { - ref - .read( - exchangeSendFromWalletIdStateProvider - .state) - .state = null; - unawaited(Navigator.of(context).pushNamed( - Step1View.routeName, - arguments: model, - )); - } - } - } - : null, - child: Text( - "Exchange", - style: STextStyles.button(context), - ), - ), - const SizedBox( - height: 20, - ), - // Text( - // "Trades", - // style: STextStyles.itemSubtitle(context).copyWith( - // color: Theme.of(context).extension()!.textDark3, - // ), - // ), - // SizedBox( - // height: 12, - // ), - ], - ), + padding: EdgeInsets.symmetric(horizontal: 16), + child: ExchangeForm(), ), ), ) @@ -1457,7 +74,8 @@ class _ExchangeViewState extends ConsumerState { slivers: [ SliverOverlapInjector( handle: NestedScrollView.sliverOverlapAbsorberHandleFor( - buildContext), + buildContext, + ), ), SliverToBoxAdapter( child: Padding( diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart new file mode 100644 index 0000000000..0ebedcebdf --- /dev/null +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -0,0 +1,203 @@ +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/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +final currentExchangeNameStateProvider = + StateProvider((ref) => ChangeNowExchange.exchangeName); + +final exchangeProvider = Provider((ref) { + switch (ref.watch(currentExchangeNameStateProvider.state).state) { + case ChangeNowExchange.exchangeName: + return ChangeNowExchange(); + case SimpleSwapExchange.exchangeName: + return SimpleSwapExchange(); + default: + const errorMessage = + "Attempted to access exchangeProvider with invalid exchange name!"; + Logging.instance.log(errorMessage, level: LogLevel.Fatal); + throw Exception(errorMessage); + } +}); + +class ExchangeProviderOptions extends ConsumerWidget { + const ExchangeProviderOptions({ + Key? key, + required this.fromAmount, + required this.toAmount, + required this.fixedRate, + required this.reversed, + }) : super(key: key); + + final Decimal? fromAmount; + final Decimal? toAmount; + final bool fixedRate; + final bool reversed; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return RoundedWhiteContainer( + child: Column( + children: [ + GestureDetector( + onTap: () { + if (ref.read(currentExchangeNameStateProvider.state).state != + ChangeNowExchange.exchangeName) { + ref.read(currentExchangeNameStateProvider.state).state = + ChangeNowExchange.exchangeName; + } + }, + child: Container( + color: Colors.transparent, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: ChangeNowExchange.exchangeName, + groupValue: ref + .watch(currentExchangeNameStateProvider.state) + .state, + onChanged: (value) { + if (value is String) { + ref + .read(currentExchangeNameStateProvider.state) + .state = value; + } + }, + ), + ), + const SizedBox( + width: 14, + ), + SvgPicture.asset( + Assets.exchange.changeNow, + width: 24, + height: 24, + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + ChangeNowExchange.exchangeName, + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark2, + ), + ), + Text( + ChangeNowExchange.exchangeName, + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + ], + ), + ), + ), + const SizedBox( + height: 16, + ), + GestureDetector( + onTap: () { + if (ref.read(currentExchangeNameStateProvider.state).state != + SimpleSwapExchange.exchangeName) { + ref.read(currentExchangeNameStateProvider.state).state = + SimpleSwapExchange.exchangeName; + } + }, + child: Container( + color: Colors.transparent, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: SimpleSwapExchange.exchangeName, + groupValue: ref + .watch(currentExchangeNameStateProvider.state) + .state, + onChanged: (value) { + if (value is String) { + ref + .read(currentExchangeNameStateProvider.state) + .state = value; + } + }, + ), + ), + const SizedBox( + width: 14, + ), + SvgPicture.asset( + Assets.exchange.simpleSwap, + width: 24, + height: 24, + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + SimpleSwapExchange.exchangeName, + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark2, + ), + ), + Text( + SimpleSwapExchange.exchangeName, + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart new file mode 100644 index 0000000000..31460c75f2 --- /dev/null +++ b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class RateTypeToggle extends ConsumerWidget { + const RateTypeToggle({ + Key? key, + this.onChanged, + }) : super(key: key); + + final void Function(ExchangeRateType)? onChanged; + + @override + Widget build(BuildContext context, WidgetRef ref) { + debugPrint("BUILD: $runtimeType"); + final estimated = ref.watch(prefsChangeNotifierProvider + .select((value) => value.exchangeRateType)) == + ExchangeRateType.estimated; + + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + if (!estimated) { + ref.read(prefsChangeNotifierProvider).exchangeRateType = + ExchangeRateType.estimated; + onChanged?.call(ExchangeRateType.estimated); + } + }, + child: RoundedContainer( + color: estimated + ? Theme.of(context) + .extension()! + .textFieldDefaultBG + : Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.svg.lock, + width: 12, + height: 14, + color: estimated + ? Theme.of(context).extension()!.textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), + const SizedBox( + width: 5, + ), + Text( + "Estimate rate", + style: STextStyles.smallMed12(context).copyWith( + color: estimated + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + ), + ), + Expanded( + child: GestureDetector( + onTap: () { + if (estimated) { + ref.read(prefsChangeNotifierProvider).exchangeRateType = + ExchangeRateType.fixed; + onChanged?.call(ExchangeRateType.fixed); + } + }, + child: RoundedContainer( + color: !estimated + ? Theme.of(context) + .extension()! + .textFieldDefaultBG + : Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.svg.lock, + width: 12, + height: 14, + color: !estimated + ? Theme.of(context).extension()!.textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), + const SizedBox( + width: 5, + ), + Text( + "Fixed rate", + style: STextStyles.smallMed12(context).copyWith( + color: !estimated + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart index dc7c4f2dd8..cfd967bd04 100644 --- a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart +++ b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart @@ -60,9 +60,6 @@ class _WalletInitiatedExchangeViewState late final TextEditingController _sendController; late final TextEditingController _receiveController; - String calcSend = ""; - String calcReceive = ""; - final FocusNode _sendFocusNode = FocusNode(); final FocusNode _receiveFocusNode = FocusNode(); From 1bd6166f2c9ed485bad411c7fd4d87fe18c0390a Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 3 Oct 2022 13:18:57 -0600 Subject: [PATCH 16/75] use exchange form in wallet initiated exchange flow --- lib/pages/exchange_view/exchange_form.dart | 207 ++- .../wallet_initiated_exchange_view.dart | 1479 +---------------- 2 files changed, 163 insertions(+), 1523 deletions(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 644863e1b2..b1bb739b6b 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -14,6 +14,7 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_1_view.dart'; +import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_options.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/rate_type_toggle.dart'; @@ -26,6 +27,7 @@ import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -33,15 +35,27 @@ import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:tuple/tuple.dart'; class ExchangeForm extends ConsumerStatefulWidget { - const ExchangeForm({Key? key}) : super(key: key); + const ExchangeForm({ + Key? key, + this.walletId, + this.coin, + }) : super(key: key); + + final String? walletId; + final Coin? coin; @override ConsumerState createState() => _ExchangeFormState(); } class _ExchangeFormState extends ConsumerState { + late final String? walletId; + late final Coin? coin; + late final bool walletInitiated; + late final TextEditingController _sendController; late final TextEditingController _receiveController; final FocusNode _sendFocusNode = FocusNode(); @@ -82,13 +96,21 @@ class _ExchangeFormState extends ConsumerState { void selectSendCurrency() async { if (ref.read(prefsChangeNotifierProvider).exchangeRateType == ExchangeRateType.estimated) { + final fromTicker = + ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? "-"; + + if (walletInitiated && + fromTicker.toLowerCase() == coin!.ticker.toLowerCase()) { + // do not allow changing away from wallet coin + return; + } + await _showFloatingRateSelectionSheet( currencies: ref.read(availableChangeNowCurrenciesStateProvider.state).state, excludedTicker: ref.read(estimatedRateExchangeFormProvider).to?.ticker ?? "-", - fromTicker: - ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? "-", + fromTicker: fromTicker, onSelected: (from) => ref .read(estimatedRateExchangeFormProvider) .updateFrom(from, true)); @@ -96,6 +118,13 @@ class _ExchangeFormState extends ConsumerState { final toTicker = ref.read(fixedRateExchangeFormProvider).market?.to ?? ""; final fromTicker = ref.read(fixedRateExchangeFormProvider).market?.from ?? ""; + + if (walletInitiated && + fromTicker.toLowerCase() == coin!.ticker.toLowerCase()) { + // do not allow changing away from wallet coin + return; + } + await _showFixedRateSelectionSheet( excludedTicker: toTicker, fromTicker: fromTicker, @@ -129,6 +158,15 @@ class _ExchangeFormState extends ConsumerState { void selectReceiveCurrency() async { if (ref.read(prefsChangeNotifierProvider).exchangeRateType == ExchangeRateType.estimated) { + final toTicker = + ref.read(estimatedRateExchangeFormProvider).to?.ticker ?? ""; + + if (walletInitiated && + toTicker.toLowerCase() == coin!.ticker.toLowerCase()) { + // do not allow changing away from wallet coin + return; + } + await _showFloatingRateSelectionSheet( currencies: ref.read(availableChangeNowCurrenciesStateProvider.state).state, @@ -141,6 +179,14 @@ class _ExchangeFormState extends ConsumerState { } else { final fromTicker = ref.read(fixedRateExchangeFormProvider).market?.from ?? ""; + final toTicker = ref.read(fixedRateExchangeFormProvider).market?.to ?? ""; + + if (walletInitiated && + toTicker.toLowerCase() == coin!.ticker.toLowerCase()) { + // do not allow changing away from wallet coin + return; + } + await _showFixedRateSelectionSheet( excludedTicker: fromTicker, fromTicker: fromTicker, @@ -173,9 +219,11 @@ class _ExchangeFormState extends ConsumerState { void receiveFieldOnChanged(String value) async { final newToAmount = Decimal.tryParse(value); + final isEstimated = + ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated; if (newToAmount != null) { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { + if (isEstimated) { // await ref // .read(estimatedRateExchangeFormProvider) // .setToAmountAndCalculateFromAmount( @@ -186,8 +234,7 @@ class _ExchangeFormState extends ConsumerState { .setToAmountAndCalculateFromAmount(newToAmount, false); } } else { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { + if (isEstimated) { // await ref // .read(estimatedRateExchangeFormProvider) // .setToAmountAndCalculateFromAmount( @@ -547,11 +594,20 @@ class _ExchangeFormState extends ConsumerState { ); if (mounted) { - ref.read(exchangeSendFromWalletIdStateProvider.state).state = null; - unawaited(Navigator.of(context).pushNamed( - Step1View.routeName, - arguments: model, - )); + if (walletInitiated) { + ref.read(exchangeSendFromWalletIdStateProvider.state).state = + Tuple2(walletId!, coin!); + unawaited(Navigator.of(context).pushNamed( + Step2View.routeName, + arguments: model, + )); + } else { + ref.read(exchangeSendFromWalletIdStateProvider.state).state = null; + unawaited(Navigator.of(context).pushNamed( + Step1View.routeName, + arguments: model, + )); + } } } else { final fromTicker = @@ -640,13 +696,53 @@ class _ExchangeFormState extends ConsumerState { ); if (mounted) { - ref.read(exchangeSendFromWalletIdStateProvider.state).state = null; - unawaited(Navigator.of(context).pushNamed( - Step1View.routeName, - arguments: model, - )); + if (walletInitiated) { + ref.read(exchangeSendFromWalletIdStateProvider.state).state = + Tuple2(walletId!, coin!); + unawaited(Navigator.of(context).pushNamed( + Step2View.routeName, + arguments: model, + )); + } else { + ref.read(exchangeSendFromWalletIdStateProvider.state).state = null; + unawaited(Navigator.of(context).pushNamed( + Step1View.routeName, + arguments: model, + )); + } + } + } + } + + bool isWalletCoin(Coin? coin, bool isSend) { + if (coin == null) { + return false; + } + + String? ticker; + + if (ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated) { + if (isSend) { + ticker = ref.watch(estimatedRateExchangeFormProvider + .select((value) => value.from?.ticker)); + } else { + ticker = ref.watch(estimatedRateExchangeFormProvider + .select((value) => value.to?.ticker)); + } + } else { + if (isSend) { + ticker = ref.read(fixedRateExchangeFormProvider).market?.from; + } else { + ticker = ref.read(fixedRateExchangeFormProvider).market?.to; } } + + if (ticker == null) { + return false; + } + + return coin.ticker.toUpperCase() == ticker.toUpperCase(); } @override @@ -654,15 +750,26 @@ class _ExchangeFormState extends ConsumerState { _sendController = TextEditingController(); _receiveController = TextEditingController(); - final isEstimated = - ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated; - _sendController.text = isEstimated - ? ref.read(estimatedRateExchangeFormProvider).fromAmountString - : ref.read(fixedRateExchangeFormProvider).fromAmountString; - _receiveController.text = isEstimated - ? "-" //ref.read(estimatedRateExchangeFormProvider).toAmountString - : ref.read(fixedRateExchangeFormProvider).toAmountString; + walletId = widget.walletId; + coin = widget.coin; + walletInitiated = walletId != null && coin != null; + + if (walletInitiated) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + ref.read(estimatedRateExchangeFormProvider).clearAmounts(true); + // ref.read(fixedRateExchangeFormProvider); + }); + } else { + final isEstimated = + ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated; + _sendController.text = isEstimated + ? ref.read(estimatedRateExchangeFormProvider).fromAmountString + : ref.read(fixedRateExchangeFormProvider).fromAmountString; + _receiveController.text = isEstimated + ? "-" //ref.read(estimatedRateExchangeFormProvider).toAmountString + : ref.read(fixedRateExchangeFormProvider).toAmountString; + } _sendFocusNode.addListener(() async { if (!_sendFocusNode.hasFocus) { @@ -930,17 +1037,19 @@ class _ExchangeFormState extends ConsumerState { .textDark, ), ), - const SizedBox( - width: 6, - ), - SvgPicture.asset( - Assets.svg.chevronDown, - width: 5, - height: 2.5, - color: Theme.of(context) - .extension()! - .textDark, - ), + if (!isWalletCoin(coin, true)) + const SizedBox( + width: 6, + ), + if (!isWalletCoin(coin, true)) + SvgPicture.asset( + Assets.svg.chevronDown, + width: 5, + height: 2.5, + color: Theme.of(context) + .extension()! + .textDark, + ), ], ), ), @@ -1148,17 +1257,19 @@ class _ExchangeFormState extends ConsumerState { .textDark, ), ), - const SizedBox( - width: 6, - ), - SvgPicture.asset( - Assets.svg.chevronDown, - width: 5, - height: 2.5, - color: Theme.of(context) - .extension()! - .textDark, - ), + if (!isWalletCoin(coin, false)) + const SizedBox( + width: 6, + ), + if (!isWalletCoin(coin, false)) + SvgPicture.asset( + Assets.svg.chevronDown, + width: 5, + height: 2.5, + color: Theme.of(context) + .extension()! + .textDark, + ), ], ), ), diff --git a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart index cfd967bd04..c816d4fe8e 100644 --- a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart +++ b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart @@ -1,39 +1,13 @@ import 'dart:async'; -import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; -import 'package:stackwallet/models/exchange/response_objects/pair.dart'; -import 'package:stackwallet/notifications/show_flush_bar.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_view.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; +import 'package:stackwallet/pages/exchange_view/exchange_form.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; -import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; -import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; -import 'package:stackwallet/providers/exchange/change_now_provider.dart'; -import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart'; -import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; -import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart'; -import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; -import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/custom_loading_overlay.dart'; -import 'package:stackwallet/widgets/loading_indicator.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:tuple/tuple.dart'; class WalletInitiatedExchangeView extends ConsumerStatefulWidget { const WalletInitiatedExchangeView({ @@ -57,260 +31,15 @@ class _WalletInitiatedExchangeViewState late final String walletId; late final Coin coin; - late final TextEditingController _sendController; - late final TextEditingController _receiveController; - - final FocusNode _sendFocusNode = FocusNode(); - final FocusNode _receiveFocusNode = FocusNode(); - - bool _swapLock = false; - - Future _swap() async { - _swapLock = true; - _sendFocusNode.unfocus(); - _receiveFocusNode.unfocus(); - - unawaited( - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => WillPopScope( - onWillPop: () async => false, - child: Container( - color: Theme.of(context) - .extension()! - .overlay - .withOpacity(0.6), - child: const CustomLoadingOverlay( - message: "Updating exchange rate", - eventBus: null, - ), - ), - ), - ), - ); - - if (ref.watch(prefsChangeNotifierProvider - .select((pref) => pref.exchangeRateType)) == - ExchangeRateType.estimated) { - await ref.read(estimatedRateExchangeFormProvider).swap(); - } else { - final from = ref.read(fixedRateExchangeFormProvider).market?.from; - final to = ref.read(fixedRateExchangeFormProvider).market?.to; - - if (to != null && from != null) { - final markets = ref - .read(fixedRateMarketPairsStateProvider.state) - .state - .where((e) => e.from == to && e.to == from); - - if (markets.isNotEmpty) { - await ref.read(fixedRateExchangeFormProvider).swap(markets.first); - } - } - } - if (mounted) { - Navigator.of(context).pop(); - } - _swapLock = false; - } - - Future _showFloatingRateSelectionSheet({ - required List currencies, - required String excludedTicker, - required String fromTicker, - required void Function(Currency) onSelected, - }) async { - _sendFocusNode.unfocus(); - _receiveFocusNode.unfocus(); - - List availablePairs = []; - if (fromTicker.isEmpty || - fromTicker == "-" || - excludedTicker.isEmpty || - excludedTicker == "-") { - availablePairs = - ref.read(availableFloatingRatePairsStateProvider.state).state; - } else if (excludedTicker == fromTicker) { - availablePairs = ref - .read(availableFloatingRatePairsStateProvider.state) - .state - .where((e) => e.from == excludedTicker) - .toList(growable: false); - } else { - availablePairs = ref - .read(availableFloatingRatePairsStateProvider.state) - .state - .where((e) => e.to == excludedTicker) - .toList(growable: false); - } - - final List tickers = currencies.where((e) { - if (excludedTicker == fromTicker) { - return e.ticker != excludedTicker && - availablePairs.where((e2) => e2.to == e.ticker).isNotEmpty; - } else { - return e.ticker != excludedTicker && - availablePairs.where((e2) => e2.from == e.ticker).isNotEmpty; - } - }).toList(growable: false); - - final result = await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => FloatingRateCurrencySelectionView( - currencies: tickers, - ), - ), - ); - - if (mounted && result is Currency) { - onSelected(result); - } - } - - String? _fetchIconUrlFromTickerForFixedRateFlow(String? ticker) { - if (ticker == null) return null; - - final possibleCurrencies = ref - .read(availableChangeNowCurrenciesStateProvider.state) - .state - .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase()); - - for (final currency in possibleCurrencies) { - if (currency.image.isNotEmpty) { - return currency.image; - } - } - - return null; - } - - Future _showFixedRateSelectionSheet({ - required String excludedTicker, - required String fromTicker, - required void Function(String) onSelected, - }) async { - _sendFocusNode.unfocus(); - _receiveFocusNode.unfocus(); - - List marketsThatPairWithExcludedTicker = []; - - if (excludedTicker == "" || - excludedTicker == "-" || - fromTicker == "" || - fromTicker == "-") { - marketsThatPairWithExcludedTicker = - ref.read(fixedRateMarketPairsStateProvider.state).state; - } else if (excludedTicker == fromTicker) { - marketsThatPairWithExcludedTicker = ref - .read(fixedRateMarketPairsStateProvider.state) - .state - .where((e) => e.from == excludedTicker && e.to != excludedTicker) - .toList(growable: false); - } else { - marketsThatPairWithExcludedTicker = ref - .read(fixedRateMarketPairsStateProvider.state) - .state - .where((e) => e.to == excludedTicker && e.from != excludedTicker) - .toList(growable: false); - } - - final result = await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => FixedRateMarketPairCoinSelectionView( - markets: marketsThatPairWithExcludedTicker, - currencies: - ref.read(availableChangeNowCurrenciesStateProvider.state).state, - isFrom: excludedTicker != fromTicker, - ), - ), - ); - - if (mounted && result is String) { - onSelected(result); - } - } - @override void initState() { walletId = widget.walletId; coin = widget.coin; - _sendController = TextEditingController(); - _receiveController = TextEditingController(); - - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - ref.read(estimatedRateExchangeFormProvider).clearAmounts(true); - // ref.read(fixedRateExchangeFormProvider); - }); - _sendFocusNode.addListener(() async { - if (!_sendFocusNode.hasFocus) { - final newFromAmount = Decimal.tryParse(_sendController.text); - if (newFromAmount != null) { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(newFromAmount, true); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(newFromAmount, true); - } - } else { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(Decimal.zero, true); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(Decimal.zero, true); - } - _receiveController.text = - ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated - ? "-" - : ""; - } - } - }); - _receiveFocusNode.addListener(() async { - if (!_receiveFocusNode.hasFocus) { - final newToAmount = Decimal.tryParse(_receiveController.text); - if (newToAmount != null) { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - // await ref - // .read(estimatedRateExchangeFormProvider) - // .setToAmountAndCalculateFromAmount(newToAmount, true); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount(newToAmount, true); - } - } else { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - // await ref - // .read(estimatedRateExchangeFormProvider) - // .setToAmountAndCalculateFromAmount(Decimal.zero, true); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount(Decimal.zero, true); - } - _sendController.text = ""; - } - } - }); super.initState(); } @override void dispose() { - _receiveController.dispose(); - _sendController.dispose(); super.dispose(); } @@ -318,48 +47,6 @@ class _WalletInitiatedExchangeViewState Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - final isEstimated = ref.watch(prefsChangeNotifierProvider - .select((pref) => pref.exchangeRateType)) == - ExchangeRateType.estimated; - - ref.listen( - isEstimated - ? estimatedRateExchangeFormProvider - .select((value) => value.toAmountString) - : fixedRateExchangeFormProvider.select( - (value) => value.toAmountString), (previous, String next) { - if (!_receiveFocusNode.hasFocus) { - _receiveController.text = isEstimated && next.isEmpty ? "-" : next; - debugPrint("RECEIVE AMOUNT LISTENER ACTIVATED"); - if (_swapLock) { - _sendController.text = isEstimated - ? ref.read(estimatedRateExchangeFormProvider).fromAmountString - : ref.read(fixedRateExchangeFormProvider).fromAmountString; - } - } - }); - ref.listen( - isEstimated - ? estimatedRateExchangeFormProvider - .select((value) => value.fromAmountString) - : fixedRateExchangeFormProvider.select( - (value) => value.fromAmountString), (previous, String next) { - if (!_sendFocusNode.hasFocus) { - _sendController.text = next; - debugPrint("SEND AMOUNT LISTENER ACTIVATED"); - if (_swapLock) { - _receiveController.text = isEstimated - ? ref - .read(estimatedRateExchangeFormProvider) - .toAmountString - .isEmpty - ? "-" - : ref.read(estimatedRateExchangeFormProvider).toAmountString - : ref.read(fixedRateExchangeFormProvider).toAmountString; - } - } - }); - return Scaffold( backgroundColor: Theme.of(context).extension()!.background, appBar: AppBar( @@ -417,1167 +104,9 @@ class _WalletInitiatedExchangeViewState const SizedBox( height: 24, ), - Text( - "You will send", - style: STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark3, - ), - ), - const SizedBox( - height: 4, - ), - TextFormField( - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - focusNode: _sendFocusNode, - controller: _sendController, - textAlign: TextAlign.right, - onTap: () { - if (_sendController.text == "-") { - _sendController.text = ""; - } - }, - onChanged: (value) async { - final newFromAmount = Decimal.tryParse(value); - if (newFromAmount != null) { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - newFromAmount, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - newFromAmount, false); - } - } else { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - Decimal.zero, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - Decimal.zero, false); - } - _receiveController.text = isEstimated ? "-" : ""; - } - }, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - inputFormatters: [ - // regex to validate a crypto amount with 8 decimal places - TextInputFormatter.withFunction((oldValue, - newValue) => - RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') - .hasMatch(newValue.text) - ? newValue - : oldValue), - ], - decoration: InputDecoration( - contentPadding: const EdgeInsets.only( - top: 12, - right: 12, - ), - hintText: "0", - hintStyle: STextStyles.fieldLabel(context).copyWith( - fontSize: 14, - ), - prefixIcon: FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: GestureDetector( - onTap: () async { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - final String fromTicker = ref - .read( - estimatedRateExchangeFormProvider) - .from - ?.ticker ?? - "-"; - - if (fromTicker.toLowerCase() == - coin.ticker.toLowerCase()) { - // do not allow changing away from wallet coin - return; - } - - await _showFloatingRateSelectionSheet( - currencies: ref - .read( - availableChangeNowCurrenciesStateProvider - .state) - .state, - excludedTicker: ref - .read( - estimatedRateExchangeFormProvider) - .to - ?.ticker ?? - "-", - fromTicker: fromTicker, - onSelected: (from) => ref - .read( - estimatedRateExchangeFormProvider) - .updateFrom(from, true)); - } else { - final toTicker = ref - .read(fixedRateExchangeFormProvider) - .market - ?.to ?? - ""; - final fromTicker = ref - .read(fixedRateExchangeFormProvider) - .market - ?.from ?? - ""; - - if (fromTicker.toLowerCase() == - coin.ticker.toLowerCase()) { - // do not allow changing away from wallet coin - return; - } - await _showFixedRateSelectionSheet( - excludedTicker: toTicker, - fromTicker: fromTicker, - onSelected: (selectedFromTicker) async { - try { - final market = ref - .read( - fixedRateMarketPairsStateProvider - .state) - .state - .firstWhere( - (e) => - e.to == toTicker && - e.from == - selectedFromTicker, - ); - - await ref - .read( - fixedRateExchangeFormProvider) - .updateMarket(market, true); - } catch (e) { - unawaited(showDialog( - context: context, - builder: (_) => const StackDialog( - title: "Fixed rate market error", - message: - "Could not find the specified fixed rate trade pair", - ), - )); - return; - } - }, - ); - } - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Container( - width: 18, - height: 18, - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(18), - ), - child: Builder( - builder: (context) { - String? image; - if (ref.watch(prefsChangeNotifierProvider - .select((value) => value - .exchangeRateType)) == - ExchangeRateType.estimated) { - image = ref - .watch( - estimatedRateExchangeFormProvider - .select((value) => - value.from)) - ?.image; - } else { - image = _fetchIconUrlFromTickerForFixedRateFlow( - ref.watch( - fixedRateExchangeFormProvider - .select((value) => - value.market - ?.from))); - } - if (image != null && - image.isNotEmpty) { - return Center( - child: SvgPicture.network( - image, - height: 18, - placeholderBuilder: (_) => - Container( - width: 18, - height: 18, - decoration: BoxDecoration( - color: Theme.of(context) - .extension< - StackColors>()! - .textSubtitle2, - borderRadius: - BorderRadius - .circular( - 18, - ), - ), - child: ClipRRect( - borderRadius: - BorderRadius - .circular( - 18, - ), - child: - const LoadingIndicator(), - ), - ), - ), - ); - } else { - return Container( - width: 18, - height: 18, - decoration: BoxDecoration( - // color: Theme.of(context).extension()!.accentColorDark - borderRadius: - BorderRadius.circular( - 18), - ), - child: SvgPicture.asset( - Assets.svg.circleQuestion, - width: 18, - height: 18, - color: Theme.of(context) - .extension< - StackColors>()! - .textSubtitle2, - ), - ); - } - }, - ), - ), - const SizedBox( - width: 6, - ), - Text( - isEstimated - ? ref.watch( - estimatedRateExchangeFormProvider - .select((value) => value - .from?.ticker - .toUpperCase())) ?? - "-" - : ref.watch( - fixedRateExchangeFormProvider - .select((value) => value - .market?.from - .toUpperCase())) ?? - "-", - style: STextStyles.smallMed14(context) - .copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - const SizedBox( - width: 6, - ), - Builder(builder: (context) { - final ticker = isEstimated - ? ref.watch( - estimatedRateExchangeFormProvider - .select((value) => - value.from - ?.ticker)) ?? - "-" - : ref.watch( - fixedRateExchangeFormProvider - .select((value) => - value.market - ?.from)) ?? - "-"; - if (ticker.toLowerCase() == - coin.ticker.toLowerCase()) { - return Container(); - } - return SvgPicture.asset( - Assets.svg.chevronDown, - width: 5, - height: 2.5, - color: Theme.of(context) - .extension()! - .accentColorDark); - }), - ], - ), - ), - ), - ), - ), - ), - ), - const SizedBox( - height: 4, - ), - Stack( - children: [ - Positioned.fill( - child: Align( - alignment: Alignment.bottomLeft, - child: Text( - "You will receive", - style: STextStyles.itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark3, - ), - ), - ), - ), - Center( - child: GestureDetector( - onTap: () async { - await _swap(); - }, - child: Padding( - padding: const EdgeInsets.all(4), - child: SvgPicture.asset( - Assets.svg.swap, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - ), - ), - Positioned.fill( - child: Align( - alignment: Alignment.topRight, - child: Text( - isEstimated - ? ref.watch( - estimatedRateExchangeFormProvider - .select((value) => - value.minimumSendWarning)) - : ref.watch(fixedRateExchangeFormProvider - .select((value) => - value.sendAmountWarning)), - style: STextStyles.errorSmall(context), - ), - ), - ), - ], - ), - const SizedBox( - height: 4, - ), - TextFormField( - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - focusNode: _receiveFocusNode, - controller: _receiveController, - readOnly: ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated, - onTap: () { - if (!isEstimated && - _receiveController.text == "-") { - _receiveController.text = ""; - } - }, - onChanged: (value) async { - final newToAmount = Decimal.tryParse(value); - if (newToAmount != null) { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - // await ref - // .read(estimatedRateExchangeFormProvider) - // .setToAmountAndCalculateFromAmount( - // newToAmount, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount( - newToAmount, false); - } - } else { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - // await ref - // .read(estimatedRateExchangeFormProvider) - // .setToAmountAndCalculateFromAmount( - // Decimal.zero, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount( - Decimal.zero, false); - } - _sendController.text = ""; - } - }, - textAlign: TextAlign.right, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), - inputFormatters: [ - // regex to validate a crypto amount with 8 decimal places - TextInputFormatter.withFunction((oldValue, - newValue) => - RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') - .hasMatch(newValue.text) - ? newValue - : oldValue), - ], - decoration: InputDecoration( - contentPadding: const EdgeInsets.only( - top: 12, - right: 12, - ), - hintText: "0", - hintStyle: STextStyles.fieldLabel(context).copyWith( - fontSize: 14, - ), - prefixIcon: FittedBox( - fit: BoxFit.scaleDown, - child: GestureDetector( - onTap: () async { - if (ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) { - final toTicker = ref - .read( - estimatedRateExchangeFormProvider) - .to - ?.ticker ?? - ""; - - if (toTicker.toLowerCase() == - coin.ticker.toLowerCase()) { - // do not allow changing away from wallet coin - return; - } - - await _showFloatingRateSelectionSheet( - currencies: ref - .read( - availableChangeNowCurrenciesStateProvider - .state) - .state, - excludedTicker: ref - .read( - estimatedRateExchangeFormProvider) - .from - ?.ticker ?? - "", - fromTicker: ref - .read( - estimatedRateExchangeFormProvider) - .from - ?.ticker ?? - "", - onSelected: (to) => ref - .read( - estimatedRateExchangeFormProvider) - .updateTo(to, true)); - } else { - final fromTicker = ref - .read(fixedRateExchangeFormProvider) - .market - ?.from ?? - ""; - final toTicker = ref - .read(fixedRateExchangeFormProvider) - .market - ?.to ?? - ""; - if (toTicker.toLowerCase() == - coin.ticker.toLowerCase()) { - // do not allow changing away from wallet coin - return; - } - await _showFixedRateSelectionSheet( - excludedTicker: fromTicker, - fromTicker: fromTicker, - onSelected: (selectedToTicker) async { - try { - final market = ref - .read( - fixedRateMarketPairsStateProvider - .state) - .state - .firstWhere( - (e) => - e.to == selectedToTicker && - e.from == fromTicker, - ); - - await ref - .read( - fixedRateExchangeFormProvider) - .updateMarket(market, true); - } catch (e) { - unawaited(showDialog( - context: context, - builder: (_) => const StackDialog( - title: "Fixed rate market error", - message: - "Could not find the specified fixed rate trade pair", - ), - )); - return; - } - }, - ); - } - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Container( - width: 18, - height: 18, - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(18), - ), - child: Builder( - builder: (context) { - String? image; - if (ref.watch(prefsChangeNotifierProvider - .select((value) => value - .exchangeRateType)) == - ExchangeRateType.estimated) { - image = ref - .watch( - estimatedRateExchangeFormProvider - .select((value) => - value.to)) - ?.image; - } else { - image = _fetchIconUrlFromTickerForFixedRateFlow( - ref.watch( - fixedRateExchangeFormProvider - .select((value) => - value.market - ?.to))); - } - if (image != null && - image.isNotEmpty) { - return Center( - child: SvgPicture.network( - image, - height: 18, - placeholderBuilder: (_) => - Container( - width: 18, - height: 18, - decoration: BoxDecoration( - color: Theme.of(context) - .extension< - StackColors>()! - .textSubtitle2, - borderRadius: - BorderRadius - .circular(18), - ), - child: ClipRRect( - borderRadius: - BorderRadius - .circular( - 18, - ), - child: - const LoadingIndicator(), - ), - ), - ), - ); - } else { - return Container( - width: 18, - height: 18, - decoration: BoxDecoration( - // color: Theme.of(context).extension()!.accentColorDark - borderRadius: - BorderRadius.circular( - 18), - ), - child: SvgPicture.asset( - Assets.svg.circleQuestion, - width: 18, - height: 18, - color: Theme.of(context) - .extension< - StackColors>()! - .textSubtitle2, - ), - ); - } - }, - ), - ), - const SizedBox( - width: 6, - ), - Text( - isEstimated - ? ref.watch( - estimatedRateExchangeFormProvider - .select((value) => value - .to?.ticker - .toUpperCase())) ?? - "-" - : ref.watch( - fixedRateExchangeFormProvider - .select((value) => value - .market?.to - .toUpperCase())) ?? - "-", - style: STextStyles.smallMed14(context) - .copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), - ), - const SizedBox( - width: 6, - ), - Builder(builder: (context) { - final ticker = isEstimated - ? ref.watch( - estimatedRateExchangeFormProvider - .select((value) => - value.to - ?.ticker)) ?? - "-" - : ref.watch( - fixedRateExchangeFormProvider - .select((value) => - value.market - ?.to)) ?? - "-"; - if (ticker.toLowerCase() == - coin.ticker.toLowerCase()) { - return Container(); - } - return SvgPicture.asset( - Assets.svg.chevronDown, - width: 5, - height: 2.5, - color: Theme.of(context) - .extension()! - .accentColorDark); - }), - ], - ), - ), - ), - ), - ), - ), - ), - // if (ref - // .watch(exchangeFormSateProvider - // .select((value) => value.minimumReceiveWarning)) - // .isNotEmpty) - // SizedBox( - // height: 4, - // ), - // - // if (ref - // .watch(exchangeFormSateProvider - // .select((value) => value.minimumReceiveWarning)) - // .isNotEmpty) - // Row( - // children: [ - // Spacer(), - // Text( - // ref.watch(exchangeFormSateProvider.select( - // (value) => value.minimumReceiveWarning)), - // style: STextStyles.errorSmall(context), - // ), - // ], - // ), - - const SizedBox( - height: 12, - ), - RateInfo( - onChanged: (rateType) async { - _receiveFocusNode.unfocus(); - _sendFocusNode.unfocus(); - switch (rateType) { - case ExchangeRateType.estimated: - final market = ref - .read(fixedRateExchangeFormProvider) - .market; - final fromTicker = market?.from ?? ""; - final toTicker = market?.to ?? ""; - if (!(fromTicker.isEmpty || - toTicker.isEmpty || - toTicker == "-" || - fromTicker == "-")) { - final available = ref - .read( - availableFloatingRatePairsStateProvider - .state) - .state - .where((e) => - e.to == toTicker && - e.from == fromTicker); - if (available.isNotEmpty) { - final availableCurrencies = ref - .read( - availableChangeNowCurrenciesStateProvider - .state) - .state - .where((e) => - e.ticker == fromTicker || - e.ticker == toTicker); - if (availableCurrencies.length > 1) { - final from = - availableCurrencies.firstWhere( - (e) => e.ticker == fromTicker); - final to = availableCurrencies.firstWhere( - (e) => e.ticker == toTicker); - - await ref - .read( - estimatedRateExchangeFormProvider) - .updateTo(to, false); - await ref - .read( - estimatedRateExchangeFormProvider) - .updateFrom(from, true); - return; - } - } - } - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Estimated rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last estimated rate pair.", - context: context, - )); - break; - case ExchangeRateType.fixed: - final fromTicker = ref - .read(estimatedRateExchangeFormProvider) - .from - ?.ticker ?? - ""; - final toTicker = ref - .read(estimatedRateExchangeFormProvider) - .to - ?.ticker ?? - ""; - if (!(fromTicker.isEmpty || - toTicker.isEmpty || - toTicker == "-" || - fromTicker == "-")) { - FixedRateMarket? market; - try { - market = ref - .read(fixedRateMarketPairsStateProvider - .state) - .state - .firstWhere((e) => - e.from == fromTicker && - e.to == toTicker); - } catch (_) { - market = null; - } - await ref - .read(fixedRateExchangeFormProvider) - .updateMarket(market, true); - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount( - Decimal.tryParse( - _sendController.text) ?? - Decimal.zero, - true, - ); - return; - } - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Fixed rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last fixed rate pair.", - context: context, - )); - break; - } - }, - ), - const SizedBox( - height: 12, - ), - const Spacer(), - TextButton( - style: ((ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) - ? ref.watch(estimatedRateExchangeFormProvider - .select((value) => value.canExchange)) - : ref.watch(fixedRateExchangeFormProvider - .select((value) => value.canExchange))) - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context) - : Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - onPressed: ((ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated) - ? ref.watch(estimatedRateExchangeFormProvider - .select((value) => value.canExchange)) - : ref.watch(fixedRateExchangeFormProvider - .select((value) => value.canExchange))) - ? () async { - final isEstimated = ref - .read(prefsChangeNotifierProvider) - .exchangeRateType == - ExchangeRateType.estimated; - - // final ft = isEstimated - // ? ref - // .read( - // estimatedRateExchangeFormProvider) - // .from - // ?.ticker ?? - // "" - // : ref - // .read( - // fixedRateExchangeFormProvider) - // .market - // ?.from ?? - // ""; - // - // final manager = ref - // .read(walletsChangeNotifierProvider) - // .getManager(walletId); - final sendAmount = Decimal.parse(ref - .read(estimatedRateExchangeFormProvider) - .fromAmountString); - - // if (ft.toLowerCase() == - // coin.ticker.toLowerCase()) { - // bool shouldPop = false; - // bool wasPopped = false; - // unawaited(showDialog( - // context: context, - // builder: (_) => WillPopScope( - // onWillPop: () async { - // if (shouldPop) { - // wasPopped = true; - // } - // return shouldPop; - // }, - // child: const CustomLoadingOverlay( - // message: "Checking available balance", - // eventBus: null, - // ), - // ), - // )); - // - // final availableBalance = - // await manager.availableBalance; - // - // final feeObject = await manager.fees; - // - // final fee = await manager.estimateFeeFor( - // Format.decimalAmountToSatoshis( - // sendAmount), - // feeObject.medium); - // - // shouldPop = true; - // if (!wasPopped && mounted) { - // Navigator.of(context).pop(); - // } - // - // if (availableBalance < - // sendAmount + - // Format.satoshisToAmount(fee)) { - // unawaited(showDialog( - // context: context, - // builder: (_) => StackOkDialog( - // title: "Insufficient balance", - // message: - // "Current ${coin.prettyName} wallet does not have enough ${coin.ticker} for this trade", - // ), - // )); - // return; - // } - // } - - if (isEstimated) { - final fromTicker = ref - .read( - estimatedRateExchangeFormProvider) - .from - ?.ticker ?? - ""; - final toTicker = ref - .read( - estimatedRateExchangeFormProvider) - .to - ?.ticker ?? - ""; - - bool isAvailable = false; - final availableFloatingPairs = ref - .read( - availableFloatingRatePairsStateProvider - .state) - .state; - for (final pair in availableFloatingPairs) { - if (pair.from == fromTicker && - pair.to == toTicker) { - isAvailable = true; - break; - } - } - - if (!isAvailable) { - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: - "Selected trade pair unavailable", - message: - "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", - ), - )); - return; - } - - final rateType = ref - .read(prefsChangeNotifierProvider) - .exchangeRateType; - - final response = await ref - .read(changeNowProvider) - .getEstimate( - fromTicker, - toTicker, - sendAmount, - false, - false, - ); - - if (response.value == null) { - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: - "Failed to update trade estimate", - message: - response.exception?.toString(), - ), - )); - return; - } - - String rate = - "1 ${fromTicker.toUpperCase()} ~${(response.value!.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; - - final model = IncompleteExchangeModel( - sendTicker: fromTicker.toUpperCase(), - receiveTicker: toTicker.toUpperCase(), - rateInfo: rate, - sendAmount: sendAmount, - receiveAmount: - response.value!.estimatedAmount, - rateType: rateType, - rateId: response.value!.rateId, - ); - - if (mounted) { - ref - .read( - exchangeSendFromWalletIdStateProvider - .state) - .state = Tuple2(walletId, coin); - unawaited(Navigator.of(context).pushNamed( - Step2View.routeName, - arguments: model, - )); - } - } else { - final fromTicker = ref - .read(fixedRateExchangeFormProvider) - .market - ?.from ?? - ""; - final toTicker = ref - .read(fixedRateExchangeFormProvider) - .market - ?.to ?? - ""; - - final sendAmount = Decimal.parse(ref - .read(fixedRateExchangeFormProvider) - .fromAmountString); - - final rateType = ref - .read(prefsChangeNotifierProvider) - .exchangeRateType; - - final response = await ref - .read(changeNowProvider) - .getEstimate( - fromTicker, - toTicker, - sendAmount, - true, - false, - ); - - bool? shouldCancel; - - if (response.value == null) { - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: - "Failed to update trade estimate", - message: - response.exception?.toString(), - ), - )); - return; - } else if (response.value!.warningMessage != - null && - response.value!.warningMessage! - .isNotEmpty) { - shouldCancel = await showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: - "Failed to update trade estimate", - message: - "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Cancel", - style: STextStyles.itemSubtitle12( - context), - ), - onPressed: () { - // notify return to cancel - Navigator.of(context).pop(true); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor( - context), - child: Text( - "Attempt", - style: - STextStyles.button(context), - ), - onPressed: () { - // continue and try to attempt trade - Navigator.of(context).pop(false); - }, - ), - ), - ); - } - - if (shouldCancel is bool && shouldCancel) { - return; - } - - String rate = - "1 $fromTicker ~${ref.read(fixedRateExchangeFormProvider).rate!.toStringAsFixed(8)} $toTicker"; - - final model = IncompleteExchangeModel( - sendTicker: fromTicker, - receiveTicker: toTicker, - rateInfo: rate, - sendAmount: sendAmount, - receiveAmount: - response.value!.estimatedAmount, - rateType: rateType, - rateId: response.value!.rateId, - ); - - if (mounted) { - ref - .read( - exchangeSendFromWalletIdStateProvider - .state) - .state = Tuple2(walletId, coin); - unawaited(Navigator.of(context).pushNamed( - Step2View.routeName, - arguments: model, - )); - } - } - } - : null, - child: Text( - "Next", - style: STextStyles.button(context), - ), + ExchangeForm( + walletId: walletId, + coin: coin, ), ], ), From d87c9981c631bd49175cd34df3171a49ab16e667 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 3 Oct 2022 17:04:59 -0600 Subject: [PATCH 17/75] changenow api fixes --- lib/services/exchange/change_now/change_now_api.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart index 7c7f679748..cdf8e9852c 100644 --- a/lib/services/exchange/change_now/change_now_api.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -290,8 +290,8 @@ class ChangeNowAPI { final json = Map.from(jsonObject as Map); return ExchangeResponse( value: Range( - max: Decimal.tryParse(json["maxAmount"] as String? ?? ""), - min: Decimal.tryParse(json["minAmount"] as String? ?? ""), + max: Decimal.tryParse(json["maxAmount"]?.toString() ?? ""), + min: Decimal.tryParse(json["minAmount"]?.toString() ?? ""), ), ); } catch (e, s) { @@ -366,9 +366,13 @@ class ChangeNowAPI { required String toTicker, required Decimal fromAmount, required bool reversed, + bool useRateId = true, String? apiKey, }) async { - Map params = {"api_key": apiKey ?? kChangeNowApiKey}; + Map params = { + "api_key": apiKey ?? kChangeNowApiKey, + "useRateId": useRateId.toString(), + }; late final Uri uri; if (reversed) { From bb12e149f6eb2767db4ee0d7bc8b0e7ae3c521d4 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 3 Oct 2022 18:01:04 -0600 Subject: [PATCH 18/75] exchange form state implementation and various small bug fixes --- lib/main.dart | 17 +- .../change_now/estimated_exchange_amount.dart | 6 +- .../estimated_rate_exchange_form_state.dart | 564 +++++++++--------- lib/models/exchange/exchange_form_state.dart | 390 ++++++++++++ .../fixed_rate_exchange_form_state.dart | 358 +++++------ lib/pages/exchange_view/exchange_form.dart | 433 +++++--------- lib/pages/exchange_view/exchange_view.dart | 142 ----- .../sub_widgets/home_view_button_bar.dart | 17 +- lib/pages/wallet_view/wallet_view.dart | 47 +- .../estimate_rate_exchange_form_provider.dart | 10 +- .../fixed_rate_exchange_form_provider.dart | 12 +- .../change_now_loading_service.dart | 15 +- ...timated_rate_exchange_form_state_test.dart | 438 +++++++------- 13 files changed, 1288 insertions(+), 1161 deletions(-) create mode 100644 lib/models/exchange/exchange_form_state.dart diff --git a/lib/main.dart b/lib/main.dart index 40f5321fca..0c74aa4a44 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,6 +18,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; +import 'package:stackwallet/models/exchange/exchange_form_state.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/isar/models/log.dart'; import 'package:stackwallet/models/models.dart'; @@ -35,8 +36,6 @@ import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; import 'package:stackwallet/providers/exchange/changenow_initial_load_status.dart'; -import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart'; -import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart'; import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/global/base_currencies_provider.dart'; @@ -47,6 +46,7 @@ import 'package:stackwallet/providers/ui/color_theme_provider.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/debug_service.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/locale_service.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/notifications_api.dart'; @@ -271,15 +271,15 @@ class _MaterialAppWithThemeState extends ConsumerState response2.value!; if (response.value!.length > 1) { - if (ref.read(estimatedRateExchangeFormProvider).from == null) { + if (ref.read(exchangeFormStateProvider).from == null) { if (response.value!.where((e) => e.ticker == "btc").isNotEmpty) { - await ref.read(estimatedRateExchangeFormProvider).updateFrom( + await ref.read(exchangeFormStateProvider).updateFrom( response.value!.firstWhere((e) => e.ticker == "btc"), false); } } - if (ref.read(estimatedRateExchangeFormProvider).to == null) { + if (ref.read(exchangeFormStateProvider).to == null) { if (response.value!.where((e) => e.ticker == "doge").isNotEmpty) { - await ref.read(estimatedRateExchangeFormProvider).updateTo( + await ref.read(exchangeFormStateProvider).updateTo( response.value!.firstWhere((e) => e.ticker == "doge"), false); } } @@ -319,12 +319,12 @@ class _MaterialAppWithThemeState extends ConsumerState ref.read(fixedRateMarketPairsStateProvider.state).state = response3.value!; - if (ref.read(fixedRateExchangeFormProvider).market == null) { + if (ref.read(exchangeFormStateProvider).market == null) { final matchingMarkets = response3.value!.where((e) => e.to == "doge" && e.from == "btc"); if (matchingMarkets.isNotEmpty) { await ref - .read(fixedRateExchangeFormProvider) + .read(exchangeFormStateProvider) .updateMarket(matchingMarkets.first, true); } } @@ -355,6 +355,7 @@ class _MaterialAppWithThemeState extends ConsumerState @override void initState() { + ref.read(exchangeFormStateProvider).exchange = ChangeNowExchange(); final colorScheme = DB.instance .get(boxName: DB.boxNameTheme, key: "colorScheme") as String?; diff --git a/lib/models/exchange/change_now/estimated_exchange_amount.dart b/lib/models/exchange/change_now/estimated_exchange_amount.dart index cf70f812f2..dccf6b3b32 100644 --- a/lib/models/exchange/change_now/estimated_exchange_amount.dart +++ b/lib/models/exchange/change_now/estimated_exchange_amount.dart @@ -35,8 +35,10 @@ class EstimatedExchangeAmount { factory EstimatedExchangeAmount.fromJson(Map json) { try { return EstimatedExchangeAmount( - estimatedAmount: Decimal.parse(json["estimatedAmount"].toString()), - transactionSpeedForecast: json["transactionSpeedForecast"] as String, + estimatedAmount: Decimal.parse(json["estimatedAmount"]?.toString() ?? + json["estimatedDeposit"].toString()), + transactionSpeedForecast: + json["transactionSpeedForecast"] as String? ?? "", warningMessage: json["warningMessage"] as String?, rateId: json["rateId"] as String?, networkFee: Decimal.tryParse(json["networkFee"].toString()), diff --git a/lib/models/exchange/estimated_rate_exchange_form_state.dart b/lib/models/exchange/estimated_rate_exchange_form_state.dart index aed815de8e..2a465c3efc 100644 --- a/lib/models/exchange/estimated_rate_exchange_form_state.dart +++ b/lib/models/exchange/estimated_rate_exchange_form_state.dart @@ -1,282 +1,282 @@ -import 'package:decimal/decimal.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; -import 'package:stackwallet/utilities/logger.dart'; - -class EstimatedRateExchangeFormState extends ChangeNotifier { - /// used in testing to inject mock - ChangeNowAPI? cnTesting; - - Decimal? _fromAmount; - Decimal? _toAmount; - - Decimal? _minFromAmount; - Decimal? _minToAmount; - - Decimal? rate; - - Currency? _from; - Currency? _to; - - void Function(String)? _onError; - - Currency? get from => _from; - Currency? get to => _to; - - String get fromAmountString => - _fromAmount == null ? "" : _fromAmount!.toStringAsFixed(8); - String get toAmountString => - _toAmount == null ? "" : _toAmount!.toStringAsFixed(8); - - String get rateDisplayString { - if (rate == null || from == null || to == null) { - return "N/A"; - } else { - return "1 ${from!.ticker.toUpperCase()} ~${rate!.toStringAsFixed(8)} ${to!.ticker.toUpperCase()}"; - } - } - - bool get canExchange { - return _fromAmount != null && - _fromAmount != Decimal.zero && - _toAmount != null && - rate != null && - minimumSendWarning.isEmpty; - } - - String get minimumSendWarning { - if (_from != null && - _fromAmount != null && - _minFromAmount != null && - _fromAmount! < _minFromAmount!) { - return "Minimum amount ${_minFromAmount!.toString()} ${from!.ticker.toUpperCase()}"; - } - - return ""; - } - - Future init(Currency? from, Currency? to) async { - _from = from; - _to = to; - } - - void clearAmounts(bool shouldNotifyListeners) { - _fromAmount = null; - _toAmount = null; - _minFromAmount = null; - _minToAmount = null; - rate = null; - - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - Future swap() async { - final Decimal? newToAmount = _fromAmount; - final Decimal? newFromAmount = _toAmount; - - final Decimal? newMinFromAmount = _minToAmount; - final Decimal? newMinToAmount = _minFromAmount; - - final Currency? newTo = from; - final Currency? newFrom = to; - - _fromAmount = newFromAmount; - _toAmount = newToAmount; - - _minToAmount = newMinToAmount; - _minFromAmount = newMinFromAmount; - - // rate = newRate; - - _to = newTo; - _from = newFrom; - - await _updateMinFromAmount(shouldNotifyListeners: false); - - await updateRate(); - - notifyListeners(); - } - - Future updateTo(Currency to, bool shouldNotifyListeners) async { - try { - _to = to; - if (_from == null) { - rate = null; - notifyListeners(); - return; - } - - await _updateMinFromAmount(shouldNotifyListeners: shouldNotifyListeners); - - await updateRate(shouldNotifyListeners: shouldNotifyListeners); - - debugPrint( - "_updated TO: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$_fromAmount _toAmount=$_toAmount rate:$rate"); - - if (shouldNotifyListeners) { - notifyListeners(); - } - } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); - } - } - - Future updateFrom(Currency from, bool shouldNotifyListeners) async { - try { - _from = from; - - if (_to == null) { - rate = null; - notifyListeners(); - return; - } - - await _updateMinFromAmount(shouldNotifyListeners: shouldNotifyListeners); - - await updateRate(shouldNotifyListeners: shouldNotifyListeners); - - debugPrint( - "_updated FROM: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$_fromAmount _toAmount=$_toAmount rate:$rate"); - if (shouldNotifyListeners) { - notifyListeners(); - } - } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); - } - } - - Future _updateMinFromAmount( - {required bool shouldNotifyListeners}) async { - _minFromAmount = await getStandardMinExchangeAmount(from: from!, to: to!); - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - // Future setToAmountAndCalculateFromAmount( - // Decimal newToAmount, - // bool shouldNotifyListeners, - // ) async { - // if (newToAmount == Decimal.zero) { - // _fromAmount = Decimal.zero; - // } - // - // _toAmount = newToAmount; - // await updateRate(); - // if (shouldNotifyListeners) { - // notifyListeners(); - // } - // } - - Future setFromAmountAndCalculateToAmount( - Decimal newFromAmount, - bool shouldNotifyListeners, - ) async { - if (newFromAmount == Decimal.zero) { - _toAmount = Decimal.zero; - } - - _fromAmount = newFromAmount; - await updateRate(shouldNotifyListeners: shouldNotifyListeners); - - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - Future getStandardEstimatedToAmount({ - required Decimal fromAmount, - required Currency from, - required Currency to, - }) async { - final response = - await (cnTesting ?? ChangeNowAPI.instance).getEstimatedExchangeAmount( - fromTicker: from.ticker, - toTicker: to.ticker, - fromAmount: fromAmount, - ); - - if (response.value != null) { - return response.value!.estimatedAmount; - } else { - _onError?.call( - "Failed to fetch estimated amount: ${response.exception?.toString()}"); - return null; - } - } - - // Future getStandardEstimatedFromAmount({ - // required Decimal toAmount, - // required Currency from, - // required Currency to, - // }) async { - // final response = await (cnTesting ?? ChangeNow.instance) - // .getEstimatedExchangeAmount( - // fromTicker: from.ticker, - // toTicker: to.ticker, - // fromAmount: toAmount, ); - // - // if (response.value != null) { - // return response.value!.fromAmount; - // } else { - // _onError?.call( - // "Failed to fetch estimated amount: ${response.exception?.toString()}"); - // return null; - // } - // } - - Future getStandardMinExchangeAmount({ - required Currency from, - required Currency to, - }) async { - final response = await (cnTesting ?? ChangeNowAPI.instance) - .getMinimalExchangeAmount(fromTicker: from.ticker, toTicker: to.ticker); - - if (response.value != null) { - return response.value!; - } else { - _onError?.call( - "Could not update minimal exchange amounts: ${response.exception?.toString()}"); - return null; - } - } - - void setOnError({ - required void Function(String)? onError, - bool shouldNotifyListeners = false, - }) { - _onError = onError; - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - Future updateRate({bool shouldNotifyListeners = false}) async { - rate = null; - final amount = _fromAmount; - final minAmount = _minFromAmount; - if (amount != null && amount > Decimal.zero) { - Decimal? amt; - if (minAmount != null) { - if (minAmount <= amount) { - amt = await getStandardEstimatedToAmount( - fromAmount: amount, from: _from!, to: _to!); - if (amt != null) { - rate = (amt / amount).toDecimal(scaleOnInfinitePrecision: 12); - } - } - } - if (rate != null && amt != null) { - _toAmount = amt; - } - } - if (shouldNotifyListeners) { - notifyListeners(); - } - } -} +// import 'package:decimal/decimal.dart'; +// import 'package:flutter/cupertino.dart'; +// import 'package:flutter/material.dart'; +// import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +// import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; +// import 'package:stackwallet/utilities/logger.dart'; +// +// class EstimatedRateExchangeFormState extends ChangeNotifier { +// /// used in testing to inject mock +// ChangeNowAPI? cnTesting; +// +// Decimal? _fromAmount; +// Decimal? _toAmount; +// +// Decimal? _minFromAmount; +// Decimal? _minToAmount; +// +// Decimal? rate; +// +// Currency? _from; +// Currency? _to; +// +// void Function(String)? _onError; +// +// Currency? get from => _from; +// Currency? get to => _to; +// +// String get fromAmountString => +// _fromAmount == null ? "" : _fromAmount!.toStringAsFixed(8); +// String get toAmountString => +// _toAmount == null ? "" : _toAmount!.toStringAsFixed(8); +// +// String get rateDisplayString { +// if (rate == null || from == null || to == null) { +// return "N/A"; +// } else { +// return "1 ${from!.ticker.toUpperCase()} ~${rate!.toStringAsFixed(8)} ${to!.ticker.toUpperCase()}"; +// } +// } +// +// bool get canExchange { +// return _fromAmount != null && +// _fromAmount != Decimal.zero && +// _toAmount != null && +// rate != null && +// minimumSendWarning.isEmpty; +// } +// +// String get minimumSendWarning { +// if (_from != null && +// _fromAmount != null && +// _minFromAmount != null && +// _fromAmount! < _minFromAmount!) { +// return "Minimum amount ${_minFromAmount!.toString()} ${from!.ticker.toUpperCase()}"; +// } +// +// return ""; +// } +// +// Future init(Currency? from, Currency? to) async { +// _from = from; +// _to = to; +// } +// +// void clearAmounts(bool shouldNotifyListeners) { +// _fromAmount = null; +// _toAmount = null; +// _minFromAmount = null; +// _minToAmount = null; +// rate = null; +// +// if (shouldNotifyListeners) { +// notifyListeners(); +// } +// } +// +// Future swap() async { +// final Decimal? newToAmount = _fromAmount; +// final Decimal? newFromAmount = _toAmount; +// +// final Decimal? newMinFromAmount = _minToAmount; +// final Decimal? newMinToAmount = _minFromAmount; +// +// final Currency? newTo = from; +// final Currency? newFrom = to; +// +// _fromAmount = newFromAmount; +// _toAmount = newToAmount; +// +// _minToAmount = newMinToAmount; +// _minFromAmount = newMinFromAmount; +// +// // rate = newRate; +// +// _to = newTo; +// _from = newFrom; +// +// await _updateMinFromAmount(shouldNotifyListeners: false); +// +// await updateRate(); +// +// notifyListeners(); +// } +// +// Future updateTo(Currency to, bool shouldNotifyListeners) async { +// try { +// _to = to; +// if (_from == null) { +// rate = null; +// notifyListeners(); +// return; +// } +// +// await _updateMinFromAmount(shouldNotifyListeners: shouldNotifyListeners); +// +// await updateRate(shouldNotifyListeners: shouldNotifyListeners); +// +// debugPrint( +// "_updated TO: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$_fromAmount _toAmount=$_toAmount rate:$rate"); +// +// if (shouldNotifyListeners) { +// notifyListeners(); +// } +// } catch (e, s) { +// Logging.instance.log("$e\n$s", level: LogLevel.Error); +// } +// } +// +// Future updateFrom(Currency from, bool shouldNotifyListeners) async { +// try { +// _from = from; +// +// if (_to == null) { +// rate = null; +// notifyListeners(); +// return; +// } +// +// await _updateMinFromAmount(shouldNotifyListeners: shouldNotifyListeners); +// +// await updateRate(shouldNotifyListeners: shouldNotifyListeners); +// +// debugPrint( +// "_updated FROM: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$_fromAmount _toAmount=$_toAmount rate:$rate"); +// if (shouldNotifyListeners) { +// notifyListeners(); +// } +// } catch (e, s) { +// Logging.instance.log("$e\n$s", level: LogLevel.Error); +// } +// } +// +// Future _updateMinFromAmount( +// {required bool shouldNotifyListeners}) async { +// _minFromAmount = await getStandardMinExchangeAmount(from: from!, to: to!); +// if (shouldNotifyListeners) { +// notifyListeners(); +// } +// } +// +// // Future setToAmountAndCalculateFromAmount( +// // Decimal newToAmount, +// // bool shouldNotifyListeners, +// // ) async { +// // if (newToAmount == Decimal.zero) { +// // _fromAmount = Decimal.zero; +// // } +// // +// // _toAmount = newToAmount; +// // await updateRate(); +// // if (shouldNotifyListeners) { +// // notifyListeners(); +// // } +// // } +// +// Future setFromAmountAndCalculateToAmount( +// Decimal newFromAmount, +// bool shouldNotifyListeners, +// ) async { +// if (newFromAmount == Decimal.zero) { +// _toAmount = Decimal.zero; +// } +// +// _fromAmount = newFromAmount; +// await updateRate(shouldNotifyListeners: shouldNotifyListeners); +// +// if (shouldNotifyListeners) { +// notifyListeners(); +// } +// } +// +// Future getStandardEstimatedToAmount({ +// required Decimal fromAmount, +// required Currency from, +// required Currency to, +// }) async { +// final response = +// await (cnTesting ?? ChangeNowAPI.instance).getEstimatedExchangeAmount( +// fromTicker: from.ticker, +// toTicker: to.ticker, +// fromAmount: fromAmount, +// ); +// +// if (response.value != null) { +// return response.value!.estimatedAmount; +// } else { +// _onError?.call( +// "Failed to fetch estimated amount: ${response.exception?.toString()}"); +// return null; +// } +// } +// +// // Future getStandardEstimatedFromAmount({ +// // required Decimal toAmount, +// // required Currency from, +// // required Currency to, +// // }) async { +// // final response = await (cnTesting ?? ChangeNow.instance) +// // .getEstimatedExchangeAmount( +// // fromTicker: from.ticker, +// // toTicker: to.ticker, +// // fromAmount: toAmount, ); +// // +// // if (response.value != null) { +// // return response.value!.fromAmount; +// // } else { +// // _onError?.call( +// // "Failed to fetch estimated amount: ${response.exception?.toString()}"); +// // return null; +// // } +// // } +// +// Future getStandardMinExchangeAmount({ +// required Currency from, +// required Currency to, +// }) async { +// final response = await (cnTesting ?? ChangeNowAPI.instance) +// .getMinimalExchangeAmount(fromTicker: from.ticker, toTicker: to.ticker); +// +// if (response.value != null) { +// return response.value!; +// } else { +// _onError?.call( +// "Could not update minimal exchange amounts: ${response.exception?.toString()}"); +// return null; +// } +// } +// +// void setOnError({ +// required void Function(String)? onError, +// bool shouldNotifyListeners = false, +// }) { +// _onError = onError; +// if (shouldNotifyListeners) { +// notifyListeners(); +// } +// } +// +// Future updateRate({bool shouldNotifyListeners = false}) async { +// rate = null; +// final amount = _fromAmount; +// final minAmount = _minFromAmount; +// if (amount != null && amount > Decimal.zero) { +// Decimal? amt; +// if (minAmount != null) { +// if (minAmount <= amount) { +// amt = await getStandardEstimatedToAmount( +// fromAmount: amount, from: _from!, to: _to!); +// if (amt != null) { +// rate = (amt / amount).toDecimal(scaleOnInfinitePrecision: 12); +// } +// } +// } +// if (rate != null && amt != null) { +// _toAmount = amt; +// } +// } +// if (shouldNotifyListeners) { +// notifyListeners(); +// } +// } +// } diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart new file mode 100644 index 0000000000..ad30e0c1f2 --- /dev/null +++ b/lib/models/exchange/exchange_form_state.dart @@ -0,0 +1,390 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; +import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/utilities/logger.dart'; + +final exchangeFormStateProvider = + ChangeNotifierProvider((ref) => ExchangeFormState()); + +class ExchangeFormState extends ChangeNotifier { + Exchange? _exchange; + Exchange? get exchange => _exchange; + set exchange(Exchange? value) { + _exchange = value; + _onExchangeTypeChanged(); + } + + ExchangeRateType _exchangeType = ExchangeRateType.estimated; + ExchangeRateType get exchangeType => _exchangeType; + set exchangeType(ExchangeRateType value) { + _exchangeType = value; + _onExchangeRateTypeChanged(); + } + + bool reversed = false; + + Decimal? fromAmount; + Decimal? toAmount; + + Decimal? minAmount; + Decimal? maxAmount; + + Decimal? rate; + Estimate? estimate; + + FixedRateMarket? _market; + FixedRateMarket? get market => _market; + + Currency? _from; + Currency? _to; + + String? get fromTicker { + switch (exchangeType) { + case ExchangeRateType.estimated: + return _from?.ticker; + case ExchangeRateType.fixed: + return _market?.from; + } + } + + String? get toTicker { + switch (exchangeType) { + case ExchangeRateType.estimated: + return _to?.ticker; + case ExchangeRateType.fixed: + return _market?.to; + } + } + + void Function(String)? _onError; + + Currency? get from => _from; + Currency? get to => _to; + + void setCurrencies(Currency from, Currency to) { + _from = from; + _to = to; + } + + String get warning { + if (reversed) { + if (toTicker != null && toAmount != null) { + if (minAmount != null && toAmount! < minAmount!) { + return "Minimum amount ${minAmount!.toString()} ${toTicker!.toUpperCase()}"; + } else if (maxAmount != null && toAmount! > maxAmount!) { + return "Maximum amount ${maxAmount!.toString()} ${toTicker!.toUpperCase()}"; + } + } + } else { + if (fromTicker != null && fromAmount != null) { + if (minAmount != null && fromAmount! < minAmount!) { + return "Minimum amount ${minAmount!.toString()} ${fromTicker!.toUpperCase()}"; + } else if (maxAmount != null && fromAmount! > maxAmount!) { + return "Maximum amount ${maxAmount!.toString()} ${fromTicker!.toUpperCase()}"; + } + } + } + + return ""; + } + + String get fromAmountString => fromAmount?.toStringAsFixed(8) ?? ""; + String get toAmountString => toAmount?.toStringAsFixed(8) ?? ""; + + bool get canExchange { + switch (exchangeType) { + case ExchangeRateType.estimated: + return fromAmount != null && + fromAmount != Decimal.zero && + toAmount != null && + rate != null && + warning.isEmpty; + case ExchangeRateType.fixed: + return _market != null && + fromAmount != null && + toAmount != null && + warning.isEmpty; + } + } + + void clearAmounts(bool shouldNotifyListeners) { + fromAmount = null; + toAmount = null; + minAmount = null; + maxAmount = null; + rate = null; + + if (shouldNotifyListeners) { + notifyListeners(); + } + } + + Future setFromAmountAndCalculateToAmount( + Decimal newFromAmount, + bool shouldNotifyListeners, + ) async { + if (newFromAmount == Decimal.zero) { + toAmount = Decimal.zero; + } + + fromAmount = newFromAmount; + reversed = false; + + await updateRanges(shouldNotifyListeners: false); + + await updateEstimate( + shouldNotifyListeners: false, + reversed: reversed, + ); + + if (shouldNotifyListeners) { + notifyListeners(); + } + } + + Future setToAmountAndCalculateFromAmount( + Decimal newToAmount, + bool shouldNotifyListeners, + ) async { + if (newToAmount == Decimal.zero) { + fromAmount = Decimal.zero; + } + + toAmount = newToAmount; + reversed = true; + + await updateRanges(shouldNotifyListeners: false); + + await updateEstimate( + shouldNotifyListeners: false, + reversed: reversed, + ); + + if (shouldNotifyListeners) { + notifyListeners(); + } + } + + Future updateTo(Currency to, bool shouldNotifyListeners) async { + try { + _to = to; + if (_from == null) { + rate = null; + notifyListeners(); + return; + } + + await updateRanges(shouldNotifyListeners: false); + + await updateEstimate( + shouldNotifyListeners: false, + reversed: reversed, + ); + + debugPrint( + "_updated TO: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$fromAmount _toAmount=$toAmount rate:$rate"); + + if (shouldNotifyListeners) { + notifyListeners(); + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + } + } + + Future updateFrom(Currency from, bool shouldNotifyListeners) async { + try { + _from = from; + + if (_to == null) { + rate = null; + notifyListeners(); + return; + } + + await updateRanges(shouldNotifyListeners: false); + + await updateEstimate( + shouldNotifyListeners: false, + reversed: reversed, + ); + + debugPrint( + "_updated FROM: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$fromAmount _toAmount=$toAmount rate:$rate"); + if (shouldNotifyListeners) { + notifyListeners(); + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + } + } + + Future updateMarket( + FixedRateMarket? market, + bool shouldNotifyListeners, + ) async { + _market = market; + + if (_market == null) { + fromAmount = null; + toAmount = null; + } else { + if (fromAmount != null) { + if (fromAmount! <= Decimal.zero) { + toAmount = Decimal.zero; + } else { + await updateRanges(shouldNotifyListeners: false); + await updateEstimate( + shouldNotifyListeners: false, + reversed: reversed, + ); + } + } + } + + if (shouldNotifyListeners) { + notifyListeners(); + } + } + + void _onExchangeRateTypeChanged() { + print("_onExchangeRateTypeChanged"); + } + + void _onExchangeTypeChanged() { + print("_onExchangeTypeChanged"); + } + + Future updateRanges({required bool shouldNotifyListeners}) async { + final _fromTicker = reversed ? toTicker : fromTicker; + final _toTicker = reversed ? fromTicker : toTicker; + if (_fromTicker == null || _toTicker == null) { + Logging.instance.log( + "Tried to $runtimeType.updateRanges where (from: $_fromTicker || to: $_toTicker)", + level: LogLevel.Info, + ); + return; + } + final response = await exchange?.getRange( + _fromTicker, + _toTicker, + exchangeType == ExchangeRateType.fixed, + ); + + if (response?.value == null) { + Logging.instance.log( + "Tried to $runtimeType.updateRanges where response: $response", + level: LogLevel.Info, + ); + return; + } + + final range = response!.value!; + + minAmount = range.min; + maxAmount = range.max; + + debugPrint("updated range for $_fromTicker-$_toTicker: $range"); + + if (shouldNotifyListeners) { + notifyListeners(); + } + } + + Future updateEstimate({ + required bool shouldNotifyListeners, + required bool reversed, + }) async { + final amount = reversed ? toAmount : fromAmount; + if (fromTicker == null || + toTicker == null || + amount == null || + amount <= Decimal.zero) { + Logging.instance.log( + "Tried to $runtimeType.updateEstimate where (from: $fromTicker || to: $toTicker || amount: $amount)", + level: LogLevel.Info, + ); + return; + } + final response = await exchange?.getEstimate( + fromTicker!, + toTicker!, + amount, + exchangeType == ExchangeRateType.fixed, + reversed, + ); + + if (response?.value == null) { + Logging.instance.log( + "Tried to $runtimeType.updateEstimate where response: $response", + level: LogLevel.Info, + ); + return; + } + + estimate = response!.value!; + + if (reversed) { + fromAmount = estimate!.estimatedAmount; + } else { + toAmount = estimate!.estimatedAmount; + } + + rate = (toAmount! / fromAmount!).toDecimal(scaleOnInfinitePrecision: 12); + + debugPrint("updated estimate for $fromTicker-$toTicker: $estimate"); + + if (shouldNotifyListeners) { + notifyListeners(); + } + } + + void setOnError({ + required void Function(String)? onError, + bool shouldNotifyListeners = false, + }) { + _onError = onError; + if (shouldNotifyListeners) { + notifyListeners(); + } + } + + Future swap({FixedRateMarket? market}) async { + final Decimal? newToAmount = fromAmount; + final Decimal? newFromAmount = toAmount; + + fromAmount = newFromAmount; + toAmount = newToAmount; + + minAmount = null; + maxAmount = null; + + switch (exchangeType) { + case ExchangeRateType.estimated: + final Currency? newTo = from; + final Currency? newFrom = to; + + _to = newTo; + _from = newFrom; + + await updateRanges(shouldNotifyListeners: false); + + await updateEstimate( + shouldNotifyListeners: false, + reversed: reversed, + ); + break; + case ExchangeRateType.fixed: + await updateMarket(market, false); + break; + } + + notifyListeners(); + } +} diff --git a/lib/models/exchange/fixed_rate_exchange_form_state.dart b/lib/models/exchange/fixed_rate_exchange_form_state.dart index 65104ef8b0..fcb686a730 100644 --- a/lib/models/exchange/fixed_rate_exchange_form_state.dart +++ b/lib/models/exchange/fixed_rate_exchange_form_state.dart @@ -1,179 +1,179 @@ -import 'package:decimal/decimal.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; -import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; -import 'package:stackwallet/utilities/logger.dart'; - -class FixedRateExchangeFormState extends ChangeNotifier { - Decimal? _fromAmount; - Decimal? _toAmount; - - FixedRateMarket? _market; - FixedRateMarket? get market => _market; - - CNExchangeEstimate? _estimate; - CNExchangeEstimate? get estimate => _estimate; - - Decimal? get rate { - if (_estimate == null) { - return null; - } else { - return (_estimate!.toAmount / _estimate!.fromAmount) - .toDecimal(scaleOnInfinitePrecision: 12); - } - } - - Future swap(FixedRateMarket reverseFixedRateMarket) async { - final Decimal? tmp = _fromAmount; - _fromAmount = _toAmount; - _toAmount = tmp; - - await updateMarket(reverseFixedRateMarket, false); - await updateRateEstimate(CNEstimateType.direct); - _toAmount = _estimate?.toAmount ?? Decimal.zero; - notifyListeners(); - } - - String get fromAmountString => - _fromAmount == null ? "" : _fromAmount!.toStringAsFixed(8); - String get toAmountString => - _toAmount == null ? "" : _toAmount!.toStringAsFixed(8); - - Future updateMarket( - FixedRateMarket? market, - bool shouldNotifyListeners, - ) async { - _market = market; - - if (_market == null) { - _fromAmount = null; - _toAmount = null; - } else { - if (_fromAmount != null) { - if (_fromAmount! <= Decimal.zero) { - _toAmount = Decimal.zero; - } else { - await updateRateEstimate(CNEstimateType.direct); - } - } - } - - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - String get rateDisplayString { - if (_market == null || _estimate == null) { - return "N/A"; - } else { - return "1 ${_estimate!.fromCurrency.toUpperCase()} ~${rate!.toStringAsFixed(8)} ${_estimate!.toCurrency.toUpperCase()}"; - } - } - - bool get canExchange { - return _market != null && - _fromAmount != null && - _toAmount != null && - sendAmountWarning.isEmpty; - } - - String get sendAmountWarning { - if (_market != null && _fromAmount != null) { - if (_fromAmount! < _market!.min) { - return "Minimum amount ${_market!.min.toString()} ${_market!.from.toUpperCase()}"; - } else if (_fromAmount! > _market!.max) { - return "Maximum amount ${_market!.max.toString()} ${_market!.from.toUpperCase()}"; - } - } - - return ""; - } - - Future setToAmountAndCalculateFromAmount( - Decimal newToAmount, - bool shouldNotifyListeners, - ) async { - _toAmount = newToAmount; - - if (shouldNotifyListeners) { - await updateRateEstimate(CNEstimateType.reverse); - notifyListeners(); - } - } - - Future setFromAmountAndCalculateToAmount( - Decimal newFromAmount, - bool shouldNotifyListeners, - ) async { - _fromAmount = newFromAmount; - - if (shouldNotifyListeners) { - await updateRateEstimate(CNEstimateType.direct); - notifyListeners(); - } - } - - void Function(String)? _onError; - - void setOnError({ - required void Function(String)? onError, - bool shouldNotifyListeners = false, - }) { - _onError = onError; - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - Future updateRateEstimate(CNEstimateType direction) async { - if (market != null) { - Decimal? amount; - // set amount based on trade estimate direction - switch (direction) { - case CNEstimateType.direct: - if (_fromAmount != null - // && - // market!.min >= _fromAmount! && - // _fromAmount! <= market!.max - ) { - amount = _fromAmount!; - } - break; - case CNEstimateType.reverse: - if (_toAmount != null - // && - // market!.min >= _toAmount! && - // _toAmount! <= market!.max - ) { - amount = _toAmount!; - } - break; - } - - if (amount != null && market != null && amount > Decimal.zero) { - final response = - await ChangeNowAPI.instance.getEstimatedExchangeAmountV2( - fromTicker: market!.from, - toTicker: market!.to, - fromOrTo: direction, - flow: CNFlowType.fixedRate, - amount: amount, - ); - - if (response.value != null) { - // update estimate if response succeeded - _estimate = response.value; - - _toAmount = _estimate?.toAmount; - _fromAmount = _estimate?.fromAmount; - notifyListeners(); - } else if (response.exception != null) { - Logging.instance.log("updateRateEstimate(): ${response.exception}", - level: LogLevel.Warning); - } - } - } - } -} +// import 'package:decimal/decimal.dart'; +// import 'package:flutter/cupertino.dart'; +// import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; +// import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; +// import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; +// import 'package:stackwallet/utilities/logger.dart'; +// +// class FixedRateExchangeFormState extends ChangeNotifier { +// Decimal? _fromAmount; +// Decimal? _toAmount; +// +// FixedRateMarket? _market; +// FixedRateMarket? get market => _market; +// +// CNExchangeEstimate? _estimate; +// CNExchangeEstimate? get estimate => _estimate; +// +// Decimal? get rate { +// if (_estimate == null) { +// return null; +// } else { +// return (_estimate!.toAmount / _estimate!.fromAmount) +// .toDecimal(scaleOnInfinitePrecision: 12); +// } +// } +// +// Future swap(FixedRateMarket reverseFixedRateMarket) async { +// final Decimal? tmp = _fromAmount; +// _fromAmount = _toAmount; +// _toAmount = tmp; +// +// await updateMarket(reverseFixedRateMarket, false); +// await updateRateEstimate(CNEstimateType.direct); +// _toAmount = _estimate?.toAmount ?? Decimal.zero; +// notifyListeners(); +// } +// +// String get fromAmountString => +// _fromAmount == null ? "" : _fromAmount!.toStringAsFixed(8); +// String get toAmountString => +// _toAmount == null ? "" : _toAmount!.toStringAsFixed(8); +// +// Future updateMarket( +// FixedRateMarket? market, +// bool shouldNotifyListeners, +// ) async { +// _market = market; +// +// if (_market == null) { +// _fromAmount = null; +// _toAmount = null; +// } else { +// if (_fromAmount != null) { +// if (_fromAmount! <= Decimal.zero) { +// _toAmount = Decimal.zero; +// } else { +// await updateRateEstimate(CNEstimateType.direct); +// } +// } +// } +// +// if (shouldNotifyListeners) { +// notifyListeners(); +// } +// } +// +// String get rateDisplayString { +// if (_market == null || _estimate == null) { +// return "N/A"; +// } else { +// return "1 ${_estimate!.fromCurrency.toUpperCase()} ~${rate!.toStringAsFixed(8)} ${_estimate!.toCurrency.toUpperCase()}"; +// } +// } +// +// bool get canExchange { +// return _market != null && +// _fromAmount != null && +// _toAmount != null && +// sendAmountWarning.isEmpty; +// } +// +// String get sendAmountWarning { +// if (_market != null && _fromAmount != null) { +// if (_fromAmount! < _market!.min) { +// return "Minimum amount ${_market!.min.toString()} ${_market!.from.toUpperCase()}"; +// } else if (_fromAmount! > _market!.max) { +// return "Maximum amount ${_market!.max.toString()} ${_market!.from.toUpperCase()}"; +// } +// } +// +// return ""; +// } +// +// Future setToAmountAndCalculateFromAmount( +// Decimal newToAmount, +// bool shouldNotifyListeners, +// ) async { +// _toAmount = newToAmount; +// +// if (shouldNotifyListeners) { +// await updateRateEstimate(CNEstimateType.reverse); +// notifyListeners(); +// } +// } +// +// Future setFromAmountAndCalculateToAmount( +// Decimal newFromAmount, +// bool shouldNotifyListeners, +// ) async { +// _fromAmount = newFromAmount; +// +// if (shouldNotifyListeners) { +// await updateRateEstimate(CNEstimateType.direct); +// notifyListeners(); +// } +// } +// +// void Function(String)? _onError; +// +// void setOnError({ +// required void Function(String)? onError, +// bool shouldNotifyListeners = false, +// }) { +// _onError = onError; +// if (shouldNotifyListeners) { +// notifyListeners(); +// } +// } +// +// Future updateRateEstimate(CNEstimateType direction) async { +// if (market != null) { +// Decimal? amount; +// // set amount based on trade estimate direction +// switch (direction) { +// case CNEstimateType.direct: +// if (_fromAmount != null +// // && +// // market!.min >= _fromAmount! && +// // _fromAmount! <= market!.max +// ) { +// amount = _fromAmount!; +// } +// break; +// case CNEstimateType.reverse: +// if (_toAmount != null +// // && +// // market!.min >= _toAmount! && +// // _toAmount! <= market!.max +// ) { +// amount = _toAmount!; +// } +// break; +// } +// +// if (amount != null && market != null && amount > Decimal.zero) { +// final response = +// await ChangeNowAPI.instance.getEstimatedExchangeAmountV2( +// fromTicker: market!.from, +// toTicker: market!.to, +// fromOrTo: direction, +// flow: CNFlowType.fixedRate, +// amount: amount, +// ); +// +// if (response.value != null) { +// // update estimate if response succeeded +// _estimate = response.value; +// +// _toAmount = _estimate?.toAmount; +// _fromAmount = _estimate?.fromAmount; +// notifyListeners(); +// } else if (response.exception != null) { +// Logging.instance.log("updateRateEstimate(): ${response.exception}", +// level: LogLevel.Warning); +// } +// } +// } +// } +// } diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index b1bb739b6b..62ea9d34b9 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -6,6 +6,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/exchange/exchange_form_state.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; @@ -21,9 +22,7 @@ import 'package:stackwallet/pages/exchange_view/sub_widgets/rate_type_toggle.dar import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; import 'package:stackwallet/providers/exchange/change_now_provider.dart'; -import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart'; import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; -import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart'; import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -62,42 +61,27 @@ class _ExchangeFormState extends ConsumerState { final FocusNode _receiveFocusNode = FocusNode(); bool _swapLock = false; - bool _reversed = false; void sendFieldOnChanged(String value) async { final newFromAmount = Decimal.tryParse(value); - final isEstimated = - ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated; - if (newFromAmount != null) { - if (isEstimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(newFromAmount, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(newFromAmount, false); - } - } else { - if (isEstimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(Decimal.zero, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(Decimal.zero, false); - } - _receiveController.text = isEstimated ? "-" : ""; + + ref.read(exchangeFormStateProvider).fromAmount = + newFromAmount ?? Decimal.zero; + + if (newFromAmount == null) { + _receiveController.text = + ref.read(prefsChangeNotifierProvider).exchangeRateType == + ExchangeRateType.estimated + ? "-" + : ""; } } void selectSendCurrency() async { if (ref.read(prefsChangeNotifierProvider).exchangeRateType == ExchangeRateType.estimated) { - final fromTicker = - ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? "-"; + final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "-"; + // ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? "-"; if (walletInitiated && fromTicker.toLowerCase() == coin!.ticker.toLowerCase()) { @@ -108,16 +92,13 @@ class _ExchangeFormState extends ConsumerState { await _showFloatingRateSelectionSheet( currencies: ref.read(availableChangeNowCurrenciesStateProvider.state).state, - excludedTicker: - ref.read(estimatedRateExchangeFormProvider).to?.ticker ?? "-", + excludedTicker: ref.read(exchangeFormStateProvider).toTicker ?? "-", fromTicker: fromTicker, - onSelected: (from) => ref - .read(estimatedRateExchangeFormProvider) - .updateFrom(from, true)); + onSelected: (from) => + ref.read(exchangeFormStateProvider).updateFrom(from, true)); } else { - final toTicker = ref.read(fixedRateExchangeFormProvider).market?.to ?? ""; - final fromTicker = - ref.read(fixedRateExchangeFormProvider).market?.from ?? ""; + final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; + final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; if (walletInitiated && fromTicker.toLowerCase() == coin!.ticker.toLowerCase()) { @@ -138,7 +119,7 @@ class _ExchangeFormState extends ConsumerState { ); await ref - .read(fixedRateExchangeFormProvider) + .read(exchangeFormStateProvider) .updateMarket(market, true); } catch (e) { unawaited(showDialog( @@ -158,8 +139,7 @@ class _ExchangeFormState extends ConsumerState { void selectReceiveCurrency() async { if (ref.read(prefsChangeNotifierProvider).exchangeRateType == ExchangeRateType.estimated) { - final toTicker = - ref.read(estimatedRateExchangeFormProvider).to?.ticker ?? ""; + final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; if (walletInitiated && toTicker.toLowerCase() == coin!.ticker.toLowerCase()) { @@ -170,16 +150,13 @@ class _ExchangeFormState extends ConsumerState { await _showFloatingRateSelectionSheet( currencies: ref.read(availableChangeNowCurrenciesStateProvider.state).state, - excludedTicker: - ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? "", - fromTicker: - ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? "", + excludedTicker: ref.read(exchangeFormStateProvider).fromTicker ?? "", + fromTicker: ref.read(exchangeFormStateProvider).fromTicker ?? "", onSelected: (to) => - ref.read(estimatedRateExchangeFormProvider).updateTo(to, true)); + ref.read(exchangeFormStateProvider).updateTo(to, true)); } else { - final fromTicker = - ref.read(fixedRateExchangeFormProvider).market?.from ?? ""; - final toTicker = ref.read(fixedRateExchangeFormProvider).market?.to ?? ""; + final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; + final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; if (walletInitiated && toTicker.toLowerCase() == coin!.ticker.toLowerCase()) { @@ -200,7 +177,7 @@ class _ExchangeFormState extends ConsumerState { ); await ref - .read(fixedRateExchangeFormProvider) + .read(exchangeFormStateProvider) .updateMarket(market, true); } catch (e) { unawaited(showDialog( @@ -222,28 +199,11 @@ class _ExchangeFormState extends ConsumerState { final isEstimated = ref.read(prefsChangeNotifierProvider).exchangeRateType == ExchangeRateType.estimated; - if (newToAmount != null) { - if (isEstimated) { - // await ref - // .read(estimatedRateExchangeFormProvider) - // .setToAmountAndCalculateFromAmount( - // newToAmount, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount(newToAmount, false); - } - } else { - if (isEstimated) { - // await ref - // .read(estimatedRateExchangeFormProvider) - // .setToAmountAndCalculateFromAmount( - // Decimal.zero, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount(Decimal.zero, false); - } + if (!isEstimated) { + ref.read(exchangeFormStateProvider).toAmount = + newToAmount ?? Decimal.zero; + } + if (newToAmount == null) { _sendController.text = ""; } } @@ -276,10 +236,10 @@ class _ExchangeFormState extends ConsumerState { if (ref.watch(prefsChangeNotifierProvider .select((pref) => pref.exchangeRateType)) == ExchangeRateType.estimated) { - await ref.read(estimatedRateExchangeFormProvider).swap(); + await ref.read(exchangeFormStateProvider).swap(); } else { - final from = ref.read(fixedRateExchangeFormProvider).market?.from; - final to = ref.read(fixedRateExchangeFormProvider).market?.to; + final from = ref.read(exchangeFormStateProvider).fromTicker; + final to = ref.read(exchangeFormStateProvider).toTicker; if (to != null && from != null) { final markets = ref @@ -288,7 +248,7 @@ class _ExchangeFormState extends ConsumerState { .where((e) => e.from == to && e.to == from); if (markets.isNotEmpty) { - await ref.read(fixedRateExchangeFormProvider).swap(markets.first); + await ref.read(exchangeFormStateProvider).swap(market: markets.first); } } } @@ -351,7 +311,7 @@ class _ExchangeFormState extends ConsumerState { } } - String? _fetchIconUrlFromTickerForFixedRateFlow(String? ticker) { + String? _fetchIconUrlFromTicker(String? ticker) { if (ticker == null) return null; final possibleCurrencies = ref @@ -417,15 +377,35 @@ class _ExchangeFormState extends ConsumerState { void onRateTypeChanged(ExchangeRateType rateType) async { _receiveFocusNode.unfocus(); _sendFocusNode.unfocus(); + + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => WillPopScope( + onWillPop: () async => false, + child: Container( + color: Theme.of(context) + .extension()! + .overlay + .withOpacity(0.6), + child: const CustomLoadingOverlay( + message: "Updating exchange rate", + eventBus: null, + ), + ), + ), + ), + ); + + final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "-"; + final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "-"; + + ref.read(exchangeFormStateProvider).exchangeType = rateType; + ref.read(exchangeFormStateProvider).reversed = false; switch (rateType) { case ExchangeRateType.estimated: - final market = ref.read(fixedRateExchangeFormProvider).market; - final fromTicker = market?.from ?? ""; - final toTicker = market?.to ?? ""; - if (!(fromTicker.isEmpty || - toTicker.isEmpty || - toTicker == "-" || - fromTicker == "-")) { + if (!(toTicker == "-" || fromTicker == "-")) { final available = ref .read(availableFloatingRatePairsStateProvider.state) .state @@ -442,28 +422,29 @@ class _ExchangeFormState extends ConsumerState { availableCurrencies.firstWhere((e) => e.ticker == toTicker); final newFromAmount = Decimal.tryParse(_sendController.text); - if (newFromAmount != null) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(newFromAmount, false); - } else { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(Decimal.zero, false); - + ref.read(exchangeFormStateProvider).fromAmount = + newFromAmount ?? Decimal.zero; + if (newFromAmount == null) { _receiveController.text = ""; } - await ref - .read(estimatedRateExchangeFormProvider) - .updateTo(to, false); - await ref - .read(estimatedRateExchangeFormProvider) - .updateFrom(from, true); + await ref.read(exchangeFormStateProvider).updateTo(to, false); + await ref.read(exchangeFormStateProvider).updateFrom(from, true); + + _receiveController.text = + ref.read(exchangeFormStateProvider).toAmountString.isEmpty + ? "-" + : ref.read(exchangeFormStateProvider).toAmountString; + if (mounted) { + Navigator.of(context).pop(); + } return; } } } + if (mounted) { + Navigator.of(context).pop(); + } unawaited(showFloatingFlushBar( type: FlushBarType.warning, message: @@ -472,14 +453,7 @@ class _ExchangeFormState extends ConsumerState { )); break; case ExchangeRateType.fixed: - final fromTicker = - ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? ""; - final toTicker = - ref.read(estimatedRateExchangeFormProvider).to?.ticker ?? ""; - if (!(fromTicker.isEmpty || - toTicker.isEmpty || - toTicker == "-" || - fromTicker == "-")) { + if (!(toTicker == "-" || fromTicker == "-")) { FixedRateMarket? market; try { market = ref @@ -491,29 +465,28 @@ class _ExchangeFormState extends ConsumerState { } final newFromAmount = Decimal.tryParse(_sendController.text); - if (newFromAmount != null) { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(newFromAmount, false); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(Decimal.zero, false); + ref.read(exchangeFormStateProvider).fromAmount = + newFromAmount ?? Decimal.zero; + if (newFromAmount == null) { _receiveController.text = ""; } + await ref.read(exchangeFormStateProvider).updateMarket(market, false); await ref - .read(fixedRateExchangeFormProvider) - .updateMarket(market, false); - await ref - .read(fixedRateExchangeFormProvider) + .read(exchangeFormStateProvider) .setFromAmountAndCalculateToAmount( Decimal.tryParse(_sendController.text) ?? Decimal.zero, true, ); + if (mounted) { + Navigator.of(context).pop(); + } return; } + if (mounted) { + Navigator.of(context).pop(); + } unawaited(showFloatingFlushBar( type: FlushBarType.warning, message: @@ -527,10 +500,8 @@ class _ExchangeFormState extends ConsumerState { void onExchangePressed() async { if (ref.read(prefsChangeNotifierProvider).exchangeRateType == ExchangeRateType.estimated) { - final fromTicker = - ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? ""; - final toTicker = - ref.read(estimatedRateExchangeFormProvider).to?.ticker ?? ""; + final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; + final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; bool isAvailable = false; final availableFloatingPairs = @@ -555,8 +526,7 @@ class _ExchangeFormState extends ConsumerState { return; } - final sendAmount = Decimal.parse( - ref.read(estimatedRateExchangeFormProvider).fromAmountString); + final sendAmount = ref.read(exchangeFormStateProvider).fromAmount!; final rateType = ref.read(prefsChangeNotifierProvider).exchangeRateType; @@ -610,12 +580,10 @@ class _ExchangeFormState extends ConsumerState { } } } else { - final fromTicker = - ref.read(fixedRateExchangeFormProvider).market?.from ?? ""; - final toTicker = ref.read(fixedRateExchangeFormProvider).market?.to ?? ""; + final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; + final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; - final sendAmount = Decimal.parse( - ref.read(fixedRateExchangeFormProvider).fromAmountString); + final sendAmount = ref.read(exchangeFormStateProvider).fromAmount!; final rateType = ref.read(prefsChangeNotifierProvider).exchangeRateType; @@ -683,7 +651,7 @@ class _ExchangeFormState extends ConsumerState { } String rate = - "1 ${fromTicker.toUpperCase()} ~${ref.read(fixedRateExchangeFormProvider).rate!.toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + "1 ${fromTicker.toUpperCase()} ~${ref.read(exchangeFormStateProvider).rate!.toStringAsFixed(8)} ${toTicker.toUpperCase()}"; final model = IncompleteExchangeModel( sendTicker: fromTicker, @@ -721,21 +689,10 @@ class _ExchangeFormState extends ConsumerState { String? ticker; - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - if (isSend) { - ticker = ref.watch(estimatedRateExchangeFormProvider - .select((value) => value.from?.ticker)); - } else { - ticker = ref.watch(estimatedRateExchangeFormProvider - .select((value) => value.to?.ticker)); - } + if (isSend) { + ticker = ref.read(exchangeFormStateProvider).fromTicker; } else { - if (isSend) { - ticker = ref.read(fixedRateExchangeFormProvider).market?.from; - } else { - ticker = ref.read(fixedRateExchangeFormProvider).market?.to; - } + ticker = ref.read(exchangeFormStateProvider).toTicker; } if (ticker == null) { @@ -756,46 +713,29 @@ class _ExchangeFormState extends ConsumerState { if (walletInitiated) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - ref.read(estimatedRateExchangeFormProvider).clearAmounts(true); + ref.read(exchangeFormStateProvider).clearAmounts(true); // ref.read(fixedRateExchangeFormProvider); }); } else { final isEstimated = ref.read(prefsChangeNotifierProvider).exchangeRateType == ExchangeRateType.estimated; - _sendController.text = isEstimated - ? ref.read(estimatedRateExchangeFormProvider).fromAmountString - : ref.read(fixedRateExchangeFormProvider).fromAmountString; + _sendController.text = + ref.read(exchangeFormStateProvider).fromAmountString; _receiveController.text = isEstimated ? "-" //ref.read(estimatedRateExchangeFormProvider).toAmountString - : ref.read(fixedRateExchangeFormProvider).toAmountString; + : ref.read(exchangeFormStateProvider).toAmountString; } _sendFocusNode.addListener(() async { if (!_sendFocusNode.hasFocus) { final newFromAmount = Decimal.tryParse(_sendController.text); - if (newFromAmount != null) { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(newFromAmount, true); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(newFromAmount, true); - } - } else { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(Decimal.zero, true); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setFromAmountAndCalculateToAmount(Decimal.zero, true); - } + await ref + .read(exchangeFormStateProvider) + .setFromAmountAndCalculateToAmount( + newFromAmount ?? Decimal.zero, true); + + if (newFromAmount == null) { _receiveController.text = ref.read(prefsChangeNotifierProvider).exchangeRateType == ExchangeRateType.estimated @@ -807,28 +747,14 @@ class _ExchangeFormState extends ConsumerState { _receiveFocusNode.addListener(() async { if (!_receiveFocusNode.hasFocus) { final newToAmount = Decimal.tryParse(_receiveController.text); - if (newToAmount != null) { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - // await ref - // .read(estimatedRateExchangeFormProvider) - // .setToAmountAndCalculateFromAmount(newToAmount, true); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount(newToAmount, true); - } - } else { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - // await ref - // .read(estimatedRateExchangeFormProvider) - // .setToAmountAndCalculateFromAmount(Decimal.zero, true); - } else { - await ref - .read(fixedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount(Decimal.zero, true); - } + if (ref.read(prefsChangeNotifierProvider).exchangeRateType != + ExchangeRateType.estimated) { + await ref + .read(exchangeFormStateProvider) + .setToAmountAndCalculateFromAmount( + newToAmount ?? Decimal.zero, true); + } + if (newToAmount == null) { _sendController.text = ""; } } @@ -853,39 +779,29 @@ class _ExchangeFormState extends ConsumerState { ExchangeRateType.estimated; ref.listen( - isEstimated - ? estimatedRateExchangeFormProvider - .select((value) => value.toAmountString) - : fixedRateExchangeFormProvider.select( - (value) => value.toAmountString), (previous, String next) { + exchangeFormStateProvider.select((value) => value.toAmountString), + (previous, String next) { if (!_receiveFocusNode.hasFocus) { _receiveController.text = isEstimated && next.isEmpty ? "-" : next; debugPrint("RECEIVE AMOUNT LISTENER ACTIVATED"); if (_swapLock) { - _sendController.text = isEstimated - ? ref.read(estimatedRateExchangeFormProvider).fromAmountString - : ref.read(fixedRateExchangeFormProvider).fromAmountString; + _sendController.text = + ref.read(exchangeFormStateProvider).fromAmountString; } } }); ref.listen( - isEstimated - ? estimatedRateExchangeFormProvider - .select((value) => value.fromAmountString) - : fixedRateExchangeFormProvider.select( - (value) => value.fromAmountString), (previous, String next) { + exchangeFormStateProvider.select((value) => value.fromAmountString), + (previous, String next) { if (!_sendFocusNode.hasFocus) { _sendController.text = next; debugPrint("SEND AMOUNT LISTENER ACTIVATED"); if (_swapLock) { _receiveController.text = isEstimated - ? ref - .read(estimatedRateExchangeFormProvider) - .toAmountString - .isEmpty + ? ref.read(exchangeFormStateProvider).toAmountString.isEmpty ? "-" - : ref.read(estimatedRateExchangeFormProvider).toAmountString - : ref.read(fixedRateExchangeFormProvider).toAmountString; + : ref.read(exchangeFormStateProvider).toAmountString + : ref.read(exchangeFormStateProvider).toAmountString; } } }); @@ -956,20 +872,10 @@ class _ExchangeFormState extends ConsumerState { ), child: Builder( builder: (context) { - String? image; - if (ref.watch(prefsChangeNotifierProvider.select( - (value) => value.exchangeRateType)) == - ExchangeRateType.estimated) { - image = ref - .watch(estimatedRateExchangeFormProvider - .select((value) => value.from)) - ?.image; - } else { - image = _fetchIconUrlFromTickerForFixedRateFlow( - ref.watch( - fixedRateExchangeFormProvider.select( - (value) => value.market?.from))); - } + final image = _fetchIconUrlFromTicker(ref.watch( + exchangeFormStateProvider + .select((value) => value.fromTicker))); + if (image != null && image.isNotEmpty) { return Center( child: SvgPicture.network( @@ -1020,17 +926,9 @@ class _ExchangeFormState extends ConsumerState { width: 6, ), Text( - ref.watch(prefsChangeNotifierProvider.select( - (value) => value.exchangeRateType)) == - ExchangeRateType.estimated - ? ref.watch(estimatedRateExchangeFormProvider - .select((value) => - value.from?.ticker.toUpperCase())) ?? - "-" - : ref.watch(fixedRateExchangeFormProvider.select( - (value) => - value.market?.from.toUpperCase())) ?? - "-", + ref.watch(exchangeFormStateProvider.select((value) => + value.fromTicker?.toUpperCase())) ?? + "-", style: STextStyles.smallMed14(context).copyWith( color: Theme.of(context) .extension()! @@ -1105,14 +1003,13 @@ class _ExchangeFormState extends ConsumerState { ), Positioned.fill( child: Align( - alignment: Alignment.topRight, + alignment: ref.watch(exchangeFormStateProvider + .select((value) => value.reversed)) + ? Alignment.bottomRight + : Alignment.topRight, child: Text( - ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated - ? ref.watch(estimatedRateExchangeFormProvider - .select((value) => value.minimumSendWarning)) - : ref.watch(fixedRateExchangeFormProvider - .select((value) => value.sendAmountWarning)), + ref.watch(exchangeFormStateProvider + .select((value) => value.warning)), style: STextStyles.errorSmall(context), ), ), @@ -1179,19 +1076,10 @@ class _ExchangeFormState extends ConsumerState { ), child: Builder( builder: (context) { - String? image; - if (ref.watch(prefsChangeNotifierProvider.select( - (value) => value.exchangeRateType)) == - ExchangeRateType.estimated) { - image = ref - .watch(estimatedRateExchangeFormProvider - .select((value) => value.to)) - ?.image; - } else { - image = _fetchIconUrlFromTickerForFixedRateFlow( - ref.watch(fixedRateExchangeFormProvider - .select((value) => value.market?.to))); - } + final image = _fetchIconUrlFromTicker(ref.watch( + exchangeFormStateProvider + .select((value) => value.toTicker))); + if (image != null && image.isNotEmpty) { return Center( child: SvgPicture.network( @@ -1240,17 +1128,9 @@ class _ExchangeFormState extends ConsumerState { width: 6, ), Text( - ref.watch(prefsChangeNotifierProvider.select( - (value) => value.exchangeRateType)) == - ExchangeRateType.estimated - ? ref.watch(estimatedRateExchangeFormProvider - .select((value) => - value.to?.ticker.toUpperCase())) ?? - "-" - : ref.watch(fixedRateExchangeFormProvider.select( - (value) => - value.market?.to.toUpperCase())) ?? - "-", + ref.watch(exchangeFormStateProvider.select( + (value) => value.toTicker?.toUpperCase())) ?? + "-", style: STextStyles.smallMed14(context).copyWith( color: Theme.of(context) .extension()! @@ -1293,24 +1173,17 @@ class _ExchangeFormState extends ConsumerState { fixedRate: ref.watch(prefsChangeNotifierProvider .select((value) => value.exchangeRateType)) == ExchangeRateType.fixed, - reversed: _reversed, + reversed: ref.watch( + exchangeFormStateProvider.select((value) => value.reversed)), ), const SizedBox( height: 12, ), PrimaryButton( - enabled: ((ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) - ? ref.watch(estimatedRateExchangeFormProvider + enabled: ref.watch( + exchangeFormStateProvider.select((value) => value.canExchange)), + onPressed: ref.watch(exchangeFormStateProvider .select((value) => value.canExchange)) - : ref.watch(fixedRateExchangeFormProvider - .select((value) => value.canExchange))), - onPressed: ((ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) - ? ref.watch(estimatedRateExchangeFormProvider - .select((value) => value.canExchange)) - : ref.watch(fixedRateExchangeFormProvider - .select((value) => value.canExchange))) ? onExchangePressed : null, label: "Exchange", diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart index 0b0ff9eda0..1b66994f4a 100644 --- a/lib/pages/exchange_view/exchange_view.dart +++ b/lib/pages/exchange_view/exchange_view.dart @@ -2,22 +2,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/exchange_view/exchange_form.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; -import 'package:stackwallet/providers/exchange/changenow_initial_load_status.dart'; -import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart'; -import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart'; import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/trade_card.dart'; import 'package:tuple/tuple.dart'; @@ -191,137 +183,3 @@ class _ExchangeViewState extends ConsumerState { ); } } - -class RateInfo extends ConsumerWidget { - const RateInfo({ - Key? key, - required this.onChanged, - }) : super(key: key); - - final void Function(ExchangeRateType) onChanged; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final type = ref.watch( - prefsChangeNotifierProvider.select((pref) => pref.exchangeRateType)); - final isEstimated = type == ExchangeRateType.estimated; - - return Container( - decoration: BoxDecoration( - color: Theme.of(context).extension()!.popupBG, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: kFixedRateEnabled - ? () async { - if (isEstimated) { - if (ref - .read( - changeNowFixedInitialLoadStatusStateProvider - .state) - .state == - ChangeNowLoadStatus.loading) { - bool userPoppedDialog = false; - await showDialog( - context: context, - builder: (context) => Consumer( - builder: (context, ref, __) { - return StackOkDialog( - title: "Loading rate data...", - message: - "Performing initial fetch of ChangeNOW fixed rate market data", - onOkPressed: (value) { - userPoppedDialog = value == "OK"; - }, - ); - }, - ), - ); - if (ref - .read( - changeNowFixedInitialLoadStatusStateProvider - .state) - .state == - ChangeNowLoadStatus.loading) { - return; - } - } - } - - unawaited(showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - builder: (_) => const ExchangeRateSheet(), - ).then((value) { - if (value is ExchangeRateType && value != type) { - onChanged(value); - } - })); - } - : null, - style: Theme.of(context).textButtonTheme.style?.copyWith( - minimumSize: MaterialStateProperty.all( - const Size(0, 0), - ), - padding: MaterialStateProperty.all( - const EdgeInsets.all(2), - ), - backgroundColor: MaterialStateProperty.all( - Colors.transparent, - ), - ), - child: Row( - children: [ - Text( - isEstimated ? "Estimated rate" : "Fixed rate", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - width: 6, - ), - if (kFixedRateEnabled) - SvgPicture.asset( - Assets.svg.chevronDown, - width: 5, - height: 2.5, - color: Theme.of(context) - .extension()! - .infoItemLabel, - ), - ], - ), - ), - FittedBox( - fit: BoxFit.scaleDown, - child: ConstrainedBox( - constraints: const BoxConstraints( - minWidth: 1, - ), - child: Text( - isEstimated - ? ref.watch(estimatedRateExchangeFormProvider - .select((value) => value.rateDisplayString)) - : ref.watch(fixedRateExchangeFormProvider - .select((value) => value.rateDisplayString)), - style: STextStyles.itemSubtitle12(context), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart index 3563df5502..aebaf0da90 100644 --- a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart +++ b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart @@ -2,12 +2,11 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/exchange/exchange_form_state.dart'; import 'package:stackwallet/pages/exchange_view/exchange_view.dart'; import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; import 'package:stackwallet/providers/exchange/change_now_provider.dart'; -import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart'; -import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart'; import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; @@ -54,15 +53,15 @@ class _HomeViewButtonBarState extends ConsumerState { response2.value!; if (response.value!.length > 1) { - if (ref.read(estimatedRateExchangeFormProvider).from == null) { + if (ref.read(exchangeFormStateProvider).from == null) { if (response.value!.where((e) => e.ticker == "btc").isNotEmpty) { - await ref.read(estimatedRateExchangeFormProvider).updateFrom( + await ref.read(exchangeFormStateProvider).updateFrom( response.value!.firstWhere((e) => e.ticker == "btc"), true); } } - if (ref.read(estimatedRateExchangeFormProvider).to == null) { + if (ref.read(exchangeFormStateProvider).to == null) { if (response.value!.where((e) => e.ticker == "doge").isNotEmpty) { - await ref.read(estimatedRateExchangeFormProvider).updateTo( + await ref.read(exchangeFormStateProvider).updateTo( response.value!.firstWhere((e) => e.ticker == "doge"), true); } } @@ -97,13 +96,13 @@ class _HomeViewButtonBarState extends ConsumerState { ref.read(fixedRateMarketPairsStateProvider.state).state = response3.value!; - if (ref.read(fixedRateExchangeFormProvider).market == null) { + if (ref.read(exchangeFormStateProvider).market == null) { final matchingMarkets = response3.value!.where((e) => e.to == "doge" && e.from == "btc"); if (matchingMarkets.isNotEmpty) { await ref - .read(fixedRateExchangeFormProvider) + .read(exchangeFormStateProvider) .updateMarket(matchingMarkets.first, true); } } @@ -126,7 +125,7 @@ class _HomeViewButtonBarState extends ConsumerState { @override void initState() { - ref.read(estimatedRateExchangeFormProvider).setOnError( + ref.read(exchangeFormStateProvider).setOnError( onError: (String message) => showDialog( context: context, barrierDismissible: true, diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 1a1918557e..94e8c5fa69 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -5,6 +5,7 @@ import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/exchange/exchange_form_state.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart'; @@ -19,7 +20,6 @@ import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_navigation_bar. import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_summary.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart'; import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; -import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart'; import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; @@ -250,6 +250,8 @@ class _WalletViewState extends ConsumerState { final walletId = ref.read(managerProvider).walletId; ref.read(prefsChangeNotifierProvider).exchangeRateType = ExchangeRateType.estimated; + ref.read(exchangeFormStateProvider).exchangeType = + ExchangeRateType.estimated; final currencies = ref .read(availableChangeNowCurrenciesStateProvider.state) @@ -258,28 +260,31 @@ class _WalletViewState extends ConsumerState { element.ticker.toLowerCase() == coin.ticker.toLowerCase()); if (currencies.isNotEmpty) { - unawaited(ref - .read(estimatedRateExchangeFormProvider) - .updateFrom(currencies.first, false)); - unawaited(ref.read(estimatedRateExchangeFormProvider).updateTo( - ref - .read(availableChangeNowCurrenciesStateProvider.state) - .state - .firstWhere( - (element) => - element.ticker.toLowerCase() != coin.ticker.toLowerCase(), - ), - false)); + ref.read(exchangeFormStateProvider).setCurrencies( + currencies.first, + ref + .read(availableChangeNowCurrenciesStateProvider.state) + .state + .firstWhere( + (element) => + element.ticker.toLowerCase() != + coin.ticker.toLowerCase(), + ), + ); } - unawaited(Navigator.of(context).pushNamed( - WalletInitiatedExchangeView.routeName, - arguments: Tuple3( - walletId, - coin, - _loadCNData, - ), - )); + if (mounted) { + unawaited( + Navigator.of(context).pushNamed( + WalletInitiatedExchangeView.routeName, + arguments: Tuple3( + walletId, + coin, + _loadCNData, + ), + ), + ); + } } } diff --git a/lib/providers/exchange/estimate_rate_exchange_form_provider.dart b/lib/providers/exchange/estimate_rate_exchange_form_provider.dart index 9a59cb7348..59705b54e5 100644 --- a/lib/providers/exchange/estimate_rate_exchange_form_provider.dart +++ b/lib/providers/exchange/estimate_rate_exchange_form_provider.dart @@ -1,5 +1,5 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/estimated_rate_exchange_form_state.dart'; - -final estimatedRateExchangeFormProvider = - ChangeNotifierProvider((ref) => EstimatedRateExchangeFormState()); +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:stackwallet/models/exchange/estimated_rate_exchange_form_state.dart'; +// +// final estimatedRateExchangeFormProvider = +// ChangeNotifierProvider((ref) => EstimatedRateExchangeFormState()); diff --git a/lib/providers/exchange/fixed_rate_exchange_form_provider.dart b/lib/providers/exchange/fixed_rate_exchange_form_provider.dart index 81b063d336..7f0af1cf11 100644 --- a/lib/providers/exchange/fixed_rate_exchange_form_provider.dart +++ b/lib/providers/exchange/fixed_rate_exchange_form_provider.dart @@ -1,6 +1,6 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/fixed_rate_exchange_form_state.dart'; - -final fixedRateExchangeFormProvider = - ChangeNotifierProvider( - (ref) => FixedRateExchangeFormState()); +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:stackwallet/models/exchange/fixed_rate_exchange_form_state.dart'; +// +// final fixedRateExchangeFormProvider = +// ChangeNotifierProvider( +// (ref) => FixedRateExchangeFormState()); diff --git a/lib/services/exchange/change_now/change_now_loading_service.dart b/lib/services/exchange/change_now/change_now_loading_service.dart index d4412d864b..0d25bccee2 100644 --- a/lib/services/exchange/change_now/change_now_loading_service.dart +++ b/lib/services/exchange/change_now/change_now_loading_service.dart @@ -1,9 +1,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/exchange/exchange_form_state.dart'; import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; import 'package:stackwallet/providers/exchange/changenow_initial_load_status.dart'; -import 'package:stackwallet/providers/exchange/estimate_rate_exchange_form_provider.dart'; -import 'package:stackwallet/providers/exchange/fixed_rate_exchange_form_provider.dart'; import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -38,7 +37,7 @@ class ChangeNowLoadingService { ref.read(fixedRateMarketPairsStateProvider.state).state = response3.value!; - if (ref.read(fixedRateExchangeFormProvider).market == null) { + if (ref.read(exchangeFormStateProvider).market == null) { String fromTicker = "btc"; String toTicker = "xmr"; @@ -50,7 +49,7 @@ class ChangeNowLoadingService { .where((e) => e.to == toTicker && e.from == fromTicker); if (matchingMarkets.isNotEmpty) { await ref - .read(fixedRateExchangeFormProvider) + .read(exchangeFormStateProvider) .updateMarket(matchingMarkets.first, true); } } @@ -99,18 +98,18 @@ class ChangeNowLoadingService { } if (response.value!.length > 1) { - if (ref.read(estimatedRateExchangeFormProvider).from == null) { + if (ref.read(exchangeFormStateProvider).from == null) { if (response.value! .where((e) => e.ticker == fromTicker) .isNotEmpty) { - await ref.read(estimatedRateExchangeFormProvider).updateFrom( + await ref.read(exchangeFormStateProvider).updateFrom( response.value!.firstWhere((e) => e.ticker == fromTicker), false); } } - if (ref.read(estimatedRateExchangeFormProvider).to == null) { + if (ref.read(exchangeFormStateProvider).to == null) { if (response.value!.where((e) => e.ticker == toTicker).isNotEmpty) { - await ref.read(estimatedRateExchangeFormProvider).updateTo( + await ref.read(exchangeFormStateProvider).updateTo( response.value!.firstWhere((e) => e.ticker == toTicker), false); } diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.dart index 21af573eb7..88b73ab47d 100644 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.dart +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.dart @@ -1,219 +1,219 @@ -import 'package:decimal/decimal.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:stackwallet/models/exchange/estimated_rate_exchange_form_state.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; -import 'package:stackwallet/services/exchange/exchange_response.dart'; - -import 'estimated_rate_exchange_form_state_test.mocks.dart'; - -@GenerateMocks([ChangeNowAPI]) -void main() { - final currencyA = Currency( - ticker: "btc", - name: "Bitcoin", - image: "image.url", - hasExternalId: false, - isFiat: false, - featured: false, - isStable: true, - supportsFixedRate: true, - network: '', - ); - final currencyB = Currency( - ticker: "xmr", - name: "Monero", - image: "image.url", - hasExternalId: false, - isFiat: false, - featured: false, - isStable: true, - supportsFixedRate: true, - network: '', - ); - final currencyC = Currency( - ticker: "firo", - name: "Firo", - image: "image.url", - hasExternalId: false, - isFiat: false, - featured: false, - isStable: true, - supportsFixedRate: true, - network: '', - ); - - test("EstimatedRateExchangeFormState constructor", () async { - final state = EstimatedRateExchangeFormState(); - - expect(state.from, null); - expect(state.to, null); - expect(state.canExchange, false); - expect(state.rate, null); - expect(state.rateDisplayString, "N/A"); - expect(state.fromAmountString, ""); - expect(state.toAmountString, ""); - expect(state.minimumSendWarning, ""); - }); - - test("init EstimatedRateExchangeFormState", () async { - final state = EstimatedRateExchangeFormState(); - - await state.init(currencyA, currencyB); - - expect(state.from, currencyA); - expect(state.to, currencyB); - expect(state.canExchange, false); - expect(state.rate, null); - expect(state.rateDisplayString, "N/A"); - expect(state.fromAmountString, ""); - expect(state.toAmountString, ""); - expect(state.minimumSendWarning, ""); - }); - - test("updateTo on fresh state", () async { - final state = EstimatedRateExchangeFormState(); - - await state.updateTo(currencyA, false); - - expect(state.from, null); - expect(state.to, currencyA); - expect(state.canExchange, false); - expect(state.rate, null); - expect(state.rateDisplayString, "N/A"); - expect(state.fromAmountString, ""); - expect(state.toAmountString, ""); - expect(state.minimumSendWarning, ""); - }); - - test( - "updateTo after updateFrom where amounts are null and getMinimalExchangeAmount succeeds", - () async { - final cn = MockChangeNowAPI(); - - final state = EstimatedRateExchangeFormState(); - state.cnTesting = cn; - - when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) - .thenAnswer((_) async => ExchangeResponse(value: Decimal.fromInt(42))); - - await state.updateFrom(currencyA, true); - await state.updateTo(currencyB, true); - - expect(state.from, currencyA); - expect(state.to, currencyB); - expect(state.canExchange, false); - expect(state.rate, null); - expect(state.rateDisplayString, "N/A"); - expect(state.fromAmountString, ""); - expect(state.toAmountString, ""); - expect(state.minimumSendWarning, ""); - - verify(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) - .called(1); - }); - - test( - "updateTo after updateFrom where amounts are null and getMinimalExchangeAmount fails", - () async { - final cn = MockChangeNowAPI(); - - final state = EstimatedRateExchangeFormState(); - state.cnTesting = cn; - - when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) - .thenAnswer((_) async => ExchangeResponse()); - - await state.updateFrom(currencyA, true); - await state.updateTo(currencyB, true); - - expect(state.from, currencyA); - expect(state.to, currencyB); - expect(state.canExchange, false); - expect(state.rate, null); - expect(state.rateDisplayString, "N/A"); - expect(state.fromAmountString, ""); - expect(state.toAmountString, ""); - expect(state.minimumSendWarning, ""); - - verify(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) - .called(1); - }); - - test( - "updateTo after updateFrom and setFromAmountAndCalculateToAmount where fromAmount is less than the minimum required exchange amount", - () async { - final cn = MockChangeNowAPI(); - - final state = EstimatedRateExchangeFormState(); - state.cnTesting = cn; - - when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) - .thenAnswer((_) async => ExchangeResponse(value: Decimal.fromInt(42))); - - await state.updateFrom(currencyA, true); - await state.setFromAmountAndCalculateToAmount(Decimal.parse("10.10"), true); - await state.updateTo(currencyB, true); - - expect(state.from, currencyA); - expect(state.to, currencyB); - expect(state.canExchange, false); - expect(state.rate, null); - expect(state.rateDisplayString, "N/A"); - expect(state.fromAmountString, "10.10000000"); - expect(state.toAmountString, ""); - expect(state.minimumSendWarning, "Minimum amount 42 BTC"); - - verify(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) - .called(1); - }); - - test( - "updateTo after updateFrom and setFromAmountAndCalculateToAmount where fromAmount is greater than the minimum required exchange amount", - () async { - final cn = MockChangeNowAPI(); - - final state = EstimatedRateExchangeFormState(); - state.cnTesting = cn; - - when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) - .thenAnswer((_) async => ExchangeResponse(value: Decimal.fromInt(42))); - when(cn.getEstimatedExchangeAmount( - fromTicker: "btc", - toTicker: "xmr", - fromAmount: Decimal.parse("110.10"))) - .thenAnswer((_) async => ExchangeResponse( - value: Estimate( - reversed: false, - fixedRate: false, - rateId: 'some rate id', - warningMessage: '', - estimatedAmount: Decimal.parse("302.002348"), - ))); - - await state.updateFrom(currencyA, true); - await state.setFromAmountAndCalculateToAmount( - Decimal.parse("110.10"), true); - await state.updateTo(currencyB, true); - - expect(state.from, currencyA); - expect(state.to, currencyB); - expect(state.canExchange, true); - expect(state.rate, Decimal.parse("2.742982270663")); - expect(state.rateDisplayString, "1 BTC ~2.74298227 XMR"); - expect(state.fromAmountString, "110.10000000"); - expect(state.toAmountString, "302.00234800"); - expect(state.minimumSendWarning, ""); - - verify(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) - .called(1); - verify(cn.getEstimatedExchangeAmount( - fromTicker: "btc", - toTicker: "xmr", - fromAmount: Decimal.parse("110.10"))) - .called(1); - }); -} +// import 'package:decimal/decimal.dart'; +// import 'package:flutter_test/flutter_test.dart'; +// import 'package:mockito/annotations.dart'; +// import 'package:mockito/mockito.dart'; +// import 'package:stackwallet/models/exchange/estimated_rate_exchange_form_state.dart'; +// import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +// import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +// import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; +// import 'package:stackwallet/services/exchange/exchange_response.dart'; +// +// import 'estimated_rate_exchange_form_state_test.mocks.dart'; +// +// @GenerateMocks([ChangeNowAPI]) +// void main() { +// final currencyA = Currency( +// ticker: "btc", +// name: "Bitcoin", +// image: "image.url", +// hasExternalId: false, +// isFiat: false, +// featured: false, +// isStable: true, +// supportsFixedRate: true, +// network: '', +// ); +// final currencyB = Currency( +// ticker: "xmr", +// name: "Monero", +// image: "image.url", +// hasExternalId: false, +// isFiat: false, +// featured: false, +// isStable: true, +// supportsFixedRate: true, +// network: '', +// ); +// final currencyC = Currency( +// ticker: "firo", +// name: "Firo", +// image: "image.url", +// hasExternalId: false, +// isFiat: false, +// featured: false, +// isStable: true, +// supportsFixedRate: true, +// network: '', +// ); +// +// test("EstimatedRateExchangeFormState constructor", () async { +// final state = EstimatedRateExchangeFormState(); +// +// expect(state.from, null); +// expect(state.to, null); +// expect(state.canExchange, false); +// expect(state.rate, null); +// expect(state.rateDisplayString, "N/A"); +// expect(state.fromAmountString, ""); +// expect(state.toAmountString, ""); +// expect(state.minimumSendWarning, ""); +// }); +// +// test("init EstimatedRateExchangeFormState", () async { +// final state = EstimatedRateExchangeFormState(); +// +// await state.init(currencyA, currencyB); +// +// expect(state.from, currencyA); +// expect(state.to, currencyB); +// expect(state.canExchange, false); +// expect(state.rate, null); +// expect(state.rateDisplayString, "N/A"); +// expect(state.fromAmountString, ""); +// expect(state.toAmountString, ""); +// expect(state.minimumSendWarning, ""); +// }); +// +// test("updateTo on fresh state", () async { +// final state = EstimatedRateExchangeFormState(); +// +// await state.updateTo(currencyA, false); +// +// expect(state.from, null); +// expect(state.to, currencyA); +// expect(state.canExchange, false); +// expect(state.rate, null); +// expect(state.rateDisplayString, "N/A"); +// expect(state.fromAmountString, ""); +// expect(state.toAmountString, ""); +// expect(state.minimumSendWarning, ""); +// }); +// +// test( +// "updateTo after updateFrom where amounts are null and getMinimalExchangeAmount succeeds", +// () async { +// final cn = MockChangeNowAPI(); +// +// final state = EstimatedRateExchangeFormState(); +// state.cnTesting = cn; +// +// when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) +// .thenAnswer((_) async => ExchangeResponse(value: Decimal.fromInt(42))); +// +// await state.updateFrom(currencyA, true); +// await state.updateTo(currencyB, true); +// +// expect(state.from, currencyA); +// expect(state.to, currencyB); +// expect(state.canExchange, false); +// expect(state.rate, null); +// expect(state.rateDisplayString, "N/A"); +// expect(state.fromAmountString, ""); +// expect(state.toAmountString, ""); +// expect(state.minimumSendWarning, ""); +// +// verify(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) +// .called(1); +// }); +// +// test( +// "updateTo after updateFrom where amounts are null and getMinimalExchangeAmount fails", +// () async { +// final cn = MockChangeNowAPI(); +// +// final state = EstimatedRateExchangeFormState(); +// state.cnTesting = cn; +// +// when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) +// .thenAnswer((_) async => ExchangeResponse()); +// +// await state.updateFrom(currencyA, true); +// await state.updateTo(currencyB, true); +// +// expect(state.from, currencyA); +// expect(state.to, currencyB); +// expect(state.canExchange, false); +// expect(state.rate, null); +// expect(state.rateDisplayString, "N/A"); +// expect(state.fromAmountString, ""); +// expect(state.toAmountString, ""); +// expect(state.minimumSendWarning, ""); +// +// verify(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) +// .called(1); +// }); +// +// test( +// "updateTo after updateFrom and setFromAmountAndCalculateToAmount where fromAmount is less than the minimum required exchange amount", +// () async { +// final cn = MockChangeNowAPI(); +// +// final state = EstimatedRateExchangeFormState(); +// state.cnTesting = cn; +// +// when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) +// .thenAnswer((_) async => ExchangeResponse(value: Decimal.fromInt(42))); +// +// await state.updateFrom(currencyA, true); +// await state.setFromAmountAndCalculateToAmount(Decimal.parse("10.10"), true); +// await state.updateTo(currencyB, true); +// +// expect(state.from, currencyA); +// expect(state.to, currencyB); +// expect(state.canExchange, false); +// expect(state.rate, null); +// expect(state.rateDisplayString, "N/A"); +// expect(state.fromAmountString, "10.10000000"); +// expect(state.toAmountString, ""); +// expect(state.minimumSendWarning, "Minimum amount 42 BTC"); +// +// verify(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) +// .called(1); +// }); +// +// test( +// "updateTo after updateFrom and setFromAmountAndCalculateToAmount where fromAmount is greater than the minimum required exchange amount", +// () async { +// final cn = MockChangeNowAPI(); +// +// final state = EstimatedRateExchangeFormState(); +// state.cnTesting = cn; +// +// when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) +// .thenAnswer((_) async => ExchangeResponse(value: Decimal.fromInt(42))); +// when(cn.getEstimatedExchangeAmount( +// fromTicker: "btc", +// toTicker: "xmr", +// fromAmount: Decimal.parse("110.10"))) +// .thenAnswer((_) async => ExchangeResponse( +// value: Estimate( +// reversed: false, +// fixedRate: false, +// rateId: 'some rate id', +// warningMessage: '', +// estimatedAmount: Decimal.parse("302.002348"), +// ))); +// +// await state.updateFrom(currencyA, true); +// await state.setFromAmountAndCalculateToAmount( +// Decimal.parse("110.10"), true); +// await state.updateTo(currencyB, true); +// +// expect(state.from, currencyA); +// expect(state.to, currencyB); +// expect(state.canExchange, true); +// expect(state.rate, Decimal.parse("2.742982270663")); +// expect(state.rateDisplayString, "1 BTC ~2.74298227 XMR"); +// expect(state.fromAmountString, "110.10000000"); +// expect(state.toAmountString, "302.00234800"); +// expect(state.minimumSendWarning, ""); +// +// verify(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) +// .called(1); +// verify(cn.getEstimatedExchangeAmount( +// fromTicker: "btc", +// toTicker: "xmr", +// fromAmount: Decimal.parse("110.10"))) +// .called(1); +// }); +// } From a8396977ef10c577b715f2472a2361c781a86d23 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 3 Oct 2022 18:55:12 -0600 Subject: [PATCH 19/75] changenow reverse fixed rate trade support --- lib/models/exchange/incomplete_exchange.dart | 3 + .../exchange/response_objects/trade.dart | 5 +- lib/pages/exchange_view/exchange_form.dart | 61 +++++-------------- .../exchange_step_views/step_2_view.dart | 57 +++-------------- .../exchange_step_views/step_3_view.dart | 7 ++- .../helpers/restore_create_backup.dart | 4 +- .../exchange/change_now/change_now_api.dart | 12 +++- .../change_now/change_now_exchange.dart | 3 + lib/services/exchange/exchange.dart | 1 + .../simpleswap/simpleswap_exchange.dart | 1 + lib/utilities/db_version_migration.dart | 2 +- 11 files changed, 54 insertions(+), 102 deletions(-) diff --git a/lib/models/exchange/incomplete_exchange.dart b/lib/models/exchange/incomplete_exchange.dart index 7d71fde2df..58219dc5d5 100644 --- a/lib/models/exchange/incomplete_exchange.dart +++ b/lib/models/exchange/incomplete_exchange.dart @@ -13,6 +13,8 @@ class IncompleteExchangeModel { final ExchangeRateType rateType; + final bool reversed; + String? recipientAddress; String? refundAddress; @@ -27,6 +29,7 @@ class IncompleteExchangeModel { required this.sendAmount, required this.receiveAmount, required this.rateType, + required this.reversed, this.rateId, }); } diff --git a/lib/models/exchange/response_objects/trade.dart b/lib/models/exchange/response_objects/trade.dart index c1b494097f..7fc019aa97 100644 --- a/lib/models/exchange/response_objects/trade.dart +++ b/lib/models/exchange/response_objects/trade.dart @@ -202,12 +202,13 @@ class Trade { ); } - factory Trade.fromExchangeTransaction(ExchangeTransaction exTx) { + factory Trade.fromExchangeTransaction( + ExchangeTransaction exTx, bool reversed) { return Trade( uuid: exTx.uuid, tradeId: exTx.id, rateType: "", - direction: "direct", + direction: reversed ? "reverse" : "direct", timestamp: exTx.date, updatedAt: DateTime.tryParse(exTx.statusObject!.updatedAt) ?? exTx.date, payInCurrency: exTx.fromCurrency, diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 62ea9d34b9..24984ecbda 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -21,7 +21,6 @@ import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet. import 'package:stackwallet/pages/exchange_view/sub_widgets/rate_type_toggle.dart'; import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; -import 'package:stackwallet/providers/exchange/change_now_provider.dart'; import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; import 'package:stackwallet/providers/providers.dart'; @@ -530,37 +529,20 @@ class _ExchangeFormState extends ConsumerState { final rateType = ref.read(prefsChangeNotifierProvider).exchangeRateType; - final response = await ref.read(changeNowProvider).getEstimate( - fromTicker, - toTicker, - sendAmount, - false, - false, - ); - - if (response.value == null) { - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Failed to update trade estimate", - message: response.exception?.toString(), - ), - )); - return; - } + final estimate = ref.read(exchangeFormStateProvider).estimate!; String rate = - "1 ${fromTicker.toUpperCase()} ~${(response.value!.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + "1 ${fromTicker.toUpperCase()} ~${(estimate.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; final model = IncompleteExchangeModel( sendTicker: fromTicker.toUpperCase(), receiveTicker: toTicker.toUpperCase(), rateInfo: rate, sendAmount: sendAmount, - receiveAmount: response.value!.estimatedAmount, + receiveAmount: estimate.estimatedAmount, rateType: rateType, - rateId: response.value!.rateId, + rateId: estimate.rateId, + reversed: estimate.reversed, ); if (mounted) { @@ -587,35 +569,19 @@ class _ExchangeFormState extends ConsumerState { final rateType = ref.read(prefsChangeNotifierProvider).exchangeRateType; - final response = await ref.read(changeNowProvider).getEstimate( - fromTicker, - toTicker, - sendAmount, - true, - false, - ); + final estimate = ref.read(exchangeFormStateProvider).estimate!; bool? shouldCancel; - if (response.value == null) { - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Failed to update trade estimate", - message: response.exception?.toString(), - ), - )); - return; - } else if (response.value!.warningMessage != null && - response.value!.warningMessage!.isNotEmpty) { + if (estimate.warningMessage != null && + estimate.warningMessage!.isNotEmpty) { shouldCancel = await showDialog( context: context, barrierDismissible: true, builder: (_) => StackDialog( title: "Failed to update trade estimate", message: - "${response.value!.warningMessage!}\n\nDo you want to attempt trade anyways?", + "${estimate.warningMessage!}\n\nDo you want to attempt trade anyways?", leftButton: TextButton( style: Theme.of(context) .extension()! @@ -657,10 +623,13 @@ class _ExchangeFormState extends ConsumerState { sendTicker: fromTicker, receiveTicker: toTicker, rateInfo: rate, - sendAmount: sendAmount, - receiveAmount: response.value!.estimatedAmount, + sendAmount: estimate.reversed ? estimate.estimatedAmount : sendAmount, + receiveAmount: estimate.reversed + ? ref.read(exchangeFormStateProvider).toAmount! + : estimate.estimatedAmount, rateType: rateType, - rateId: response.value!.rateId, + rateId: estimate.rateId, + reversed: estimate.reversed, ); if (mounted) { diff --git a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart index 5681018de9..800d1e146a 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart @@ -348,23 +348,9 @@ class _Step2ViewState extends ConsumerState { "sendViewScanQrButtonKey"), onTap: () async { try { - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = false; final qrResult = await scanner.scan(); - // Future.delayed( - // const Duration(seconds: 2), - // () => ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true, - // ); - final results = AddressUtils.parseUri( qrResult.rawContent); @@ -385,16 +371,10 @@ class _Step2ViewState extends ConsumerState { setState(() {}); } } on PlatformException catch (e, s) { - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true; - // here we ignore the exception caused by not giving permission - // to use the camera to scan a qr code Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning); + "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", + level: LogLevel.Warning, + ); } }, child: const QrCodeIcon(), @@ -585,23 +565,9 @@ class _Step2ViewState extends ConsumerState { "sendViewScanQrButtonKey"), onTap: () async { try { - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = false; final qrResult = await scanner.scan(); - // Future.delayed( - // const Duration(seconds: 2), - // () => ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true, - // ); - final results = AddressUtils.parseUri( qrResult.rawContent); @@ -622,16 +588,10 @@ class _Step2ViewState extends ConsumerState { setState(() {}); } } on PlatformException catch (e, s) { - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true; - // here we ignore the exception caused by not giving permission - // to use the camera to scan a qr code Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning); + "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", + level: LogLevel.Warning, + ); } }, child: const QrCodeIcon(), @@ -680,8 +640,9 @@ class _Step2ViewState extends ConsumerState { child: TextButton( onPressed: () { Navigator.of(context).pushNamed( - Step3View.routeName, - arguments: model); + Step3View.routeName, + arguments: model, + ); }, style: Theme.of(context) .extension()! diff --git a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart index 20b6740ad7..1858787db2 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 @@ -251,10 +251,15 @@ class _Step3ViewState extends ConsumerState { to: model.receiveTicker, fixedRate: model.rateType != ExchangeRateType.estimated, - amount: model.sendAmount, + amount: model.reversed + ? model.receiveAmount + : model.sendAmount, addressTo: model.recipientAddress!, + extraId: null, addressRefund: model.refundAddress!, refundExtraId: "", + rateId: model.rateId, + reversed: model.reversed, ); if (response.value == null) { diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index dac0b37d66..bf964fec42 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -1198,7 +1198,7 @@ abstract class SWB { Trade trade; if (exTx != null) { - trade = Trade.fromExchangeTransaction(exTx); + trade = Trade.fromExchangeTransaction(exTx, false); } else { trade = Trade.fromMap(trades[i] as Map); } @@ -1220,7 +1220,7 @@ abstract class SWB { Trade trade; if (exTx != null) { - trade = Trade.fromExchangeTransaction(exTx); + trade = Trade.fromExchangeTransaction(exTx, false); } else { trade = Trade.fromMap(trades.last as Map); } diff --git a/lib/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart index cdf8e9852c..d957eaf1a6 100644 --- a/lib/services/exchange/change_now/change_now_api.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -678,6 +678,7 @@ class ChangeNowAPI { required String receivingAddress, required Decimal amount, required String rateId, + required bool reversed, String extraId = "", String userId = "", String contactEmail = "", @@ -689,7 +690,6 @@ class ChangeNowAPI { "from": fromTicker, "to": toTicker, "address": receivingAddress, - "amount": amount.toString(), "flow": "fixed-rate", "extraId": extraId, "userId": userId, @@ -699,8 +699,16 @@ class ChangeNowAPI { "rateId": rateId, }; + if (reversed) { + map["result"] = amount.toString(); + } else { + map["amount"] = amount.toString(); + } + final uri = _buildUri( - "/transactions/fixed-rate/${apiKey ?? kChangeNowApiKey}", null); + "/transactions/fixed-rate${reversed ? "/from-result" : ""}/${apiKey ?? kChangeNowApiKey}", + null, + ); try { // simple json object is expected here diff --git a/lib/services/exchange/change_now/change_now_exchange.dart b/lib/services/exchange/change_now/change_now_exchange.dart index ba6ce3f0fd..4042863d1e 100644 --- a/lib/services/exchange/change_now/change_now_exchange.dart +++ b/lib/services/exchange/change_now/change_now_exchange.dart @@ -27,6 +27,7 @@ class ChangeNowExchange extends Exchange { required String addressRefund, required String refundExtraId, String? rateId, + required bool reversed, }) async { late final ExchangeResponse response; if (fixedRate) { @@ -39,6 +40,7 @@ class ChangeNowExchange extends Exchange { extraId: extraId ?? "", refundAddress: addressRefund, refundExtraId: refundExtraId, + reversed: reversed, ); } else { response = await ChangeNowAPI.instance.createStandardExchangeTransaction( @@ -66,6 +68,7 @@ class ChangeNowExchange extends Exchange { response.value!.copyWith( statusObject: statusResponse.value!, ), + reversed, ), ); } diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart index c3210cddc3..88a9075649 100644 --- a/lib/services/exchange/exchange.dart +++ b/lib/services/exchange/exchange.dart @@ -47,5 +47,6 @@ abstract class Exchange { required String addressRefund, required String refundExtraId, String? rateId, + required bool reversed, }); } diff --git a/lib/services/exchange/simpleswap/simpleswap_exchange.dart b/lib/services/exchange/simpleswap/simpleswap_exchange.dart index 1fbfa4f3c9..4c8d7e9aae 100644 --- a/lib/services/exchange/simpleswap/simpleswap_exchange.dart +++ b/lib/services/exchange/simpleswap/simpleswap_exchange.dart @@ -25,6 +25,7 @@ class SimpleSwapExchange extends Exchange { required String addressRefund, required String refundExtraId, String? rateId, + required bool reversed, }) async { return await SimpleSwapAPI.instance.createNewExchange( isFixedRate: fixedRate, diff --git a/lib/utilities/db_version_migration.dart b/lib/utilities/db_version_migration.dart index e88a0cafb8..26af1c48a5 100644 --- a/lib/utilities/db_version_migration.dart +++ b/lib/utilities/db_version_migration.dart @@ -128,7 +128,7 @@ class DbVersionMigrator { for (final old in trades) { if (old.statusObject != null) { - final trade = Trade.fromExchangeTransaction(old); + final trade = Trade.fromExchangeTransaction(old, false); await DB.instance.put( boxName: DB.boxNameTradesV2, key: trade.uuid, From 3d5f6a8d12f020c12820f25c6b2697e3d3e3522b Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 3 Oct 2022 18:59:49 -0600 Subject: [PATCH 20/75] update mocks --- ...d_rate_exchange_form_state_test.mocks.dart | 249 ------------------ .../exchange/exchange_view_test.mocks.dart | 4 + test/services/change_now/change_now_test.dart | 3 + 3 files changed, 7 insertions(+), 249 deletions(-) delete mode 100644 test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart deleted file mode 100644 index 82a7eceb75..0000000000 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart +++ /dev/null @@ -1,249 +0,0 @@ -// Mocks generated by Mockito 5.2.0 from annotations -// in stackwallet/test/models/exchange/estimated_rate_exchange_form_state_test.dart. -// Do not manually edit this file. - -import 'dart:async' as _i5; - -import 'package:decimal/decimal.dart' as _i7; -import 'package:http/http.dart' as _i4; -import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' - as _i10; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' - as _i12; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' - as _i13; -import 'package:stackwallet/models/exchange/response_objects/currency.dart' - as _i6; -import 'package:stackwallet/models/exchange/response_objects/estimate.dart' - as _i9; -import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart' - as _i11; -import 'package:stackwallet/models/exchange/response_objects/pair.dart' as _i14; -import 'package:stackwallet/models/exchange/response_objects/range.dart' as _i8; -import 'package:stackwallet/services/exchange/change_now/change_now_api.dart' - as _i3; -import 'package:stackwallet/services/exchange/exchange_response.dart' as _i2; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types - -class _FakeExchangeResponse_0 extends _i1.Fake - implements _i2.ExchangeResponse {} - -/// A class which mocks [ChangeNowAPI]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockChangeNowAPI extends _i1.Mock implements _i3.ChangeNowAPI { - MockChangeNowAPI() { - _i1.throwOnMissingStub(this); - } - - @override - set client(_i4.Client? _client) => - super.noSuchMethod(Invocation.setter(#client, _client), - returnValueForMissingStub: null); - @override - _i5.Future<_i2.ExchangeResponse>> getAvailableCurrencies( - {bool? fixedRate, bool? active}) => - (super.noSuchMethod( - Invocation.method(#getAvailableCurrencies, [], - {#fixedRate: fixedRate, #active: active}), - returnValue: Future<_i2.ExchangeResponse>>.value( - _FakeExchangeResponse_0>())) as _i5 - .Future<_i2.ExchangeResponse>>); - @override - _i5.Future<_i2.ExchangeResponse>> getPairedCurrencies( - {String? ticker, bool? fixedRate}) => - (super.noSuchMethod( - Invocation.method(#getPairedCurrencies, [], - {#ticker: ticker, #fixedRate: fixedRate}), - returnValue: Future<_i2.ExchangeResponse>>.value( - _FakeExchangeResponse_0>())) as _i5 - .Future<_i2.ExchangeResponse>>); - @override - _i5.Future<_i2.ExchangeResponse<_i7.Decimal>> getMinimalExchangeAmount( - {String? fromTicker, String? toTicker, String? apiKey}) => - (super.noSuchMethod( - Invocation.method(#getMinimalExchangeAmount, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #apiKey: apiKey - }), - returnValue: Future<_i2.ExchangeResponse<_i7.Decimal>>.value( - _FakeExchangeResponse_0<_i7.Decimal>())) - as _i5.Future<_i2.ExchangeResponse<_i7.Decimal>>); - @override - _i5.Future<_i2.ExchangeResponse<_i8.Range>> getRange( - {String? fromTicker, - String? toTicker, - bool? isFixedRate, - String? apiKey}) => - (super.noSuchMethod( - Invocation.method(#getRange, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #isFixedRate: isFixedRate, - #apiKey: apiKey - }), - returnValue: Future<_i2.ExchangeResponse<_i8.Range>>.value( - _FakeExchangeResponse_0<_i8.Range>())) - as _i5.Future<_i2.ExchangeResponse<_i8.Range>>); - @override - _i5.Future<_i2.ExchangeResponse<_i9.Estimate>> getEstimatedExchangeAmount( - {String? fromTicker, - String? toTicker, - _i7.Decimal? fromAmount, - String? apiKey}) => - (super.noSuchMethod( - Invocation.method(#getEstimatedExchangeAmount, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #fromAmount: fromAmount, - #apiKey: apiKey - }), - returnValue: Future<_i2.ExchangeResponse<_i9.Estimate>>.value( - _FakeExchangeResponse_0<_i9.Estimate>())) - as _i5.Future<_i2.ExchangeResponse<_i9.Estimate>>); - @override - _i5.Future<_i2.ExchangeResponse<_i9.Estimate>> - getEstimatedExchangeAmountFixedRate( - {String? fromTicker, - String? toTicker, - _i7.Decimal? fromAmount, - bool? reversed, - String? apiKey}) => - (super.noSuchMethod( - Invocation.method(#getEstimatedExchangeAmountFixedRate, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #fromAmount: fromAmount, - #reversed: reversed, - #apiKey: apiKey - }), - returnValue: Future<_i2.ExchangeResponse<_i9.Estimate>>.value( - _FakeExchangeResponse_0<_i9.Estimate>())) - as _i5.Future<_i2.ExchangeResponse<_i9.Estimate>>); - @override - _i5.Future<_i2.ExchangeResponse<_i10.CNExchangeEstimate>> - getEstimatedExchangeAmountV2( - {String? fromTicker, - String? toTicker, - _i10.CNEstimateType? fromOrTo, - _i7.Decimal? amount, - String? fromNetwork, - String? toNetwork, - _i10.CNFlowType? flow = _i10.CNFlowType.standard, - String? apiKey}) => - (super.noSuchMethod( - Invocation.method(#getEstimatedExchangeAmountV2, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #fromOrTo: fromOrTo, - #amount: amount, - #fromNetwork: fromNetwork, - #toNetwork: toNetwork, - #flow: flow, - #apiKey: apiKey - }), - returnValue: Future< - _i2.ExchangeResponse<_i10.CNExchangeEstimate>>.value( - _FakeExchangeResponse_0<_i10.CNExchangeEstimate>())) - as _i5.Future<_i2.ExchangeResponse<_i10.CNExchangeEstimate>>); - @override - _i5.Future<_i2.ExchangeResponse>> - getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod( - Invocation.method( - #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), - returnValue: - Future<_i2.ExchangeResponse>>.value( - _FakeExchangeResponse_0>())) as _i5 - .Future<_i2.ExchangeResponse>>); - @override - _i5.Future<_i2.ExchangeResponse<_i12.ExchangeTransaction>> - createStandardExchangeTransaction( - {String? fromTicker, - String? toTicker, - String? receivingAddress, - _i7.Decimal? amount, - String? extraId = r'', - String? userId = r'', - String? contactEmail = r'', - String? refundAddress = r'', - String? refundExtraId = r'', - String? apiKey}) => - (super.noSuchMethod( - Invocation.method(#createStandardExchangeTransaction, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #receivingAddress: receivingAddress, - #amount: amount, - #extraId: extraId, - #userId: userId, - #contactEmail: contactEmail, - #refundAddress: refundAddress, - #refundExtraId: refundExtraId, - #apiKey: apiKey - }), - returnValue: Future< - _i2.ExchangeResponse<_i12.ExchangeTransaction>>.value( - _FakeExchangeResponse_0<_i12.ExchangeTransaction>())) - as _i5.Future<_i2.ExchangeResponse<_i12.ExchangeTransaction>>); - @override - _i5.Future<_i2.ExchangeResponse<_i12.ExchangeTransaction>> - createFixedRateExchangeTransaction( - {String? fromTicker, - String? toTicker, - String? receivingAddress, - _i7.Decimal? amount, - String? rateId, - String? extraId = r'', - String? userId = r'', - String? contactEmail = r'', - String? refundAddress = r'', - String? refundExtraId = r'', - String? apiKey}) => - (super.noSuchMethod( - Invocation.method(#createFixedRateExchangeTransaction, [], { - #fromTicker: fromTicker, - #toTicker: toTicker, - #receivingAddress: receivingAddress, - #amount: amount, - #rateId: rateId, - #extraId: extraId, - #userId: userId, - #contactEmail: contactEmail, - #refundAddress: refundAddress, - #refundExtraId: refundExtraId, - #apiKey: apiKey - }), - returnValue: Future< - _i2.ExchangeResponse<_i12.ExchangeTransaction>>.value( - _FakeExchangeResponse_0<_i12.ExchangeTransaction>())) - as _i5.Future<_i2.ExchangeResponse<_i12.ExchangeTransaction>>); - @override - _i5.Future<_i2.ExchangeResponse<_i13.ExchangeTransactionStatus>> - getTransactionStatus({String? id, String? apiKey}) => (super.noSuchMethod( - Invocation.method( - #getTransactionStatus, [], {#id: id, #apiKey: apiKey}), - returnValue: - Future<_i2.ExchangeResponse<_i13.ExchangeTransactionStatus>>.value( - _FakeExchangeResponse_0<_i13.ExchangeTransactionStatus>())) as _i5 - .Future<_i2.ExchangeResponse<_i13.ExchangeTransactionStatus>>); - @override - _i5.Future<_i2.ExchangeResponse>> - getAvailableFloatingRatePairs({bool? includePartners = false}) => - (super.noSuchMethod( - Invocation.method(#getAvailableFloatingRatePairs, [], - {#includePartners: includePartners}), - returnValue: Future<_i2.ExchangeResponse>>.value( - _FakeExchangeResponse_0>())) as _i5 - .Future<_i2.ExchangeResponse>>); -} diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index da09726457..6f98b499ec 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -427,6 +427,7 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { String? toTicker, _i15.Decimal? fromAmount, bool? reversed, + bool? useRateId = true, String? apiKey}) => (super.noSuchMethod( Invocation.method(#getEstimatedExchangeAmountFixedRate, [], { @@ -434,6 +435,7 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { #toTicker: toTicker, #fromAmount: fromAmount, #reversed: reversed, + #useRateId: useRateId, #apiKey: apiKey }), returnValue: Future<_i2.ExchangeResponse<_i17.Estimate>>.value( @@ -512,6 +514,7 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { String? receivingAddress, _i15.Decimal? amount, String? rateId, + bool? reversed, String? extraId = r'', String? userId = r'', String? contactEmail = r'', @@ -525,6 +528,7 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { #receivingAddress: receivingAddress, #amount: amount, #rateId: rateId, + #reversed: reversed, #extraId: extraId, #userId: userId, #contactEmail: contactEmail, diff --git a/test/services/change_now/change_now_test.dart b/test/services/change_now/change_now_test.dart index e9e0132459..9c8efb1acf 100644 --- a/test/services/change_now/change_now_test.dart +++ b/test/services/change_now/change_now_test.dart @@ -589,6 +589,7 @@ void main() { refundAddress: "", apiKey: "testAPIKEY", rateId: '', + reversed: false, ); expect(result.exception, null); @@ -621,6 +622,7 @@ void main() { refundAddress: "", apiKey: "testAPIKEY", rateId: '', + reversed: false, ); expect( @@ -652,6 +654,7 @@ void main() { "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H", apiKey: "testAPIKEY", rateId: '', + reversed: false, ); expect(result.exception!.type, ExchangeExceptionType.generic); From 9189754363d6476098db3feadf6ca413f5c26b9a Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 4 Oct 2022 08:46:22 -0600 Subject: [PATCH 21/75] centralize changenow loading --- lib/main.dart | 215 ++++++++---------- lib/pages/exchange_view/exchange_view.dart | 2 - .../sub_widgets/home_view_button_bar.dart | 109 +-------- .../changenow_initial_load_status.dart | 5 +- .../change_now_loading_service.dart | 6 +- 5 files changed, 110 insertions(+), 227 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 0c74aa4a44..0e54306f80 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -25,7 +25,6 @@ import 'package:stackwallet/models/models.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/notification_model.dart'; import 'package:stackwallet/models/trade_wallet_lookup.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_view.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/intro_view.dart'; import 'package:stackwallet/pages/loading_view.dart'; @@ -33,10 +32,6 @@ import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart'; import 'package:stackwallet/pages_desktop_specific/home/desktop_home_view.dart'; -import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; -import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; -import 'package:stackwallet/providers/exchange/changenow_initial_load_status.dart'; -import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/global/base_currencies_provider.dart'; // import 'package:stackwallet/providers/global/has_authenticated_start_state_provider.dart'; @@ -45,8 +40,8 @@ import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/color_theme_provider.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/debug_service.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_loading_service.dart'; import 'package:stackwallet/services/locale_service.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/notifications_api.dart'; @@ -232,6 +227,11 @@ class _MaterialAppWithThemeState extends ConsumerState // TODO: this should probably run unawaited. Keep commented out for now as proper community nodes ui hasn't been implemented yet // unawaited(_nodeService.updateCommunityNodes()); + // run without awaiting + if (Constants.enableExchange) { + unawaited(ChangeNowLoadingService().loadAll(ref)); + } + if (_prefs.isAutoBackupEnabled) { switch (_prefs.backupFrequencyType) { case BackupFrequencyType.everyTenMinutes: @@ -249,109 +249,101 @@ class _MaterialAppWithThemeState extends ConsumerState } } - Future _loadChangeNowStandardCurrencies() async { - if (ref - .read(availableChangeNowCurrenciesStateProvider.state) - .state - .isNotEmpty && - ref - .read(availableFloatingRatePairsStateProvider.state) - .state - .isNotEmpty) { - return; - } - final response = await ChangeNowAPI.instance.getAvailableCurrencies(); - final response2 = - await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); - if (response.value != null) { - ref.read(availableChangeNowCurrenciesStateProvider.state).state = - response.value!; - if (response2.value != null) { - ref.read(availableFloatingRatePairsStateProvider.state).state = - response2.value!; - - if (response.value!.length > 1) { - if (ref.read(exchangeFormStateProvider).from == null) { - if (response.value!.where((e) => e.ticker == "btc").isNotEmpty) { - await ref.read(exchangeFormStateProvider).updateFrom( - response.value!.firstWhere((e) => e.ticker == "btc"), false); - } - } - if (ref.read(exchangeFormStateProvider).to == null) { - if (response.value!.where((e) => e.ticker == "doge").isNotEmpty) { - await ref.read(exchangeFormStateProvider).updateTo( - response.value!.firstWhere((e) => e.ticker == "doge"), false); - } - } - } - } else { - Logging.instance.log( - "Failed to load changeNOW available floating rate pairs: ${response2.exception?.errorMessage}", - level: LogLevel.Error); - ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = - ChangeNowLoadStatus.failed; - return; - } - } else { - Logging.instance.log( - "Failed to load changeNOW currencies: ${response.exception?.errorMessage}", - level: LogLevel.Error); - await Future.delayed(const Duration(seconds: 1)); - ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = - ChangeNowLoadStatus.failed; - return; - } - - ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = - ChangeNowLoadStatus.success; - } - - Future _loadFixedRateMarkets() async { - Logging.instance.log("Starting initial fixed rate market data loading...", - level: LogLevel.Info); - if (ref.read(fixedRateMarketPairsStateProvider.state).state.isNotEmpty) { - return; - } - - final response3 = - await ChangeNowAPI.instance.getAvailableFixedRateMarkets(); - if (response3.value != null) { - ref.read(fixedRateMarketPairsStateProvider.state).state = - response3.value!; - - if (ref.read(exchangeFormStateProvider).market == null) { - final matchingMarkets = - response3.value!.where((e) => e.to == "doge" && e.from == "btc"); - if (matchingMarkets.isNotEmpty) { - await ref - .read(exchangeFormStateProvider) - .updateMarket(matchingMarkets.first, true); - } - } - - Logging.instance.log("Initial fixed rate market data loading complete.", - level: LogLevel.Info); - } else { - Logging.instance.log( - "Failed to load changeNOW fixed rate markets: ${response3.exception?.errorMessage}", - level: LogLevel.Error); - - ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state = - ChangeNowLoadStatus.failed; - return; - } - - ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state = - ChangeNowLoadStatus.success; - } - - Future _loadChangeNowData() async { - List> concurrentFutures = []; - concurrentFutures.add(_loadChangeNowStandardCurrencies()); - if (kFixedRateEnabled) { - concurrentFutures.add(_loadFixedRateMarkets()); - } - } + // Future _loadChangeNowStandardCurrencies() async { + // if (ref + // .read(availableChangeNowCurrenciesStateProvider.state) + // .state + // .isNotEmpty && + // ref + // .read(availableFloatingRatePairsStateProvider.state) + // .state + // .isNotEmpty) { + // return; + // } + // final response = await ChangeNowAPI.instance.getAvailableCurrencies(); + // final response2 = + // await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); + // if (response.value != null) { + // ref.read(availableChangeNowCurrenciesStateProvider.state).state = + // response.value!; + // if (response2.value != null) { + // ref.read(availableFloatingRatePairsStateProvider.state).state = + // response2.value!; + // + // if (response.value!.length > 1) { + // if (ref.read(exchangeFormStateProvider).from == null) { + // if (response.value!.where((e) => e.ticker == "btc").isNotEmpty) { + // await ref.read(exchangeFormStateProvider).updateFrom( + // response.value!.firstWhere((e) => e.ticker == "btc"), false); + // } + // } + // if (ref.read(exchangeFormStateProvider).to == null) { + // if (response.value!.where((e) => e.ticker == "doge").isNotEmpty) { + // await ref.read(exchangeFormStateProvider).updateTo( + // response.value!.firstWhere((e) => e.ticker == "doge"), false); + // } + // } + // } + // } else { + // Logging.instance.log( + // "Failed to load changeNOW available floating rate pairs: ${response2.exception?.errorMessage}", + // level: LogLevel.Error); + // ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = + // ChangeNowLoadStatus.failed; + // return; + // } + // } else { + // Logging.instance.log( + // "Failed to load changeNOW currencies: ${response.exception?.errorMessage}", + // level: LogLevel.Error); + // await Future.delayed(const Duration(seconds: 1)); + // ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = + // ChangeNowLoadStatus.failed; + // return; + // } + // + // ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = + // ChangeNowLoadStatus.success; + // } + + // Future _loadFixedRateMarkets() async { + // Logging.instance.log("Starting initial fixed rate market data loading...", + // level: LogLevel.Info); + // if (ref.read(fixedRateMarketPairsStateProvider.state).state.isNotEmpty) { + // return; + // } + // + // final response3 = + // await ChangeNowAPI.instance.getAvailableFixedRateMarkets(); + // if (response3.value != null) { + // ref.read(fixedRateMarketPairsStateProvider.state).state = + // response3.value!; + // + // if (ref.read(exchangeFormStateProvider).market == null) { + // final matchingMarkets = + // response3.value!.where((e) => e.to == "doge" && e.from == "btc"); + // if (matchingMarkets.isNotEmpty) { + // await ref + // .read(exchangeFormStateProvider) + // .updateMarket(matchingMarkets.first, true); + // } + // } + // + // Logging.instance.log("Initial fixed rate market data loading complete.", + // level: LogLevel.Info); + // } else { + // Logging.instance.log( + // "Failed to load changeNOW fixed rate markets: ${response3.exception?.errorMessage}", + // level: LogLevel.Error); + // + // ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state = + // ChangeNowLoadStatus.failed; + // return; + // } + // + // ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state = + // ChangeNowLoadStatus.success; + // } @override void initState() { @@ -636,11 +628,6 @@ class _MaterialAppWithThemeState extends ConsumerState if (_wallets.hasWallets || _prefs.hasPin) { // return HomeView(); - // run without awaiting - if (Constants.enableExchange) { - _loadChangeNowData(); - } - String? startupWalletId; if (ref.read(prefsChangeNotifierProvider).gotoWalletOnStartup) { startupWalletId = diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart index 1b66994f4a..0006d53b05 100644 --- a/lib/pages/exchange_view/exchange_view.dart +++ b/lib/pages/exchange_view/exchange_view.dart @@ -13,8 +13,6 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/trade_card.dart'; import 'package:tuple/tuple.dart'; -const kFixedRateEnabled = true; - class ExchangeView extends ConsumerStatefulWidget { const ExchangeView({Key? key}) : super(key: key); diff --git a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart index aebaf0da90..a933b4d2f6 100644 --- a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart +++ b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart @@ -1,16 +1,8 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/exchange/exchange_form_state.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_view.dart'; -import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; -import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; -import 'package:stackwallet/providers/exchange/change_now_provider.dart'; -import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; -import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_loading_service.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -26,103 +18,6 @@ class _HomeViewButtonBarState extends ConsumerState { final DateTime _lastRefreshed = DateTime.now(); final Duration _refreshInterval = const Duration(hours: 1); - Future _loadChangeNowData( - BuildContext context, - WidgetRef ref, - ) async { - List> futures = []; - if (kFixedRateEnabled) { - futures.add(_loadFixedRateMarkets(context, ref)); - } - futures.add(_loadStandardCurrencies(context, ref)); - - await Future.wait(futures); - } - - Future _loadStandardCurrencies( - BuildContext context, - WidgetRef ref, - ) async { - final response = await ref.read(changeNowProvider).getAllCurrencies(false); - final response2 = - await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); - if (response.value != null && response2.value != null) { - ref.read(availableChangeNowCurrenciesStateProvider.state).state = - response.value!; - ref.read(availableFloatingRatePairsStateProvider.state).state = - response2.value!; - - if (response.value!.length > 1) { - if (ref.read(exchangeFormStateProvider).from == null) { - if (response.value!.where((e) => e.ticker == "btc").isNotEmpty) { - await ref.read(exchangeFormStateProvider).updateFrom( - response.value!.firstWhere((e) => e.ticker == "btc"), true); - } - } - if (ref.read(exchangeFormStateProvider).to == null) { - if (response.value!.where((e) => e.ticker == "doge").isNotEmpty) { - await ref.read(exchangeFormStateProvider).updateTo( - response.value!.firstWhere((e) => e.ticker == "doge"), true); - } - } - } - - Logging.instance - .log("loaded floating rate change now data", level: LogLevel.Info); - } else { - Logging.instance.log( - "Failed to load changeNOW floating rate market data: \n${response.exception?.errorMessage}\n${response2.exception?.toString()}", - level: LogLevel.Error); - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Failed to fetch available currencies", - message: - "${response.exception?.toString()}\n\n${response2.exception?.toString()}", - ), - )); - } - } - - Future _loadFixedRateMarkets( - BuildContext context, - WidgetRef ref, - ) async { - final response3 = - await ChangeNowAPI.instance.getAvailableFixedRateMarkets(); - - if (response3.value != null) { - ref.read(fixedRateMarketPairsStateProvider.state).state = - response3.value!; - - if (ref.read(exchangeFormStateProvider).market == null) { - final matchingMarkets = - response3.value!.where((e) => e.to == "doge" && e.from == "btc"); - - if (matchingMarkets.isNotEmpty) { - await ref - .read(exchangeFormStateProvider) - .updateMarket(matchingMarkets.first, true); - } - } - Logging.instance - .log("loaded fixed rate change now data", level: LogLevel.Info); - } else { - Logging.instance.log( - "Failed to load changeNOW fixed rate markets: ${response3.exception?.errorMessage}", - level: LogLevel.Error); - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "ChangeNOW API call failed", - message: "${response3.exception?.toString()}", - ), - )); - } - } - @override void initState() { ref.read(exchangeFormStateProvider).setOnError( @@ -224,7 +119,7 @@ class _HomeViewButtonBarState extends ConsumerState { // // }, // ), // ); - await _loadChangeNowData(context, ref); + await ChangeNowLoadingService().loadAll(ref); // if (!okPressed && mounted) { // Navigator.of(context).pop(); // } diff --git a/lib/providers/exchange/changenow_initial_load_status.dart b/lib/providers/exchange/changenow_initial_load_status.dart index f8a5bf5df5..0cb4f12789 100644 --- a/lib/providers/exchange/changenow_initial_load_status.dart +++ b/lib/providers/exchange/changenow_initial_load_status.dart @@ -1,13 +1,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; enum ChangeNowLoadStatus { + waiting, loading, success, failed, } final changeNowEstimatedInitialLoadStatusStateProvider = - StateProvider((ref) => ChangeNowLoadStatus.loading); + StateProvider((ref) => ChangeNowLoadStatus.waiting); final changeNowFixedInitialLoadStatusStateProvider = - StateProvider((ref) => ChangeNowLoadStatus.loading); + StateProvider((ref) => ChangeNowLoadStatus.waiting); diff --git a/lib/services/exchange/change_now/change_now_loading_service.dart b/lib/services/exchange/change_now/change_now_loading_service.dart index 0d25bccee2..2442831825 100644 --- a/lib/services/exchange/change_now/change_now_loading_service.dart +++ b/lib/services/exchange/change_now/change_now_loading_service.dart @@ -67,8 +67,10 @@ class ChangeNowLoadingService { ChangeNowLoadStatus.success; } - Future _loadChangeNowStandardCurrencies(WidgetRef ref, - {Coin? coin}) async { + Future _loadChangeNowStandardCurrencies( + WidgetRef ref, { + Coin? coin, + }) async { if (ref .read(changeNowEstimatedInitialLoadStatusStateProvider.state) .state == From 2c8ba8405fe1dbc43b0aaeb8db8e2432f514a767 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 4 Oct 2022 11:06:14 -0600 Subject: [PATCH 22/75] added simpleswap data loading, cleaned up providers, modified exchange process --- lib/main.dart | 101 +-- .../change_now/cn_available_currencies.dart | 24 + lib/models/exchange/exchange_form_state.dart | 4 - .../simpleswap/sp_available_currencies.dart | 30 + lib/pages/exchange_view/exchange_form.dart | 721 +++++++++++------- .../exchange_step_views/step_3_view.dart | 4 +- .../exchange_step_views/step_4_view.dart | 4 +- .../exchange_provider_options.dart | 20 +- .../exchange_view/trade_details_view.dart | 6 +- lib/pages/home_view/home_view.dart | 4 +- .../sub_widgets/home_view_button_bar.dart | 5 +- lib/pages/wallet_view/wallet_view.dart | 19 +- ...ailable_changenow_currencies_provider.dart | 6 + .../available_currencies_state_provider.dart | 5 - ...le_floating_rate_pairs_state_provider.dart | 5 - ...ilable_simpleswap_currencies_provider.dart | 6 + ...current_exchange_name_state_provider.dart} | 5 +- .../estimate_rate_exchange_form_provider.dart | 5 - .../exchange_form_state_provider.dart | 6 + lib/providers/exchange/exchange_provider.dart | 9 + .../fixed_rate_exchange_form_provider.dart | 6 - .../fixed_rate_market_pairs_provider.dart | 5 - lib/providers/providers.dart | 10 + lib/services/exchange/exchange.dart | 13 + ...art => exchange_data_loading_service.dart} | 85 ++- 25 files changed, 649 insertions(+), 459 deletions(-) create mode 100644 lib/models/exchange/change_now/cn_available_currencies.dart create mode 100644 lib/models/exchange/simpleswap/sp_available_currencies.dart create mode 100644 lib/providers/exchange/available_changenow_currencies_provider.dart delete mode 100644 lib/providers/exchange/available_currencies_state_provider.dart delete mode 100644 lib/providers/exchange/available_floating_rate_pairs_state_provider.dart create mode 100644 lib/providers/exchange/available_simpleswap_currencies_provider.dart rename lib/providers/exchange/{change_now_provider.dart => current_exchange_name_state_provider.dart} (50%) delete mode 100644 lib/providers/exchange/estimate_rate_exchange_form_provider.dart create mode 100644 lib/providers/exchange/exchange_form_state_provider.dart create mode 100644 lib/providers/exchange/exchange_provider.dart delete mode 100644 lib/providers/exchange/fixed_rate_exchange_form_provider.dart delete mode 100644 lib/providers/exchange/fixed_rate_market_pairs_provider.dart rename lib/services/exchange/{change_now/change_now_loading_service.dart => exchange_data_loading_service.dart} (64%) diff --git a/lib/main.dart b/lib/main.dart index 0e54306f80..6062a7cd69 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,7 +18,6 @@ import 'package:path_provider/path_provider.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; -import 'package:stackwallet/models/exchange/exchange_form_state.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/isar/models/log.dart'; import 'package:stackwallet/models/models.dart'; @@ -41,7 +40,7 @@ import 'package:stackwallet/providers/ui/color_theme_provider.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/debug_service.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_loading_service.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; import 'package:stackwallet/services/locale_service.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/notifications_api.dart'; @@ -229,7 +228,7 @@ class _MaterialAppWithThemeState extends ConsumerState // run without awaiting if (Constants.enableExchange) { - unawaited(ChangeNowLoadingService().loadAll(ref)); + unawaited(ExchangeDataLoadingService().loadAll(ref)); } if (_prefs.isAutoBackupEnabled) { @@ -249,102 +248,6 @@ class _MaterialAppWithThemeState extends ConsumerState } } - // Future _loadChangeNowStandardCurrencies() async { - // if (ref - // .read(availableChangeNowCurrenciesStateProvider.state) - // .state - // .isNotEmpty && - // ref - // .read(availableFloatingRatePairsStateProvider.state) - // .state - // .isNotEmpty) { - // return; - // } - // final response = await ChangeNowAPI.instance.getAvailableCurrencies(); - // final response2 = - // await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); - // if (response.value != null) { - // ref.read(availableChangeNowCurrenciesStateProvider.state).state = - // response.value!; - // if (response2.value != null) { - // ref.read(availableFloatingRatePairsStateProvider.state).state = - // response2.value!; - // - // if (response.value!.length > 1) { - // if (ref.read(exchangeFormStateProvider).from == null) { - // if (response.value!.where((e) => e.ticker == "btc").isNotEmpty) { - // await ref.read(exchangeFormStateProvider).updateFrom( - // response.value!.firstWhere((e) => e.ticker == "btc"), false); - // } - // } - // if (ref.read(exchangeFormStateProvider).to == null) { - // if (response.value!.where((e) => e.ticker == "doge").isNotEmpty) { - // await ref.read(exchangeFormStateProvider).updateTo( - // response.value!.firstWhere((e) => e.ticker == "doge"), false); - // } - // } - // } - // } else { - // Logging.instance.log( - // "Failed to load changeNOW available floating rate pairs: ${response2.exception?.errorMessage}", - // level: LogLevel.Error); - // ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = - // ChangeNowLoadStatus.failed; - // return; - // } - // } else { - // Logging.instance.log( - // "Failed to load changeNOW currencies: ${response.exception?.errorMessage}", - // level: LogLevel.Error); - // await Future.delayed(const Duration(seconds: 1)); - // ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = - // ChangeNowLoadStatus.failed; - // return; - // } - // - // ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = - // ChangeNowLoadStatus.success; - // } - - // Future _loadFixedRateMarkets() async { - // Logging.instance.log("Starting initial fixed rate market data loading...", - // level: LogLevel.Info); - // if (ref.read(fixedRateMarketPairsStateProvider.state).state.isNotEmpty) { - // return; - // } - // - // final response3 = - // await ChangeNowAPI.instance.getAvailableFixedRateMarkets(); - // if (response3.value != null) { - // ref.read(fixedRateMarketPairsStateProvider.state).state = - // response3.value!; - // - // if (ref.read(exchangeFormStateProvider).market == null) { - // final matchingMarkets = - // response3.value!.where((e) => e.to == "doge" && e.from == "btc"); - // if (matchingMarkets.isNotEmpty) { - // await ref - // .read(exchangeFormStateProvider) - // .updateMarket(matchingMarkets.first, true); - // } - // } - // - // Logging.instance.log("Initial fixed rate market data loading complete.", - // level: LogLevel.Info); - // } else { - // Logging.instance.log( - // "Failed to load changeNOW fixed rate markets: ${response3.exception?.errorMessage}", - // level: LogLevel.Error); - // - // ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state = - // ChangeNowLoadStatus.failed; - // return; - // } - // - // ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state = - // ChangeNowLoadStatus.success; - // } - @override void initState() { ref.read(exchangeFormStateProvider).exchange = ChangeNowExchange(); diff --git a/lib/models/exchange/change_now/cn_available_currencies.dart b/lib/models/exchange/change_now/cn_available_currencies.dart new file mode 100644 index 0000000000..aaf6a5d05d --- /dev/null +++ b/lib/models/exchange/change_now/cn_available_currencies.dart @@ -0,0 +1,24 @@ +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; + +class CNAvailableCurrencies { + final List currencies = []; + final List pairs = []; + final List markets = []; + + void updateCurrencies(List newCurrencies) { + currencies.clear(); + currencies.addAll(newCurrencies); + } + + void updateFloatingPairs(List newPairs) { + pairs.clear(); + pairs.addAll(newPairs); + } + + void updateMarkets(List newMarkets) { + markets.clear(); + markets.addAll(newMarkets); + } +} diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart index ad30e0c1f2..e5aac8f0a1 100644 --- a/lib/models/exchange/exchange_form_state.dart +++ b/lib/models/exchange/exchange_form_state.dart @@ -1,7 +1,6 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; @@ -9,9 +8,6 @@ import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet. import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/utilities/logger.dart'; -final exchangeFormStateProvider = - ChangeNotifierProvider((ref) => ExchangeFormState()); - class ExchangeFormState extends ChangeNotifier { Exchange? _exchange; Exchange? get exchange => _exchange; diff --git a/lib/models/exchange/simpleswap/sp_available_currencies.dart b/lib/models/exchange/simpleswap/sp_available_currencies.dart new file mode 100644 index 0000000000..787ad88ab8 --- /dev/null +++ b/lib/models/exchange/simpleswap/sp_available_currencies.dart @@ -0,0 +1,30 @@ +import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/exchange/response_objects/pair.dart'; + +class SPAvailableCurrencies { + final List floatingRateCurrencies = []; + final List fixedRateCurrencies = []; + + final List floatingRatePairs = []; + final List fixedRatePairs = []; + + void updateFloatingCurrencies(List newCurrencies) { + floatingRateCurrencies.clear(); + floatingRateCurrencies.addAll(newCurrencies); + } + + void updateFixedCurrencies(List newCurrencies) { + fixedRateCurrencies.clear(); + fixedRateCurrencies.addAll(newCurrencies); + } + + void updateFloatingPairs(List newPairs) { + floatingRatePairs.clear(); + floatingRatePairs.addAll(newPairs); + } + + void updateFixedPairs(List newPairs) { + fixedRatePairs.clear(); + fixedRatePairs.addAll(newPairs); + } +} diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 24984ecbda..30d333b8cb 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -6,7 +6,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/exchange_form_state.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; @@ -19,11 +18,10 @@ import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view. import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_options.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/rate_type_toggle.dart'; -import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; -import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; -import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; @@ -88,9 +86,23 @@ class _ExchangeFormState extends ConsumerState { return; } + List currencies; + switch (ref.read(currentExchangeNameStateProvider.state).state) { + case ChangeNowExchange.exchangeName: + currencies = + ref.read(availableChangeNowCurrenciesProvider).currencies; + break; + case SimpleSwapExchange.exchangeName: + currencies = ref + .read(availableSimpleswapCurrenciesProvider) + .floatingRateCurrencies; + break; + default: + currencies = []; + } + await _showFloatingRateSelectionSheet( - currencies: - ref.read(availableChangeNowCurrenciesStateProvider.state).state, + currencies: currencies, excludedTicker: ref.read(exchangeFormStateProvider).toTicker ?? "-", fromTicker: fromTicker, onSelected: (from) => @@ -105,33 +117,51 @@ class _ExchangeFormState extends ConsumerState { return; } - await _showFixedRateSelectionSheet( - excludedTicker: toTicker, - fromTicker: fromTicker, - onSelected: (selectedFromTicker) async { - try { - final market = ref - .read(fixedRateMarketPairsStateProvider.state) - .state - .firstWhere( - (e) => e.to == toTicker && e.from == selectedFromTicker, - ); - - await ref - .read(exchangeFormStateProvider) - .updateMarket(market, true); - } catch (e) { - unawaited(showDialog( - context: context, - builder: (_) => const StackDialog( - title: "Fixed rate market error", - message: "Could not find the specified fixed rate trade pair", - ), - )); - return; - } - }, - ); + switch (ref.read(currentExchangeNameStateProvider.state).state) { + case ChangeNowExchange.exchangeName: + await _showFixedRateSelectionSheet( + excludedTicker: toTicker, + fromTicker: fromTicker, + onSelected: (selectedFromTicker) async { + try { + final market = ref + .read(availableChangeNowCurrenciesProvider) + .markets + .firstWhere( + (e) => e.to == toTicker && e.from == selectedFromTicker, + ); + + await ref + .read(exchangeFormStateProvider) + .updateMarket(market, true); + } catch (e) { + unawaited(showDialog( + context: context, + builder: (_) => const StackDialog( + title: "Fixed rate market error", + message: + "Could not find the specified fixed rate trade pair", + ), + )); + return; + } + }, + ); + break; + case SimpleSwapExchange.exchangeName: + await _showFloatingRateSelectionSheet( + currencies: ref + .read(availableSimpleswapCurrenciesProvider) + .fixedRateCurrencies, + excludedTicker: + ref.read(exchangeFormStateProvider).toTicker ?? "-", + fromTicker: fromTicker, + onSelected: (from) => + ref.read(exchangeFormStateProvider).updateFrom(from, true)); + break; + default: + // TODO show error? + } } } @@ -146,9 +176,23 @@ class _ExchangeFormState extends ConsumerState { return; } + List currencies; + switch (ref.read(currentExchangeNameStateProvider.state).state) { + case ChangeNowExchange.exchangeName: + currencies = + ref.read(availableChangeNowCurrenciesProvider).currencies; + break; + case SimpleSwapExchange.exchangeName: + currencies = ref + .read(availableSimpleswapCurrenciesProvider) + .floatingRateCurrencies; + break; + default: + currencies = []; + } + await _showFloatingRateSelectionSheet( - currencies: - ref.read(availableChangeNowCurrenciesStateProvider.state).state, + currencies: currencies, excludedTicker: ref.read(exchangeFormStateProvider).fromTicker ?? "", fromTicker: ref.read(exchangeFormStateProvider).fromTicker ?? "", onSelected: (to) => @@ -163,33 +207,51 @@ class _ExchangeFormState extends ConsumerState { return; } - await _showFixedRateSelectionSheet( - excludedTicker: fromTicker, - fromTicker: fromTicker, - onSelected: (selectedToTicker) async { - try { - final market = ref - .read(fixedRateMarketPairsStateProvider.state) - .state - .firstWhere( - (e) => e.to == selectedToTicker && e.from == fromTicker, - ); - - await ref - .read(exchangeFormStateProvider) - .updateMarket(market, true); - } catch (e) { - unawaited(showDialog( - context: context, - builder: (_) => const StackDialog( - title: "Fixed rate market error", - message: "Could not find the specified fixed rate trade pair", - ), - )); - return; - } - }, - ); + switch (ref.read(currentExchangeNameStateProvider.state).state) { + case ChangeNowExchange.exchangeName: + await _showFixedRateSelectionSheet( + excludedTicker: fromTicker, + fromTicker: fromTicker, + onSelected: (selectedToTicker) async { + try { + final market = ref + .read(availableChangeNowCurrenciesProvider) + .markets + .firstWhere( + (e) => e.to == selectedToTicker && e.from == fromTicker, + ); + + await ref + .read(exchangeFormStateProvider) + .updateMarket(market, true); + } catch (e) { + unawaited(showDialog( + context: context, + builder: (_) => const StackDialog( + title: "Fixed rate market error", + message: + "Could not find the specified fixed rate trade pair", + ), + )); + return; + } + }, + ); + break; + case SimpleSwapExchange.exchangeName: + await _showFloatingRateSelectionSheet( + currencies: ref + .read(availableSimpleswapCurrenciesProvider) + .fixedRateCurrencies, + excludedTicker: + ref.read(exchangeFormStateProvider).fromTicker ?? "", + fromTicker: ref.read(exchangeFormStateProvider).fromTicker ?? "", + onSelected: (to) => + ref.read(exchangeFormStateProvider).updateTo(to, true)); + break; + default: + // TODO show error? + } } } @@ -237,18 +299,29 @@ class _ExchangeFormState extends ConsumerState { ExchangeRateType.estimated) { await ref.read(exchangeFormStateProvider).swap(); } else { - final from = ref.read(exchangeFormStateProvider).fromTicker; - final to = ref.read(exchangeFormStateProvider).toTicker; - - if (to != null && from != null) { - final markets = ref - .read(fixedRateMarketPairsStateProvider.state) - .state - .where((e) => e.from == to && e.to == from); - - if (markets.isNotEmpty) { - await ref.read(exchangeFormStateProvider).swap(market: markets.first); - } + switch (ref.read(currentExchangeNameStateProvider.state).state) { + case ChangeNowExchange.exchangeName: + final from = ref.read(exchangeFormStateProvider).fromTicker; + final to = ref.read(exchangeFormStateProvider).toTicker; + + if (to != null && from != null) { + final markets = ref + .read(availableChangeNowCurrenciesProvider) + .markets + .where((e) => e.from == to && e.to == from); + + if (markets.isNotEmpty) { + await ref + .read(exchangeFormStateProvider) + .swap(market: markets.first); + } + } + break; + case SimpleSwapExchange.exchangeName: + await ref.read(exchangeFormStateProvider).swap(); + break; + default: + // } } if (mounted) { @@ -266,25 +339,35 @@ class _ExchangeFormState extends ConsumerState { _sendFocusNode.unfocus(); _receiveFocusNode.unfocus(); - List availablePairs = []; + List allPairs; + + switch (ref.read(currentExchangeNameStateProvider.state).state) { + case ChangeNowExchange.exchangeName: + allPairs = ref.read(availableChangeNowCurrenciesProvider).pairs; + break; + case SimpleSwapExchange.exchangeName: + allPairs = ref.read(exchangeFormStateProvider).exchangeType == + ExchangeRateType.fixed + ? ref.read(availableSimpleswapCurrenciesProvider).fixedRatePairs + : ref.read(availableSimpleswapCurrenciesProvider).floatingRatePairs; + break; + default: + allPairs = []; + } + + List availablePairs; if (fromTicker.isEmpty || fromTicker == "-" || excludedTicker.isEmpty || excludedTicker == "-") { - availablePairs = - ref.read(availableFloatingRatePairsStateProvider.state).state; + availablePairs = allPairs; } else if (excludedTicker == fromTicker) { - availablePairs = ref - .read(availableFloatingRatePairsStateProvider.state) - .state + availablePairs = allPairs .where((e) => e.from == excludedTicker) .toList(growable: false); } else { - availablePairs = ref - .read(availableFloatingRatePairsStateProvider.state) - .state - .where((e) => e.to == excludedTicker) - .toList(growable: false); + availablePairs = + allPairs.where((e) => e.to == excludedTicker).toList(growable: false); } final List tickers = currencies.where((e) { @@ -313,10 +396,30 @@ class _ExchangeFormState extends ConsumerState { String? _fetchIconUrlFromTicker(String? ticker) { if (ticker == null) return null; - final possibleCurrencies = ref - .read(availableChangeNowCurrenciesStateProvider.state) - .state - .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase()); + Iterable possibleCurrencies; + + switch (ref.read(currentExchangeNameStateProvider.state).state) { + case ChangeNowExchange.exchangeName: + possibleCurrencies = ref + .read(availableChangeNowCurrenciesProvider) + .currencies + .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase()); + break; + case SimpleSwapExchange.exchangeName: + possibleCurrencies = [ + ...ref + .read(availableSimpleswapCurrenciesProvider) + .fixedRateCurrencies + .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase()), + ...ref + .read(availableSimpleswapCurrenciesProvider) + .floatingRateCurrencies + .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase()), + ]; + break; + default: + possibleCurrencies = []; + } for (final currency in possibleCurrencies) { if (currency.image.isNotEmpty) { @@ -342,17 +445,17 @@ class _ExchangeFormState extends ConsumerState { fromTicker == "" || fromTicker == "-") { marketsThatPairWithExcludedTicker = - ref.read(fixedRateMarketPairsStateProvider.state).state; + ref.read(availableChangeNowCurrenciesProvider).markets; } else if (excludedTicker == fromTicker) { marketsThatPairWithExcludedTicker = ref - .read(fixedRateMarketPairsStateProvider.state) - .state + .read(availableChangeNowCurrenciesProvider) + .markets .where((e) => e.from == excludedTicker && e.to != excludedTicker) .toList(growable: false); } else { marketsThatPairWithExcludedTicker = ref - .read(fixedRateMarketPairsStateProvider.state) - .state + .read(availableChangeNowCurrenciesProvider) + .markets .where((e) => e.to == excludedTicker && e.from != excludedTicker) .toList(growable: false); } @@ -361,8 +464,7 @@ class _ExchangeFormState extends ConsumerState { MaterialPageRoute( builder: (_) => FixedRateMarketPairCoinSelectionView( markets: marketsThatPairWithExcludedTicker, - currencies: - ref.read(availableChangeNowCurrenciesStateProvider.state).state, + currencies: ref.read(availableChangeNowCurrenciesProvider).currencies, isFrom: excludedTicker != fromTicker, ), ), @@ -405,15 +507,46 @@ class _ExchangeFormState extends ConsumerState { switch (rateType) { case ExchangeRateType.estimated: if (!(toTicker == "-" || fromTicker == "-")) { - final available = ref - .read(availableFloatingRatePairsStateProvider.state) - .state - .where((e) => e.to == toTicker && e.from == fromTicker); + late final Iterable available; + + switch (ref.read(currentExchangeNameStateProvider.state).state) { + case ChangeNowExchange.exchangeName: + available = ref + .read(availableChangeNowCurrenciesProvider) + .pairs + .where((e) => e.to == toTicker && e.from == fromTicker); + break; + case SimpleSwapExchange.exchangeName: + available = ref + .read(availableSimpleswapCurrenciesProvider) + .floatingRatePairs + .where((e) => e.to == toTicker && e.from == fromTicker); + break; + default: + available = []; + } + if (available.isNotEmpty) { - final availableCurrencies = ref - .read(availableChangeNowCurrenciesStateProvider.state) - .state - .where((e) => e.ticker == fromTicker || e.ticker == toTicker); + late final Iterable availableCurrencies; + switch (ref.read(currentExchangeNameStateProvider.state).state) { + case ChangeNowExchange.exchangeName: + availableCurrencies = ref + .read(availableChangeNowCurrenciesProvider) + .currencies + .where( + (e) => e.ticker == fromTicker || e.ticker == toTicker); + break; + case SimpleSwapExchange.exchangeName: + availableCurrencies = ref + .read(availableSimpleswapCurrenciesProvider) + .floatingRateCurrencies + .where( + (e) => e.ticker == fromTicker || e.ticker == toTicker); + break; + default: + availableCurrencies = []; + } + if (availableCurrencies.length > 1) { final from = availableCurrencies.firstWhere((e) => e.ticker == fromTicker); @@ -444,209 +577,245 @@ class _ExchangeFormState extends ConsumerState { if (mounted) { Navigator.of(context).pop(); } - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Estimated rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last estimated rate pair.", - context: context, - )); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: + "Estimated rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last estimated rate pair.", + context: context, + ), + ); break; case ExchangeRateType.fixed: if (!(toTicker == "-" || fromTicker == "-")) { - FixedRateMarket? market; - try { - market = ref - .read(fixedRateMarketPairsStateProvider.state) - .state - .firstWhere((e) => e.from == fromTicker && e.to == toTicker); - } catch (_) { - market = null; - } + switch (ref.read(currentExchangeNameStateProvider.state).state) { + case ChangeNowExchange.exchangeName: + FixedRateMarket? market; + try { + market = ref + .read(availableChangeNowCurrenciesProvider) + .markets + .firstWhere( + (e) => e.from == fromTicker && e.to == toTicker); + } catch (_) { + market = null; + } - final newFromAmount = Decimal.tryParse(_sendController.text); - ref.read(exchangeFormStateProvider).fromAmount = - newFromAmount ?? Decimal.zero; + final newFromAmount = Decimal.tryParse(_sendController.text); + ref.read(exchangeFormStateProvider).fromAmount = + newFromAmount ?? Decimal.zero; - if (newFromAmount == null) { - _receiveController.text = ""; - } + if (newFromAmount == null) { + _receiveController.text = ""; + } - await ref.read(exchangeFormStateProvider).updateMarket(market, false); - await ref - .read(exchangeFormStateProvider) - .setFromAmountAndCalculateToAmount( - Decimal.tryParse(_sendController.text) ?? Decimal.zero, - true, - ); - if (mounted) { - Navigator.of(context).pop(); + await ref + .read(exchangeFormStateProvider) + .updateMarket(market, false); + await ref + .read(exchangeFormStateProvider) + .setFromAmountAndCalculateToAmount( + Decimal.tryParse(_sendController.text) ?? Decimal.zero, + true, + ); + if (mounted) { + Navigator.of(context).pop(); + } + return; + case SimpleSwapExchange.exchangeName: + final available = ref + .read(availableSimpleswapCurrenciesProvider) + .floatingRatePairs + .where((e) => e.to == toTicker && e.from == fromTicker); + if (available.isNotEmpty) { + final availableCurrencies = ref + .read(availableSimpleswapCurrenciesProvider) + .fixedRateCurrencies + .where( + (e) => e.ticker == fromTicker || e.ticker == toTicker); + if (availableCurrencies.length > 1) { + final from = availableCurrencies + .firstWhere((e) => e.ticker == fromTicker); + final to = availableCurrencies + .firstWhere((e) => e.ticker == toTicker); + + final newFromAmount = Decimal.tryParse(_sendController.text); + ref.read(exchangeFormStateProvider).fromAmount = + newFromAmount ?? Decimal.zero; + if (newFromAmount == null) { + _receiveController.text = ""; + } + + await ref.read(exchangeFormStateProvider).updateTo(to, false); + await ref + .read(exchangeFormStateProvider) + .updateFrom(from, true); + + _receiveController.text = + ref.read(exchangeFormStateProvider).toAmountString.isEmpty + ? "-" + : ref.read(exchangeFormStateProvider).toAmountString; + if (mounted) { + Navigator.of(context).pop(); + } + return; + } + } + + break; + default: + // } - return; } if (mounted) { Navigator.of(context).pop(); } - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Fixed rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last fixed rate pair.", - context: context, - )); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: + "Fixed rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last fixed rate pair.", + context: context, + ), + ); break; } } void onExchangePressed() async { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; - final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; - - bool isAvailable = false; - final availableFloatingPairs = - ref.read(availableFloatingRatePairsStateProvider.state).state; - for (final pair in availableFloatingPairs) { - if (pair.from == fromTicker && pair.to == toTicker) { - isAvailable = true; - break; - } - } - - if (!isAvailable) { - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Selected trade pair unavailable", - message: - "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", - ), - )); - return; - } - - final sendAmount = ref.read(exchangeFormStateProvider).fromAmount!; - - final rateType = ref.read(prefsChangeNotifierProvider).exchangeRateType; + final rateType = ref.read(prefsChangeNotifierProvider).exchangeRateType; + final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; + final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; + final sendAmount = ref.read(exchangeFormStateProvider).fromAmount!; + final estimate = ref.read(exchangeFormStateProvider).estimate!; - final estimate = ref.read(exchangeFormStateProvider).estimate!; + String rate; - String rate = - "1 ${fromTicker.toUpperCase()} ~${(estimate.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + switch (rateType) { + case ExchangeRateType.estimated: + bool isAvailable = false; + late final Iterable availableFloatingPairs; + + switch (ref.read(currentExchangeNameStateProvider.state).state) { + case ChangeNowExchange.exchangeName: + availableFloatingPairs = ref + .read(availableChangeNowCurrenciesProvider) + .pairs + .where((e) => e.to == toTicker && e.from == fromTicker); + break; + case SimpleSwapExchange.exchangeName: + availableFloatingPairs = ref + .read(availableSimpleswapCurrenciesProvider) + .floatingRatePairs + .where((e) => e.to == toTicker && e.from == fromTicker); + break; + default: + availableFloatingPairs = []; + } - final model = IncompleteExchangeModel( - sendTicker: fromTicker.toUpperCase(), - receiveTicker: toTicker.toUpperCase(), - rateInfo: rate, - sendAmount: sendAmount, - receiveAmount: estimate.estimatedAmount, - rateType: rateType, - rateId: estimate.rateId, - reversed: estimate.reversed, - ); + for (final pair in availableFloatingPairs) { + if (pair.from == fromTicker && pair.to == toTicker) { + isAvailable = true; + break; + } + } - if (mounted) { - if (walletInitiated) { - ref.read(exchangeSendFromWalletIdStateProvider.state).state = - Tuple2(walletId!, coin!); - unawaited(Navigator.of(context).pushNamed( - Step2View.routeName, - arguments: model, - )); - } else { - ref.read(exchangeSendFromWalletIdStateProvider.state).state = null; - unawaited(Navigator.of(context).pushNamed( - Step1View.routeName, - arguments: model, + if (!isAvailable) { + unawaited(showDialog( + context: context, + barrierDismissible: true, + builder: (_) => StackDialog( + title: "Selected trade pair unavailable", + message: + "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", + ), )); + return; } - } - } else { - final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; - final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; - - final sendAmount = ref.read(exchangeFormStateProvider).fromAmount!; - - final rateType = ref.read(prefsChangeNotifierProvider).exchangeRateType; - - final estimate = ref.read(exchangeFormStateProvider).estimate!; - - bool? shouldCancel; - - if (estimate.warningMessage != null && - estimate.warningMessage!.isNotEmpty) { - shouldCancel = await showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Failed to update trade estimate", - message: - "${estimate.warningMessage!}\n\nDo you want to attempt trade anyways?", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.itemSubtitle12(context), + rate = + "1 ${fromTicker.toUpperCase()} ~${(estimate.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + break; + case ExchangeRateType.fixed: + bool? shouldCancel; + + if (estimate.warningMessage != null && + estimate.warningMessage!.isNotEmpty) { + shouldCancel = await showDialog( + context: context, + barrierDismissible: true, + builder: (_) => StackDialog( + title: "Failed to update trade estimate", + message: + "${estimate.warningMessage!}\n\nDo you want to attempt trade anyways?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + child: Text( + "Cancel", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + // notify return to cancel + Navigator.of(context).pop(true); + }, ), - onPressed: () { - // notify return to cancel - Navigator.of(context).pop(true); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Attempt", - style: STextStyles.button(context), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + child: Text( + "Attempt", + style: STextStyles.button(context), + ), + onPressed: () { + // continue and try to attempt trade + Navigator.of(context).pop(false); + }, ), - onPressed: () { - // continue and try to attempt trade - Navigator.of(context).pop(false); - }, ), - ), - ); - } + ); + } - if (shouldCancel is bool && shouldCancel) { - return; - } + if (shouldCancel is bool && shouldCancel) { + return; + } + rate = + "1 ${fromTicker.toUpperCase()} ~${ref.read(exchangeFormStateProvider).rate!.toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + break; + } + + final model = IncompleteExchangeModel( + sendTicker: fromTicker.toUpperCase(), + receiveTicker: toTicker.toUpperCase(), + rateInfo: rate, + sendAmount: estimate.reversed ? estimate.estimatedAmount : sendAmount, + receiveAmount: estimate.reversed + ? ref.read(exchangeFormStateProvider).toAmount! + : estimate.estimatedAmount, + rateType: rateType, + rateId: estimate.rateId, + reversed: estimate.reversed, + ); - String rate = - "1 ${fromTicker.toUpperCase()} ~${ref.read(exchangeFormStateProvider).rate!.toStringAsFixed(8)} ${toTicker.toUpperCase()}"; - - final model = IncompleteExchangeModel( - sendTicker: fromTicker, - receiveTicker: toTicker, - rateInfo: rate, - sendAmount: estimate.reversed ? estimate.estimatedAmount : sendAmount, - receiveAmount: estimate.reversed - ? ref.read(exchangeFormStateProvider).toAmount! - : estimate.estimatedAmount, - rateType: rateType, - rateId: estimate.rateId, - reversed: estimate.reversed, - ); - - if (mounted) { - if (walletInitiated) { - ref.read(exchangeSendFromWalletIdStateProvider.state).state = - Tuple2(walletId!, coin!); - unawaited(Navigator.of(context).pushNamed( + if (mounted) { + if (walletInitiated) { + ref.read(exchangeSendFromWalletIdStateProvider.state).state = + Tuple2(walletId!, coin!); + unawaited( + Navigator.of(context).pushNamed( Step2View.routeName, arguments: model, - )); - } else { - ref.read(exchangeSendFromWalletIdStateProvider.state).state = null; - unawaited(Navigator.of(context).pushNamed( + ), + ); + } else { + ref.read(exchangeSendFromWalletIdStateProvider.state).state = null; + unawaited( + Navigator.of(context).pushNamed( Step1View.routeName, arguments: model, - )); - } + ), + ); } } } 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 1858787db2..0f03d42169 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart @@ -7,7 +7,7 @@ import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_4_view.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; -import 'package:stackwallet/providers/exchange/change_now_provider.dart'; +import 'package:stackwallet/providers/exchange/exchange_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/notifications_api.dart'; @@ -245,7 +245,7 @@ class _Step3ViewState extends ConsumerState { final ExchangeResponse response = await ref - .read(changeNowProvider) + .read(exchangeProvider) .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 45f67c152e..e8dbd7a230 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 @@ -12,8 +12,6 @@ import 'package:stackwallet/pages/exchange_view/send_from_view.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; -import 'package:stackwallet/providers/exchange/change_now_provider.dart'; -import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -68,7 +66,7 @@ class _Step4ViewState extends ConsumerState { Future _updateStatus() async { final statusResponse = - await ref.read(changeNowProvider).updateTrade(model.trade!); + await ref.read(exchangeProvider).updateTrade(model.trade!); String status = "Waiting"; if (statusResponse.value != null) { status = statusResponse.value!.status; 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 0ebedcebdf..fc960f88a3 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -2,32 +2,14 @@ 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/providers/providers.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; -import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -final currentExchangeNameStateProvider = - StateProvider((ref) => ChangeNowExchange.exchangeName); - -final exchangeProvider = Provider((ref) { - switch (ref.watch(currentExchangeNameStateProvider.state).state) { - case ChangeNowExchange.exchangeName: - return ChangeNowExchange(); - case SimpleSwapExchange.exchangeName: - return SimpleSwapExchange(); - default: - const errorMessage = - "Attempted to access exchangeProvider with invalid exchange name!"; - Logging.instance.log(errorMessage, level: LogLevel.Fatal); - throw Exception(errorMessage); - } -}); - class ExchangeProviderOptions extends ConsumerWidget { const ExchangeProviderOptions({ Key? key, diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index 5570757a6e..0ea344fd60 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -13,10 +13,9 @@ import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart'; import 'package:stackwallet/pages/exchange_view/send_from_view.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; -import 'package:stackwallet/providers/exchange/change_now_provider.dart'; -import 'package:stackwallet/providers/exchange/trade_note_service_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -85,7 +84,8 @@ class _TradeDetailsViewState extends ConsumerState { .firstWhere((e) => e.tradeId == tradeId); if (mounted) { - final response = await ref.read(changeNowProvider).updateTrade(trade); + final exchange = Exchange.fromName(trade.exchangeName); + final response = await exchange.updateTrade(trade); if (mounted && response.value != null) { await ref diff --git a/lib/pages/home_view/home_view.dart b/lib/pages/home_view/home_view.dart index 2503d05dc2..5dd2e0cada 100644 --- a/lib/pages/home_view/home_view.dart +++ b/lib/pages/home_view/home_view.dart @@ -13,7 +13,7 @@ import 'package:stackwallet/pages/wallets_view/wallets_view.dart'; import 'package:stackwallet/providers/global/notifications_provider.dart'; import 'package:stackwallet/providers/ui/home_view_index_provider.dart'; import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_loading_service.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -41,7 +41,7 @@ class _HomeViewState extends ConsumerState { bool _exitEnabled = false; - final _cnLoadingService = ChangeNowLoadingService(); + final _cnLoadingService = ExchangeDataLoadingService(); Future _onWillPop() async { // go to home view when tapping back on the main exchange view diff --git a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart index a933b4d2f6..6ba5e3208e 100644 --- a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart +++ b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/exchange_form_state.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_loading_service.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -119,7 +118,7 @@ class _HomeViewButtonBarState extends ConsumerState { // // }, // ), // ); - await ChangeNowLoadingService().loadAll(ref); + await ExchangeDataLoadingService().loadAll(ref); // if (!okPressed && mounted) { // Navigator.of(context).pop(); // } diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 94e8c5fa69..9a3f1e4ba0 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -5,7 +5,6 @@ import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/exchange_form_state.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart'; @@ -19,7 +18,6 @@ import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_summary.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart'; -import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; @@ -31,7 +29,8 @@ import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_loading_service.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; @@ -79,7 +78,7 @@ class _WalletViewState extends ConsumerState { late StreamSubscription _syncStatusSubscription; late StreamSubscription _nodeStatusSubscription; - final _cnLoadingService = ChangeNowLoadingService(); + final _cnLoadingService = ExchangeDataLoadingService(); @override void initState() { @@ -247,15 +246,19 @@ class _WalletViewState extends ConsumerState { ), ); } else { + ref.read(currentExchangeNameStateProvider.state).state = + ChangeNowExchange.exchangeName; final walletId = ref.read(managerProvider).walletId; ref.read(prefsChangeNotifierProvider).exchangeRateType = ExchangeRateType.estimated; + + ref.read(exchangeFormStateProvider).exchange = ref.read(exchangeProvider); ref.read(exchangeFormStateProvider).exchangeType = ExchangeRateType.estimated; final currencies = ref - .read(availableChangeNowCurrenciesStateProvider.state) - .state + .read(availableChangeNowCurrenciesProvider) + .currencies .where((element) => element.ticker.toLowerCase() == coin.ticker.toLowerCase()); @@ -263,8 +266,8 @@ class _WalletViewState extends ConsumerState { ref.read(exchangeFormStateProvider).setCurrencies( currencies.first, ref - .read(availableChangeNowCurrenciesStateProvider.state) - .state + .read(availableChangeNowCurrenciesProvider) + .currencies .firstWhere( (element) => element.ticker.toLowerCase() != diff --git a/lib/providers/exchange/available_changenow_currencies_provider.dart b/lib/providers/exchange/available_changenow_currencies_provider.dart new file mode 100644 index 0000000000..04bf040077 --- /dev/null +++ b/lib/providers/exchange/available_changenow_currencies_provider.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/exchange/change_now/cn_available_currencies.dart'; + +final availableChangeNowCurrenciesProvider = Provider( + (ref) => CNAvailableCurrencies(), +); diff --git a/lib/providers/exchange/available_currencies_state_provider.dart b/lib/providers/exchange/available_currencies_state_provider.dart deleted file mode 100644 index e47fab8d6b..0000000000 --- a/lib/providers/exchange/available_currencies_state_provider.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; - -final availableChangeNowCurrenciesStateProvider = - StateProvider>((ref) => []); diff --git a/lib/providers/exchange/available_floating_rate_pairs_state_provider.dart b/lib/providers/exchange/available_floating_rate_pairs_state_provider.dart deleted file mode 100644 index e65f367b51..0000000000 --- a/lib/providers/exchange/available_floating_rate_pairs_state_provider.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/response_objects/pair.dart'; - -final availableFloatingRatePairsStateProvider = - StateProvider>((ref) => []); diff --git a/lib/providers/exchange/available_simpleswap_currencies_provider.dart b/lib/providers/exchange/available_simpleswap_currencies_provider.dart new file mode 100644 index 0000000000..ae1bd5fa07 --- /dev/null +++ b/lib/providers/exchange/available_simpleswap_currencies_provider.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/exchange/simpleswap/sp_available_currencies.dart'; + +final availableSimpleswapCurrenciesProvider = Provider( + (ref) => SPAvailableCurrencies(), +); diff --git a/lib/providers/exchange/change_now_provider.dart b/lib/providers/exchange/current_exchange_name_state_provider.dart similarity index 50% rename from lib/providers/exchange/change_now_provider.dart rename to lib/providers/exchange/current_exchange_name_state_provider.dart index 8493680f4d..67b630ad7f 100644 --- a/lib/providers/exchange/change_now_provider.dart +++ b/lib/providers/exchange/current_exchange_name_state_provider.dart @@ -1,5 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; -import 'package:stackwallet/services/exchange/exchange.dart'; -final changeNowProvider = Provider((ref) => ChangeNowExchange()); +final currentExchangeNameStateProvider = StateProvider( + (ref) => ChangeNowExchange.exchangeName, +); diff --git a/lib/providers/exchange/estimate_rate_exchange_form_provider.dart b/lib/providers/exchange/estimate_rate_exchange_form_provider.dart deleted file mode 100644 index 59705b54e5..0000000000 --- a/lib/providers/exchange/estimate_rate_exchange_form_provider.dart +++ /dev/null @@ -1,5 +0,0 @@ -// import 'package:flutter_riverpod/flutter_riverpod.dart'; -// import 'package:stackwallet/models/exchange/estimated_rate_exchange_form_state.dart'; -// -// final estimatedRateExchangeFormProvider = -// ChangeNotifierProvider((ref) => EstimatedRateExchangeFormState()); diff --git a/lib/providers/exchange/exchange_form_state_provider.dart b/lib/providers/exchange/exchange_form_state_provider.dart new file mode 100644 index 0000000000..568660d486 --- /dev/null +++ b/lib/providers/exchange/exchange_form_state_provider.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/exchange/exchange_form_state.dart'; + +final exchangeFormStateProvider = ChangeNotifierProvider( + (ref) => ExchangeFormState(), +); diff --git a/lib/providers/exchange/exchange_provider.dart b/lib/providers/exchange/exchange_provider.dart new file mode 100644 index 0000000000..e65847a185 --- /dev/null +++ b/lib/providers/exchange/exchange_provider.dart @@ -0,0 +1,9 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/exchange/current_exchange_name_state_provider.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; + +final exchangeProvider = Provider( + (ref) => Exchange.fromName( + ref.watch(currentExchangeNameStateProvider.state).state, + ), +); diff --git a/lib/providers/exchange/fixed_rate_exchange_form_provider.dart b/lib/providers/exchange/fixed_rate_exchange_form_provider.dart deleted file mode 100644 index 7f0af1cf11..0000000000 --- a/lib/providers/exchange/fixed_rate_exchange_form_provider.dart +++ /dev/null @@ -1,6 +0,0 @@ -// import 'package:flutter_riverpod/flutter_riverpod.dart'; -// import 'package:stackwallet/models/exchange/fixed_rate_exchange_form_state.dart'; -// -// final fixedRateExchangeFormProvider = -// ChangeNotifierProvider( -// (ref) => FixedRateExchangeFormState()); diff --git a/lib/providers/exchange/fixed_rate_market_pairs_provider.dart b/lib/providers/exchange/fixed_rate_market_pairs_provider.dart deleted file mode 100644 index 1b2cbdaca7..0000000000 --- a/lib/providers/exchange/fixed_rate_market_pairs_provider.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; - -final fixedRateMarketPairsStateProvider = - StateProvider>((ref) => []); diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart index 0f73f04e15..5cef388308 100644 --- a/lib/providers/providers.dart +++ b/lib/providers/providers.dart @@ -1,3 +1,13 @@ +export './exchange/available_changenow_currencies_provider.dart'; +export './exchange/available_simpleswap_currencies_provider.dart'; +export './exchange/changenow_initial_load_status.dart'; +export './exchange/current_exchange_name_state_provider.dart'; +export './exchange/exchange_flow_is_active_state_provider.dart'; +export './exchange/exchange_form_state_provider.dart'; +export './exchange/exchange_provider.dart'; +export './exchange/exchange_send_from_wallet_id_provider.dart'; +export './exchange/trade_note_service_provider.dart'; +export './exchange/trade_sent_from_stack_lookup_provider.dart'; export './global/favorites_provider.dart'; export './global/locale_provider.dart'; export './global/node_service_provider.dart'; diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart index 88a9075649..6437f90659 100644 --- a/lib/services/exchange/exchange.dart +++ b/lib/services/exchange/exchange.dart @@ -4,9 +4,22 @@ import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; abstract class Exchange { + static Exchange fromName(String name) { + switch (name) { + case ChangeNowExchange.exchangeName: + return ChangeNowExchange(); + case SimpleSwapExchange.exchangeName: + return SimpleSwapExchange(); + default: + throw ArgumentError("Unknown exchange name"); + } + } + String get name; Future>> getAllCurrencies(bool fixedRate); diff --git a/lib/services/exchange/change_now/change_now_loading_service.dart b/lib/services/exchange/exchange_data_loading_service.dart similarity index 64% rename from lib/services/exchange/change_now/change_now_loading_service.dart rename to lib/services/exchange/exchange_data_loading_service.dart index 2442831825..3af4acf679 100644 --- a/lib/services/exchange/change_now/change_now_loading_service.dart +++ b/lib/services/exchange/exchange_data_loading_service.dart @@ -1,19 +1,18 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/exchange_form_state.dart'; -import 'package:stackwallet/providers/exchange/available_currencies_state_provider.dart'; -import 'package:stackwallet/providers/exchange/available_floating_rate_pairs_state_provider.dart'; -import 'package:stackwallet/providers/exchange/changenow_initial_load_status.dart'; -import 'package:stackwallet/providers/exchange/fixed_rate_market_pairs_provider.dart'; +import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; +import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; -class ChangeNowLoadingService { +class ExchangeDataLoadingService { Future loadAll(WidgetRef ref, {Coin? coin}) async { try { await Future.wait([ _loadFixedRateMarkets(ref, coin: coin), _loadChangeNowStandardCurrencies(ref, coin: coin), + loadSimpleswapFixedRateCurrencies(ref), + loadSimpleswapFloatingRateCurrencies(ref), ]); } catch (e, s) { Logging.instance.log("ChangeNowLoadingService.loadAll failed: $e\n$s", @@ -34,8 +33,9 @@ class ChangeNowLoadingService { final response3 = await ChangeNowAPI.instance.getAvailableFixedRateMarkets(); if (response3.value != null) { - ref.read(fixedRateMarketPairsStateProvider.state).state = - response3.value!; + ref + .read(availableChangeNowCurrenciesProvider) + .updateMarkets(response3.value!); if (ref.read(exchangeFormStateProvider).market == null) { String fromTicker = "btc"; @@ -86,11 +86,14 @@ class ChangeNowLoadingService { final response2 = await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); if (response.value != null) { - ref.read(availableChangeNowCurrenciesStateProvider.state).state = - response.value!; + ref + .read(availableChangeNowCurrenciesProvider) + .updateCurrencies(response.value!); + if (response2.value != null) { - ref.read(availableFloatingRatePairsStateProvider.state).state = - response2.value!; + ref + .read(availableChangeNowCurrenciesProvider) + .updateFloatingPairs(response2.value!); String fromTicker = "btc"; String toTicker = "xmr"; @@ -138,4 +141,62 @@ class ChangeNowLoadingService { ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = ChangeNowLoadStatus.success; } + + Future loadSimpleswapFloatingRateCurrencies(WidgetRef ref) async { + final exchange = SimpleSwapExchange(); + final responseCurrencies = await exchange.getAllCurrencies(false); + + if (responseCurrencies.value != null) { + ref + .read(availableSimpleswapCurrenciesProvider) + .updateFloatingCurrencies(responseCurrencies.value!); + + final responsePairs = await exchange.getAllPairs(false); + + if (responsePairs.value != null) { + ref + .read(availableSimpleswapCurrenciesProvider) + .updateFloatingPairs(responsePairs.value!); + } else { + Logging.instance.log( + "loadSimpleswapFloatingRateCurrencies: $responsePairs", + level: LogLevel.Warning, + ); + } + } else { + Logging.instance.log( + "loadSimpleswapFloatingRateCurrencies: $responseCurrencies", + level: LogLevel.Warning, + ); + } + } + + Future loadSimpleswapFixedRateCurrencies(WidgetRef ref) async { + final exchange = SimpleSwapExchange(); + final responseCurrencies = await exchange.getAllCurrencies(true); + + if (responseCurrencies.value != null) { + ref + .read(availableSimpleswapCurrenciesProvider) + .updateFixedCurrencies(responseCurrencies.value!); + + final responsePairs = await exchange.getAllPairs(true); + + if (responsePairs.value != null) { + ref + .read(availableSimpleswapCurrenciesProvider) + .updateFixedPairs(responsePairs.value!); + } else { + Logging.instance.log( + "loadSimpleswapFixedRateCurrencies: $responsePairs", + level: LogLevel.Warning, + ); + } + } else { + Logging.instance.log( + "loadSimpleswapFixedRateCurrencies: $responseCurrencies", + level: LogLevel.Warning, + ); + } + } } From a37b518f7efea14c482bc775cdefd54373bc6c84 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 4 Oct 2022 13:10:44 -0600 Subject: [PATCH 23/75] WIP show rates --- lib/models/exchange/exchange_form_state.dart | 32 ++- lib/pages/exchange_view/exchange_form.dart | 26 +- .../exchange_provider_options.dart | 223 ++++++++++++++++-- 3 files changed, 251 insertions(+), 30 deletions(-) diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart index e5aac8f0a1..892149f3f5 100644 --- a/lib/models/exchange/exchange_form_state.dart +++ b/lib/models/exchange/exchange_form_state.dart @@ -6,6 +6,7 @@ import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/utilities/logger.dart'; class ExchangeFormState extends ChangeNotifier { @@ -184,7 +185,7 @@ class ExchangeFormState extends ChangeNotifier { ); debugPrint( - "_updated TO: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$fromAmount _toAmount=$toAmount rate:$rate"); + "_updated TO: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$fromAmount _toAmount=$toAmount rate:$rate for: $exchange"); if (shouldNotifyListeners) { notifyListeners(); @@ -212,7 +213,7 @@ class ExchangeFormState extends ChangeNotifier { ); debugPrint( - "_updated FROM: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$fromAmount _toAmount=$toAmount rate:$rate"); + "_updated FROM: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$fromAmount _toAmount=$toAmount rate:$rate for: $exchange"); if (shouldNotifyListeners) { notifyListeners(); } @@ -254,15 +255,23 @@ class ExchangeFormState extends ChangeNotifier { } void _onExchangeTypeChanged() { - print("_onExchangeTypeChanged"); + updateRanges(shouldNotifyListeners: true).then( + (_) => updateEstimate( + shouldNotifyListeners: true, + reversed: reversed, + ), + ); } Future updateRanges({required bool shouldNotifyListeners}) async { + if (exchange?.name == SimpleSwapExchange.exchangeName) { + reversed = false; + } final _fromTicker = reversed ? toTicker : fromTicker; final _toTicker = reversed ? fromTicker : toTicker; if (_fromTicker == null || _toTicker == null) { Logging.instance.log( - "Tried to $runtimeType.updateRanges where (from: $_fromTicker || to: $_toTicker)", + "Tried to $runtimeType.updateRanges where (from: $_fromTicker || to: $_toTicker) for: $exchange", level: LogLevel.Info, ); return; @@ -275,7 +284,7 @@ class ExchangeFormState extends ChangeNotifier { if (response?.value == null) { Logging.instance.log( - "Tried to $runtimeType.updateRanges where response: $response", + "Tried to $runtimeType.updateRanges for: $exchange where response: $response", level: LogLevel.Info, ); return; @@ -286,7 +295,8 @@ class ExchangeFormState extends ChangeNotifier { minAmount = range.min; maxAmount = range.max; - debugPrint("updated range for $_fromTicker-$_toTicker: $range"); + debugPrint( + "updated range for: $exchange for $_fromTicker-$_toTicker: $range"); if (shouldNotifyListeners) { notifyListeners(); @@ -297,13 +307,16 @@ class ExchangeFormState extends ChangeNotifier { required bool shouldNotifyListeners, required bool reversed, }) async { + if (exchange?.name == SimpleSwapExchange.exchangeName) { + reversed = false; + } final amount = reversed ? toAmount : fromAmount; if (fromTicker == null || toTicker == null || amount == null || amount <= Decimal.zero) { Logging.instance.log( - "Tried to $runtimeType.updateEstimate where (from: $fromTicker || to: $toTicker || amount: $amount)", + "Tried to $runtimeType.updateEstimate for: $exchange where (from: $fromTicker || to: $toTicker || amount: $amount)", level: LogLevel.Info, ); return; @@ -318,7 +331,7 @@ class ExchangeFormState extends ChangeNotifier { if (response?.value == null) { Logging.instance.log( - "Tried to $runtimeType.updateEstimate where response: $response", + "Tried to $runtimeType.updateEstimate for: $exchange where response: $response", level: LogLevel.Info, ); return; @@ -334,7 +347,8 @@ class ExchangeFormState extends ChangeNotifier { rate = (toAmount! / fromAmount!).toDecimal(scaleOnInfinitePrecision: 12); - debugPrint("updated estimate for $fromTicker-$toTicker: $estimate"); + debugPrint( + "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate"); if (shouldNotifyListeners) { notifyListeners(); diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 30d333b8cb..4928cec57a 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -294,8 +294,7 @@ class _ExchangeFormState extends ConsumerState { ), ); - if (ref.watch(prefsChangeNotifierProvider - .select((pref) => pref.exchangeRateType)) == + if (ref.read(prefsChangeNotifierProvider).exchangeRateType == ExchangeRateType.estimated) { await ref.read(exchangeFormStateProvider).swap(); } else { @@ -912,6 +911,10 @@ class _ExchangeFormState extends ConsumerState { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); + ref.listen(currentExchangeNameStateProvider, (previous, next) { + ref.read(exchangeFormStateProvider).exchange = ref.read(exchangeProvider); + }); + final isEstimated = ref.watch(prefsChangeNotifierProvider .select((pref) => pref.exchangeRateType)) == ExchangeRateType.estimated; @@ -920,7 +923,12 @@ class _ExchangeFormState extends ConsumerState { exchangeFormStateProvider.select((value) => value.toAmountString), (previous, String next) { if (!_receiveFocusNode.hasFocus) { - _receiveController.text = isEstimated && next.isEmpty ? "-" : next; + _receiveController.text = isEstimated && + ref.watch(exchangeProvider).name == + SimpleSwapExchange.exchangeName && + next.isEmpty + ? "-" + : next; debugPrint("RECEIVE AMOUNT LISTENER ACTIVATED"); if (_swapLock) { _sendController.text = @@ -1164,8 +1172,10 @@ class _ExchangeFormState extends ConsumerState { focusNode: _receiveFocusNode, controller: _receiveController, readOnly: ref.watch(prefsChangeNotifierProvider - .select((value) => value.exchangeRateType)) == - ExchangeRateType.estimated, + .select((value) => value.exchangeRateType)) == + ExchangeRateType.estimated || + ref.watch(exchangeProvider).name == + SimpleSwapExchange.exchangeName, onTap: () { if (!(ref.read(prefsChangeNotifierProvider).exchangeRateType == ExchangeRateType.estimated) && @@ -1306,8 +1316,10 @@ class _ExchangeFormState extends ConsumerState { height: 8, ), ExchangeProviderOptions( - fromAmount: null, - toAmount: null, + from: ref.watch(exchangeFormStateProvider).fromTicker, + to: ref.watch(exchangeFormStateProvider).toTicker, + fromAmount: ref.watch(exchangeFormStateProvider).fromAmount, + toAmount: ref.watch(exchangeFormStateProvider).toAmount, fixedRate: ref.watch(prefsChangeNotifierProvider .select((value) => value.exchangeRateType)) == ExchangeRateType.fixed, 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 fc960f88a3..20d39f986c 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -2,23 +2,34 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/format.dart'; +import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class ExchangeProviderOptions extends ConsumerWidget { const ExchangeProviderOptions({ Key? key, + required this.from, + required this.to, required this.fromAmount, required this.toAmount, required this.fixedRate, required this.reversed, }) : super(key: key); + final String? from; + final String? to; final Decimal? fromAmount; final Decimal? toAmount; final bool fixedRate; @@ -87,14 +98,106 @@ class ExchangeProviderOptions extends ConsumerWidget { .textDark2, ), ), - Text( - ChangeNowExchange.exchangeName, - style: STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + if (from != null && + to != null && + toAmount != null && + toAmount! > Decimal.zero && + fromAmount != null && + fromAmount! > Decimal.zero) + FutureBuilder( + future: ChangeNowExchange().getEstimate( + from!, + to!, + reversed ? toAmount! : fromAmount!, + fixedRate, + reversed, + ), + builder: (context, + AsyncSnapshot> + snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + final estimate = snapshot.data?.value; + if (estimate != null) { + Decimal rate; + if (estimate.reversed) { + rate = + (toAmount! / estimate.estimatedAmount) + .toDecimal( + scaleOnInfinitePrecision: 12); + } else { + rate = + (estimate.estimatedAmount / fromAmount!) + .toDecimal( + scaleOnInfinitePrecision: 12); + } + return Text( + "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( + value: rate, + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + decimalPlaces: to!.toUpperCase() == + Coin.monero.ticker.toUpperCase() + ? Constants.decimalPlacesMonero + : Constants.decimalPlaces, + )} ${to!.toUpperCase()}", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } else { + Logging.instance.log( + "$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}", + level: LogLevel.Warning, + ); + return Text( + "Failed to fetch rate", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } + } else { + return AnimatedText( + stringsToLoopThrough: const [ + "Loading", + "Loading.", + "Loading..", + "Loading...", + ], + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } + }, + ), + if (!(from != null && + to != null && + toAmount != null && + toAmount! > Decimal.zero && + fromAmount != null && + fromAmount! > Decimal.zero)) + Text( + "n/a", + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), ), - ), ], ), ), @@ -163,14 +266,106 @@ class ExchangeProviderOptions extends ConsumerWidget { .textDark2, ), ), - Text( - SimpleSwapExchange.exchangeName, - style: STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + if (from != null && + to != null && + toAmount != null && + toAmount! > Decimal.zero && + fromAmount != null && + fromAmount! > Decimal.zero) + FutureBuilder( + future: SimpleSwapExchange().getEstimate( + from!, + to!, + // reversed ? toAmount! : fromAmount!, + fromAmount!, + fixedRate, + // reversed, + false, + ), + builder: (context, + AsyncSnapshot> + snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + final estimate = snapshot.data?.value; + if (estimate != null) { + Decimal rate = (estimate.estimatedAmount / + fromAmount!) + .toDecimal(scaleOnInfinitePrecision: 12); + + return Text( + "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( + value: rate, + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + decimalPlaces: to!.toUpperCase() == + Coin.monero.ticker.toUpperCase() + ? Constants.decimalPlacesMonero + : Constants.decimalPlaces, + )} ${to!.toUpperCase()}", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } else { + Logging.instance.log( + "$runtimeType failed to fetch rate for SimpleSwap: ${snapshot.data}", + level: LogLevel.Warning, + ); + return Text( + "Failed to fetch rate", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } + } else { + return AnimatedText( + stringsToLoopThrough: const [ + "Loading", + "Loading.", + "Loading..", + "Loading...", + ], + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } + }, + ), + // if (!(from != null && + // to != null && + // (reversed + // ? toAmount != null && toAmount! > Decimal.zero + // : fromAmount != null && + // fromAmount! > Decimal.zero))) + if (!(from != null && + to != null && + toAmount != null && + toAmount! > Decimal.zero && + fromAmount != null && + fromAmount! > Decimal.zero)) + Text( + "n/a", + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), ), - ), ], ), ), From 1c0e023fcacc10c507c6657d333f02a02c006b1b Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 4 Oct 2022 16:10:50 -0600 Subject: [PATCH 24/75] simpleswap api call parse fix as well as ui trade exchange name fixes --- .../confirm_change_now_send.dart | 2 +- .../exchange_loading_overlay.dart | 6 +-- .../exchange_step_views/step_4_view.dart | 2 +- lib/pages/exchange_view/exchange_view.dart | 1 - .../exchange_view/trade_details_view.dart | 51 +++++++++++-------- .../sub_widgets/home_view_button_bar.dart | 2 +- .../stack_restore_progress_view.dart | 2 +- lib/pages/wallet_view/wallet_view.dart | 4 +- .../exchange_data_loading_service.dart | 2 +- .../exchange/simpleswap/simpleswap_api.dart | 12 ++--- 10 files changed, 45 insertions(+), 39 deletions(-) diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index cf1c7f0150..d77ad6b8c9 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -207,7 +207,7 @@ class _ConfirmChangeNowSendViewState crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( - "ChangeNOW address", + "${trade.exchangeName} address", style: STextStyles.smallMed12(context), ), const SizedBox( diff --git a/lib/pages/exchange_view/exchange_loading_overlay.dart b/lib/pages/exchange_view/exchange_loading_overlay.dart index e4e6c1f279..15d8dccec6 100644 --- a/lib/pages/exchange_view/exchange_loading_overlay.dart +++ b/lib/pages/exchange_view/exchange_loading_overlay.dart @@ -70,7 +70,7 @@ class _ExchangeLoadingOverlayViewState .overlay .withOpacity(0.7), child: const CustomLoadingOverlay( - message: "Loading ChangeNOW data", eventBus: null), + message: "Loading Exchange data", eventBus: null), ), if ((_statusEst == ChangeNowLoadStatus.failed || _statusFixed == ChangeNowLoadStatus.failed) && @@ -85,9 +85,9 @@ class _ExchangeLoadingOverlayViewState mainAxisAlignment: MainAxisAlignment.end, children: [ StackDialog( - title: "Failed to fetch ChangeNow data", + title: "Failed to fetch Exchange data", message: - "ChangeNOW requires a working internet connection. Tap OK to try fetching again.", + "Exchange requires a working internet connection. Tap OK to try fetching again.", rightButton: TextButton( style: Theme.of(context) .extension()! diff --git a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart index e8dbd7a230..0921f68e07 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart @@ -158,7 +158,7 @@ class _Step4ViewState extends ConsumerState { height: 8, ), Text( - "Send ${model.sendTicker.toUpperCase()} to the address below. Once it is received, ChangeNOW will send the ${model.receiveTicker.toUpperCase()} to the recipient address you provided. You can find this trade details and check its status in the list of trades.", + "Send ${model.sendTicker.toUpperCase()} to the address below. Once it is received, ${model.trade!.exchangeName} will send the ${model.receiveTicker.toUpperCase()} to the recipient address you provided. You can find this trade details and check its status in the list of trades.", style: STextStyles.itemSubtitle(context), ), const SizedBox( diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart index 0006d53b05..84e054eacc 100644 --- a/lib/pages/exchange_view/exchange_view.dart +++ b/lib/pages/exchange_view/exchange_view.dart @@ -4,7 +4,6 @@ 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/exchange_view/trade_details_view.dart'; -import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/utilities/constants.dart'; diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index 0ea344fd60..bba561ead8 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -15,7 +15,9 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.d import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -331,7 +333,7 @@ class _TradeDetailsViewState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "ChangeNOW address", + "${trade.exchangeName} address", style: STextStyles.itemSubtitle(context), ), const SizedBox( @@ -674,16 +676,10 @@ class _TradeDetailsViewState extends ConsumerState { "Exchange", style: STextStyles.itemSubtitle(context), ), - // Flexible( - // child: FittedBox( - // fit: BoxFit.scaleDown, - // child: SelectableText( - "ChangeNOW", + trade.exchangeName, style: STextStyles.itemSubtitle12(context), ), - // ), - // ), ], ), ), @@ -744,20 +740,31 @@ class _TradeDetailsViewState extends ConsumerState { const SizedBox( height: 4, ), - GestureDetector( - onTap: () { - final url = - "https://changenow.io/exchange/txs/${trade.tradeId}"; - launchUrl( - Uri.parse(url), - mode: LaunchMode.externalApplication, - ); - }, - child: Text( - "https://changenow.io/exchange/txs/${trade.tradeId}", - style: STextStyles.link2(context), - ), - ), + Builder(builder: (context) { + late final String url; + switch (trade.exchangeName) { + case ChangeNowExchange.exchangeName: + url = + "https://changenow.io/exchange/txs/${trade.tradeId}"; + break; + case SimpleSwapExchange.exchangeName: + url = + "https://simpleswap.io/exchange?id=${trade.tradeId}"; + break; + } + return GestureDetector( + onTap: () { + launchUrl( + Uri.parse(url), + mode: LaunchMode.externalApplication, + ); + }, + child: Text( + url, + style: STextStyles.link2(context), + ), + ); + }), ], ), ), diff --git a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart index 6ba5e3208e..e897fe4f85 100644 --- a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart +++ b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart @@ -24,7 +24,7 @@ class _HomeViewButtonBarState extends ConsumerState { context: context, barrierDismissible: true, builder: (_) => StackDialog( - title: "ChangeNOW API Call Failed", + title: "Exchange API Call Failed", message: message, ), ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart index 367d211ed0..5ad8cc4f30 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart @@ -389,7 +389,7 @@ class _StackRestoreProgressViewState height: 20, child: _getIconForState(state), ), - title: "ChangeNOW history", + title: "Exchange history", subTitle: state == StackRestoringStatus.failed ? Text( "Something went wrong", diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 9a3f1e4ba0..aad412b6f6 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -235,14 +235,14 @@ class _WalletViewState extends ConsumerState { await showDialog( context: context, builder: (_) => const StackOkDialog( - title: "ChangeNOW not available for Epic Cash", + title: "Exchange not available for Epic Cash", ), ); } else if (coin.name.endsWith("TestNet")) { await showDialog( context: context, builder: (_) => const StackOkDialog( - title: "ChangeNOW not available for test net coins", + title: "Exchange not available for test net coins", ), ); } else { diff --git a/lib/services/exchange/exchange_data_loading_service.dart b/lib/services/exchange/exchange_data_loading_service.dart index 3af4acf679..d06f8b7269 100644 --- a/lib/services/exchange/exchange_data_loading_service.dart +++ b/lib/services/exchange/exchange_data_loading_service.dart @@ -15,7 +15,7 @@ class ExchangeDataLoadingService { loadSimpleswapFloatingRateCurrencies(ref), ]); } catch (e, s) { - Logging.instance.log("ChangeNowLoadingService.loadAll failed: $e\n$s", + Logging.instance.log("ExchangeDataLoadingService.loadAll failed: $e\n$s", level: LogLevel.Error); } } diff --git a/lib/services/exchange/simpleswap/simpleswap_api.dart b/lib/services/exchange/simpleswap/simpleswap_api.dart index 4c78820113..addf732de2 100644 --- a/lib/services/exchange/simpleswap/simpleswap_api.dart +++ b/lib/services/exchange/simpleswap/simpleswap_api.dart @@ -112,14 +112,14 @@ class SimpleSwapAPI { payInAmount: json["amount_from"] as String, payInAddress: json["address_from"] as String, payInNetwork: "", - payInExtraId: json["extra_id_payIn"] as String, - payInTxid: json["tx_from"] as String, + payInExtraId: json["extra_id_from"] as String? ?? "", + payInTxid: json["tx_from"] as String? ?? "", payOutCurrency: json["currency_to"] as String, payOutAmount: json["amount_to"] as String, payOutAddress: json["address_to"] as String, payOutNetwork: "", payOutExtraId: json["extra_id_to"] as String? ?? "", - payOutTxid: json["tx_to"] as String, + payOutTxid: json["tx_to"] as String? ?? "", refundAddress: json["user_refund_address"] as String, refundExtraId: json["user_refund_extra_id"] as String, status: json["status"] as String, @@ -360,14 +360,14 @@ class SimpleSwapAPI { payInAmount: json["amount_from"] as String, payInAddress: json["address_from"] as String, payInNetwork: "", - payInExtraId: json["extra_id_payIn"] as String, - payInTxid: json["tx_from"] as String, + payInExtraId: json["extra_id_from"] as String? ?? "", + payInTxid: json["tx_from"] as String? ?? "", payOutCurrency: json["currency_to"] as String, payOutAmount: json["amount_to"] as String, payOutAddress: json["address_to"] as String, payOutNetwork: "", payOutExtraId: json["extra_id_to"] as String? ?? "", - payOutTxid: json["tx_to"] as String, + payOutTxid: json["tx_to"] as String? ?? "", refundAddress: json["user_refund_address"] as String, refundExtraId: json["user_refund_extra_id"] as String, status: json["status"] as String, From cc38ad5edb8ebea63d29087f6fa32ed206960bad Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 4 Oct 2022 16:35:53 -0600 Subject: [PATCH 25/75] remove unused import --- lib/pages/exchange_view/exchange_form.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 4928cec57a..4085ed4017 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -18,7 +18,6 @@ import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view. import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_options.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/rate_type_toggle.dart'; -import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; From 9acdf9cfc442f9ba89d31e49ce7c7b71e138300c Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 4 Oct 2022 18:57:01 -0600 Subject: [PATCH 26/75] remove unused files --- .../estimated_rate_exchange_form_state.dart | 282 ------------------ .../fixed_rate_exchange_form_state.dart | 179 ----------- 2 files changed, 461 deletions(-) delete mode 100644 lib/models/exchange/estimated_rate_exchange_form_state.dart delete mode 100644 lib/models/exchange/fixed_rate_exchange_form_state.dart diff --git a/lib/models/exchange/estimated_rate_exchange_form_state.dart b/lib/models/exchange/estimated_rate_exchange_form_state.dart deleted file mode 100644 index 2a465c3efc..0000000000 --- a/lib/models/exchange/estimated_rate_exchange_form_state.dart +++ /dev/null @@ -1,282 +0,0 @@ -// import 'package:decimal/decimal.dart'; -// import 'package:flutter/cupertino.dart'; -// import 'package:flutter/material.dart'; -// import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -// import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; -// import 'package:stackwallet/utilities/logger.dart'; -// -// class EstimatedRateExchangeFormState extends ChangeNotifier { -// /// used in testing to inject mock -// ChangeNowAPI? cnTesting; -// -// Decimal? _fromAmount; -// Decimal? _toAmount; -// -// Decimal? _minFromAmount; -// Decimal? _minToAmount; -// -// Decimal? rate; -// -// Currency? _from; -// Currency? _to; -// -// void Function(String)? _onError; -// -// Currency? get from => _from; -// Currency? get to => _to; -// -// String get fromAmountString => -// _fromAmount == null ? "" : _fromAmount!.toStringAsFixed(8); -// String get toAmountString => -// _toAmount == null ? "" : _toAmount!.toStringAsFixed(8); -// -// String get rateDisplayString { -// if (rate == null || from == null || to == null) { -// return "N/A"; -// } else { -// return "1 ${from!.ticker.toUpperCase()} ~${rate!.toStringAsFixed(8)} ${to!.ticker.toUpperCase()}"; -// } -// } -// -// bool get canExchange { -// return _fromAmount != null && -// _fromAmount != Decimal.zero && -// _toAmount != null && -// rate != null && -// minimumSendWarning.isEmpty; -// } -// -// String get minimumSendWarning { -// if (_from != null && -// _fromAmount != null && -// _minFromAmount != null && -// _fromAmount! < _minFromAmount!) { -// return "Minimum amount ${_minFromAmount!.toString()} ${from!.ticker.toUpperCase()}"; -// } -// -// return ""; -// } -// -// Future init(Currency? from, Currency? to) async { -// _from = from; -// _to = to; -// } -// -// void clearAmounts(bool shouldNotifyListeners) { -// _fromAmount = null; -// _toAmount = null; -// _minFromAmount = null; -// _minToAmount = null; -// rate = null; -// -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// Future swap() async { -// final Decimal? newToAmount = _fromAmount; -// final Decimal? newFromAmount = _toAmount; -// -// final Decimal? newMinFromAmount = _minToAmount; -// final Decimal? newMinToAmount = _minFromAmount; -// -// final Currency? newTo = from; -// final Currency? newFrom = to; -// -// _fromAmount = newFromAmount; -// _toAmount = newToAmount; -// -// _minToAmount = newMinToAmount; -// _minFromAmount = newMinFromAmount; -// -// // rate = newRate; -// -// _to = newTo; -// _from = newFrom; -// -// await _updateMinFromAmount(shouldNotifyListeners: false); -// -// await updateRate(); -// -// notifyListeners(); -// } -// -// Future updateTo(Currency to, bool shouldNotifyListeners) async { -// try { -// _to = to; -// if (_from == null) { -// rate = null; -// notifyListeners(); -// return; -// } -// -// await _updateMinFromAmount(shouldNotifyListeners: shouldNotifyListeners); -// -// await updateRate(shouldNotifyListeners: shouldNotifyListeners); -// -// debugPrint( -// "_updated TO: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$_fromAmount _toAmount=$_toAmount rate:$rate"); -// -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } catch (e, s) { -// Logging.instance.log("$e\n$s", level: LogLevel.Error); -// } -// } -// -// Future updateFrom(Currency from, bool shouldNotifyListeners) async { -// try { -// _from = from; -// -// if (_to == null) { -// rate = null; -// notifyListeners(); -// return; -// } -// -// await _updateMinFromAmount(shouldNotifyListeners: shouldNotifyListeners); -// -// await updateRate(shouldNotifyListeners: shouldNotifyListeners); -// -// debugPrint( -// "_updated FROM: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$_fromAmount _toAmount=$_toAmount rate:$rate"); -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } catch (e, s) { -// Logging.instance.log("$e\n$s", level: LogLevel.Error); -// } -// } -// -// Future _updateMinFromAmount( -// {required bool shouldNotifyListeners}) async { -// _minFromAmount = await getStandardMinExchangeAmount(from: from!, to: to!); -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// // Future setToAmountAndCalculateFromAmount( -// // Decimal newToAmount, -// // bool shouldNotifyListeners, -// // ) async { -// // if (newToAmount == Decimal.zero) { -// // _fromAmount = Decimal.zero; -// // } -// // -// // _toAmount = newToAmount; -// // await updateRate(); -// // if (shouldNotifyListeners) { -// // notifyListeners(); -// // } -// // } -// -// Future setFromAmountAndCalculateToAmount( -// Decimal newFromAmount, -// bool shouldNotifyListeners, -// ) async { -// if (newFromAmount == Decimal.zero) { -// _toAmount = Decimal.zero; -// } -// -// _fromAmount = newFromAmount; -// await updateRate(shouldNotifyListeners: shouldNotifyListeners); -// -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// Future getStandardEstimatedToAmount({ -// required Decimal fromAmount, -// required Currency from, -// required Currency to, -// }) async { -// final response = -// await (cnTesting ?? ChangeNowAPI.instance).getEstimatedExchangeAmount( -// fromTicker: from.ticker, -// toTicker: to.ticker, -// fromAmount: fromAmount, -// ); -// -// if (response.value != null) { -// return response.value!.estimatedAmount; -// } else { -// _onError?.call( -// "Failed to fetch estimated amount: ${response.exception?.toString()}"); -// return null; -// } -// } -// -// // Future getStandardEstimatedFromAmount({ -// // required Decimal toAmount, -// // required Currency from, -// // required Currency to, -// // }) async { -// // final response = await (cnTesting ?? ChangeNow.instance) -// // .getEstimatedExchangeAmount( -// // fromTicker: from.ticker, -// // toTicker: to.ticker, -// // fromAmount: toAmount, ); -// // -// // if (response.value != null) { -// // return response.value!.fromAmount; -// // } else { -// // _onError?.call( -// // "Failed to fetch estimated amount: ${response.exception?.toString()}"); -// // return null; -// // } -// // } -// -// Future getStandardMinExchangeAmount({ -// required Currency from, -// required Currency to, -// }) async { -// final response = await (cnTesting ?? ChangeNowAPI.instance) -// .getMinimalExchangeAmount(fromTicker: from.ticker, toTicker: to.ticker); -// -// if (response.value != null) { -// return response.value!; -// } else { -// _onError?.call( -// "Could not update minimal exchange amounts: ${response.exception?.toString()}"); -// return null; -// } -// } -// -// void setOnError({ -// required void Function(String)? onError, -// bool shouldNotifyListeners = false, -// }) { -// _onError = onError; -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// Future updateRate({bool shouldNotifyListeners = false}) async { -// rate = null; -// final amount = _fromAmount; -// final minAmount = _minFromAmount; -// if (amount != null && amount > Decimal.zero) { -// Decimal? amt; -// if (minAmount != null) { -// if (minAmount <= amount) { -// amt = await getStandardEstimatedToAmount( -// fromAmount: amount, from: _from!, to: _to!); -// if (amt != null) { -// rate = (amt / amount).toDecimal(scaleOnInfinitePrecision: 12); -// } -// } -// } -// if (rate != null && amt != null) { -// _toAmount = amt; -// } -// } -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// } diff --git a/lib/models/exchange/fixed_rate_exchange_form_state.dart b/lib/models/exchange/fixed_rate_exchange_form_state.dart deleted file mode 100644 index fcb686a730..0000000000 --- a/lib/models/exchange/fixed_rate_exchange_form_state.dart +++ /dev/null @@ -1,179 +0,0 @@ -// import 'package:decimal/decimal.dart'; -// import 'package:flutter/cupertino.dart'; -// import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; -// import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; -// import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; -// import 'package:stackwallet/utilities/logger.dart'; -// -// class FixedRateExchangeFormState extends ChangeNotifier { -// Decimal? _fromAmount; -// Decimal? _toAmount; -// -// FixedRateMarket? _market; -// FixedRateMarket? get market => _market; -// -// CNExchangeEstimate? _estimate; -// CNExchangeEstimate? get estimate => _estimate; -// -// Decimal? get rate { -// if (_estimate == null) { -// return null; -// } else { -// return (_estimate!.toAmount / _estimate!.fromAmount) -// .toDecimal(scaleOnInfinitePrecision: 12); -// } -// } -// -// Future swap(FixedRateMarket reverseFixedRateMarket) async { -// final Decimal? tmp = _fromAmount; -// _fromAmount = _toAmount; -// _toAmount = tmp; -// -// await updateMarket(reverseFixedRateMarket, false); -// await updateRateEstimate(CNEstimateType.direct); -// _toAmount = _estimate?.toAmount ?? Decimal.zero; -// notifyListeners(); -// } -// -// String get fromAmountString => -// _fromAmount == null ? "" : _fromAmount!.toStringAsFixed(8); -// String get toAmountString => -// _toAmount == null ? "" : _toAmount!.toStringAsFixed(8); -// -// Future updateMarket( -// FixedRateMarket? market, -// bool shouldNotifyListeners, -// ) async { -// _market = market; -// -// if (_market == null) { -// _fromAmount = null; -// _toAmount = null; -// } else { -// if (_fromAmount != null) { -// if (_fromAmount! <= Decimal.zero) { -// _toAmount = Decimal.zero; -// } else { -// await updateRateEstimate(CNEstimateType.direct); -// } -// } -// } -// -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// String get rateDisplayString { -// if (_market == null || _estimate == null) { -// return "N/A"; -// } else { -// return "1 ${_estimate!.fromCurrency.toUpperCase()} ~${rate!.toStringAsFixed(8)} ${_estimate!.toCurrency.toUpperCase()}"; -// } -// } -// -// bool get canExchange { -// return _market != null && -// _fromAmount != null && -// _toAmount != null && -// sendAmountWarning.isEmpty; -// } -// -// String get sendAmountWarning { -// if (_market != null && _fromAmount != null) { -// if (_fromAmount! < _market!.min) { -// return "Minimum amount ${_market!.min.toString()} ${_market!.from.toUpperCase()}"; -// } else if (_fromAmount! > _market!.max) { -// return "Maximum amount ${_market!.max.toString()} ${_market!.from.toUpperCase()}"; -// } -// } -// -// return ""; -// } -// -// Future setToAmountAndCalculateFromAmount( -// Decimal newToAmount, -// bool shouldNotifyListeners, -// ) async { -// _toAmount = newToAmount; -// -// if (shouldNotifyListeners) { -// await updateRateEstimate(CNEstimateType.reverse); -// notifyListeners(); -// } -// } -// -// Future setFromAmountAndCalculateToAmount( -// Decimal newFromAmount, -// bool shouldNotifyListeners, -// ) async { -// _fromAmount = newFromAmount; -// -// if (shouldNotifyListeners) { -// await updateRateEstimate(CNEstimateType.direct); -// notifyListeners(); -// } -// } -// -// void Function(String)? _onError; -// -// void setOnError({ -// required void Function(String)? onError, -// bool shouldNotifyListeners = false, -// }) { -// _onError = onError; -// if (shouldNotifyListeners) { -// notifyListeners(); -// } -// } -// -// Future updateRateEstimate(CNEstimateType direction) async { -// if (market != null) { -// Decimal? amount; -// // set amount based on trade estimate direction -// switch (direction) { -// case CNEstimateType.direct: -// if (_fromAmount != null -// // && -// // market!.min >= _fromAmount! && -// // _fromAmount! <= market!.max -// ) { -// amount = _fromAmount!; -// } -// break; -// case CNEstimateType.reverse: -// if (_toAmount != null -// // && -// // market!.min >= _toAmount! && -// // _toAmount! <= market!.max -// ) { -// amount = _toAmount!; -// } -// break; -// } -// -// if (amount != null && market != null && amount > Decimal.zero) { -// final response = -// await ChangeNowAPI.instance.getEstimatedExchangeAmountV2( -// fromTicker: market!.from, -// toTicker: market!.to, -// fromOrTo: direction, -// flow: CNFlowType.fixedRate, -// amount: amount, -// ); -// -// if (response.value != null) { -// // update estimate if response succeeded -// _estimate = response.value; -// -// _toAmount = _estimate?.toAmount; -// _fromAmount = _estimate?.fromAmount; -// notifyListeners(); -// } else if (response.exception != null) { -// Logging.instance.log("updateRateEstimate(): ${response.exception}", -// level: LogLevel.Warning); -// } -// } -// } -// } -// } From 3d77c37771efa06327a373176ba82509345b4a3b Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 6 Oct 2022 16:41:24 -0500 Subject: [PATCH 27/75] Add @msalazarm's setup script Love this thing --- README.md | 41 ++++++++++++++++++++++++++++++++++------ scripts/setup.sh | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 scripts/setup.sh diff --git a/README.md b/README.md index fe380e5aba..9df4695633 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,22 @@ Highlights include: - Custom Nodes. - Open source software. -## Build and run +## Building ### Prerequisites - The only OS supported for building is Ubuntu 20.04 - A machine with at least 100 GB of Storage + +The following prerequisities can be installed with the setup script `scripts/setup.sh` or manually as described below: + - Flutter 3.0.5 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install) - Dart SDK Requirement (>=2.17.0, up until <3.0.0) - Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies) -After that download the project and init the submodules +### Scripted setup +`scripts/setup.sh` is provided as a tool to set up a stock Ubuntu 20.04 installation for building. This script should skip the entire [Manual setup](#manual-setup) section below and prepare you for [running](#running) + +### Manual setup +After installing the prerequisites listed above, download the code and init the submodules ``` git clone https://github.com/cypherstack/stack_wallet.git cd stack_wallet @@ -57,7 +64,6 @@ cd scripts cd .. ``` - Remove pre-installed system libraries for the following packages built by cryptography plugins in the crypto_plugins folder: `boost iconv libjson-dev libsecret openssl sodium unbound zmq`. You can use ``` sudo apt list --installed | grep boost @@ -76,7 +82,7 @@ cd scripts/android/ cd ../.. ``` -Building plugins for testing on Linux +Building plugins for Linux ``` cd scripts/linux/ @@ -85,10 +91,33 @@ cd scripts/linux/ cd ../.. ``` -Finally, plug in your android device or use the emulator available via Android Studio and then run the following commands: +## Running +### Android +Plug in your android device or use the emulator available via Android Studio and then run the following commands: ``` flutter pub get -flutter run +flutter run android ``` Note on Emulators: Only x86_64 emulators are supported, x86 emulators will not work + +### Linux +Plug in your android device or use the emulator available via Android Studio and then run the following commands: +``` +flutter pub get Linux +flutter run linux +``` + +## Android Studio +Android Studio is the recommended IDE for development, not just for launching on Android devices and emulators but also for Linux desktop development. Install it and configure it as follows: +``` +# setup android studio +sudo apt install -y openjdk-11-jdk +sudo snap install android-studio --classic +``` + +Use Tools > SDK Manager to install the Android SDK, NDK, Android SDK command line tools, and cmake + +Then install the Flutter plugin. In Android Studio's options for the Flutter language, enable auto format on save to match the project's code style + +Make a Pixel 4 (API 30) x86_64 emulator with 2GB of storage space for emulation diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100644 index 0000000000..aad8cccaa1 --- /dev/null +++ b/scripts/setup.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +sudo apt update && sudo apt upgrade -y && sudo apt dist-upgrade -y +mkdir "$HOME/development" +mkdir "$HOME/projects" +sudo apt install -y git build-essential curl +export DEVELOPMENT=$HOME/development +export PROJECTS=$HOME/projects + +# setup flutter +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.0.3 +export FLUTTER_DIR=$(pwd)/bin +echo 'export PATH="$PATH:'${FLUTTER_DIR}'"' >> ~/.bashrc +source ~/.bashrc +flutter doctor + +# setup stack_wallet github +cd $PROJECTS +git clone https://github.com/cypherstack/stack_wallet.git +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 +cd $STACK_WALLET +cd scripts +./prebuild.sh + +#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 + +sudo apt install -y libc6-dev-i386 + +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +cargo install cargo-ndk +rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android + +# build stack wallet plugins +cd $STACK_WALLET +cd scripts/android +./build_all.sh From 0b6b92df8668bf17358014719375736f58fe409f Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 6 Oct 2022 16:52:57 -0500 Subject: [PATCH 28/75] Give advice to download script and run it vs. pulling repo then running --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9df4695633..450f5d0bc3 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The following prerequisities can be installed with the setup script `scripts/set - Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies) ### Scripted setup -`scripts/setup.sh` is provided as a tool to set up a stock Ubuntu 20.04 installation for building. This script should skip the entire [Manual setup](#manual-setup) section below and prepare you for [running](#running) +[`scripts/setup.sh`](https://github.com/cypherstack/stack_wallet/blob/main/scripts/setup.sh) is provided as a tool to set up a stock Ubuntu 20.04 installation for building: download the script and run it anywhere. This script should skip the entire [Manual setup](#manual-setup) section below and prepare you for [running](#running). It will set up the stack_wallet repository in `~/projects/stack_wallet` and build it there. ### Manual setup After installing the prerequisites listed above, download the code and init the submodules From 832804606cf6115b57b2677cea8fd8bd39705cb5 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 6 Oct 2022 17:25:29 -0500 Subject: [PATCH 29/75] Be more specific about Android Studio configuration --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 450f5d0bc3..8cb7670102 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ sudo apt install -y openjdk-11-jdk sudo snap install android-studio --classic ``` -Use Tools > SDK Manager to install the Android SDK, NDK, Android SDK command line tools, and cmake +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. In Android Studio's options for the Flutter language, enable auto format on save to match the project's code style From 1d205d274898b21f27fbf33b0bac4ef1a45c2c40 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 6 Oct 2022 18:39:33 -0500 Subject: [PATCH 30/75] Note to restart the IDE --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8cb7670102..320234a875 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,6 @@ 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. In Android Studio's options for the Flutter language, enable auto format on save to match the project's code style +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 Make a Pixel 4 (API 30) x86_64 emulator with 2GB of storage space for emulation From 33f3cc0c0b25fc98c8dc6e1fb95be9366cc89acc Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 6 Oct 2022 19:48:25 -0500 Subject: [PATCH 31/75] Dart SDK tip --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 320234a875..37321c848b 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,6 @@ 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 +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 297204d200f630e591929d680a06992e965c0eb3 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Thu, 6 Oct 2022 20:01:19 -0500 Subject: [PATCH 32/75] don't call prebuild.sh, just do what it does --- scripts/setup.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/setup.sh b/scripts/setup.sh index aad8cccaa1..f65e6640f6 100644 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -25,10 +25,12 @@ 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 -cd $STACK_WALLET -cd scripts -./prebuild.sh +# 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' + echo 'const kChangeNowApiKey = "";' > $KEYS +fi #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 From e1e4100a6d976502a4aaa9cf75bd77da28ee8cf5 Mon Sep 17 00:00:00 2001 From: Marco Date: Fri, 7 Oct 2022 11:50:26 -0600 Subject: [PATCH 33/75] namecoin electrumx fixes --- lib/services/coins/namecoin/namecoin_wallet.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index af42174885..e9cae1ab2c 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -2444,8 +2444,8 @@ class NamecoinWallet extends CoinServiceAPI { for (final out in tx["vout"] as List) { if (prevOut == out["n"]) { String? address = out["scriptPubKey"]["address"] as String?; - if (address == null && out["scriptPubKey"]["addresses"] != null) { - address = out["scriptPubKey"]["addresses"][0] as String?; + if (address == null && out["scriptPubKey"]["address"] != null) { + address = out["scriptPubKey"]["address"] as String?; } if (address != null) { @@ -2459,8 +2459,8 @@ class NamecoinWallet extends CoinServiceAPI { for (final output in txObject["vout"] as List) { String? address = output["scriptPubKey"]["address"] as String?; - if (address == null && output["scriptPubKey"]["addresses"] != null) { - address = output["scriptPubKey"]["addresses"][0] as String?; + if (address == null && output["scriptPubKey"]["address"] != null) { + address = output["scriptPubKey"]["address"] as String?; } if (address != null) { recipientsArray.add(address); @@ -2528,8 +2528,8 @@ class NamecoinWallet extends CoinServiceAPI { // add up received tx value for (final output in txObject["vout"] as List) { String? address = output["scriptPubKey"]["address"] as String?; - if (address == null && output["scriptPubKey"]["addresses"] != null) { - address = output["scriptPubKey"]["addresses"][0] as String?; + if (address == null && output["scriptPubKey"]["address"] != null) { + address = output["scriptPubKey"]["address"] as String?; } if (address != null) { final value = (Decimal.parse(output["value"].toString()) * @@ -3049,7 +3049,7 @@ class NamecoinWallet extends CoinServiceAPI { for (final output in tx["vout"] as List) { final n = output["n"]; if (n != null && n == utxosToUse[i].vout) { - final address = output["scriptPubKey"]["addresses"][0] as String; + final address = output["scriptPubKey"]["address"] as String; if (!addressTxid.containsKey(address)) { addressTxid[address] = []; } From ea52f9444346554b8a684d161829cf91be883ca4 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 7 Oct 2022 13:34:48 -0600 Subject: [PATCH 34/75] change build --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 60f65f3e31..dc52179ffe 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.5.0+70 +version: 1.5.1+71 environment: sdk: ">=2.17.0 <3.0.0" From 3e8e2903f720a4b8601b06d26e03ac02ee941797 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 7 Oct 2022 17:13:02 -0600 Subject: [PATCH 35/75] stack privacy calls page --- lib/pages/intro_view.dart | 4 +- lib/pages/stack_privacy_calls.dart | 92 ++++++++++++++++++++++++++++++ lib/route_generator.dart | 7 +++ 3 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 lib/pages/stack_privacy_calls.dart diff --git a/lib/pages/intro_view.dart b/lib/pages/intro_view.dart index ca8725886f..990281b685 100644 --- a/lib/pages/intro_view.dart +++ b/lib/pages/intro_view.dart @@ -2,7 +2,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; -import 'package:stackwallet/pages_desktop_specific/create_password/create_password_view.dart'; +import 'package:stackwallet/pages/stack_privacy_calls.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -255,7 +255,7 @@ class GetStartedButton extends StatelessWidget { .extension()! .getPrimaryEnabledButtonColor(context), onPressed: () { - Navigator.of(context).pushNamed(CreatePasswordView.routeName); + Navigator.of(context).pushNamed(StackPrivacyCalls.routeName); }, child: Text( "Get started", diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart new file mode 100644 index 0000000000..738dce47ba --- /dev/null +++ b/lib/pages/stack_privacy_calls.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class StackPrivacyCalls extends ConsumerStatefulWidget { + const StackPrivacyCalls({ + Key? key, + }) : super(key: key); + + static const String routeName = "/stackPrivacy"; + + @override + ConsumerState createState() => _StackPrivacyCalls(); +} + +class _StackPrivacyCalls extends ConsumerState { + final PageController _pageController = + PageController(initialPage: 0, keepPage: true); + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SafeArea( + child: PageView( + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Choose your Stack experience", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 8, + ), + Text( + "You can change it later in Settings", + style: STextStyles.subtitle(context), + ), + const SizedBox( + height: 36, + ), + RoundedWhiteContainer( + child: Center( + child: RichText( + textAlign: TextAlign.left, + text: TextSpan( + style: + STextStyles.label(context).copyWith(fontSize: 12.0), + children: [ + const TextSpan( + text: + "Exchange data preloaded for a seamless experience."), + const TextSpan( + text: + "CoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), + TextSpan( + text: "Recommended for most crypto users.", + style: + const TextStyle(fontWeight: FontWeight.bold)), + ], + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 3681881713..8db03e8d04 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -73,6 +73,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/delete_wallet_warning_view.dart'; 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/pages/wallet_view/transaction_views/all_transactions_view.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; @@ -120,6 +121,12 @@ class RouteGenerator { builder: (_) => const CreatePinView(), settings: RouteSettings(name: settings.name)); + case StackPrivacyCalls.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const StackPrivacyCalls(), + settings: RouteSettings(name: settings.name)); + case WalletsView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, From 8e727ff999e12a528555af68c2e4c522251b9bf7 Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 9 Oct 2022 13:28:39 -0600 Subject: [PATCH 36/75] delete account ui for ios --- lib/pages/intro_view.dart | 2 + .../delete_account_view.dart | 75 +++++++++++++++++++ .../global_settings_view.dart | 17 +++++ lib/route_generator.dart | 14 ++++ 4 files changed, 108 insertions(+) create mode 100644 lib/pages/settings_views/global_settings_view/delete_account_view.dart diff --git a/lib/pages/intro_view.dart b/lib/pages/intro_view.dart index ca8725886f..17ee90f1d5 100644 --- a/lib/pages/intro_view.dart +++ b/lib/pages/intro_view.dart @@ -12,6 +12,8 @@ import 'package:url_launcher/url_launcher.dart'; class IntroView extends StatefulWidget { const IntroView({Key? key}) : super(key: key); + static const String routeName = "/introView"; + @override State createState() => _IntroViewState(); } diff --git a/lib/pages/settings_views/global_settings_view/delete_account_view.dart b/lib/pages/settings_views/global_settings_view/delete_account_view.dart new file mode 100644 index 0000000000..5fdabf6ada --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/delete_account_view.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/pages/intro_view.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class DeleteAccountView extends StatefulWidget { + const DeleteAccountView({Key? key}) : super(key: key); + + static const String routeName = "/deleteAccountView"; + + @override + State createState() => _DeleteAccountViewState(); +} + +class _DeleteAccountViewState extends State { + final isDesktop = Util.isDesktop; + + Future onConfirmDeleteAccount() async { + // TODO delete everything then pop to intro view + await Navigator.of(context).pushNamedAndRemoveUntil( + IntroView.routeName, + (route) => false, + ); + } + + @override + Widget build(BuildContext context) { + return MasterScaffold( + isDesktop: isDesktop, + appBar: isDesktop + ? DesktopAppBar(isCompactHeight: true) + : AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Delete account", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(14), + child: Column( + children: [ + RoundedWhiteContainer( + child: Text( + "There is no account to delete, but Apple requires that we have a way to 'delete accounts' in the app and will reject our app updates if we don't, so here it is. Clicking this will delete all app data (not from our servers, because we never had it in the first place).\n\nWhen you click confirm, all app data will be deleted, including wallets and preferences, and you will be taken back to the very first onboarding screen. BE SURE TO BACKUP ALL SEEDS!!\n\nAre you sure you want to delete your \"account\"?", + style: STextStyles.smallMed12(context), + ), + ), + const Spacer(), + PrimaryButton( + label: "Confirm", + onPressed: onConfirmDeleteAccount, + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/global_settings_view.dart b/lib/pages/settings_views/global_settings_view/global_settings_view.dart index 1b334cf18e..85e71f44fe 100644 --- a/lib/pages/settings_views/global_settings_view/global_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/global_settings_view.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; @@ -5,6 +7,7 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/about_view import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/currency_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/delete_account_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/language_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/manage_nodes_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/security_views/security_view.dart'; @@ -224,6 +227,20 @@ class GlobalSettingsView extends StatelessWidget { .pushNamed(SupportView.routeName); }, ), + if (Platform.isIOS) + const SizedBox( + height: 8, + ), + if (Platform.isIOS) + SettingsListButton( + iconAssetName: Assets.svg.circleAlert, + iconSize: 16, + title: "Delete account", + onPressed: () async { + await Navigator.of(context) + .pushNamed(DeleteAccountView.routeName); + }, + ), // TextButton( // style: Theme.of(context) // .textButtonTheme diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 3681881713..2529a1e393 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -33,6 +33,7 @@ import 'package:stackwallet/pages/exchange_view/send_from_view.dart'; import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; +import 'package:stackwallet/pages/intro_view.dart'; import 'package:stackwallet/pages/manage_favorites_view/manage_favorites_view.dart'; import 'package:stackwallet/pages/notification_views/notifications_view.dart'; import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; @@ -43,6 +44,7 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_v import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/currency_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/delete_account_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/global_settings_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/hidden_settings.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/language_view.dart'; @@ -97,6 +99,18 @@ class RouteGenerator { final args = settings.arguments; switch (settings.name) { + case IntroView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const IntroView(), + settings: RouteSettings(name: settings.name)); + + case DeleteAccountView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const DeleteAccountView(), + settings: RouteSettings(name: settings.name)); + case HomeView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, From 4339c19631bfd5466d0d4a75d492024f67f81d60 Mon Sep 17 00:00:00 2001 From: Marco Date: Sun, 9 Oct 2022 21:36:43 -0600 Subject: [PATCH 37/75] easy or more private option added at startup --- assets/svg/persona-easy-1.svg | 1 + assets/svg/persona-incognito-1.svg | 1 + lib/pages/home_view/home_view.dart | 14 +- .../sub_widgets/home_view_button_bar.dart | 12 +- lib/pages/intro_view.dart | 8 +- lib/pages/stack_privacy_calls.dart | 232 +++++++++++++++++- lib/pages/wallet_view/wallet_view.dart | 13 +- lib/services/price.dart | 22 +- lib/utilities/assets.dart | 2 + lib/widgets/rounded_container.dart | 3 + lib/widgets/rounded_white_container.dart | 3 + pubspec.yaml | 2 + 12 files changed, 291 insertions(+), 22 deletions(-) create mode 100644 assets/svg/persona-easy-1.svg create mode 100644 assets/svg/persona-incognito-1.svg diff --git a/assets/svg/persona-easy-1.svg b/assets/svg/persona-easy-1.svg new file mode 100644 index 0000000000..136f29d618 --- /dev/null +++ b/assets/svg/persona-easy-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/svg/persona-incognito-1.svg b/assets/svg/persona-incognito-1.svg new file mode 100644 index 0000000000..c31c285b12 --- /dev/null +++ b/assets/svg/persona-incognito-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/pages/home_view/home_view.dart b/lib/pages/home_view/home_view.dart index b561207ab9..bb098208db 100644 --- a/lib/pages/home_view/home_view.dart +++ b/lib/pages/home_view/home_view.dart @@ -21,6 +21,10 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/hive/db.dart'; + +import 'package:stackwallet/utilities/logger.dart'; + class HomeView extends ConsumerStatefulWidget { const HomeView({Key? key}) : super(key: key); @@ -81,7 +85,15 @@ class _HomeViewState extends ConsumerState { void _loadCNData() { // unawaited future - _cnLoadingService.loadAll(ref); + // + final externalCalls = DB.instance + .get(boxName: DB.boxNamePrefs, key: "externalCalls") as bool?; + if (externalCalls ?? false) { + _cnLoadingService.loadAll(ref); + } else { + Logging.instance.log("User does not want to use external calls", + level: LogLevel.Info); + } } @override diff --git a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart index 7541b166f1..ccc70c17d8 100644 --- a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart +++ b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart @@ -15,6 +15,9 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/services/change_now/change_now_loading_service.dart'; + class HomeViewButtonBar extends ConsumerStatefulWidget { const HomeViewButtonBar({Key? key}) : super(key: key); @@ -207,7 +210,14 @@ class _HomeViewButtonBarState extends ConsumerState { ref.read(homeViewPageIndexStateProvider.state).state = 1; } DateTime now = DateTime.now(); - + // TODO: find out why it infinitely loads + final _cnLoadingService = ChangeNowLoadingService(); + final externalCalls = DB.instance.get( + boxName: DB.boxNamePrefs, key: "externalCalls") as bool?; + if (!(externalCalls ?? false)) { + print("loading?"); + unawaited(_cnLoadingService.loadAll(ref)); + } if (now.difference(_lastRefreshed) > _refreshInterval) { // bool okPressed = false; // showDialog( diff --git a/lib/pages/intro_view.dart b/lib/pages/intro_view.dart index 990281b685..1c78efb770 100644 --- a/lib/pages/intro_view.dart +++ b/lib/pages/intro_view.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; @@ -9,6 +11,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:stackwallet/hive/db.dart'; + class IntroView extends StatefulWidget { const IntroView({Key? key}) : super(key: key); @@ -240,7 +244,9 @@ class GetStartedButton extends StatelessWidget { .extension()! .getPrimaryEnabledButtonColor(context), onPressed: () { - Navigator.of(context).pushNamed(CreatePinView.routeName); + unawaited(DB.instance.put( + boxName: DB.boxNamePrefs, key: "externalCalls", value: true)); + Navigator.of(context).pushNamed(StackPrivacyCalls.routeName); }, child: Text( "Get started", diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart index 738dce47ba..c16e9d7416 100644 --- a/lib/pages/stack_privacy_calls.dart +++ b/lib/pages/stack_privacy_calls.dart @@ -1,10 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/util.dart'; + class StackPrivacyCalls extends ConsumerStatefulWidget { const StackPrivacyCalls({ Key? key, @@ -17,9 +23,17 @@ class StackPrivacyCalls extends ConsumerStatefulWidget { } class _StackPrivacyCalls extends ConsumerState { + late final bool isDesktop; + bool isEasy = true; final PageController _pageController = PageController(initialPage: 0, keepPage: true); + @override + void initState() { + isDesktop = Util.isDesktop; + super.initState(); + } + @override void dispose() { _pageController.dispose(); @@ -59,6 +73,21 @@ class _StackPrivacyCalls extends ConsumerState { const SizedBox( height: 36, ), + Center( + child: CustomRadio((bool isEasy) { + setState(() { + this.isEasy = isEasy; + + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "externalCalls", + value: isEasy); + }); + }), + ), + const SizedBox( + height: 36, + ), RoundedWhiteContainer( child: Center( child: RichText( @@ -66,22 +95,55 @@ class _StackPrivacyCalls extends ConsumerState { text: TextSpan( style: STextStyles.label(context).copyWith(fontSize: 12.0), - children: [ - const TextSpan( - text: - "Exchange data preloaded for a seamless experience."), - const TextSpan( - text: - "CoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), - TextSpan( - text: "Recommended for most crypto users.", - style: - const TextStyle(fontWeight: FontWeight.bold)), - ], + children: isEasy + ? const [ + TextSpan( + text: + "Exchange data preloaded for a seamless experience."), + TextSpan( + text: + "\n\nCoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), + TextSpan( + text: + "\n\nRecommended for most crypto users.", + style: + TextStyle(fontWeight: FontWeight.bold)), + ] + : const [ + TextSpan( + text: + "Exchange data not preloaded (slower experience)."), + TextSpan( + text: + "\n\nCoinGecko disabled (price changes not shown, no wallet value shown in other currencies)."), + TextSpan( + text: + "\n\nRecommended for the privacy conscious.", + style: + TextStyle(fontWeight: FontWeight.bold)), + ], ), ), ), ), + const Spacer( + flex: 4, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + child: Row( + children: [ + Expanded( + child: ContinueButton( + isDesktop: isDesktop, + ), + ), + ], + ), + ), ], ), ], @@ -90,3 +152,149 @@ class _StackPrivacyCalls extends ConsumerState { ); } } + +class ContinueButton extends StatelessWidget { + const ContinueButton({Key? key, required this.isDesktop}) : super(key: key); + + final bool isDesktop; + + @override + Widget build(BuildContext context) { + return !isDesktop + ? TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: () { + Navigator.of(context).pushNamed(CreatePinView.routeName); + }, + child: Text( + "Continue", + style: STextStyles.button(context), + ), + ) + : SizedBox( + width: 328, + height: 70, + child: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: () { + Navigator.of(context).pushNamed(StackPrivacyCalls.routeName); + }, + child: Text( + "Continue", + style: STextStyles.button(context).copyWith(fontSize: 20), + ), + ), + ); + } +} + +class CustomRadio extends StatefulWidget { + CustomRadio(this.upperCall, {Key? key}) : super(key: key); + + Function upperCall; + + @override + createState() { + return CustomRadioState(); + } +} + +class CustomRadioState extends State { + List sampleData = []; + + @override + void initState() { + super.initState(); + sampleData.add( + RadioModel(true, Assets.svg.personaEasy, 'Easy Crypto', 'Recommended')); + sampleData.add(RadioModel( + false, Assets.svg.personaIncognito, 'Incognito', 'Privacy conscious')); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + onTap: () { + setState(() { + // if (!sampleData[0].isSelected) { + widget.upperCall.call(true); + // } + for (var element in sampleData) { + element.isSelected = false; + } + sampleData[0].isSelected = true; + }); + }, + child: RadioItem(sampleData[0]), + ), + InkWell( + onTap: () { + setState(() { + // if (!sampleData[1].isSelected) { + widget.upperCall.call(false); + // } + for (var element in sampleData) { + element.isSelected = false; + } + sampleData[1].isSelected = true; + }); + }, + child: RadioItem(sampleData[1]), + ) + ], + ); + } +} + +class RadioItem extends StatelessWidget { + final RadioModel _item; + const RadioItem(this._item, {Key? key}) : super(key: key); + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.all(15.0), + child: RoundedWhiteContainer( + borderColor: _item.isSelected ? const Color(0xFF0056D2) : null, + child: Center( + child: Column( + children: [ + SvgPicture.asset( + _item.svg, + // color: Theme.of(context).extension()!.textWhite, + width: 96, + height: 96, + ), + RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: STextStyles.label(context).copyWith(fontSize: 12.0), + children: [ + TextSpan( + text: _item.topText, + style: const TextStyle(fontWeight: FontWeight.bold)), + TextSpan(text: "\n${_item.bottomText}"), + ], + ), + ), + ], + )), + ), + ); + } +} + +class RadioModel { + bool isSelected; + final String svg; + final String topText; + final String bottomText; + + RadioModel(this.isSelected, this.svg, this.topText, this.bottomText); +} diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 95dfc4a981..8a97d94326 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -46,6 +46,10 @@ import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:tuple/tuple.dart'; +import 'package:stackwallet/hive/db.dart'; + +import 'package:stackwallet/utilities/logger.dart'; + /// [eventBus] should only be set during testing class WalletView extends ConsumerStatefulWidget { const WalletView({ @@ -351,7 +355,14 @@ class _WalletViewState extends ConsumerState { void _loadCNData() { // unawaited future - _cnLoadingService.loadAll(ref, coin: ref.read(managerProvider).coin); + final externalCalls = DB.instance + .get(boxName: DB.boxNamePrefs, key: "externalCalls") as bool?; + if (externalCalls ?? false) { + _cnLoadingService.loadAll(ref, coin: ref.read(managerProvider).coin); + } else { + Logging.instance.log("User does not want to use external calls", + level: LogLevel.Info); + } } @override diff --git a/lib/services/price.dart b/lib/services/price.dart index d157580ad2..96c849eb63 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -76,6 +76,14 @@ class PriceAPI { return _cachedPrices; } + final externalCalls = DB.instance + .get(boxName: DB.boxNamePrefs, key: "externalCalls") as bool?; + if (!(externalCalls ?? false)) { + Logging.instance.log("User does not want to use external calls", + level: LogLevel.Info); + return _cachedPrices; + } + Map> result = {}; try { final uri = Uri.parse( @@ -88,14 +96,8 @@ class PriceAPI { headers: {'Content-Type': 'application/json'}, ); - // debugPrint(coinGeckoResponse.statusCode.toString()); - // debugPrint(coinGeckoResponse.body.toString()); - // debugPrint(coinGeckoResponse.headers.toString()); - final coinGeckoData = jsonDecode(coinGeckoResponse.body) as List; - // log(JsonEncoder.withIndent(" ").convert(coinGeckoData)); - for (final map in coinGeckoData) { final String coinName = map["name"] as String; final coin = coinFromPrettyName(coinName); @@ -120,6 +122,14 @@ class PriceAPI { } static Future?> availableBaseCurrencies() async { + final externalCalls = DB.instance + .get(boxName: DB.boxNamePrefs, key: "externalCalls") as bool?; + print("externalCalls hi2 $externalCalls"); + if (!(externalCalls ?? false)) { + Logging.instance.log("User does not want to use external calls", + level: LogLevel.Info); + return null; + } const uriString = "https://api.coingecko.com/api/v3/simple/supported_vs_currencies"; try { diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 1b38f42bc1..3bf2fa250e 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -52,6 +52,8 @@ class _SVG { "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-exchange-icon-failed.svg"; String get polygon => "assets/svg/Polygon.svg"; + String get personaIncognito => "assets/svg/persona-incognito-1.svg"; + String get personaEasy => "assets/svg/persona-easy-1.svg"; String get drd => "assets/svg/drd-icon.svg"; String get boxAuto => "assets/svg/box-auto.svg"; String get plus => "assets/svg/plus.svg"; diff --git a/lib/widgets/rounded_container.dart b/lib/widgets/rounded_container.dart index 7ae35afddd..9f9bfbd8d9 100644 --- a/lib/widgets/rounded_container.dart +++ b/lib/widgets/rounded_container.dart @@ -10,6 +10,7 @@ class RoundedContainer extends StatelessWidget { this.radiusMultiplier = 1.0, this.width, this.height, + this.borderColor, }) : super(key: key); final Widget? child; @@ -18,6 +19,7 @@ class RoundedContainer extends StatelessWidget { final double radiusMultiplier; final double? width; final double? height; + final Color? borderColor; @override Widget build(BuildContext context) { @@ -29,6 +31,7 @@ class RoundedContainer extends StatelessWidget { borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius * radiusMultiplier, ), + border: borderColor == null ? null : Border.all(color: borderColor!), ), child: Padding( padding: padding, diff --git a/lib/widgets/rounded_white_container.dart b/lib/widgets/rounded_white_container.dart index 7525eafe5f..a574dc1ce4 100644 --- a/lib/widgets/rounded_white_container.dart +++ b/lib/widgets/rounded_white_container.dart @@ -10,6 +10,7 @@ class RoundedWhiteContainer extends StatelessWidget { this.radiusMultiplier = 1.0, this.width, this.height, + this.borderColor, }) : super(key: key); final Widget? child; @@ -17,6 +18,7 @@ class RoundedWhiteContainer extends StatelessWidget { final double radiusMultiplier; final double? width; final double? height; + final Color? borderColor; @override Widget build(BuildContext context) { @@ -27,6 +29,7 @@ class RoundedWhiteContainer extends StatelessWidget { width: width, height: height, child: child, + borderColor: borderColor, ); } } diff --git a/pubspec.yaml b/pubspec.yaml index dc52179ffe..318f143a09 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -290,6 +290,8 @@ flutter: - assets/svg/tx-icon-anonymize-pending.svg - assets/svg/tx-icon-anonymize-failed.svg - assets/svg/Polygon.svg + - assets/svg/persona-easy-1.svg + - assets/svg/persona-incognito-1.svg # coin icons - assets/svg/coin_icons/Bitcoin.svg - assets/svg/coin_icons/Bitcoincash.svg From 1ce5740c24884b18b54eb5817d783eb67bd2b70f Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 10 Oct 2022 10:35:20 -0600 Subject: [PATCH 38/75] font fix and box padding --- lib/pages/stack_privacy_calls.dart | 197 ++++++++++++++++------------- 1 file changed, 108 insertions(+), 89 deletions(-) diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart index c16e9d7416..9113a36534 100644 --- a/lib/pages/stack_privacy_calls.dart +++ b/lib/pages/stack_privacy_calls.dart @@ -1,16 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; +import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/util.dart'; - class StackPrivacyCalls extends ConsumerStatefulWidget { const StackPrivacyCalls({ Key? key, @@ -56,95 +55,111 @@ class _StackPrivacyCalls extends ConsumerState { controller: _pageController, physics: const NeverScrollableScrollPhysics(), children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Choose your Stack experience", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 8, - ), - Text( - "You can change it later in Settings", - style: STextStyles.subtitle(context), - ), - const SizedBox( - height: 36, - ), - Center( - child: CustomRadio((bool isEasy) { - setState(() { - this.isEasy = isEasy; + Padding( + padding: const EdgeInsets.fromLTRB(0, 40, 0, 0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Choose your Stack experience", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 8, + ), + Text( + "You can change it later in Settings", + style: STextStyles.subtitle(context), + ), + const SizedBox( + height: 36, + ), + Center( + child: CustomRadio((bool isEasy) { + setState(() { + this.isEasy = isEasy; - DB.instance.put( - boxName: DB.boxNamePrefs, - key: "externalCalls", - value: isEasy); - }); - }), - ), - const SizedBox( - height: 36, - ), - RoundedWhiteContainer( - child: Center( - child: RichText( - textAlign: TextAlign.left, - text: TextSpan( - style: - STextStyles.label(context).copyWith(fontSize: 12.0), - children: isEasy - ? const [ - TextSpan( - text: - "Exchange data preloaded for a seamless experience."), - TextSpan( - text: - "\n\nCoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), - TextSpan( - text: - "\n\nRecommended for most crypto users.", - style: - TextStyle(fontWeight: FontWeight.bold)), - ] - : const [ - TextSpan( - text: - "Exchange data not preloaded (slower experience)."), - TextSpan( - text: - "\n\nCoinGecko disabled (price changes not shown, no wallet value shown in other currencies)."), - TextSpan( - text: - "\n\nRecommended for the privacy conscious.", - style: - TextStyle(fontWeight: FontWeight.bold)), - ], + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "externalCalls", + value: isEasy); + }); + }), + ), + const SizedBox( + height: 36, + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: RoundedWhiteContainer( + child: Center( + child: RichText( + textAlign: TextAlign.left, + text: TextSpan( + style: STextStyles.label(context) + .copyWith(fontSize: 12.0), + children: isEasy + ? [ + const TextSpan( + text: + "Exchange data preloaded for a seamless experience."), + const TextSpan( + text: + "\n\nCoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), + TextSpan( + text: + "\n\nRecommended for most crypto users.", + style: TextStyle( + color: Theme.of(context) + .extension()! + .textDark, + fontWeight: FontWeight.w600, + ), + ), + ] + : [ + const TextSpan( + text: + "Exchange data not preloaded (slower experience)."), + const TextSpan( + text: + "\n\nCoinGecko disabled (price changes not shown, no wallet value shown in other currencies)."), + TextSpan( + text: + "\n\nRecommended for the privacy conscious.", + style: TextStyle( + color: Theme.of(context) + .extension()! + .textDark, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), ), ), ), - ), - const Spacer( - flex: 4, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, + const Spacer( + flex: 4, ), - child: Row( - children: [ - Expanded( - child: ContinueButton( - isDesktop: isDesktop, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + child: Row( + children: [ + Expanded( + child: ContinueButton( + isDesktop: isDesktop, + ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ], ), @@ -268,8 +283,8 @@ class RadioItem extends StatelessWidget { SvgPicture.asset( _item.svg, // color: Theme.of(context).extension()!.textWhite, - width: 96, - height: 96, + width: 140, + height: 140, ), RichText( textAlign: TextAlign.center, @@ -278,7 +293,11 @@ class RadioItem extends StatelessWidget { children: [ TextSpan( text: _item.topText, - style: const TextStyle(fontWeight: FontWeight.bold)), + style: TextStyle( + color: Theme.of(context) + .extension()! + .textDark, + fontWeight: FontWeight.bold)), TextSpan(text: "\n${_item.bottomText}"), ], ), From 8625e6f75a8b932cd376528e460f55a6b542acbb Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 10 Oct 2022 13:49:53 -0600 Subject: [PATCH 39/75] added stack_privacy_calls to advanced settings --- lib/pages/intro_view.dart | 10 ++-- .../advanced_settings_view.dart | 53 +++++++++++++++++++ lib/pages/stack_privacy_calls.dart | 48 +++++++++++++---- lib/route_generator.dart | 9 +++- 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/lib/pages/intro_view.dart b/lib/pages/intro_view.dart index 1c78efb770..7a00b1b549 100644 --- a/lib/pages/intro_view.dart +++ b/lib/pages/intro_view.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; +import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/pages/stack_privacy_calls.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -11,8 +11,6 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:stackwallet/hive/db.dart'; - class IntroView extends StatefulWidget { const IntroView({Key? key}) : super(key: key); @@ -246,7 +244,8 @@ class GetStartedButton extends StatelessWidget { onPressed: () { unawaited(DB.instance.put( boxName: DB.boxNamePrefs, key: "externalCalls", value: true)); - Navigator.of(context).pushNamed(StackPrivacyCalls.routeName); + Navigator.of(context) + .pushNamed(StackPrivacyCalls.routeName, arguments: false); }, child: Text( "Get started", @@ -261,7 +260,8 @@ class GetStartedButton extends StatelessWidget { .extension()! .getPrimaryEnabledButtonColor(context), onPressed: () { - Navigator.of(context).pushNamed(StackPrivacyCalls.routeName); + Navigator.of(context) + .pushNamed(StackPrivacyCalls.routeName, arguments: false); }, child: Text( "Get started", 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 035efb2db7..f1fc2e16db 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 @@ -9,6 +9,9 @@ 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 '../../../../hive/db.dart'; +import '../../../stack_privacy_calls.dart'; + class AdvancedSettingsView extends StatelessWidget { const AdvancedSettingsView({ Key? key, @@ -20,6 +23,10 @@ class AdvancedSettingsView extends StatelessWidget { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); + final externalCalls = DB.instance + .get(boxName: DB.boxNamePrefs, key: "externalCalls") ?? + true; + return Scaffold( backgroundColor: Theme.of(context).extension()!.background, appBar: AppBar( @@ -115,6 +122,52 @@ class AdvancedSettingsView extends StatelessWidget { }, ), ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + Navigator.of(context) + .pushNamed(StackPrivacyCalls.routeName, arguments: true); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + children: [ + RichText( + textAlign: TextAlign.left, + text: TextSpan( + children: [ + TextSpan( + text: "Stack Experience", + style: STextStyles.titleBold12(context), + ), + TextSpan( + text: externalCalls as bool + ? "\nEasy crypto" + : "\nIncognito", + style: STextStyles.label(context) + .copyWith(fontSize: 15.0), + ) + ], + ), + ), + ], + ), + ), + ), + ), ], ), ), diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart index 9113a36534..03530fc077 100644 --- a/lib/pages/stack_privacy_calls.dart +++ b/lib/pages/stack_privacy_calls.dart @@ -13,8 +13,11 @@ import 'package:stackwallet/widgets/rounded_white_container.dart'; class StackPrivacyCalls extends ConsumerStatefulWidget { const StackPrivacyCalls({ Key? key, + required this.isSettings, }) : super(key: key); + final bool isSettings; + static const String routeName = "/stackPrivacy"; @override @@ -78,11 +81,6 @@ class _StackPrivacyCalls extends ConsumerState { child: CustomRadio((bool isEasy) { setState(() { this.isEasy = isEasy; - - DB.instance.put( - boxName: DB.boxNamePrefs, - key: "externalCalls", - value: isEasy); }); }), ), @@ -153,6 +151,8 @@ class _StackPrivacyCalls extends ConsumerState { Expanded( child: ContinueButton( isDesktop: isDesktop, + isSettings: widget.isSettings, + isEasy: isEasy, ), ), ], @@ -169,9 +169,16 @@ class _StackPrivacyCalls extends ConsumerState { } class ContinueButton extends StatelessWidget { - const ContinueButton({Key? key, required this.isDesktop}) : super(key: key); + const ContinueButton( + {Key? key, + required this.isDesktop, + required this.isSettings, + required this.isEasy}) + : super(key: key); final bool isDesktop; + final bool isSettings; + final bool isEasy; @override Widget build(BuildContext context) { @@ -181,10 +188,20 @@ class ContinueButton extends StatelessWidget { .extension()! .getPrimaryEnabledButtonColor(context), onPressed: () { - Navigator.of(context).pushNamed(CreatePinView.routeName); + print("Output of isEasy:"); + print(isEasy); + + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "externalCalls", + value: isEasy, + ); + if (!isSettings) { + Navigator.of(context).pushNamed(CreatePinView.routeName); + } }, child: Text( - "Continue", + !isSettings ? "Continue" : "Save changes", style: STextStyles.button(context), ), ) @@ -196,10 +213,21 @@ class ContinueButton extends StatelessWidget { .extension()! .getPrimaryEnabledButtonColor(context), onPressed: () { - Navigator.of(context).pushNamed(StackPrivacyCalls.routeName); + print("Output of isEasy:"); + print(isEasy); + + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "externalCalls", + value: isEasy, + ); + + if (!isSettings) { + Navigator.of(context).pushNamed(StackPrivacyCalls.routeName); + } }, child: Text( - "Continue", + !isSettings ? "Continue" : "Save changes", style: STextStyles.button(context).copyWith(fontSize: 20), ), ), diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 8db03e8d04..5e026cf24b 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -122,9 +122,16 @@ class RouteGenerator { settings: RouteSettings(name: settings.name)); case StackPrivacyCalls.routeName: + if (args is bool) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => StackPrivacyCalls(isSettings: args), + settings: RouteSettings(name: settings.name), + ); + } return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => const StackPrivacyCalls(), + builder: (_) => StackPrivacyCalls(isSettings: false), settings: RouteSettings(name: settings.name)); case WalletsView.routeName: From 7f8392522d507fc84659f9ed3a9046d33ffe1159 Mon Sep 17 00:00:00 2001 From: Marco Date: Mon, 10 Oct 2022 17:41:43 -0600 Subject: [PATCH 40/75] fully working setting with ease of use option, tests pass --- .../svg/exchange_icons/change_now_logo_1.svg | 26 +----- lib/pages/home_view/home_view.dart | 9 +- .../sub_widgets/home_view_button_bar.dart | 13 ++- lib/pages/intro_view.dart | 21 +++-- .../advanced_settings_view.dart | 92 ++++++++++--------- lib/pages/stack_privacy_calls.dart | 46 +++++----- lib/pages/wallet_view/wallet_view.dart | 8 ++ lib/services/price.dart | 13 ++- lib/utilities/prefs.dart | 22 +++++ test/cached_electrumx_test.mocks.dart | 8 ++ test/electrumx_test.mocks.dart | 8 ++ ...timated_rate_exchange_form_state_test.dart | 2 + .../exchange/exchange_view_test.mocks.dart | 8 ++ test/services/change_now/change_now_test.dart | 8 +- 14 files changed, 163 insertions(+), 121 deletions(-) diff --git a/assets/svg/exchange_icons/change_now_logo_1.svg b/assets/svg/exchange_icons/change_now_logo_1.svg index 323488e529..f901f35623 100644 --- a/assets/svg/exchange_icons/change_now_logo_1.svg +++ b/assets/svg/exchange_icons/change_now_logo_1.svg @@ -1,25 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lib/pages/home_view/home_view.dart b/lib/pages/home_view/home_view.dart index 3080706ecc..2bdcbace03 100644 --- a/lib/pages/home_view/home_view.dart +++ b/lib/pages/home_view/home_view.dart @@ -21,10 +21,10 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:stackwallet/hive/db.dart'; - import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; + class HomeView extends ConsumerStatefulWidget { const HomeView({Key? key}) : super(key: key); @@ -86,9 +86,8 @@ class _HomeViewState extends ConsumerState { void _loadCNData() { // unawaited future // - final externalCalls = DB.instance - .get(boxName: DB.boxNamePrefs, key: "externalCalls") as bool?; - if (externalCalls ?? false) { + final externalCalls = Prefs.instance.externalCalls; + if (externalCalls) { _cnLoadingService.loadAll(ref); } else { Logging.instance.log("User does not want to use external calls", diff --git a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart index fd3536ef1d..e502d4537a 100644 --- a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart +++ b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/providers.dart'; @@ -6,8 +8,7 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/services/change_now/change_now_loading_service.dart'; +import 'package:stackwallet/utilities/prefs.dart'; class HomeViewButtonBar extends ConsumerStatefulWidget { const HomeViewButtonBar({Key? key}) : super(key: key); @@ -104,11 +105,9 @@ class _HomeViewButtonBarState extends ConsumerState { ref.read(homeViewPageIndexStateProvider.state).state = 1; } DateTime now = DateTime.now(); - // TODO: find out why it infinitely loads - final _cnLoadingService = ChangeNowLoadingService(); - final externalCalls = DB.instance.get( - boxName: DB.boxNamePrefs, key: "externalCalls") as bool?; - if (!(externalCalls ?? false)) { + final _cnLoadingService = ExchangeDataLoadingService(); + final externalCalls = Prefs.instance.externalCalls; + if (!externalCalls) { print("loading?"); unawaited(_cnLoadingService.loadAll(ref)); } diff --git a/lib/pages/intro_view.dart b/lib/pages/intro_view.dart index fe7d2a2544..494f23973b 100644 --- a/lib/pages/intro_view.dart +++ b/lib/pages/intro_view.dart @@ -1,16 +1,16 @@ -import 'dart:async'; - import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/pages/stack_privacy_calls.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:tuple/tuple.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:stackwallet/utilities/prefs.dart'; + class IntroView extends StatefulWidget { const IntroView({Key? key}) : super(key: key); @@ -244,10 +244,11 @@ class GetStartedButton extends StatelessWidget { .extension()! .getPrimaryEnabledButtonColor(context), onPressed: () { - unawaited(DB.instance.put( - boxName: DB.boxNamePrefs, key: "externalCalls", value: true)); - Navigator.of(context) - .pushNamed(StackPrivacyCalls.routeName, arguments: false); + Prefs.instance.externalCalls = true; + Navigator.of(context).pushNamed( + StackPrivacyCalls.routeName, + arguments: false, + ); }, child: Text( "Get started", @@ -262,8 +263,10 @@ class GetStartedButton extends StatelessWidget { .extension()! .getPrimaryEnabledButtonColor(context), onPressed: () { - Navigator.of(context) - .pushNamed(StackPrivacyCalls.routeName, arguments: false); + Navigator.of(context).pushNamed( + StackPrivacyCalls.routeName, + arguments: false, + ); }, child: Text( "Get started", 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 f1fc2e16db..86212e0c7e 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 @@ -8,9 +8,9 @@ import 'package:stackwallet/utilities/theme/stack_colors.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 '../../../../hive/db.dart'; -import '../../../stack_privacy_calls.dart'; +import 'package:stackwallet/pages/stack_privacy_calls.dart'; class AdvancedSettingsView extends StatelessWidget { const AdvancedSettingsView({ @@ -23,10 +23,6 @@ class AdvancedSettingsView extends StatelessWidget { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - final externalCalls = DB.instance - .get(boxName: DB.boxNamePrefs, key: "externalCalls") ?? - true; - return Scaffold( backgroundColor: Theme.of(context).extension()!.background, appBar: AppBar( @@ -127,45 +123,55 @@ class AdvancedSettingsView extends StatelessWidget { ), RoundedWhiteContainer( padding: const EdgeInsets.all(0), - child: RawMaterialButton( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - Navigator.of(context) - .pushNamed(StackPrivacyCalls.routeName, arguments: true); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 20, - ), - child: Row( - children: [ - RichText( - textAlign: TextAlign.left, - text: TextSpan( - children: [ - TextSpan( - text: "Stack Experience", - style: STextStyles.titleBold12(context), + child: Consumer( + builder: (_, ref, __) { + final externalCalls = ref.watch( + prefsChangeNotifierProvider + .select((value) => value.externalCalls), + ); + return RawMaterialButton( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + Navigator.of(context).pushNamed( + StackPrivacyCalls.routeName, + arguments: true, + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + children: [ + RichText( + textAlign: TextAlign.left, + text: TextSpan( + children: [ + TextSpan( + text: "Stack Experience", + style: STextStyles.titleBold12(context), + ), + TextSpan( + text: externalCalls + ? "\nEasy crypto" + : "\nIncognito", + style: STextStyles.label(context) + .copyWith(fontSize: 15.0), + ) + ], ), - TextSpan( - text: externalCalls as bool - ? "\nEasy crypto" - : "\nIncognito", - style: STextStyles.label(context) - .copyWith(fontSize: 15.0), - ) - ], - ), + ), + ], ), - ], - ), - ), + ), + ); + }, ), ), ], diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart index 03530fc077..e299572d02 100644 --- a/lib/pages/stack_privacy_calls.dart +++ b/lib/pages/stack_privacy_calls.dart @@ -10,6 +10,9 @@ import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/utilities/prefs.dart'; + class StackPrivacyCalls extends ConsumerStatefulWidget { const StackPrivacyCalls({ Key? key, @@ -26,7 +29,7 @@ class StackPrivacyCalls extends ConsumerStatefulWidget { class _StackPrivacyCalls extends ConsumerState { late final bool isDesktop; - bool isEasy = true; + bool isEasy = Prefs.instance.externalCalls; final PageController _pageController = PageController(initialPage: 0, keepPage: true); @@ -78,11 +81,19 @@ class _StackPrivacyCalls extends ConsumerState { height: 36, ), Center( - child: CustomRadio((bool isEasy) { - setState(() { - this.isEasy = isEasy; - }); - }), + child: Consumer( + builder: (_, ref, __) { + final externalCalls = ref.watch( + prefsChangeNotifierProvider + .select((value) => value.externalCalls), + ); + return CustomRadio((bool isEasy) { + setState(() { + this.isEasy = isEasy; + }); + }, externalCalls); + }, + ), ), const SizedBox( height: 36, @@ -191,11 +202,7 @@ class ContinueButton extends StatelessWidget { print("Output of isEasy:"); print(isEasy); - DB.instance.put( - boxName: DB.boxNamePrefs, - key: "externalCalls", - value: isEasy, - ); + Prefs.instance.externalCalls = isEasy; if (!isSettings) { Navigator.of(context).pushNamed(CreatePinView.routeName); } @@ -216,14 +223,10 @@ class ContinueButton extends StatelessWidget { print("Output of isEasy:"); print(isEasy); - DB.instance.put( - boxName: DB.boxNamePrefs, - key: "externalCalls", - value: isEasy, - ); + Prefs.instance.externalCalls = isEasy; if (!isSettings) { - Navigator.of(context).pushNamed(StackPrivacyCalls.routeName); + Navigator.of(context).pushNamed(CreatePinView.routeName); } }, child: Text( @@ -236,8 +239,9 @@ class ContinueButton extends StatelessWidget { } class CustomRadio extends StatefulWidget { - CustomRadio(this.upperCall, {Key? key}) : super(key: key); + CustomRadio(this.upperCall, this.isEasy, {Key? key}) : super(key: key); + bool isEasy; Function upperCall; @override @@ -252,10 +256,10 @@ class CustomRadioState extends State { @override void initState() { super.initState(); - sampleData.add( - RadioModel(true, Assets.svg.personaEasy, 'Easy Crypto', 'Recommended')); sampleData.add(RadioModel( - false, Assets.svg.personaIncognito, 'Incognito', 'Privacy conscious')); + widget.isEasy, Assets.svg.personaEasy, 'Easy Crypto', 'Recommended')); + sampleData.add(RadioModel(!widget.isEasy, Assets.svg.personaIncognito, + 'Incognito', 'Privacy conscious')); } @override diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 74299db984..488ed19cd4 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -49,6 +49,8 @@ import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; + /// [eventBus] should only be set during testing class WalletView extends ConsumerStatefulWidget { const WalletView({ @@ -233,6 +235,12 @@ class _WalletViewState extends ConsumerState { } void _onExchangePressed(BuildContext context) async { + final _cnLoadingService = ExchangeDataLoadingService(); + final externalCalls = Prefs.instance.externalCalls; + if (!externalCalls) { + print("loading?"); + unawaited(_cnLoadingService.loadAll(ref)); + } final coin = ref.read(managerProvider).coin; if (coin == Coin.epicCash) { diff --git a/lib/services/price.dart b/lib/services/price.dart index 96c849eb63..af25276960 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -9,6 +9,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:tuple/tuple.dart'; +import 'package:stackwallet/utilities/prefs.dart'; + class PriceAPI { static const refreshInterval = 60; @@ -76,9 +78,8 @@ class PriceAPI { return _cachedPrices; } - final externalCalls = DB.instance - .get(boxName: DB.boxNamePrefs, key: "externalCalls") as bool?; - if (!(externalCalls ?? false)) { + final externalCalls = Prefs.instance.externalCalls; + if (!externalCalls) { Logging.instance.log("User does not want to use external calls", level: LogLevel.Info); return _cachedPrices; @@ -122,10 +123,8 @@ class PriceAPI { } static Future?> availableBaseCurrencies() async { - final externalCalls = DB.instance - .get(boxName: DB.boxNamePrefs, key: "externalCalls") as bool?; - print("externalCalls hi2 $externalCalls"); - if (!(externalCalls ?? false)) { + final externalCalls = Prefs.instance.externalCalls; + if (!externalCalls) { Logging.instance.log("User does not want to use external calls", level: LogLevel.Info); return null; diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index 578cf35a4d..ce76ecf2c9 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -36,6 +36,7 @@ class Prefs extends ChangeNotifier { _hideBlockExplorerWarning = await _getHideBlockExplorerWarning(); _gotoWalletOnStartup = await _getGotoWalletOnStartup(); _startupWalletId = await _getStartupWalletId(); + _externalCalls = await _getExternalCalls(); _initialized = true; } @@ -179,6 +180,27 @@ class Prefs extends ChangeNotifier { false; } + // external calls + + bool _externalCalls = true; + + bool get externalCalls => _externalCalls; + + set externalCalls(bool eCalls) { + if (_externalCalls != eCalls) { + DB.instance.put( + boxName: DB.boxNamePrefs, key: "externalCalls", value: eCalls); + _externalCalls = eCalls; + notifyListeners(); + } + } + + Future _getExternalCalls() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, key: "externalCalls") as bool? ?? + true; + } + // show favorites bool _showFavoriteWallets = true; diff --git a/test/cached_electrumx_test.mocks.dart b/test/cached_electrumx_test.mocks.dart index ce37c93d1a..52f6c40a57 100644 --- a/test/cached_electrumx_test.mocks.dart +++ b/test/cached_electrumx_test.mocks.dart @@ -276,6 +276,14 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { super.noSuchMethod(Invocation.setter(#wifiOnly, wifiOnly), returnValueForMissingStub: null); @override + bool get externalCalls => + (super.noSuchMethod(Invocation.getter(#externalCalls), returnValue: false) + as bool); + @override + set externalCalls(bool? eCalls) => + super.noSuchMethod(Invocation.setter(#externalCalls, eCalls), + returnValueForMissingStub: null); + @override bool get showFavoriteWallets => (super.noSuchMethod(Invocation.getter(#showFavoriteWallets), returnValue: false) as bool); diff --git a/test/electrumx_test.mocks.dart b/test/electrumx_test.mocks.dart index fb115f9abc..b8ec89ff89 100644 --- a/test/electrumx_test.mocks.dart +++ b/test/electrumx_test.mocks.dart @@ -125,6 +125,14 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs { super.noSuchMethod(Invocation.setter(#wifiOnly, wifiOnly), returnValueForMissingStub: null); @override + bool get externalCalls => + (super.noSuchMethod(Invocation.getter(#externalCalls), returnValue: false) + as bool); + @override + set externalCalls(bool? eCalls) => + super.noSuchMethod(Invocation.setter(#externalCalls, eCalls), + returnValueForMissingStub: null); + @override bool get showFavoriteWallets => (super.noSuchMethod(Invocation.getter(#showFavoriteWallets), returnValue: false) as bool); diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.dart index 88b73ab47d..42efa52e8d 100644 --- a/test/models/exchange/estimated_rate_exchange_form_state_test.dart +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.dart @@ -10,6 +10,8 @@ // // import 'estimated_rate_exchange_form_state_test.mocks.dart'; // +void main() {} + // @GenerateMocks([ChangeNowAPI]) // void main() { // final currencyA = Currency( diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index 6f98b499ec..73667c366c 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -107,6 +107,14 @@ class MockPrefs extends _i1.Mock implements _i3.Prefs { super.noSuchMethod(Invocation.setter(#wifiOnly, wifiOnly), returnValueForMissingStub: null); @override + bool get externalCalls => + (super.noSuchMethod(Invocation.getter(#externalCalls), returnValue: false) + as bool); + @override + set externalCalls(bool? eCalls) => + super.noSuchMethod(Invocation.setter(#externalCalls, eCalls), + returnValueForMissingStub: null); + @override bool get showFavoriteWallets => (super.noSuchMethod(Invocation.getter(#showFavoriteWallets), returnValue: false) as bool); diff --git a/test/services/change_now/change_now_test.dart b/test/services/change_now/change_now_test.dart index 9c8efb1acf..b7b504e98c 100644 --- a/test/services/change_now/change_now_test.dart +++ b/test/services/change_now/change_now_test.dart @@ -8,6 +8,7 @@ import 'package:mockito/mockito.dart'; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; @@ -290,7 +291,7 @@ void main() { expect(result.exception, null); expect(result.value == null, false); - expect(result.value, isA()); + expect(result.value, isA()); }); test( @@ -574,7 +575,7 @@ void main() { "https://api.ChangeNow.io/v1/transactions/fixed-rate/testAPIKEY"), headers: {'Content-Type': 'application/json'}, body: - '{"from":"btc","to":"eth","address":"0x57f31ad4b64095347F87eDB1675566DAfF5EC886","amount":"0.3","flow":"fixed-rate","extraId":"","userId":"","contactEmail":"","refundAddress":"","refundExtraId":"","rateId":""}', + '{"from":"btc","to":"eth","address":"0x57f31ad4b64095347F87eDB1675566DAfF5EC886","flow":"fixed-rate","extraId":"","userId":"","contactEmail":"","refundAddress":"","refundExtraId":"","rateId":"","amount":"0.3"}', encoding: null, )).thenAnswer((realInvocation) async => Response( '{"payinAddress": "33eFX2jfeWbXMSmRe9ewUUTrmSVSxZi5cj", "payoutAddress": "0x57f31ad4b64095347F87eDB1675566DAfF5EC886","payoutExtraId": "", "fromCurrency": "btc", "toCurrency": "eth", "refundAddress": "","refundExtraId": "","validUntil": "2019-09-09T14:01:04.921Z","id": "a5c73e2603f40d","amount": 62.9737711}', @@ -625,8 +626,7 @@ void main() { reversed: false, ); - expect( - result.exception!.type, ExchangeExceptionType.serializeResponseError); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); From bf27f38bb51c799d0bf5829999f77fbc6c319c6c Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 11 Oct 2022 08:15:51 -0600 Subject: [PATCH 41/75] WIP startup privacy options page layout --- lib/pages/stack_privacy_calls.dart | 260 +++++++++++++++++++++++------ lib/utilities/prefs.dart | 25 +++ 2 files changed, 236 insertions(+), 49 deletions(-) diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart index c16e9d7416..6f971408ae 100644 --- a/lib/pages/stack_privacy_calls.dart +++ b/lib/pages/stack_privacy_calls.dart @@ -2,15 +2,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/utilities/assets.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/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/util.dart'; - class StackPrivacyCalls extends ConsumerStatefulWidget { const StackPrivacyCalls({ Key? key, @@ -73,55 +73,66 @@ class _StackPrivacyCalls extends ConsumerState { const SizedBox( height: 36, ), - Center( - child: CustomRadio((bool isEasy) { - setState(() { - this.isEasy = isEasy; - - DB.instance.put( - boxName: DB.boxNamePrefs, - key: "externalCalls", - value: isEasy); - }); - }), + const Padding( + padding: EdgeInsets.symmetric( + horizontal: 16, + ), + child: PrivacyToggle(), ), + // Center( + // child: CustomRadio((bool isEasy) { + // setState(() { + // this.isEasy = isEasy; + // + // DB.instance.put( + // boxName: DB.boxNamePrefs, + // key: "externalCalls", + // value: isEasy); + // }); + // }), + // ), const SizedBox( height: 36, ), - RoundedWhiteContainer( - child: Center( - child: RichText( - textAlign: TextAlign.left, - text: TextSpan( - style: - STextStyles.label(context).copyWith(fontSize: 12.0), - children: isEasy - ? const [ - TextSpan( - text: - "Exchange data preloaded for a seamless experience."), - TextSpan( - text: - "\n\nCoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), - TextSpan( - text: - "\n\nRecommended for most crypto users.", - style: - TextStyle(fontWeight: FontWeight.bold)), - ] - : const [ - TextSpan( - text: - "Exchange data not preloaded (slower experience)."), - TextSpan( - text: - "\n\nCoinGecko disabled (price changes not shown, no wallet value shown in other currencies)."), - TextSpan( - text: - "\n\nRecommended for the privacy conscious.", - style: - TextStyle(fontWeight: FontWeight.bold)), - ], + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: RoundedWhiteContainer( + child: Center( + child: RichText( + textAlign: TextAlign.left, + text: TextSpan( + style: STextStyles.label(context) + .copyWith(fontSize: 12.0), + children: isEasy + ? const [ + TextSpan( + text: + "Exchange data preloaded for a seamless experience."), + TextSpan( + text: + "\n\nCoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), + TextSpan( + text: + "\n\nRecommended for most crypto users.", + style: TextStyle( + fontWeight: FontWeight.bold)), + ] + : const [ + TextSpan( + text: + "Exchange data not preloaded (slower experience)."), + TextSpan( + text: + "\n\nCoinGecko disabled (price changes not shown, no wallet value shown in other currencies)."), + TextSpan( + text: + "\n\nRecommended for the privacy conscious.", + style: TextStyle( + fontWeight: FontWeight.bold)), + ], + ), ), ), ), @@ -153,6 +164,157 @@ class _StackPrivacyCalls extends ConsumerState { } } +class PrivacyToggle extends ConsumerStatefulWidget { + const PrivacyToggle({Key? key}) : super(key: key); + + @override + ConsumerState createState() => _PrivacyToggleState(); +} + +class _PrivacyToggleState extends ConsumerState { + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: RawMaterialButton( + fillColor: Theme.of(context).extension()!.popupBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius * 2, + ), + ), + onPressed: () { + ref.read(prefsChangeNotifierProvider).externalCalls = true; + }, + child: Padding( + padding: const EdgeInsets.all( + 12, + ), + child: Column( + children: [ + SvgPicture.asset( + Assets.svg.personaEasy, + // color: Theme.of(context).extension()!.textWhite, + width: 96, + height: 96, + ), + const Text( + "Easy Crypto", + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + const Text( + "Recommended", + ), + ], + ), + ), + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: RawMaterialButton( + elevation: 0, + fillColor: Theme.of(context).extension()!.popupBG, + shape: RoundedRectangleBorder( + side: ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + ) + ? BorderSide.none + : BorderSide( + color: Theme.of(context) + .extension()! + .infoItemIcons, + width: 2, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius * 2, + ), + ), + onPressed: () { + ref.read(prefsChangeNotifierProvider).externalCalls = false; + }, + child: Padding( + padding: const EdgeInsets.all( + 12, + ), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SvgPicture.asset( + Assets.svg.personaIncognito, + width: 96, + height: 96, + ), + const Center( + child: Text( + "Incognito", + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + const Center( + child: Text( + "Privacy conscious", + ), + ), + ], + ), + if (!ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + )) + Positioned( + top: 4, + right: 4, + child: SvgPicture.asset( + Assets.svg.checkCircle, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), + ), + if (ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + )) + Positioned( + top: 4, + right: 4, + child: Container( + width: 20, + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(1000), + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + ), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } +} + class ContinueButton extends StatelessWidget { const ContinueButton({Key? key, required this.isDesktop}) : super(key: key); diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index 578cf35a4d..7e10965506 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -36,6 +36,7 @@ class Prefs extends ChangeNotifier { _hideBlockExplorerWarning = await _getHideBlockExplorerWarning(); _gotoWalletOnStartup = await _getGotoWalletOnStartup(); _startupWalletId = await _getStartupWalletId(); + _externalCalls = await _getHasExternalCalls(); _initialized = true; } @@ -544,4 +545,28 @@ class Prefs extends ChangeNotifier { return await DB.instance.get( boxName: DB.boxNamePrefs, key: "startupWalletId") as String?; } + + bool _externalCalls = false; + + bool get externalCalls => _externalCalls; + + set externalCalls(bool externalCalls) { + if (_externalCalls != externalCalls) { + DB.instance + .put( + boxName: DB.boxNamePrefs, + key: "externalCalls", + value: externalCalls) + .then((_) { + _externalCalls = externalCalls; + notifyListeners(); + }); + } + } + + Future _getHasExternalCalls() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, key: "externalCalls") as bool? ?? + false; + } } From 8742496a24f931e2c9d68c2d6d85ea74ef277dc8 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 11 Oct 2022 10:34:36 -0600 Subject: [PATCH 42/75] new icon without white border --- .../{persona-easy-1.svg => persona-easy.svg} | 0 lib/pages/stack_privacy_calls.dart | 255 +++++++++--------- lib/utilities/assets.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 131 insertions(+), 128 deletions(-) rename assets/svg/{persona-easy-1.svg => persona-easy.svg} (100%) diff --git a/assets/svg/persona-easy-1.svg b/assets/svg/persona-easy.svg similarity index 100% rename from assets/svg/persona-easy-1.svg rename to assets/svg/persona-easy.svg diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart index c077235469..bcd73dc84c 100644 --- a/lib/pages/stack_privacy_calls.dart +++ b/lib/pages/stack_privacy_calls.dart @@ -3,7 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; -import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -13,10 +12,6 @@ import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/util.dart'; - class StackPrivacyCalls extends ConsumerStatefulWidget { const StackPrivacyCalls({ Key? key, @@ -82,18 +77,24 @@ class _StackPrivacyCalls extends ConsumerState { const SizedBox( height: 36, ), - Center( - child: CustomRadio((bool isEasy) { - setState(() { - this.isEasy = isEasy; - - DB.instance.put( - boxName: DB.boxNamePrefs, - key: "externalCalls", - value: isEasy); - }); - }), + const Padding( + padding: EdgeInsets.symmetric( + horizontal: 16, + ), + child: PrivacyToggle(), ), + // Center( + // child: CustomRadio((bool isEasy) { + // setState(() { + // this.isEasy = isEasy; + // + // DB.instance.put( + // boxName: DB.boxNamePrefs, + // key: "externalCalls", + // value: isEasy); + // }); + // }), + // ), const SizedBox( height: 36, ), @@ -148,6 +149,8 @@ class _StackPrivacyCalls extends ConsumerState { Expanded( child: ContinueButton( isDesktop: isDesktop, + isSettings: widget.isSettings, + isEasy: isEasy, ), ), ], @@ -380,113 +383,113 @@ class ContinueButton extends StatelessWidget { } } -class CustomRadio extends StatefulWidget { - CustomRadio(this.upperCall, {Key? key}) : super(key: key); - - Function upperCall; - - @override - createState() { - return CustomRadioState(); - } -} - -class CustomRadioState extends State { - List sampleData = []; - - @override - void initState() { - super.initState(); - sampleData.add( - RadioModel(true, Assets.svg.personaEasy, 'Easy Crypto', 'Recommended')); - sampleData.add(RadioModel( - false, Assets.svg.personaIncognito, 'Incognito', 'Privacy conscious')); - } - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - InkWell( - onTap: () { - setState(() { - // if (!sampleData[0].isSelected) { - widget.upperCall.call(true); - // } - for (var element in sampleData) { - element.isSelected = false; - } - sampleData[0].isSelected = true; - }); - }, - child: RadioItem(sampleData[0]), - ), - InkWell( - onTap: () { - setState(() { - // if (!sampleData[1].isSelected) { - widget.upperCall.call(false); - // } - for (var element in sampleData) { - element.isSelected = false; - } - sampleData[1].isSelected = true; - }); - }, - child: RadioItem(sampleData[1]), - ) - ], - ); - } -} - -class RadioItem extends StatelessWidget { - final RadioModel _item; - const RadioItem(this._item, {Key? key}) : super(key: key); - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(15.0), - child: RoundedWhiteContainer( - borderColor: _item.isSelected ? const Color(0xFF0056D2) : null, - child: Center( - child: Column( - children: [ - SvgPicture.asset( - _item.svg, - // color: Theme.of(context).extension()!.textWhite, - width: 140, - height: 140, - ), - RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: STextStyles.label(context).copyWith(fontSize: 12.0), - children: [ - TextSpan( - text: _item.topText, - style: TextStyle( - color: Theme.of(context) - .extension()! - .textDark, - fontWeight: FontWeight.bold)), - TextSpan(text: "\n${_item.bottomText}"), - ], - ), - ), - ], - )), - ), - ); - } -} - -class RadioModel { - bool isSelected; - final String svg; - final String topText; - final String bottomText; - - RadioModel(this.isSelected, this.svg, this.topText, this.bottomText); -} +// class CustomRadio extends StatefulWidget { +// CustomRadio(this.upperCall, {Key? key}) : super(key: key); +// +// Function upperCall; +// +// @override +// createState() { +// return CustomRadioState(); +// } +// } +// +// class CustomRadioState extends State { +// List sampleData = []; +// +// @override +// void initState() { +// super.initState(); +// sampleData.add( +// RadioModel(true, Assets.svg.personaEasy, 'Easy Crypto', 'Recommended')); +// sampleData.add(RadioModel( +// false, Assets.svg.personaIncognito, 'Incognito', 'Privacy conscious')); +// } +// +// @override +// Widget build(BuildContext context) { +// return Row( +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// InkWell( +// onTap: () { +// setState(() { +// // if (!sampleData[0].isSelected) { +// widget.upperCall.call(true); +// // } +// for (var element in sampleData) { +// element.isSelected = false; +// } +// sampleData[0].isSelected = true; +// }); +// }, +// child: RadioItem(sampleData[0]), +// ), +// InkWell( +// onTap: () { +// setState(() { +// // if (!sampleData[1].isSelected) { +// widget.upperCall.call(false); +// // } +// for (var element in sampleData) { +// element.isSelected = false; +// } +// sampleData[1].isSelected = true; +// }); +// }, +// child: RadioItem(sampleData[1]), +// ) +// ], +// ); +// } +// } +// +// class RadioItem extends StatelessWidget { +// final RadioModel _item; +// const RadioItem(this._item, {Key? key}) : super(key: key); +// @override +// Widget build(BuildContext context) { +// return Container( +// margin: const EdgeInsets.all(15.0), +// child: RoundedWhiteContainer( +// borderColor: _item.isSelected ? const Color(0xFF0056D2) : null, +// child: Center( +// child: Column( +// children: [ +// SvgPicture.asset( +// _item.svg, +// // color: Theme.of(context).extension()!.textWhite, +// width: 140, +// height: 140, +// ), +// RichText( +// textAlign: TextAlign.center, +// text: TextSpan( +// style: STextStyles.label(context).copyWith(fontSize: 12.0), +// children: [ +// TextSpan( +// text: _item.topText, +// style: TextStyle( +// color: Theme.of(context) +// .extension()! +// .textDark, +// fontWeight: FontWeight.bold)), +// TextSpan(text: "\n${_item.bottomText}"), +// ], +// ), +// ), +// ], +// )), +// ), +// ); +// } +// } +// +// class RadioModel { +// bool isSelected; +// final String svg; +// final String topText; +// final String bottomText; +// +// RadioModel(this.isSelected, this.svg, this.topText, this.bottomText); +// } diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 3bf2fa250e..ec5ca21bdf 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -53,7 +53,7 @@ class _SVG { String get polygon => "assets/svg/Polygon.svg"; String get personaIncognito => "assets/svg/persona-incognito-1.svg"; - String get personaEasy => "assets/svg/persona-easy-1.svg"; + String get personaEasy => "assets/svg/persona-easy.svg"; String get drd => "assets/svg/drd-icon.svg"; String get boxAuto => "assets/svg/box-auto.svg"; String get plus => "assets/svg/plus.svg"; diff --git a/pubspec.yaml b/pubspec.yaml index 318f143a09..80f4c30aac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -290,7 +290,7 @@ flutter: - assets/svg/tx-icon-anonymize-pending.svg - assets/svg/tx-icon-anonymize-failed.svg - assets/svg/Polygon.svg - - assets/svg/persona-easy-1.svg + - assets/svg/persona-easy.svg - assets/svg/persona-incognito-1.svg # coin icons - assets/svg/coin_icons/Bitcoin.svg From 0c9b23d8e5fcf99036d10281af0eee076c7237a3 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 11 Oct 2022 11:36:07 -0600 Subject: [PATCH 43/75] toggle button and border for recommended privacy setting --- lib/pages/stack_privacy_calls.dart | 101 +++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 27 deletions(-) diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart index bcd73dc84c..22856de4ce 100644 --- a/lib/pages/stack_privacy_calls.dart +++ b/lib/pages/stack_privacy_calls.dart @@ -83,18 +83,6 @@ class _StackPrivacyCalls extends ConsumerState { ), child: PrivacyToggle(), ), - // Center( - // child: CustomRadio((bool isEasy) { - // setState(() { - // this.isEasy = isEasy; - // - // DB.instance.put( - // boxName: DB.boxNamePrefs, - // key: "externalCalls", - // value: isEasy); - // }); - // }), - // ), const SizedBox( height: 36, ), @@ -105,7 +93,11 @@ class _StackPrivacyCalls extends ConsumerState { text: TextSpan( style: STextStyles.label(context).copyWith(fontSize: 12.0), - children: isEasy + children: ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + ) ? const [ TextSpan( text: @@ -181,6 +173,18 @@ class _PrivacyToggleState extends ConsumerState { child: RawMaterialButton( fillColor: Theme.of(context).extension()!.popupBG, shape: RoundedRectangleBorder( + side: !ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + ) + ? BorderSide.none + : BorderSide( + color: Theme.of(context) + .extension()! + .infoItemIcons, + width: 2, + ), borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius * 2, ), @@ -192,23 +196,66 @@ class _PrivacyToggleState extends ConsumerState { padding: const EdgeInsets.all( 12, ), - child: Column( + child: Stack( children: [ - SvgPicture.asset( - Assets.svg.personaEasy, - // color: Theme.of(context).extension()!.textWhite, - width: 96, - height: 96, + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SvgPicture.asset( + Assets.svg.personaEasy, + width: 96, + height: 96, + ), + const Center( + child: Text( + "Easy Crypto", + style: TextStyle( + fontWeight: FontWeight.bold, + ), + )), + const Center( + child: Text( + "Recommended", + ), + ), + ], ), - const Text( - "Easy Crypto", - style: TextStyle( - fontWeight: FontWeight.bold, + if (ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + )) + Positioned( + top: 4, + right: 4, + child: SvgPicture.asset( + Assets.svg.checkCircle, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), + ), + if (!ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + )) + Positioned( + top: 4, + right: 4, + child: Container( + width: 20, + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(1000), + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + ), + ), ), - ), - const Text( - "Recommended", - ), ], ), ), From e66feaae2843d38f9fa3664622bb547a14468be2 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Tue, 11 Oct 2022 14:19:48 -0600 Subject: [PATCH 44/75] font fix and added padding --- assets/svg/persona-easy-1.svg | 9 ++ assets/svg/persona-easy.svg | 1 - lib/pages/stack_privacy_calls.dart | 190 ++++++++++++++++------------- lib/utilities/assets.dart | 2 +- pubspec.yaml | 2 +- 5 files changed, 116 insertions(+), 88 deletions(-) create mode 100644 assets/svg/persona-easy-1.svg delete mode 100644 assets/svg/persona-easy.svg diff --git a/assets/svg/persona-easy-1.svg b/assets/svg/persona-easy-1.svg new file mode 100644 index 0000000000..6cb3d4b625 --- /dev/null +++ b/assets/svg/persona-easy-1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/svg/persona-easy.svg b/assets/svg/persona-easy.svg deleted file mode 100644 index 136f29d618..0000000000 --- a/assets/svg/persona-easy.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart index 22856de4ce..916e10f170 100644 --- a/lib/pages/stack_privacy_calls.dart +++ b/lib/pages/stack_privacy_calls.dart @@ -60,95 +60,115 @@ class _StackPrivacyCalls extends ConsumerState { controller: _pageController, physics: const NeverScrollableScrollPhysics(), children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Choose your Stack experience", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 8, - ), - Text( - "You can change it later in Settings", - style: STextStyles.subtitle(context), - ), - const SizedBox( - height: 36, - ), - const Padding( - padding: EdgeInsets.symmetric( - horizontal: 16, + Padding( + padding: const EdgeInsets.fromLTRB(0, 40, 0, 0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Choose your Stack experience", + style: STextStyles.pageTitleH1(context), ), - child: PrivacyToggle(), - ), - const SizedBox( - height: 36, - ), - RoundedWhiteContainer( - child: Center( - child: RichText( - textAlign: TextAlign.left, - text: TextSpan( - style: - STextStyles.label(context).copyWith(fontSize: 12.0), - children: ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.externalCalls, + const SizedBox( + height: 8, + ), + Text( + "You can change it later in Settings", + style: STextStyles.subtitle(context), + ), + const SizedBox( + height: 36, + ), + const Padding( + padding: EdgeInsets.symmetric( + horizontal: 16, + ), + child: PrivacyToggle(), + ), + const SizedBox( + height: 36, + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: RoundedWhiteContainer( + child: Center( + child: RichText( + textAlign: TextAlign.left, + text: TextSpan( + style: STextStyles.label(context) + .copyWith(fontSize: 12.0), + children: ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + ) + ? [ + const TextSpan( + text: + "Exchange data preloaded for a seamless experience."), + const TextSpan( + text: + "\n\nCoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), + TextSpan( + text: + "\n\nRecommended for most crypto users.", + style: TextStyle( + color: Theme.of(context) + .extension()! + .textDark, + fontWeight: FontWeight.w600, + ), + ), + ] + : [ + const TextSpan( + text: + "Exchange data not preloaded (slower experience)."), + const TextSpan( + text: + "\n\nCoinGecko disabled (price changes not shown, no wallet value shown in other currencies)."), + TextSpan( + text: + "\n\nRecommended for the privacy conscious.", + style: TextStyle( + color: Theme.of(context) + .extension()! + .textDark, + fontWeight: FontWeight.w600, + ), + ), + ], ), - ) - ? const [ - TextSpan( - text: - "Exchange data preloaded for a seamless experience."), - TextSpan( - text: - "\n\nCoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), - TextSpan( - text: - "\n\nRecommended for most crypto users.", - style: - TextStyle(fontWeight: FontWeight.bold)), - ] - : const [ - TextSpan( - text: - "Exchange data not preloaded (slower experience)."), - TextSpan( - text: - "\n\nCoinGecko disabled (price changes not shown, no wallet value shown in other currencies)."), - TextSpan( - text: - "\n\nRecommended for the privacy conscious.", - style: - TextStyle(fontWeight: FontWeight.bold)), - ], + ), ), ), ), - ), - const Spacer( - flex: 4, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, + const Spacer( + flex: 4, ), - child: Row( - children: [ - Expanded( - child: ContinueButton( - isDesktop: isDesktop, - isSettings: widget.isSettings, - isEasy: isEasy, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + child: Row( + children: [ + Expanded( + child: ContinueButton( + isDesktop: isDesktop, + isSettings: widget.isSettings, + isEasy: ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + ), + ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ], ), @@ -203,8 +223,8 @@ class _PrivacyToggleState extends ConsumerState { children: [ SvgPicture.asset( Assets.svg.personaEasy, - width: 96, - height: 96, + width: 140, + height: 140, ), const Center( child: Text( @@ -299,8 +319,8 @@ class _PrivacyToggleState extends ConsumerState { children: [ SvgPicture.asset( Assets.svg.personaIncognito, - width: 96, - height: 96, + width: 140, + height: 140, ), const Center( child: Text( diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index ec5ca21bdf..3bf2fa250e 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -53,7 +53,7 @@ class _SVG { String get polygon => "assets/svg/Polygon.svg"; String get personaIncognito => "assets/svg/persona-incognito-1.svg"; - String get personaEasy => "assets/svg/persona-easy.svg"; + String get personaEasy => "assets/svg/persona-easy-1.svg"; String get drd => "assets/svg/drd-icon.svg"; String get boxAuto => "assets/svg/box-auto.svg"; String get plus => "assets/svg/plus.svg"; diff --git a/pubspec.yaml b/pubspec.yaml index 80f4c30aac..318f143a09 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -290,7 +290,7 @@ flutter: - assets/svg/tx-icon-anonymize-pending.svg - assets/svg/tx-icon-anonymize-failed.svg - assets/svg/Polygon.svg - - assets/svg/persona-easy.svg + - assets/svg/persona-easy-1.svg - assets/svg/persona-incognito-1.svg # coin icons - assets/svg/coin_icons/Bitcoin.svg From 548bc273f7358b416b590bce56d1e90d32aa23c9 Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 11 Oct 2022 14:45:11 -0600 Subject: [PATCH 45/75] add delete all functionality for IOS --- .../delete_account_view.dart | 46 +++++++++++++++++-- .../global_settings_view.dart | 30 ++++++------ lib/utilities/delete_everything.dart | 30 ++++++++++++ 3 files changed, 89 insertions(+), 17 deletions(-) create mode 100644 lib/utilities/delete_everything.dart diff --git a/lib/pages/settings_views/global_settings_view/delete_account_view.dart b/lib/pages/settings_views/global_settings_view/delete_account_view.dart index 5fdabf6ada..e9cc819dcb 100644 --- a/lib/pages/settings_views/global_settings_view/delete_account_view.dart +++ b/lib/pages/settings_views/global_settings_view/delete_account_view.dart @@ -8,6 +8,10 @@ import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/utilities/delete_everything.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; + class DeleteAccountView extends StatefulWidget { const DeleteAccountView({Key? key}) : super(key: key); @@ -22,9 +26,45 @@ class _DeleteAccountViewState extends State { Future onConfirmDeleteAccount() async { // TODO delete everything then pop to intro view - await Navigator.of(context).pushNamedAndRemoveUntil( - IntroView.routeName, - (route) => false, + + await showDialog( + barrierDismissible: true, + context: context, + builder: (_) => StackDialog( + title: "Are you sure you want to delete all Wallets?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonColor(context), + onPressed: () { + Navigator.pop(context); + }, + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: () async { + await deleteEverything(); + + await Navigator.of(context).pushNamedAndRemoveUntil( + IntroView.routeName, + (route) => false, + ); + }, + child: Text( + "Delete", + style: STextStyles.button(context), + ), + ), + ), ); } diff --git a/lib/pages/settings_views/global_settings_view/global_settings_view.dart b/lib/pages/settings_views/global_settings_view/global_settings_view.dart index 85e71f44fe..fe6529c206 100644 --- a/lib/pages/settings_views/global_settings_view/global_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/global_settings_view.dart @@ -23,6 +23,8 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/utilities/delete_everything.dart'; + class GlobalSettingsView extends StatelessWidget { const GlobalSettingsView({ Key? key, @@ -191,6 +193,20 @@ class GlobalSettingsView extends StatelessWidget { AppearanceSettingsView.routeName); }, ), + if (Platform.isIOS) + const SizedBox( + height: 8, + ), + if (Platform.isIOS) + SettingsListButton( + iconAssetName: Assets.svg.circleAlert, + iconSize: 16, + title: "Delete account", + onPressed: () async { + await Navigator.of(context) + .pushNamed(DeleteAccountView.routeName); + }, + ), const SizedBox( height: 8, ), @@ -227,20 +243,6 @@ class GlobalSettingsView extends StatelessWidget { .pushNamed(SupportView.routeName); }, ), - if (Platform.isIOS) - const SizedBox( - height: 8, - ), - if (Platform.isIOS) - SettingsListButton( - iconAssetName: Assets.svg.circleAlert, - iconSize: 16, - title: "Delete account", - onPressed: () async { - await Navigator.of(context) - .pushNamed(DeleteAccountView.routeName); - }, - ), // TextButton( // style: Theme.of(context) // .textButtonTheme diff --git a/lib/utilities/delete_everything.dart b/lib/utilities/delete_everything.dart new file mode 100644 index 0000000000..497cd71f4e --- /dev/null +++ b/lib/utilities/delete_everything.dart @@ -0,0 +1,30 @@ +import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/utilities/logger.dart'; + +Future deleteEverything() async { + try { + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameAddressBook); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameDebugInfo); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameNodeModels); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePrimaryNodes); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameAllWalletsData); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameNotifications); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameWatchedTransactions); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameWatchedTrades); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTrades); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTradesV2); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTradeNotes); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTradeLookup); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameFavoriteWallets); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePrefs); + await DB.instance + .deleteBoxFromDisk(boxName: DB.boxNameWalletsToDeleteOnStart); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePriceCache); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameDBInfo); + await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTheme); + return true; + } catch (e, s) { + Logging.instance.log("$e $s", level: LogLevel.Error); + return false; + } +} From c2f3c77d2faf630b95da1ffcc34417e111ace649 Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 11 Oct 2022 15:28:56 -0600 Subject: [PATCH 46/75] ui bug fixes --- assets/svg/persona-easy-1.svg | 10 +--------- lib/pages/stack_privacy_calls.dart | 14 ++++++++------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/assets/svg/persona-easy-1.svg b/assets/svg/persona-easy-1.svg index 6cb3d4b625..1e15b8dab9 100644 --- a/assets/svg/persona-easy-1.svg +++ b/assets/svg/persona-easy-1.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart index 7c4015edfe..841dacf9e1 100644 --- a/lib/pages/stack_privacy_calls.dart +++ b/lib/pages/stack_privacy_calls.dart @@ -229,16 +229,17 @@ class _PrivacyToggleState extends ConsumerState { width: 140, height: 140, ), - const Center( + Center( child: Text( "Easy Crypto", - style: TextStyle( + style: STextStyles.label(context).copyWith( fontWeight: FontWeight.bold, ), )), - const Center( + Center( child: Text( "Recommended", + style: STextStyles.label(context), ), ), ], @@ -325,17 +326,18 @@ class _PrivacyToggleState extends ConsumerState { width: 140, height: 140, ), - const Center( + Center( child: Text( "Incognito", - style: TextStyle( + style: STextStyles.label(context).copyWith( fontWeight: FontWeight.bold, ), ), ), - const Center( + Center( child: Text( "Privacy conscious", + style: STextStyles.label(context), ), ), ], From da941376be1fd339f298f0950472b07549cb0610 Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 11 Oct 2022 16:36:41 -0600 Subject: [PATCH 47/75] fix monero and wownero sendall --- lib/services/coins/monero/monero_wallet.dart | 11 +++++++++++ lib/services/coins/wownero/wownero_wallet.dart | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 2488acc503..378295ceef 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -1447,6 +1447,16 @@ class MoneroWallet extends CoinServiceAPI { Future? awaitPendingTransaction; try { + // check for send all + bool isSendAll = false; + final balance = await availableBalance; + final satInDecimal = ((Decimal.fromInt(satoshiAmount) / + Decimal.fromInt(Constants.satsPerCoinWownero)) + .toDecimal() * + Decimal.fromInt(10000)); + if (satInDecimal == balance) { + isSendAll = true; + } Logging.instance .log("$toAddress $amount $args", level: LogLevel.Info); String amountToSend = moneroAmountToString(amount: amount * 10000); @@ -1454,6 +1464,7 @@ class MoneroWallet extends CoinServiceAPI { monero_output.Output output = monero_output.Output(walletBase!); output.address = toAddress; + output.sendAll = isSendAll; output.setCryptoAmount(amountToSend); List outputs = [output]; diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index b284c8733b..687c257647 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -1453,6 +1453,16 @@ class WowneroWallet extends CoinServiceAPI { Future? awaitPendingTransaction; try { + // check for send all + bool isSendAll = false; + final balance = await availableBalance; + final satInDecimal = ((Decimal.fromInt(satoshiAmount) / + Decimal.fromInt(Constants.satsPerCoinWownero)) + .toDecimal() * + Decimal.fromInt(1000)); + if (satInDecimal == balance) { + isSendAll = true; + } Logging.instance .log("$toAddress $amount $args", level: LogLevel.Info); String amountToSend = wowneroAmountToString(amount: amount * 1000); @@ -1460,6 +1470,7 @@ class WowneroWallet extends CoinServiceAPI { wownero_output.Output output = wownero_output.Output(walletBase!); output.address = toAddress; + output.sendAll = isSendAll; output.setCryptoAmount(amountToSend); List outputs = [output]; From deec0cdddbd350e07ad2a06227565974b67e69f3 Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 11 Oct 2022 19:54:50 -0600 Subject: [PATCH 48/75] fix monero and wownero fee estimates for sending. fix some ui bugs --- .../transaction_fee_selection_sheet.dart | 4 +- lib/services/coins/monero/monero_wallet.dart | 39 ++++++++++++++++--- .../coins/wownero/wownero_wallet.dart | 35 +++++++++++++++-- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart index ff26fe782d..05644cf701 100644 --- a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart +++ b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart @@ -410,8 +410,8 @@ class _TransactionFeeSelectionSheetState FutureBuilder( future: feeFor( coin: manager.coin, - feeRateType: FeeRateType.fast, - feeRate: feeObject!.fast, + feeRateType: FeeRateType.average, + feeRate: feeObject!.medium, amount: Format .decimalAmountToSatoshis( amount)), diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 378295ceef..b0ebac4e6b 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -1451,7 +1451,7 @@ class MoneroWallet extends CoinServiceAPI { bool isSendAll = false; final balance = await availableBalance; final satInDecimal = ((Decimal.fromInt(satoshiAmount) / - Decimal.fromInt(Constants.satsPerCoinWownero)) + Decimal.fromInt(Constants.satsPerCoinMonero)) .toDecimal() * Decimal.fromInt(10000)); if (satInDecimal == balance) { @@ -1471,7 +1471,9 @@ class MoneroWallet extends CoinServiceAPI { Object tmp = monero.createMoneroTransactionCreationCredentials( outputs: outputs, priority: feePriority); - awaitPendingTransaction = walletBase!.createTransaction(tmp); + await prepareSendMutex.protect(() async { + awaitPendingTransaction = walletBase!.createTransaction(tmp); + }); } catch (e, s) { Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", level: LogLevel.Warning); @@ -1510,30 +1512,57 @@ class MoneroWallet extends CoinServiceAPI { } } + Mutex prepareSendMutex = Mutex(); + Mutex estimateFeeMutex = Mutex(); + @override Future estimateFeeFor(int satoshiAmount, int feeRate) async { MoneroTransactionPriority? priority; + FeeRateType feeRateType = FeeRateType.slow; switch (feeRate) { case 1: priority = MoneroTransactionPriority.regular; + feeRateType = FeeRateType.slow; break; case 2: priority = MoneroTransactionPriority.medium; + feeRateType = FeeRateType.average; break; case 3: priority = MoneroTransactionPriority.fast; + feeRateType = FeeRateType.average; break; case 4: priority = MoneroTransactionPriority.fastest; + feeRateType = FeeRateType.fast; break; case 0: default: priority = MoneroTransactionPriority.slow; + feeRateType = FeeRateType.slow; break; } - final fee = - (walletBase?.calculateEstimatedFee(priority, satoshiAmount) ?? 0) ~/ - 10000; + var aprox; + + await estimateFeeMutex.protect(() async { + { + try { + aprox = (await prepareSend( + // This address is only used for getting an approximate fee, never for sending + address: + "8347huhmj6Ggzr1BpZPJAD5oa96ob5Fe8GtQdGZDYVVYVsCgtUNH3pEEzExDuaAVZdC16D4FkAb24J6wUfsKkcZtC8EPXB7", + satoshiAmount: satoshiAmount, + args: {"feeRate": feeRateType}))['fee']; + await Future.delayed(const Duration(milliseconds: 1000)); + } catch (e, s) { + Logging.instance.log("$feeRateType $e $s", level: LogLevel.Error); + aprox = -9999999999999999; + } + } + }); + + print("this is the aprox fee $aprox for $satoshiAmount"); + final fee = (aprox as int); return fee; } diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 687c257647..afdc491173 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -1477,7 +1477,9 @@ class WowneroWallet extends CoinServiceAPI { Object tmp = wownero.createWowneroTransactionCreationCredentials( outputs: outputs, priority: feePriority); - awaitPendingTransaction = walletBase!.createTransaction(tmp); + await prepareSendMutex.protect(() async { + awaitPendingTransaction = walletBase!.createTransaction(tmp); + }); } catch (e, s) { Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", level: LogLevel.Warning); @@ -1516,30 +1518,55 @@ class WowneroWallet extends CoinServiceAPI { } } + Mutex prepareSendMutex = Mutex(); + Mutex estimateFeeMutex = Mutex(); + @override Future estimateFeeFor(int satoshiAmount, int feeRate) async { MoneroTransactionPriority? priority; + FeeRateType feeRateType = FeeRateType.slow; switch (feeRate) { case 1: priority = MoneroTransactionPriority.regular; + feeRateType = FeeRateType.slow; break; case 2: priority = MoneroTransactionPriority.medium; + feeRateType = FeeRateType.average; break; case 3: priority = MoneroTransactionPriority.fast; + feeRateType = FeeRateType.average; break; case 4: priority = MoneroTransactionPriority.fastest; + feeRateType = FeeRateType.fast; break; case 0: default: priority = MoneroTransactionPriority.slow; + feeRateType = FeeRateType.slow; break; } - final fee = - (walletBase?.calculateEstimatedFee(priority, satoshiAmount) ?? 0) ~/ - 10000; + var aprox; + await estimateFeeMutex.protect(() async { + { + try { + aprox = (await prepareSend( + // This address is only used for getting an approximate fee, never for sending + address: + "WW3iVcnoAY6K9zNdU4qmdvZELefx6xZz4PMpTwUifRkvMQckyadhSPYMVPJhBdYE8P9c27fg9RPmVaWNFx1cDaj61HnetqBiy", + satoshiAmount: satoshiAmount, + args: {"feeRate": feeRateType}))['fee']; + await Future.delayed(const Duration(milliseconds: 100)); + } catch (e, s) { + aprox = -9999999999999999; + } + } + }); + + print("this is the aprox fee $aprox for $satoshiAmount"); + final fee = (aprox as int); return fee; } From 2f7585a5e028699e9bb36971f2debad89fff4644 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 12 Oct 2022 10:26:14 -0600 Subject: [PATCH 49/75] fix test --- lib/services/price.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/price.dart b/lib/services/price.dart index af25276960..924c77f747 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -79,7 +79,7 @@ class PriceAPI { } final externalCalls = Prefs.instance.externalCalls; - if (!externalCalls) { + if (!Logger.isTestEnv && !externalCalls) { Logging.instance.log("User does not want to use external calls", level: LogLevel.Info); return _cachedPrices; @@ -124,7 +124,7 @@ class PriceAPI { static Future?> availableBaseCurrencies() async { final externalCalls = Prefs.instance.externalCalls; - if (!externalCalls) { + if (!Logger.isTestEnv && !externalCalls) { Logging.instance.log("User does not want to use external calls", level: LogLevel.Info); return null; From 764c66ee084e7c5f427bfd797aac4b604bd480e1 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 12 Oct 2022 18:09:17 -0600 Subject: [PATCH 50/75] fix ui bug where chosen fee field doesnt show the updated user fee --- lib/pages/send_view/send_view.dart | 6 ++ .../transaction_fee_selection_sheet.dart | 56 +++++++++++++++++++ .../coins/wownero/wownero_wallet.dart | 2 +- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 25264e19c1..17dd9219db 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -1319,6 +1319,12 @@ class _SendViewState extends ConsumerState { cryptoAmountController .text) ?? Decimal.zero, + updateChosen: (String fee) { + setState(() { + _calculateFeesFuture = + Future(() => fee); + }); + }, ), ); }, diff --git a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart index 05644cf701..982086d3c9 100644 --- a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart +++ b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart @@ -32,10 +32,12 @@ class TransactionFeeSelectionSheet extends ConsumerStatefulWidget { Key? key, required this.walletId, required this.amount, + required this.updateChosen, }) : super(key: key); final String walletId; final Decimal amount; + final Function updateChosen; @override ConsumerState createState() => @@ -223,6 +225,10 @@ class _TransactionFeeSelectionSheetState ref.read(feeRateTypeStateProvider.state).state = FeeRateType.fast; } + String? fee = getAmount(FeeRateType.fast); + if (fee != null) { + widget.updateChosen(fee); + } Navigator.of(context).pop(); }, child: Container( @@ -352,6 +358,10 @@ class _TransactionFeeSelectionSheetState ref.read(feeRateTypeStateProvider.state).state = FeeRateType.average; } + String? fee = getAmount(FeeRateType.average); + if (fee != null) { + widget.updateChosen(fee); + } Navigator.of(context).pop(); }, child: Container( @@ -479,6 +489,11 @@ class _TransactionFeeSelectionSheetState ref.read(feeRateTypeStateProvider.state).state = FeeRateType.slow; } + String? fee = getAmount(FeeRateType.slow); + print("fee $fee"); + if (fee != null) { + widget.updateChosen(fee); + } Navigator.of(context).pop(); }, child: Container( @@ -608,4 +623,45 @@ class _TransactionFeeSelectionSheetState ), ); } + + String? getAmount(FeeRateType feeRateType) { + try { + print(feeRateType); + var amount = Format.decimalAmountToSatoshis(this.amount); + print(amount); + print(ref.read(feeSheetSessionCacheProvider).fast); + print(ref.read(feeSheetSessionCacheProvider).average); + print(ref.read(feeSheetSessionCacheProvider).slow); + switch (feeRateType) { + case FeeRateType.fast: + if (ref.read(feeSheetSessionCacheProvider).fast[amount] != null) { + return (ref.read(feeSheetSessionCacheProvider).fast[amount] + as Decimal) + .toString(); + } + return null; + + case FeeRateType.average: + if (ref.read(feeSheetSessionCacheProvider).average[amount] != null) { + return (ref.read(feeSheetSessionCacheProvider).average[amount] + as Decimal) + .toString(); + } + return null; + + case FeeRateType.slow: + print(ref.read(feeSheetSessionCacheProvider).slow); + print(ref.read(feeSheetSessionCacheProvider).slow[amount]); + if (ref.read(feeSheetSessionCacheProvider).slow[amount] != null) { + return (ref.read(feeSheetSessionCacheProvider).slow[amount] + as Decimal) + .toString(); + } + return null; + } + } catch (e, s) { + print("$e $s"); + return null; + } + } } diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index afdc491173..d3aba5bbbf 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -1558,7 +1558,7 @@ class WowneroWallet extends CoinServiceAPI { "WW3iVcnoAY6K9zNdU4qmdvZELefx6xZz4PMpTwUifRkvMQckyadhSPYMVPJhBdYE8P9c27fg9RPmVaWNFx1cDaj61HnetqBiy", satoshiAmount: satoshiAmount, args: {"feeRate": feeRateType}))['fee']; - await Future.delayed(const Duration(milliseconds: 100)); + await Future.delayed(const Duration(milliseconds: 500)); } catch (e, s) { aprox = -9999999999999999; } From 3c29b9acdf752aa3f4d7b03c5090d794ab2d7003 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 13 Oct 2022 13:01:49 -0600 Subject: [PATCH 51/75] temp fix for firo infinite fee estimate --- lib/services/coins/firo/firo_wallet.dart | 80 ++++++++++++++++-------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index aafa5e6f2a..3a925f0156 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -4493,6 +4493,7 @@ class FiroWallet extends CoinServiceAPI { } } + // TODO: investigate the bug here where chosen is null, conditions, given one mint spendVal += chosen!.amount; coinsToSpend.insert(coinsToSpend.length, chosen); } @@ -4514,36 +4515,61 @@ class FiroWallet extends CoinServiceAPI { Future estimateJoinSplitFee( int spendAmount, ) async { - int fee; - int size; - - for (fee = 0;;) { - int currentRequired = spendAmount; - - var map = await getCoinsToJoinSplit(currentRequired); - if (map is bool && !map) { - return 0; - } - - List coinsToBeSpent = - map['coinsToSpend'] as List; - - // 1054 is constant part, mainly Schnorr and Range proofs, 2560 is for each sigma/aux data - // 179 other parts of tx, assuming 1 utxo and 1 jmint - size = 1054 + 2560 * coinsToBeSpent.length + 180; - // uint64_t feeNeeded = GetMinimumFee(size, DEFAULT_TX_CONFIRM_TARGET); - int feeNeeded = - size; //TODO(Levon) temporary, use real estimation methods here - - if (fee >= feeNeeded) { - break; - } - - fee = feeNeeded; + var lelantusEntry = await _getLelantusEntry(); + final balance = await availableBalance; + int spendAmount = + (balance * Decimal.fromInt(Constants.satsPerCoin)).toBigInt().toInt(); + if (spendAmount == 0 || lelantusEntry.isEmpty) { + return LelantusFeeData(0, 0, []).fee; } + ReceivePort receivePort = await getIsolate({ + "function": "estimateJoinSplit", + "spendAmount": spendAmount, + "subtractFeeFromAmount": true, + "lelantusEntries": lelantusEntry, + "coin": coin, + }); - return fee; + final message = await receivePort.first; + if (message is String) { + Logging.instance.log("this is a string", level: LogLevel.Error); + stop(receivePort); + throw Exception("_fetchMaxFee isolate failed"); + } + stop(receivePort); + Logging.instance.log('Closing estimateJoinSplit!', level: LogLevel.Info); + return (message as LelantusFeeData).fee; } + // int fee; + // int size; + // + // for (fee = 0;;) { + // int currentRequired = spendAmount; + // + // TODO: investigate the bug here + // var map = await getCoinsToJoinSplit(currentRequired); + // if (map is bool && !map) { + // return 0; + // } + // + // List coinsToBeSpent = + // map['coinsToSpend'] as List; + // + // // 1054 is constant part, mainly Schnorr and Range proofs, 2560 is for each sigma/aux data + // // 179 other parts of tx, assuming 1 utxo and 1 jmint + // size = 1054 + 2560 * coinsToBeSpent.length + 180; + // // uint64_t feeNeeded = GetMinimumFee(size, DEFAULT_TX_CONFIRM_TARGET); + // int feeNeeded = + // size; //TODO(Levon) temporary, use real estimation methods here + // + // if (fee >= feeNeeded) { + // break; + // } + // + // fee = feeNeeded; + // } + // + // return fee; @override Future estimateFeeFor(int satoshiAmount, int feeRate) async { From a30ee6f32980994e3b7384f7d59cb095d97dae09 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 13 Oct 2022 13:31:24 -0600 Subject: [PATCH 52/75] increase firo gap check to 50 from 20 --- .../restore_wallet_view.dart | 2 +- .../helpers/restore_create_backup.dart | 2 +- .../sub_widgets/restoring_wallet_card.dart | 19 ++++++++++++++----- .../wallet_network_settings_view.dart | 10 +++++++++- .../services/coins/firo/firo_wallet_test.dart | 4 ++-- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart index 68f116a959..def0724b5c 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart @@ -275,7 +275,7 @@ class _RestoreWalletViewState extends ConsumerState { // without using them await manager.recoverFromMnemonic( mnemonic: mnemonic, - maxUnusedAddressGap: 20, + maxUnusedAddressGap: widget.coin == Coin.firo ? 50 : 20, maxNumberOfIndexesToCheck: 1000, height: height, ); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index bf964fec42..9c75f36e6a 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -402,7 +402,7 @@ abstract class SWB { // without using them await manager.recoverFromMnemonic( mnemonic: mnemonic, - maxUnusedAddressGap: 20, + maxUnusedAddressGap: manager.coin == Coin.firo ? 50 : 20, maxNumberOfIndexesToCheck: 1000, height: restoreHeight, ); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart index 8f9890a6a9..55f0588d28 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart @@ -35,7 +35,8 @@ class _RestoringWalletCardState extends ConsumerState { case StackRestoringStatus.waiting: return SvgPicture.asset( Assets.svg.loader, - color: Theme.of(context).extension()!.buttonBackSecondary, + color: + Theme.of(context).extension()!.buttonBackSecondary, ); case StackRestoringStatus.restoring: return const LoadingIndicator(); @@ -95,7 +96,10 @@ class _RestoringWalletCardState extends ConsumerState { try { final mnemonicList = await manager.mnemonic; - const maxUnusedAddressGap = 20; + int maxUnusedAddressGap = 20; + if (coin == Coin.firo) { + maxUnusedAddressGap = 50; + } const maxNumberOfIndexesToCheck = 1000; if (mnemonicList.isEmpty) { @@ -155,14 +159,17 @@ class _RestoringWalletCardState extends ConsumerState { ? Container( height: 20, decoration: BoxDecoration( - color: Theme.of(context).extension()!.buttonBackSecondary, + color: Theme.of(context) + .extension()! + .buttonBackSecondary, borderRadius: BorderRadius.circular( 1000, ), ), child: RawMaterialButton( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - splashColor: Theme.of(context).extension()!.highlight, + splashColor: + Theme.of(context).extension()!.highlight, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 1000, @@ -187,7 +194,9 @@ class _RestoringWalletCardState extends ConsumerState { child: Text( "Show recovery phrase", style: STextStyles.infoSmall(context).copyWith( - color: Theme.of(context).extension()!.accentColorDark), + color: Theme.of(context) + .extension()! + .accentColorDark), ), ), ), diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart index c4045dc9b7..5b1c572141 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart @@ -77,7 +77,8 @@ class _WalletNetworkSettingsViewState Future _attemptRescan() async { if (!Platform.isLinux) Wakelock.enable(); - const int maxUnusedAddressGap = 20; + int maxUnusedAddressGap = 20; + const int maxNumberOfIndexesToCheck = 1000; showDialog( @@ -88,6 +89,13 @@ class _WalletNetworkSettingsViewState ); try { + if (ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .coin == + Coin.firo) { + maxUnusedAddressGap = 50; + } await ref .read(walletsChangeNotifierProvider) .getManager(widget.walletId) diff --git a/test/services/coins/firo/firo_wallet_test.dart b/test/services/coins/firo/firo_wallet_test.dart index b309044b8c..22a505adfe 100644 --- a/test/services/coins/firo/firo_wallet_test.dart +++ b/test/services/coins/firo/firo_wallet_test.dart @@ -3283,9 +3283,9 @@ void main() { "a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w", ]); - expect(await firo.maxFee, 1234); // ??? + expect(await firo.maxFee, 0); // ??? - expect(await firo.balanceMinusMaxFee, Decimal.parse("-0.00001234")); + expect(await firo.balanceMinusMaxFee, Decimal.parse("0")); }); test("wallet balance minus maxfee - wallet balance is not zero", () async { From 3bd0fca7378b71c13b0441626f16e32724f664de Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 13 Oct 2022 16:19:25 -0600 Subject: [PATCH 53/75] fix stack backup saving issues. android 10 still not working --- .../stack_backup_views/helpers/stack_file_system.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart index 3196938da5..e57c5493f6 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/stack_file_system.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; class StackFileSystem { Directory? rootPath; @@ -14,6 +15,9 @@ class StackFileSystem { final bool isDesktop = !(Platform.isAndroid || Platform.isIOS); Future prepareStorage() async { + if (Platform.isAndroid) { + await Permission.storage.request(); + } rootPath = (await getApplicationDocumentsDirectory()); debugPrint(rootPath!.absolute.toString()); if (Platform.isAndroid) { @@ -22,13 +26,13 @@ class StackFileSystem { debugPrint(rootPath!.absolute.toString()); Directory sampleFolder = - Directory('${rootPath!.path}/Documents/Stack_backups'); + Directory('${rootPath!.path}Documents/Stack_backups'); if (Platform.isIOS) { sampleFolder = Directory(rootPath!.path); } try { if (!sampleFolder.existsSync()) { - sampleFolder.createSync(); + sampleFolder.createSync(recursive: true); } } catch (e, s) { debugPrint("$e $s"); @@ -65,8 +69,7 @@ class StackFileSystem { result = await FilePicker.platform.pickFiles( dialogTitle: "Load backup file", initialDirectory: startPath!.path, - type: FileType.custom, - allowedExtensions: ['bin'], + type: FileType.any, allowCompression: false, lockParentWindow: true, ); From 907f55a418afd2dcb19a31ac9ce53c09d0b3f16e Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 14 Oct 2022 11:30:36 -0600 Subject: [PATCH 54/75] fixed rate currency selection ui update fix --- lib/models/exchange/exchange_form_state.dart | 24 ++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart index 892149f3f5..4568dc6c93 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/currency.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -41,12 +42,24 @@ class ExchangeFormState extends ChangeNotifier { Currency? _from; Currency? _to; + @override + String toString() { + return 'ExchangeFormState: {_exchange: $_exchange, _exchangeType: $_exchangeType, reversed: $reversed, fromAmount: $fromAmount, toAmount: $toAmount, minAmount: $minAmount, maxAmount: $maxAmount, rate: $rate, estimate: $estimate, _market: $_market, _from: $_from, _to: $_to, _onError: $_onError}'; + } + String? get fromTicker { switch (exchangeType) { case ExchangeRateType.estimated: return _from?.ticker; case ExchangeRateType.fixed: - return _market?.from; + switch (exchange?.name) { + case SimpleSwapExchange.exchangeName: + return _from?.ticker; + case ChangeNowExchange.exchangeName: + return market?.from; + default: + return null; + } } } @@ -55,7 +68,14 @@ class ExchangeFormState extends ChangeNotifier { case ExchangeRateType.estimated: return _to?.ticker; case ExchangeRateType.fixed: - return _market?.to; + switch (exchange?.name) { + case SimpleSwapExchange.exchangeName: + return _to?.ticker; + case ChangeNowExchange.exchangeName: + return market?.to; + default: + return null; + } } } From b7d8e5db637b16471608ac9b968465ecb456ea57 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 14 Oct 2022 11:41:10 -0600 Subject: [PATCH 55/75] exchange swap currency fix --- lib/models/exchange/exchange_form_state.dart | 28 +++++++------ lib/pages/exchange_view/exchange_form.dart | 41 ++++++++------------ 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart index 4568dc6c93..72d84e2a94 100644 --- a/lib/models/exchange/exchange_form_state.dart +++ b/lib/models/exchange/exchange_form_state.dart @@ -395,24 +395,22 @@ class ExchangeFormState extends ChangeNotifier { minAmount = null; maxAmount = null; - switch (exchangeType) { - case ExchangeRateType.estimated: - final Currency? newTo = from; - final Currency? newFrom = to; + if (exchangeType == ExchangeRateType.fixed && + exchange?.name == ChangeNowExchange.exchangeName) { + await updateMarket(market, false); + } else { + final Currency? newTo = from; + final Currency? newFrom = to; - _to = newTo; - _from = newFrom; + _to = newTo; + _from = newFrom; - await updateRanges(shouldNotifyListeners: false); + await updateRanges(shouldNotifyListeners: false); - await updateEstimate( - shouldNotifyListeners: false, - reversed: reversed, - ); - break; - case ExchangeRateType.fixed: - await updateMarket(market, false); - break; + await updateEstimate( + shouldNotifyListeners: false, + reversed: reversed, + ); } notifyListeners(); diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 4085ed4017..0451709525 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -294,33 +294,24 @@ class _ExchangeFormState extends ConsumerState { ); if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - await ref.read(exchangeFormStateProvider).swap(); - } else { - switch (ref.read(currentExchangeNameStateProvider.state).state) { - case ChangeNowExchange.exchangeName: - final from = ref.read(exchangeFormStateProvider).fromTicker; - final to = ref.read(exchangeFormStateProvider).toTicker; - - if (to != null && from != null) { - final markets = ref - .read(availableChangeNowCurrenciesProvider) - .markets - .where((e) => e.from == to && e.to == from); + ExchangeRateType.fixed && + ref.read(exchangeFormStateProvider).exchange?.name == + ChangeNowExchange.exchangeName) { + final from = ref.read(exchangeFormStateProvider).fromTicker; + final to = ref.read(exchangeFormStateProvider).toTicker; + + if (to != null && from != null) { + final markets = ref + .read(availableChangeNowCurrenciesProvider) + .markets + .where((e) => e.from == to && e.to == from); - if (markets.isNotEmpty) { - await ref - .read(exchangeFormStateProvider) - .swap(market: markets.first); - } - } - break; - case SimpleSwapExchange.exchangeName: - await ref.read(exchangeFormStateProvider).swap(); - break; - default: - // + if (markets.isNotEmpty) { + await ref.read(exchangeFormStateProvider).swap(market: markets.first); + } } + } else { + await ref.read(exchangeFormStateProvider).swap(); } if (mounted) { Navigator.of(context).pop(); From cd40c9f0f9c29430cc523e8796796457d871c84e Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Fri, 14 Oct 2022 12:20:43 -0600 Subject: [PATCH 56/75] change build --- ios/Runner.xcodeproj/project.pbxproj | 14 +++++++------- pubspec.yaml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 98c80851c2..356e1aa285 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 46; objects = { /* Begin PBXBuildFile section */ @@ -451,7 +451,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 63; + CURRENT_PROJECT_VERSION = 72; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -505,7 +505,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.4.48; + MARKETING_VERSION = 1.5.2; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -635,7 +635,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 63; + CURRENT_PROJECT_VERSION = 72; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -689,7 +689,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.4.48; + MARKETING_VERSION = 1.5.2; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -711,7 +711,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 63; + CURRENT_PROJECT_VERSION = 72; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -765,7 +765,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.4.48; + MARKETING_VERSION = 1.5.2; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/pubspec.yaml b/pubspec.yaml index 8291433458..9d3205b721 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.5.1+71 +version: 1.5.2+72 environment: sdk: ">=2.17.0 <3.0.0" From 8f2567f340522c0a13f318660dfcdcd230ca43e9 Mon Sep 17 00:00:00 2001 From: julian Date: Fri, 14 Oct 2022 12:21:00 -0600 Subject: [PATCH 57/75] can exchange check updated --- lib/models/exchange/exchange_form_state.dart | 24 +++++++++---------- lib/pages/exchange_view/exchange_form.dart | 6 +++++ .../exchange/simpleswap/simpleswap_api.dart | 9 +++++-- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart index 72d84e2a94..645642b9d7 100644 --- a/lib/models/exchange/exchange_form_state.dart +++ b/lib/models/exchange/exchange_form_state.dart @@ -115,18 +115,18 @@ class ExchangeFormState extends ChangeNotifier { String get toAmountString => toAmount?.toStringAsFixed(8) ?? ""; bool get canExchange { - switch (exchangeType) { - case ExchangeRateType.estimated: - return fromAmount != null && - fromAmount != Decimal.zero && - toAmount != null && - rate != null && - warning.isEmpty; - case ExchangeRateType.fixed: - return _market != null && - fromAmount != null && - toAmount != null && - warning.isEmpty; + if (exchange?.name == ChangeNowExchange.exchangeName && + exchangeType == ExchangeRateType.fixed) { + return _market != null && + fromAmount != null && + toAmount != null && + warning.isEmpty; + } else { + return fromAmount != null && + fromAmount != Decimal.zero && + toAmount != null && + rate != null && + warning.isEmpty; } } diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 0451709525..d5fa29b621 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -24,6 +24,7 @@ import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dar import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; +import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; @@ -308,6 +309,11 @@ class _ExchangeFormState extends ConsumerState { if (markets.isNotEmpty) { await ref.read(exchangeFormStateProvider).swap(market: markets.first); + } else { + Logging.instance.log( + "swap to fixed rate market failed", + level: LogLevel.Warning, + ); } } } else { diff --git a/lib/services/exchange/simpleswap/simpleswap_api.dart b/lib/services/exchange/simpleswap/simpleswap_api.dart index addf732de2..3d626b2eb2 100644 --- a/lib/services/exchange/simpleswap/simpleswap_api.dart +++ b/lib/services/exchange/simpleswap/simpleswap_api.dart @@ -32,17 +32,22 @@ class SimpleSwapAPI { Future _makeGetRequest(Uri uri) async { final client = this.client ?? http.Client(); + int code = -1; try { final response = await client.get( uri, ); + code = response.statusCode; + final parsed = jsonDecode(response.body); return parsed; } catch (e, s) { - Logging.instance - .log("_makeRequest($uri) threw: $e\n$s", level: LogLevel.Error); + Logging.instance.log( + "_makeRequest($uri) HTTP:$code threw: $e\n$s", + level: LogLevel.Error, + ); rethrow; } } From b4b9e09a0e3c7796fef0bb9b9d124ebcc5ed75e4 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 15 Oct 2022 11:57:00 -0600 Subject: [PATCH 58/75] firo mint notification description --- lib/services/coins/firo/firo_wallet.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 3a925f0156..af3d93445a 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -2040,7 +2040,8 @@ class FiroWallet extends CoinServiceAPI { case "Sent": unawaited( NotificationApi.showNotification( - title: "Outgoing transaction", + title: + tx.subType == "mint" ? "Anonymizing" : "Outgoing transaction", body: walletName, walletId: walletId, iconAssetName: Assets.svg.iconFor(coin: coin), @@ -2076,7 +2077,9 @@ class FiroWallet extends CoinServiceAPI { } else if (tx.txType == "Sent" && tx.subType == "join") { unawaited( NotificationApi.showNotification( - title: "Outgoing transaction confirmed", + title: tx.subType == "mint" + ? "Anonymized" + : "Outgoing transaction confirmed", body: walletName, walletId: walletId, iconAssetName: Assets.svg.iconFor(coin: coin), From 5f804b3e69a8e41d2dfc1a9d752048374700f0bf Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 15 Oct 2022 14:25:14 -0600 Subject: [PATCH 59/75] hide fiat currency picker when in incognito and calls are broken --- lib/pages/send_view/send_view.dart | 210 +++++++++++++++-------------- lib/pages/stack_privacy_calls.dart | 8 +- 2 files changed, 110 insertions(+), 108 deletions(-) diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 17dd9219db..5e689a580c 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -27,6 +27,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/animated_text.dart'; @@ -1107,115 +1108,120 @@ class _SendViewState extends ConsumerState { ), ), ), - const SizedBox( - height: 8, - ), - TextField( - style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - key: const Key("amountInputFieldFiatTextFieldKey"), - controller: baseAmountController, - focusNode: _baseFocus, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, + if (Prefs.instance.externalCalls) + const SizedBox( + height: 8, ), - textAlign: TextAlign.right, - inputFormatters: [ - // regex to validate a fiat amount with 2 decimal places - TextInputFormatter.withFunction((oldValue, - newValue) => - RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$') - .hasMatch(newValue.text) - ? newValue - : oldValue), - ], - onChanged: (baseAmountString) { - if (baseAmountString.isNotEmpty && - baseAmountString != "." && - baseAmountString != ",") { - final baseAmount = baseAmountString.contains(",") - ? Decimal.parse( - baseAmountString.replaceFirst(",", ".")) - : Decimal.parse(baseAmountString); - - var _price = ref - .read(priceAnd24hChangeNotifierProvider) - .getPrice(coin) - .item1; - - if (_price == Decimal.zero) { - _amountToSend = Decimal.zero; - } else { - _amountToSend = baseAmount <= Decimal.zero - ? Decimal.zero - : (baseAmount / _price).toDecimal( - scaleOnInfinitePrecision: - Constants.decimalPlaces); - } - if (_cachedAmountToSend != null && - _cachedAmountToSend == _amountToSend) { - return; - } - _cachedAmountToSend = _amountToSend; - Logging.instance.log( - "it changed $_amountToSend $_cachedAmountToSend", - level: LogLevel.Info); - - final amountString = - Format.localizedStringAsFixed( - value: _amountToSend!, - locale: ref - .read(localeServiceChangeNotifierProvider) - .locale, - decimalPlaces: Constants.decimalPlaces, - ); - - _cryptoAmountChangeLock = true; - cryptoAmountController.text = amountString; - _cryptoAmountChangeLock = false; - } else { - _amountToSend = Decimal.zero; - _cryptoAmountChangeLock = true; - cryptoAmountController.text = ""; - _cryptoAmountChangeLock = false; - } - // setState(() { - // _calculateFeesFuture = calculateFees( - // Format.decimalAmountToSatoshis( - // _amountToSend!)); - // }); - _updatePreviewButtonState(_address, _amountToSend); - }, - decoration: InputDecoration( - contentPadding: const EdgeInsets.only( - top: 12, - right: 12, + if (Prefs.instance.externalCalls) + TextField( + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, ), - hintText: "0", - hintStyle: STextStyles.fieldLabel(context).copyWith( - fontSize: 14, + key: const Key("amountInputFieldFiatTextFieldKey"), + controller: baseAmountController, + focusNode: _baseFocus, + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: true, ), - prefixIcon: FittedBox( - fit: BoxFit.scaleDown, - child: Padding( - padding: const EdgeInsets.all(12), - child: Text( - ref.watch(prefsChangeNotifierProvider - .select((value) => value.currency)), - style: STextStyles.smallMed14(context) - .copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark), + textAlign: TextAlign.right, + inputFormatters: [ + // regex to validate a fiat amount with 2 decimal places + TextInputFormatter.withFunction((oldValue, + newValue) => + RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$') + .hasMatch(newValue.text) + ? newValue + : oldValue), + ], + onChanged: (baseAmountString) { + if (baseAmountString.isNotEmpty && + baseAmountString != "." && + baseAmountString != ",") { + final baseAmount = baseAmountString + .contains(",") + ? Decimal.parse( + baseAmountString.replaceFirst(",", ".")) + : Decimal.parse(baseAmountString); + + var _price = ref + .read(priceAnd24hChangeNotifierProvider) + .getPrice(coin) + .item1; + + if (_price == Decimal.zero) { + _amountToSend = Decimal.zero; + } else { + _amountToSend = baseAmount <= Decimal.zero + ? Decimal.zero + : (baseAmount / _price).toDecimal( + scaleOnInfinitePrecision: + Constants.decimalPlaces); + } + if (_cachedAmountToSend != null && + _cachedAmountToSend == _amountToSend) { + return; + } + _cachedAmountToSend = _amountToSend; + Logging.instance.log( + "it changed $_amountToSend $_cachedAmountToSend", + level: LogLevel.Info); + + final amountString = + Format.localizedStringAsFixed( + value: _amountToSend!, + locale: ref + .read(localeServiceChangeNotifierProvider) + .locale, + decimalPlaces: Constants.decimalPlaces, + ); + + _cryptoAmountChangeLock = true; + cryptoAmountController.text = amountString; + _cryptoAmountChangeLock = false; + } else { + _amountToSend = Decimal.zero; + _cryptoAmountChangeLock = true; + cryptoAmountController.text = ""; + _cryptoAmountChangeLock = false; + } + // setState(() { + // _calculateFeesFuture = calculateFees( + // Format.decimalAmountToSatoshis( + // _amountToSend!)); + // }); + _updatePreviewButtonState( + _address, _amountToSend); + }, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + top: 12, + right: 12, + ), + hintText: "0", + hintStyle: + STextStyles.fieldLabel(context).copyWith( + fontSize: 14, + ), + prefixIcon: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + ref.watch(prefsChangeNotifierProvider + .select((value) => value.currency)), + style: STextStyles.smallMed14(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), ), ), ), ), - ), const SizedBox( height: 12, ), diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart index 841dacf9e1..6065c90dc7 100644 --- a/lib/pages/stack_privacy_calls.dart +++ b/lib/pages/stack_privacy_calls.dart @@ -408,12 +408,11 @@ class ContinueButton extends StatelessWidget { .extension()! .getPrimaryEnabledButtonColor(context), onPressed: () { - print("Output of isEasy:"); - print(isEasy); - Prefs.instance.externalCalls = isEasy; if (!isSettings) { Navigator.of(context).pushNamed(CreatePinView.routeName); + } else { + Navigator.pop(context); } }, child: Text( @@ -429,9 +428,6 @@ class ContinueButton extends StatelessWidget { .extension()! .getPrimaryEnabledButtonColor(context), onPressed: () { - print("Output of isEasy:"); - print(isEasy); - Prefs.instance.externalCalls = isEasy; if (!isSettings) { From 49c252b7f153f9d5e3a067bded70ee2acdb6c11e Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 15 Oct 2022 14:32:17 -0600 Subject: [PATCH 60/75] watch variable so page can be refreshed correctly --- .../sub_widgets/wallet_list_item.dart | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart b/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart index 842b1f4c4a..e3fd5d614a 100644 --- a/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart +++ b/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart @@ -7,6 +7,7 @@ import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/format.dart'; +import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -67,6 +68,8 @@ class WalletListItem extends ConsumerWidget { builder: (_, ref, __) { final tuple = ref.watch(priceAnd24hChangeNotifierProvider .select((value) => value.getPrice(coin))); + final calls = + ref.watch(prefsChangeNotifierProvider).externalCalls; final priceString = Format.localizedStringAsFixed( value: tuple.item1, @@ -100,10 +103,11 @@ class WalletListItem extends ConsumerWidget { style: STextStyles.titleBold12(context), ), const Spacer(), - Text( - "$priceString $currency/${coin.ticker}", - style: STextStyles.itemSubtitle(context), - ), + if (calls) + Text( + "$priceString $currency/${coin.ticker}", + style: STextStyles.itemSubtitle(context), + ), ], ), const SizedBox( @@ -116,12 +120,13 @@ class WalletListItem extends ConsumerWidget { style: STextStyles.itemSubtitle(context), ), const Spacer(), - Text( - "${percentChange.toStringAsFixed(2)}%", - style: STextStyles.itemSubtitle(context).copyWith( - color: percentChangedColor, + if (calls) + Text( + "${percentChange.toStringAsFixed(2)}%", + style: STextStyles.itemSubtitle(context).copyWith( + color: percentChangedColor, + ), ), - ), ], ), ], From c7d4c4fc70aaf117abbeceb4f66632479e71e24b Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 15 Oct 2022 14:49:52 -0600 Subject: [PATCH 61/75] fix null error bug, firo restores faster again --- lib/services/coins/firo/firo_wallet.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 3a925f0156..2b423a818d 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -753,10 +753,11 @@ Future?> getInitialAnonymitySetCache( ); final response = jsonDecode(anonSetResult.body.toString()); + Logging.instance.log(response, level: LogLevel.Info); if (response['status'] == 'success') { final anonResponse = jsonDecode(response['result'] as String); - final setData = Map.from(anonResponse["result"] as Map); + final setData = Map.from(anonResponse as Map); return setData; } else { return null; From ac849c71fceee09da5e4c5235d219a76fc542085 Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 15 Oct 2022 14:54:00 -0600 Subject: [PATCH 62/75] should fix #109, for coins that dont exist --- .../helpers/restore_create_backup.dart | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index 9c75f36e6a..84dd2b5d98 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -997,10 +997,14 @@ abstract class SWB { // primary nodes if (primaryNodes != null) { for (var node in primaryNodes) { - await nodeService.setPrimaryNodeFor( - coin: coinFromPrettyName(node['coinName'] as String), - node: nodeService.getNodeById(id: node['id'] as String)!, - ); + try { + await nodeService.setPrimaryNodeFor( + coin: coinFromPrettyName(node['coinName'] as String), + node: nodeService.getNodeById(id: node['id'] as String)!, + ); + } catch (e, s) { + Logging.instance.log("$e $s", level: LogLevel.Error); + } } } await nodeService.updateDefaults(); @@ -1175,10 +1179,14 @@ abstract class SWB { } if (primaryNodes != null) { for (var node in primaryNodes) { - await nodeService.setPrimaryNodeFor( - coin: coinFromPrettyName(node['coinName'] as String), - node: nodeService.getNodeById(id: node['id'] as String)!, - ); + try { + await nodeService.setPrimaryNodeFor( + coin: coinFromPrettyName(node['coinName'] as String), + node: nodeService.getNodeById(id: node['id'] as String)!, + ); + } catch (e, s) { + Logging.instance.log("$e $s", level: LogLevel.Error); + } } } await nodeService.updateDefaults(); From d26ea4c284b199cd10c7b13ebde9e2933fe4f5b2 Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 15 Oct 2022 17:56:59 -0600 Subject: [PATCH 63/75] hide exchange options until amount is entered --- lib/pages/exchange_view/exchange_form.dart | 32 ++++++++++++---------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index d5fa29b621..785fae49e1 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -1308,20 +1308,24 @@ class _ExchangeFormState extends ConsumerState { RateTypeToggle( onChanged: onRateTypeChanged, ), - const SizedBox( - height: 8, - ), - ExchangeProviderOptions( - from: ref.watch(exchangeFormStateProvider).fromTicker, - to: ref.watch(exchangeFormStateProvider).toTicker, - fromAmount: ref.watch(exchangeFormStateProvider).fromAmount, - toAmount: ref.watch(exchangeFormStateProvider).toAmount, - fixedRate: ref.watch(prefsChangeNotifierProvider - .select((value) => value.exchangeRateType)) == - ExchangeRateType.fixed, - reversed: ref.watch( - exchangeFormStateProvider.select((value) => value.reversed)), - ), + if (ref.read(exchangeFormStateProvider).fromAmount != null && + ref.read(exchangeFormStateProvider).fromAmount != Decimal.zero) + const SizedBox( + height: 8, + ), + if (ref.read(exchangeFormStateProvider).fromAmount != null && + ref.read(exchangeFormStateProvider).fromAmount != Decimal.zero) + ExchangeProviderOptions( + from: ref.watch(exchangeFormStateProvider).fromTicker, + to: ref.watch(exchangeFormStateProvider).toTicker, + fromAmount: ref.watch(exchangeFormStateProvider).fromAmount, + toAmount: ref.watch(exchangeFormStateProvider).toAmount, + fixedRate: ref.watch(prefsChangeNotifierProvider + .select((value) => value.exchangeRateType)) == + ExchangeRateType.fixed, + reversed: ref.watch( + exchangeFormStateProvider.select((value) => value.reversed)), + ), const SizedBox( height: 12, ), From 3dd27d9b5313c945b005c47ab58101f76365cdbe Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 15 Oct 2022 18:03:58 -0600 Subject: [PATCH 64/75] fix bug for privacy settings --- lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index d01e0e2a77..04c48ae498 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -231,7 +231,7 @@ class _MaterialAppWithThemeState extends ConsumerState // unawaited(_nodeService.updateCommunityNodes()); // run without awaiting - if (Constants.enableExchange) { + if (Constants.enableExchange && _prefs.externalCalls) { unawaited(ExchangeDataLoadingService().loadAll(ref)); } From 45ebc0707d7ae8e18025c46cf603c0f400c05585 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Sat, 15 Oct 2022 20:00:33 -0600 Subject: [PATCH 65/75] change build --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 9d3205b721..3bc356ec5a 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.5.2+72 +version: 1.5.3+73 environment: sdk: ">=2.17.0 <3.0.0" From dd00e766230dd76720ee5061f51d7deb3d9156da Mon Sep 17 00:00:00 2001 From: Marco Date: Sat, 15 Oct 2022 20:47:59 -0600 Subject: [PATCH 66/75] red warnings not on 0 for exchange --- lib/models/exchange/exchange_form_state.dart | 8 ++++++-- lib/pages/exchange_view/exchange_form.dart | 18 ++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart index 645642b9d7..2e237dd1c5 100644 --- a/lib/models/exchange/exchange_form_state.dart +++ b/lib/models/exchange/exchange_form_state.dart @@ -92,7 +92,9 @@ class ExchangeFormState extends ChangeNotifier { String get warning { if (reversed) { if (toTicker != null && toAmount != null) { - if (minAmount != null && toAmount! < minAmount!) { + if (minAmount != null && + toAmount! < minAmount! && + toAmount! > Decimal.zero) { return "Minimum amount ${minAmount!.toString()} ${toTicker!.toUpperCase()}"; } else if (maxAmount != null && toAmount! > maxAmount!) { return "Maximum amount ${maxAmount!.toString()} ${toTicker!.toUpperCase()}"; @@ -100,7 +102,9 @@ class ExchangeFormState extends ChangeNotifier { } } else { if (fromTicker != null && fromAmount != null) { - if (minAmount != null && fromAmount! < minAmount!) { + if (minAmount != null && + fromAmount! < minAmount! && + fromAmount! > Decimal.zero) { return "Minimum amount ${minAmount!.toString()} ${fromTicker!.toUpperCase()}"; } else if (maxAmount != null && fromAmount! > maxAmount!) { return "Maximum amount ${maxAmount!.toString()} ${fromTicker!.toUpperCase()}"; diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index 785fae49e1..7b04f90b37 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -572,14 +572,16 @@ class _ExchangeFormState extends ConsumerState { if (mounted) { Navigator.of(context).pop(); } - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Estimated rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last estimated rate pair.", - context: context, - ), - ); + if (!(fromTicker == "-" || toTicker == "-")) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: + "Estimated rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last estimated rate pair.", + context: context, + ), + ); + } break; case ExchangeRateType.fixed: if (!(toTicker == "-" || fromTicker == "-")) { From fd9b09d37a15cc5293ab518aa878ef48ca70ddfc Mon Sep 17 00:00:00 2001 From: Marco Date: Sun, 16 Oct 2022 12:54:52 -0600 Subject: [PATCH 67/75] show the balances when toggle switching --- .../wallet_balance_toggle_sheet.dart | 171 ++++++++++++++---- 1 file changed, 135 insertions(+), 36 deletions(-) diff --git a/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart b/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart index afdac6d8ec..c9ff643932 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart @@ -1,3 +1,4 @@ +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/providers.dart'; @@ -8,6 +9,8 @@ import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; + class WalletBalanceToggleSheet extends ConsumerWidget { const WalletBalanceToggleSheet({ Key? key, @@ -23,6 +26,22 @@ class WalletBalanceToggleSheet extends ConsumerWidget { final coin = ref.watch(walletsChangeNotifierProvider .select((value) => value.getManager(walletId).coin)); + Future? totalBalanceFuture; + Future? availableBalanceFuture; + if (coin == Coin.firo || coin == Coin.firoTestNet) { + final firoWallet = ref + .watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId))) + .wallet as FiroWallet; + totalBalanceFuture = firoWallet.availablePublicBalance(); + availableBalanceFuture = firoWallet.availablePrivateBalance(); + } else { + final wallet = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId))); + totalBalanceFuture = wallet.totalBalance; + availableBalanceFuture = wallet.availableBalance; + } + return Container( decoration: BoxDecoration( color: Theme.of(context).extension()!.popupBG, @@ -125,15 +144,35 @@ class WalletBalanceToggleSheet extends ConsumerWidget { const SizedBox( height: 2, ), - Text( - "Current spendable (unlocked) balance", - style: - STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), + FutureBuilder( + future: availableBalanceFuture, + builder: (fbContext, + AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData && + snapshot.data != null) { + return Text( + "${snapshot.data!}", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } else { + return Text( + "", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } + }), ], ), if (coin == Coin.firo || coin == Coin.firoTestNet) @@ -147,15 +186,35 @@ class WalletBalanceToggleSheet extends ConsumerWidget { const SizedBox( height: 2, ), - Text( - "Current private spendable (unlocked) balance", - style: - STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), + FutureBuilder( + future: availableBalanceFuture, + builder: (fbContext, + AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData && + snapshot.data != null) { + return Text( + "${snapshot.data!}", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } else { + return Text( + "", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } + }), ], ), ], @@ -219,15 +278,35 @@ class WalletBalanceToggleSheet extends ConsumerWidget { const SizedBox( height: 2, ), - Text( - "Total wallet balance", - style: - STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), + FutureBuilder( + future: totalBalanceFuture, + builder: (fbContext, + AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData && + snapshot.data != null) { + return Text( + "${snapshot.data!}", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } else { + return Text( + "", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } + }), ], ), if (coin == Coin.firo || coin == Coin.firoTestNet) @@ -241,15 +320,35 @@ class WalletBalanceToggleSheet extends ConsumerWidget { const SizedBox( height: 2, ), - Text( - "Current public spendable (unlocked) balance", - style: - STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), + FutureBuilder( + future: totalBalanceFuture, + builder: (fbContext, + AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData && + snapshot.data != null) { + return Text( + "${snapshot.data!}", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } else { + return Text( + "", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ); + } + }), ], ), ], From cd14f899618ca04f12cb60055f8ade40afc775cf Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Sun, 16 Oct 2022 17:10:54 -0600 Subject: [PATCH 68/75] change build --- ios/Runner.xcodeproj/project.pbxproj | 12 ++++++------ pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 356e1aa285..ea5b8b126b 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -451,7 +451,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 72; + CURRENT_PROJECT_VERSION = 74; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -505,7 +505,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.5.2; + MARKETING_VERSION = 1.5.4; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -635,7 +635,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 72; + CURRENT_PROJECT_VERSION = 74; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -689,7 +689,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.5.2; + MARKETING_VERSION = 1.5.4; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -711,7 +711,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 72; + CURRENT_PROJECT_VERSION = 74; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -765,7 +765,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.5.2; + MARKETING_VERSION = 1.5.4; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/pubspec.yaml b/pubspec.yaml index 3bc356ec5a..a34e300a93 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.5.3+73 +version: 1.5.4+74 environment: sdk: ">=2.17.0 <3.0.0" From 3a2042eb9268be56d93c53e392f9aa29953e8c83 Mon Sep 17 00:00:00 2001 From: Marco Date: Sun, 16 Oct 2022 17:40:33 -0600 Subject: [PATCH 69/75] quick fix for isar async write problem --- lib/utilities/logger.dart | 69 +++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/lib/utilities/logger.dart b/lib/utilities/logger.dart index 0771d5350c..6a5083cbcc 100644 --- a/lib/utilities/logger.dart +++ b/lib/utilities/logger.dart @@ -12,8 +12,7 @@ import 'package:stackwallet/utilities/enums/log_level_enum.dart'; export 'enums/log_level_enum.dart'; class Logging { - static const isArmLinux = - bool.fromEnvironment("IS_ARM"); + static const isArmLinux = bool.fromEnvironment("IS_ARM"); static final isTestEnv = Platform.environment["FLUTTER_TEST"] == "true"; Logging._(); static final Logging _instance = Logging._(); @@ -45,41 +44,47 @@ class Logging { core.bool printToConsole = true, core.bool printFullLength = false, }) { - if (isTestEnv || isArmLinux) { - Logger.print(object, normalLength: !printFullLength); - return; - } - final now = core.DateTime.now().toUtc(); - final log = Log() - ..message = object.toString() - ..logLevel = level - ..timestampInMillisUTC = now.millisecondsSinceEpoch; - if (level == LogLevel.Error || level == LogLevel.Fatal) { - printFullLength = true; - } + try { + if (isTestEnv || isArmLinux) { + Logger.print(object, normalLength: !printFullLength); + return; + } + final now = core.DateTime.now().toUtc(); + final log = Log() + ..message = object.toString() + ..logLevel = level + ..timestampInMillisUTC = now.millisecondsSinceEpoch; + if (level == LogLevel.Error || level == LogLevel.Fatal) { + printFullLength = true; + } - isar!.writeTxnSync(() => log.id = isar!.logs.putSync(log)); + isar!.writeTxnSync(() => log.id = isar!.logs.putSync(log)); - if (printToConsole) { - final core.String logStr = "Log: ${log.toString()}"; - final core.int logLength = logStr.length; + if (printToConsole) { + final core.String logStr = "Log: ${log.toString()}"; + final core.int logLength = logStr.length; - if (!printFullLength || logLength <= defaultPrintLength) { - debugPrint(logStr); - } else { - core.int start = 0; - core.int endIndex = defaultPrintLength; - core.int tmpLogLength = logLength; - while (endIndex < logLength) { - debugPrint(logStr.substring(start, endIndex)); - endIndex += defaultPrintLength; - start += defaultPrintLength; - tmpLogLength -= defaultPrintLength; - } - if (tmpLogLength > 0) { - debugPrint(logStr.substring(start, logLength)); + if (!printFullLength || logLength <= defaultPrintLength) { + debugPrint(logStr); + } else { + core.int start = 0; + core.int endIndex = defaultPrintLength; + core.int tmpLogLength = logLength; + while (endIndex < logLength) { + debugPrint(logStr.substring(start, endIndex)); + endIndex += defaultPrintLength; + start += defaultPrintLength; + tmpLogLength -= defaultPrintLength; + } + if (tmpLogLength > 0) { + debugPrint(logStr.substring(start, logLength)); + } } } + } catch (e, s) { + print("problem trying to log"); + print("$e $s"); + Logger.print(object); } } } From d8fa93b06b0820247323a7f13a819ce9a6d153ac Mon Sep 17 00:00:00 2001 From: Marco Date: Sun, 16 Oct 2022 20:23:48 -0600 Subject: [PATCH 70/75] default settings --- lib/utilities/prefs.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index 7e10965506..cc818f9823 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -546,7 +546,7 @@ class Prefs extends ChangeNotifier { boxName: DB.boxNamePrefs, key: "startupWalletId") as String?; } - bool _externalCalls = false; + bool _externalCalls = true; bool get externalCalls => _externalCalls; From 1e2f71cb6e7f6f5e30bfb9b5c35d1f4ca2eb8d45 Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 17 Oct 2022 08:29:41 -0600 Subject: [PATCH 71/75] change build --- ios/Runner.xcodeproj/project.pbxproj | 12 ++++++------ pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index ea5b8b126b..c76f92b0e6 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -451,7 +451,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 74; + CURRENT_PROJECT_VERSION = 75; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -505,7 +505,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.5.4; + MARKETING_VERSION = 1.5.5; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -635,7 +635,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 74; + CURRENT_PROJECT_VERSION = 75; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -689,7 +689,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.5.4; + MARKETING_VERSION = 1.5.5; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -711,7 +711,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 74; + CURRENT_PROJECT_VERSION = 75; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -765,7 +765,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.5.4; + MARKETING_VERSION = 1.5.5; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/pubspec.yaml b/pubspec.yaml index a34e300a93..f21127507c 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.5.4+74 +version: 1.5.5+75 environment: sdk: ">=2.17.0 <3.0.0" From e984a7f09e8300c82446dc8f9459d63386696e8b Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 17 Oct 2022 09:21:45 -0600 Subject: [PATCH 72/75] change build --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index f21127507c..de8a71101a 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.5.5+75 +version: 1.5.6+76 environment: sdk: ">=2.17.0 <3.0.0" From a62d8f49a82c6696c461a17760566f090b483367 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 17 Oct 2022 13:42:10 -0600 Subject: [PATCH 73/75] load exchange data everytime on button or swipe to exchange screen --- lib/pages/home_view/home_view.dart | 13 ++++--- .../sub_widgets/home_view_button_bar.dart | 38 ++++--------------- lib/utilities/prefs.dart | 2 + 3 files changed, 16 insertions(+), 37 deletions(-) diff --git a/lib/pages/home_view/home_view.dart b/lib/pages/home_view/home_view.dart index 2bdcbace03..2e7c38202b 100644 --- a/lib/pages/home_view/home_view.dart +++ b/lib/pages/home_view/home_view.dart @@ -16,15 +16,13 @@ import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:stackwallet/utilities/logger.dart'; - -import 'package:stackwallet/utilities/prefs.dart'; - class HomeView extends ConsumerStatefulWidget { const HomeView({Key? key}) : super(key: key); @@ -45,7 +43,7 @@ class _HomeViewState extends ConsumerState { bool _exitEnabled = false; - final _cnLoadingService = ExchangeDataLoadingService(); + final _exchangeDataLoadingService = ExchangeDataLoadingService(); Future _onWillPop() async { // go to home view when tapping back on the main exchange view @@ -88,7 +86,7 @@ class _HomeViewState extends ConsumerState { // final externalCalls = Prefs.instance.externalCalls; if (externalCalls) { - _cnLoadingService.loadAll(ref); + _exchangeDataLoadingService.loadAll(ref); } else { Logging.instance.log("User does not want to use external calls", level: LogLevel.Info); @@ -290,6 +288,9 @@ class _HomeViewState extends ConsumerState { _ref.listen(homeViewPageIndexStateProvider, (previous, next) { if (next is int) { + if (next == 1) { + _loadCNData(); + } if (next >= 0 && next <= 1) { _pageController.animateToPage( next, diff --git a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart index e502d4537a..61a6c41859 100644 --- a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart +++ b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/providers.dart'; @@ -8,8 +6,6 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:stackwallet/utilities/prefs.dart'; - class HomeViewButtonBar extends ConsumerStatefulWidget { const HomeViewButtonBar({Key? key}) : super(key: key); @@ -18,8 +14,8 @@ class HomeViewButtonBar extends ConsumerStatefulWidget { } class _HomeViewButtonBarState extends ConsumerState { - final DateTime _lastRefreshed = DateTime.now(); - final Duration _refreshInterval = const Duration(hours: 1); + // final DateTime _lastRefreshed = DateTime.now(); + // final Duration _refreshInterval = const Duration(hours: 1); @override void initState() { @@ -104,34 +100,14 @@ class _HomeViewButtonBarState extends ConsumerState { if (selectedIndex != 1) { ref.read(homeViewPageIndexStateProvider.state).state = 1; } - DateTime now = DateTime.now(); - final _cnLoadingService = ExchangeDataLoadingService(); - final externalCalls = Prefs.instance.externalCalls; - if (!externalCalls) { + // DateTime now = DateTime.now(); + if (ref.read(prefsChangeNotifierProvider).externalCalls) { print("loading?"); - unawaited(_cnLoadingService.loadAll(ref)); - } - if (now.difference(_lastRefreshed) > _refreshInterval) { - // bool okPressed = false; - // showDialog( - // context: context, - // barrierDismissible: false, - // builder: (_) => const StackDialog( - // // builder: (_) => StackOkDialog( - // title: "Refreshing ChangeNOW data", - // message: "This may take a while", - // // onOkPressed: (value) { - // // if (value == "OK") { - // // okPressed = true; - // // } - // // }, - // ), - // ); await ExchangeDataLoadingService().loadAll(ref); - // if (!okPressed && mounted) { - // Navigator.of(context).pop(); - // } } + // if (now.difference(_lastRefreshed) > _refreshInterval) { + // await ExchangeDataLoadingService().loadAll(ref); + // } }, child: Text( "Exchange", diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index cc818f9823..0109755f7c 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -546,6 +546,8 @@ class Prefs extends ChangeNotifier { boxName: DB.boxNamePrefs, key: "startupWalletId") as String?; } + // incognito mode off by default + // allow external network calls such as exchange data and price info bool _externalCalls = true; bool get externalCalls => _externalCalls; From 78b6d096fe29164e887024cdaebeeb4524c20355 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 17 Oct 2022 14:15:25 -0600 Subject: [PATCH 74/75] only save incognito setting on explicit button press --- lib/pages/stack_privacy_calls.dart | 402 ++++++++++++++--------------- 1 file changed, 200 insertions(+), 202 deletions(-) diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart index 6065c90dc7..2aa2a5c8af 100644 --- a/lib/pages/stack_privacy_calls.dart +++ b/lib/pages/stack_privacy_calls.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; +import 'package:stackwallet/pages_desktop_specific/create_password/create_password_view.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -12,9 +12,6 @@ import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -import 'package:stackwallet/providers/global/prefs_provider.dart'; -import 'package:stackwallet/utilities/prefs.dart'; - class StackPrivacyCalls extends ConsumerStatefulWidget { const StackPrivacyCalls({ Key? key, @@ -31,19 +28,19 @@ class StackPrivacyCalls extends ConsumerStatefulWidget { class _StackPrivacyCalls extends ConsumerState { late final bool isDesktop; - bool isEasy = Prefs.instance.externalCalls; - final PageController _pageController = - PageController(initialPage: 0, keepPage: true); + late bool isEasy; + late bool infoToggle; @override void initState() { isDesktop = Util.isDesktop; + isEasy = ref.read(prefsChangeNotifierProvider).externalCalls; + infoToggle = isEasy; super.initState(); } @override void dispose() { - _pageController.dispose(); super.dispose(); } @@ -59,135 +56,161 @@ class _StackPrivacyCalls extends ConsumerState { ), ), body: SafeArea( - child: PageView( - controller: _pageController, - physics: const NeverScrollableScrollPhysics(), - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(0, 40, 0, 0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Choose your Stack experience", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 8, - ), - Text( - "You can change it later in Settings", - style: STextStyles.subtitle(context), - ), - const SizedBox( - height: 36, - ), - const Padding( - padding: EdgeInsets.symmetric( - horizontal: 16, - ), - child: PrivacyToggle(), - ), - const SizedBox( - height: 36, - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: RoundedWhiteContainer( - child: Center( - child: RichText( - textAlign: TextAlign.left, - text: TextSpan( - style: STextStyles.label(context) - .copyWith(fontSize: 12.0), - children: ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.externalCalls, - ), - ) - ? [ - const TextSpan( - text: - "Exchange data preloaded for a seamless experience."), - const TextSpan( - text: - "\n\nCoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), - TextSpan( - text: - "\n\nRecommended for most crypto users.", - style: TextStyle( - color: Theme.of(context) - .extension()! - .textDark, - fontWeight: FontWeight.w600, - ), - ), - ] - : [ - const TextSpan( - text: - "Exchange data not preloaded (slower experience)."), - const TextSpan( - text: - "\n\nCoinGecko disabled (price changes not shown, no wallet value shown in other currencies)."), - TextSpan( - text: - "\n\nRecommended for the privacy conscious.", - style: TextStyle( - color: Theme.of(context) - .extension()! - .textDark, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), + child: Padding( + padding: const EdgeInsets.fromLTRB(0, 40, 0, 0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Choose your Stack experience", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 8, + ), + Text( + "You can change it later in Settings", + style: STextStyles.subtitle(context), + ), + const SizedBox( + height: 36, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: PrivacyToggle( + externalCallsEnabled: isEasy, + onChanged: (externalCalls) { + isEasy = externalCalls; + setState(() { + infoToggle = isEasy; + }); + }, + ), + ), + const SizedBox( + height: 36, + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: RoundedWhiteContainer( + child: Center( + child: RichText( + textAlign: TextAlign.left, + text: TextSpan( + style: + STextStyles.label(context).copyWith(fontSize: 12.0), + children: infoToggle + ? [ + const TextSpan( + text: + "Exchange data preloaded for a seamless experience."), + const TextSpan( + text: + "\n\nCoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), + TextSpan( + text: + "\n\nRecommended for most crypto users.", + style: TextStyle( + color: Theme.of(context) + .extension()! + .textDark, + fontWeight: FontWeight.w600, + ), + ), + ] + : [ + const TextSpan( + text: + "Exchange data not preloaded (slower experience)."), + const TextSpan( + text: + "\n\nCoinGecko disabled (price changes not shown, no wallet value shown in other currencies)."), + TextSpan( + text: + "\n\nRecommended for the privacy conscious.", + style: TextStyle( + color: Theme.of(context) + .extension()! + .textDark, + fontWeight: FontWeight.w600, + ), + ), + ], ), ), ), - const Spacer( - flex: 4, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, - ), - child: Row( - children: [ - Expanded( - child: ContinueButton( - isDesktop: isDesktop, - isSettings: widget.isSettings, - isEasy: ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.externalCalls, - ), - ), - ), - ), - ], + ), + ), + const Spacer( + flex: 4, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + child: Row( + children: [ + Expanded( + child: ContinueButton( + isDesktop: isDesktop, + label: !widget.isSettings ? "Continue" : "Save changes", + onPressed: () { + ref.read(prefsChangeNotifierProvider).externalCalls = + isEasy; + if (!widget.isSettings) { + if (isDesktop) { + Navigator.of(context).pushNamed( + CreatePasswordView.routeName, + ); + } else { + Navigator.of(context).pushNamed( + CreatePinView.routeName, + ); + } + } else { + Navigator.pop(context); + } + }, + ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), ); } } -class PrivacyToggle extends ConsumerStatefulWidget { - const PrivacyToggle({Key? key}) : super(key: key); +class PrivacyToggle extends StatefulWidget { + const PrivacyToggle({ + Key? key, + required this.externalCallsEnabled, + this.onChanged, + }) : super(key: key); + + final bool externalCallsEnabled; + final void Function(bool)? onChanged; @override - ConsumerState createState() => _PrivacyToggleState(); + State createState() => _PrivacyToggleState(); } -class _PrivacyToggleState extends ConsumerState { +class _PrivacyToggleState extends State { + late bool externalCallsEnabled; + + @override + void initState() { + // initial toggle state + externalCallsEnabled = widget.externalCallsEnabled; + super.initState(); + } + @override Widget build(BuildContext context) { return Row( @@ -196,11 +219,7 @@ class _PrivacyToggleState extends ConsumerState { child: RawMaterialButton( fillColor: Theme.of(context).extension()!.popupBG, shape: RoundedRectangleBorder( - side: !ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.externalCalls, - ), - ) + side: !externalCallsEnabled ? BorderSide.none : BorderSide( color: Theme.of(context) @@ -213,7 +232,12 @@ class _PrivacyToggleState extends ConsumerState { ), ), onPressed: () { - ref.read(prefsChangeNotifierProvider).externalCalls = true; + setState(() { + // update toggle state + externalCallsEnabled = true; + }); + // call callback with newly set value + widget.onChanged?.call(externalCallsEnabled); }, child: Padding( padding: const EdgeInsets.all( @@ -244,11 +268,7 @@ class _PrivacyToggleState extends ConsumerState { ), ], ), - if (ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.externalCalls, - ), - )) + if (externalCallsEnabled) Positioned( top: 4, right: 4, @@ -261,11 +281,7 @@ class _PrivacyToggleState extends ConsumerState { .infoItemIcons, ), ), - if (!ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.externalCalls, - ), - )) + if (!externalCallsEnabled) Positioned( top: 4, right: 4, @@ -293,11 +309,7 @@ class _PrivacyToggleState extends ConsumerState { elevation: 0, fillColor: Theme.of(context).extension()!.popupBG, shape: RoundedRectangleBorder( - side: ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.externalCalls, - ), - ) + side: externalCallsEnabled ? BorderSide.none : BorderSide( color: Theme.of(context) @@ -310,7 +322,12 @@ class _PrivacyToggleState extends ConsumerState { ), ), onPressed: () { - ref.read(prefsChangeNotifierProvider).externalCalls = false; + setState(() { + // update toggle state + externalCallsEnabled = false; + }); + // call callback with newly set value + widget.onChanged?.call(externalCallsEnabled); }, child: Padding( padding: const EdgeInsets.all( @@ -342,11 +359,7 @@ class _PrivacyToggleState extends ConsumerState { ), ], ), - if (!ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.externalCalls, - ), - )) + if (!externalCallsEnabled) Positioned( top: 4, right: 4, @@ -359,11 +372,7 @@ class _PrivacyToggleState extends ConsumerState { .infoItemIcons, ), ), - if (ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.externalCalls, - ), - )) + if (externalCallsEnabled) Positioned( top: 4, right: 4, @@ -388,58 +397,47 @@ class _PrivacyToggleState extends ConsumerState { } } -class ContinueButton extends StatelessWidget { - const ContinueButton( - {Key? key, - required this.isDesktop, - required this.isSettings, - required this.isEasy}) - : super(key: key); +class ContinueButton extends ConsumerWidget { + const ContinueButton({ + Key? key, + required this.isDesktop, + required this.onPressed, + required this.label, + }) : super(key: key); + final String label; final bool isDesktop; - final bool isSettings; - final bool isEasy; + final VoidCallback onPressed; @override - Widget build(BuildContext context) { - return !isDesktop - ? TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - Prefs.instance.externalCalls = isEasy; - if (!isSettings) { - Navigator.of(context).pushNamed(CreatePinView.routeName); - } else { - Navigator.pop(context); - } - }, - child: Text( - !isSettings ? "Continue" : "Save changes", - style: STextStyles.button(context), - ), - ) - : SizedBox( - width: 328, - height: 70, - child: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - onPressed: () { - Prefs.instance.externalCalls = isEasy; - - if (!isSettings) { - Navigator.of(context).pushNamed(CreatePinView.routeName); - } - }, - child: Text( - !isSettings ? "Continue" : "Save changes", - style: STextStyles.button(context).copyWith(fontSize: 20), - ), - ), - ); + Widget build(BuildContext context, WidgetRef ref) { + if (isDesktop) { + return SizedBox( + width: 328, + height: 70, + child: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: onPressed, + child: Text( + label, + style: STextStyles.button(context).copyWith(fontSize: 20), + ), + ), + ); + } else { + return TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonColor(context), + onPressed: onPressed, + child: Text( + label, + style: STextStyles.button(context), + ), + ); + } } } From 77174d58fea94115a584157f7f273fb79021171f Mon Sep 17 00:00:00 2001 From: ryleedavis Date: Mon, 17 Oct 2022 14:32:24 -0600 Subject: [PATCH 75/75] change build --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index de8a71101a..7a25796c08 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.5.6+76 +version: 1.5.7+77 environment: sdk: ">=2.17.0 <3.0.0"