From b6597a0b7843c7657ff37758c8fe3199bd7690ec Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 3 Sep 2022 09:35:32 -0600 Subject: [PATCH 01/11] text fix --- lib/pages/exchange_view/exchange_step_views/step_4_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7ce46630d..91c3f1a6d 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 @@ -164,7 +164,7 @@ class _Step4ViewState extends ConsumerState { height: 8, ), Text( - "Send FIRO to the address below. Once it is received, ChangeNOW will send the BTC to the recipient address you provided. You can find this trade details and check its status in the list of trades.", + "Send ${model.sendTicker} to the address below. Once it is received, ChangeNOW will send the ${model.receiveTicker} to the recipient address you provided. You can find this trade details and check its status in the list of trades.", style: STextStyles.itemSubtitle, ), const SizedBox( From 51675c65387b5ec763a08b0d3f72653e7c989856 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 3 Sep 2022 09:35:59 -0600 Subject: [PATCH 02/11] exchange step indicator fix --- lib/pages/exchange_view/sub_widgets/step_row.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/exchange_view/sub_widgets/step_row.dart b/lib/pages/exchange_view/sub_widgets/step_row.dart index 88d6e06cd..5404eb98b 100644 --- a/lib/pages/exchange_view/sub_widgets/step_row.dart +++ b/lib/pages/exchange_view/sub_widgets/step_row.dart @@ -55,7 +55,7 @@ class StepRow extends StatelessWidget { )); } list.add(StepIndicator( - step: count - 1, + step: count, status: getStatus(count - 1), )); return list; From 4833c4ca28f45f24efa5f58655600df055fb6e42 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 3 Sep 2022 10:30:19 -0600 Subject: [PATCH 03/11] couple more tests --- test/hive/db_test.dart | 69 +++++ ...timated_rate_exchange_form_state_test.dart | 215 +++++++++++++ ...d_rate_exchange_form_state_test.mocks.dart | 212 +++++++++++++ test/models/isar/log_test.dart | 15 + .../exchange/exchange_view_test.mocks.dart | 287 ++++++++++++++---- 5 files changed, 746 insertions(+), 52 deletions(-) create mode 100644 test/hive/db_test.dart create mode 100644 test/models/exchange/estimated_rate_exchange_form_state_test.dart create mode 100644 test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart create mode 100644 test/models/isar/log_test.dart diff --git a/test/hive/db_test.dart b/test/hive/db_test.dart new file mode 100644 index 000000000..a87f568bd --- /dev/null +++ b/test/hive/db_test.dart @@ -0,0 +1,69 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hive_test/hive_test.dart'; +import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +void main() { + group("DB box names", () { + test("address book", () => expect(DB.boxNameAddressBook, "addressBook")); + test("debug info", () => expect(DB.boxNameDebugInfo, "debugInfoBox")); + test("nodes", () => expect(DB.boxNameNodeModels, "nodeModels")); + test("primary nodes", () => expect(DB.boxNamePrimaryNodes, "primaryNodes")); + test("wallets info", () => expect(DB.boxNameAllWalletsData, "wallets")); + test("notifications", + () => expect(DB.boxNameNotifications, "notificationModels")); + test( + "watched transactions", + () => expect( + DB.boxNameWatchedTransactions, "watchedTxNotificationModels")); + test( + "watched trades", + () => + expect(DB.boxNameWatchedTrades, "watchedTradesNotificationModels")); + test("trades", () => expect(DB.boxNameTrades, "exchangeTransactionsBox")); + test("trade notes", () => expect(DB.boxNameTradeNotes, "tradeNotesBox")); + test("tx <> trade lookup table", + () => expect(DB.boxNameTradeLookup, "tradeToTxidLookUpBox")); + test("favorite wallets", + () => expect(DB.boxNameFavoriteWallets, "favoriteWallets")); + test("preferences", () => expect(DB.boxNamePrefs, "prefs")); + test( + "deleted wallets to clear out on start", + () => + expect(DB.boxNameWalletsToDeleteOnStart, "walletsToDeleteOnStart")); + test("price cache", + () => expect(DB.boxNamePriceCache, "priceAPIPrice24hCache")); + + test("boxNameTxCache", () { + for (final coin in Coin.values) { + expect(DB.instance.boxNameTxCache(coin: coin), "${coin.name}_txCache"); + } + }); + + test("boxNameSetCache", () { + for (final coin in Coin.values) { + expect(DB.instance.boxNameSetCache(coin: coin), + "${coin.name}_anonymitySetCache"); + } + }); + + test("boxNameUsedSerialsCache", () { + for (final coin in Coin.values) { + expect(DB.instance.boxNameUsedSerialsCache(coin: coin), + "${coin.name}_usedSerialsCache"); + } + }); + }); + + group("tests requiring test hive environment", () { + setUp(() async { + await setUpTestHive(); + }); + + test("DB init", () async {}); + + tearDown(() async { + await tearDownTestHive(); + }); + }); +} diff --git a/test/models/exchange/estimated_rate_exchange_form_state_test.dart b/test/models/exchange/estimated_rate_exchange_form_state_test.dart new file mode 100644 index 000000000..3c6c869e5 --- /dev/null +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.dart @@ -0,0 +1,215 @@ +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/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/services/change_now/change_now.dart'; + +import 'estimated_rate_exchange_form_state_test.mocks.dart'; + +@GenerateMocks([ChangeNow]) +void main() { + final currencyA = Currency( + ticker: "btc", + name: "Bitcoin", + image: "image.url", + hasExternalId: false, + isFiat: false, + featured: false, + isStable: true, + supportsFixedRate: true, + ); + final currencyB = Currency( + ticker: "xmr", + name: "Monero", + image: "image.url", + hasExternalId: false, + isFiat: false, + featured: false, + isStable: true, + supportsFixedRate: true, + ); + final currencyC = Currency( + ticker: "firo", + name: "Firo", + image: "image.url", + hasExternalId: false, + isFiat: false, + featured: false, + isStable: true, + supportsFixedRate: true, + ); + + 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 = MockChangeNow(); + + final state = EstimatedRateExchangeFormState(); + state.cnTesting = cn; + + when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) + .thenAnswer((_) async => ChangeNowResponse(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 = MockChangeNow(); + + final state = EstimatedRateExchangeFormState(); + state.cnTesting = cn; + + when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) + .thenAnswer((_) async => ChangeNowResponse()); + + 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 = MockChangeNow(); + + final state = EstimatedRateExchangeFormState(); + state.cnTesting = cn; + + when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) + .thenAnswer((_) async => ChangeNowResponse(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 = MockChangeNow(); + + final state = EstimatedRateExchangeFormState(); + state.cnTesting = cn; + + when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr")) + .thenAnswer((_) async => ChangeNowResponse(value: Decimal.fromInt(42))); + when(cn.getEstimatedExchangeAmount( + fromTicker: "btc", + toTicker: "xmr", + fromAmount: Decimal.parse("110.10"))) + .thenAnswer((_) async => ChangeNowResponse( + value: EstimatedExchangeAmount( + transactionSpeedForecast: '10-60', + 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); + }); +} 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 new file mode 100644 index 000000000..27aa1a772 --- /dev/null +++ b/test/models/exchange/estimated_rate_exchange_form_state_test.mocks.dart @@ -0,0 +1,212 @@ +// 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/available_floating_rate_pair.dart' + as _i12; +import 'package:stackwallet/models/exchange/change_now/change_now_response.dart' + as _i2; +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' + as _i10; +import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' + as _i11; +import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' + as _i9; +import 'package:stackwallet/services/change_now/change_now.dart' as _i3; + +// 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 _FakeChangeNowResponse_0 extends _i1.Fake + implements _i2.ChangeNowResponse {} + +/// A class which mocks [ChangeNow]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockChangeNow extends _i1.Mock implements _i3.ChangeNow { + MockChangeNow() { + _i1.throwOnMissingStub(this); + } + + @override + set client(_i4.Client? _client) => + super.noSuchMethod(Invocation.setter(#client, _client), + returnValueForMissingStub: null); + @override + _i5.Future<_i2.ChangeNowResponse>> getAvailableCurrencies( + {bool? fixedRate, bool? active}) => + (super.noSuchMethod( + Invocation.method(#getAvailableCurrencies, [], + {#fixedRate: fixedRate, #active: active}), + returnValue: Future<_i2.ChangeNowResponse>>.value( + _FakeChangeNowResponse_0>())) as _i5 + .Future<_i2.ChangeNowResponse>>); + @override + _i5.Future<_i2.ChangeNowResponse>> getPairedCurrencies( + {String? ticker, bool? fixedRate}) => + (super.noSuchMethod( + Invocation.method(#getPairedCurrencies, [], + {#ticker: ticker, #fixedRate: fixedRate}), + returnValue: Future<_i2.ChangeNowResponse>>.value( + _FakeChangeNowResponse_0>())) as _i5 + .Future<_i2.ChangeNowResponse>>); + @override + _i5.Future<_i2.ChangeNowResponse<_i7.Decimal>> getMinimalExchangeAmount( + {String? fromTicker, String? toTicker, String? apiKey}) => + (super.noSuchMethod( + Invocation.method(#getMinimalExchangeAmount, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #apiKey: apiKey + }), + returnValue: Future<_i2.ChangeNowResponse<_i7.Decimal>>.value( + _FakeChangeNowResponse_0<_i7.Decimal>())) + as _i5.Future<_i2.ChangeNowResponse<_i7.Decimal>>); + @override + _i5.Future<_i2.ChangeNowResponse<_i8.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.ChangeNowResponse< + _i8.EstimatedExchangeAmount>>.value( + _FakeChangeNowResponse_0<_i8.EstimatedExchangeAmount>())) + as _i5 + .Future<_i2.ChangeNowResponse<_i8.EstimatedExchangeAmount>>); + @override + _i5.Future<_i2.ChangeNowResponse<_i8.EstimatedExchangeAmount>> + getEstimatedFixedRateExchangeAmount( + {String? fromTicker, + String? toTicker, + _i7.Decimal? fromAmount, + bool? useRateId = true, + String? apiKey}) => + (super.noSuchMethod( + Invocation.method(#getEstimatedFixedRateExchangeAmount, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #fromAmount: fromAmount, + #useRateId: useRateId, + #apiKey: apiKey + }), + returnValue: Future< + _i2.ChangeNowResponse< + _i8.EstimatedExchangeAmount>>.value( + _FakeChangeNowResponse_0<_i8.EstimatedExchangeAmount>())) + as _i5 + .Future<_i2.ChangeNowResponse<_i8.EstimatedExchangeAmount>>); + @override + _i5.Future<_i2.ChangeNowResponse>> + getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod( + Invocation.method( + #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), + returnValue: + Future<_i2.ChangeNowResponse>>.value( + _FakeChangeNowResponse_0>())) as _i5 + .Future<_i2.ChangeNowResponse>>); + @override + _i5.Future<_i2.ChangeNowResponse<_i10.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.ChangeNowResponse<_i10.ExchangeTransaction>>.value( + _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i5 + .Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>); + @override + _i5.Future<_i2.ChangeNowResponse<_i10.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.ChangeNowResponse<_i10.ExchangeTransaction>>.value( + _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i5 + .Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>); + @override + _i5.Future<_i2.ChangeNowResponse<_i11.ExchangeTransactionStatus>> + getTransactionStatus({String? id, String? apiKey}) => (super.noSuchMethod( + Invocation.method( + #getTransactionStatus, [], {#id: id, #apiKey: apiKey}), + returnValue: + Future<_i2.ChangeNowResponse<_i11.ExchangeTransactionStatus>>.value( + _FakeChangeNowResponse_0<_i11.ExchangeTransactionStatus>())) as _i5 + .Future<_i2.ChangeNowResponse<_i11.ExchangeTransactionStatus>>); + @override + _i5.Future<_i2.ChangeNowResponse>> + getAvailableFloatingRatePairs({bool? includePartners = false}) => (super + .noSuchMethod( + Invocation.method(#getAvailableFloatingRatePairs, [], + {#includePartners: includePartners}), + returnValue: + Future<_i2.ChangeNowResponse>>.value( + _FakeChangeNowResponse_0>())) as _i5 + .Future<_i2.ChangeNowResponse>>); +} diff --git a/test/models/isar/log_test.dart b/test/models/isar/log_test.dart new file mode 100644 index 000000000..15e1e245b --- /dev/null +++ b/test/models/isar/log_test.dart @@ -0,0 +1,15 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:stackwallet/models/isar/models/log.dart'; +import 'package:stackwallet/utilities/logger.dart'; + +void main() { + test("Log class", () { + final log = Log() + ..message = "hello" + ..timestampInMillisUTC = 100000001 + ..logLevel = LogLevel.Fatal; + + expect(log.toString(), "[Fatal][1970-01-02 03:46:40.001Z]: hello"); + expect(log.id, -9223372036854775808); + }); +} diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index f659e3104..aa517d09b 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -2,20 +2,33 @@ // in stackwallet/test/screen_tests/exchange/exchange_view_test.dart. // Do not manually edit this file. -import 'dart:async' as _i6; -import 'dart:ui' as _i7; +import 'dart:async' as _i7; +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 _i19; +import 'package:stackwallet/models/exchange/change_now/change_now_response.dart' + as _i2; +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' - as _i9; + as _i10; +import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' + as _i18; +import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart' + as _i17; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart' - as _i4; -import 'package:stackwallet/services/change_now/change_now.dart' as _i11; -import 'package:stackwallet/services/trade_notes_service.dart' as _i10; -import 'package:stackwallet/services/trade_service.dart' as _i8; -import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i5; -import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i3; -import 'package:stackwallet/utilities/prefs.dart' as _i2; + as _i5; +import 'package:stackwallet/services/change_now/change_now.dart' as _i12; +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; +import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i4; +import 'package:stackwallet/utilities/prefs.dart' as _i3; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -27,10 +40,13 @@ import 'package:stackwallet/utilities/prefs.dart' as _i2; // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types +class _FakeChangeNowResponse_0 extends _i1.Fake + implements _i2.ChangeNowResponse {} + /// A class which mocks [Prefs]. /// /// See the documentation for Mockito's code generation for more information. -class MockPrefs extends _i1.Mock implements _i2.Prefs { +class MockPrefs extends _i1.Mock implements _i3.Prefs { MockPrefs() { _i1.throwOnMissingStub(this); } @@ -69,11 +85,11 @@ class MockPrefs extends _i1.Mock implements _i2.Prefs { Invocation.setter(#walletIdsSyncOnStartup, walletIdsSyncOnStartup), returnValueForMissingStub: null); @override - _i3.SyncingType get syncType => + _i4.SyncingType get syncType => (super.noSuchMethod(Invocation.getter(#syncType), - returnValue: _i3.SyncingType.currentWalletOnly) as _i3.SyncingType); + returnValue: _i4.SyncingType.currentWalletOnly) as _i4.SyncingType); @override - set syncType(_i3.SyncingType? syncType) => + set syncType(_i4.SyncingType? syncType) => super.noSuchMethod(Invocation.setter(#syncType, syncType), returnValueForMissingStub: null); @override @@ -109,11 +125,11 @@ class MockPrefs extends _i1.Mock implements _i2.Prefs { super.noSuchMethod(Invocation.setter(#currency, newCurrency), returnValueForMissingStub: null); @override - _i4.ExchangeRateType get exchangeRateType => + _i5.ExchangeRateType get exchangeRateType => (super.noSuchMethod(Invocation.getter(#exchangeRateType), - returnValue: _i4.ExchangeRateType.estimated) as _i4.ExchangeRateType); + returnValue: _i5.ExchangeRateType.estimated) as _i5.ExchangeRateType); @override - set exchangeRateType(_i4.ExchangeRateType? exchangeRateType) => + set exchangeRateType(_i5.ExchangeRateType? exchangeRateType) => super.noSuchMethod(Invocation.setter(#exchangeRateType, exchangeRateType), returnValueForMissingStub: null); @override @@ -153,12 +169,12 @@ class MockPrefs extends _i1.Mock implements _i2.Prefs { Invocation.setter(#autoBackupLocation, autoBackupLocation), returnValueForMissingStub: null); @override - _i5.BackupFrequencyType get backupFrequencyType => + _i6.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod(Invocation.getter(#backupFrequencyType), - returnValue: _i5.BackupFrequencyType.everyTenMinutes) - as _i5.BackupFrequencyType); + returnValue: _i6.BackupFrequencyType.everyTenMinutes) + as _i6.BackupFrequencyType); @override - set backupFrequencyType(_i5.BackupFrequencyType? backupFrequencyType) => + set backupFrequencyType(_i6.BackupFrequencyType? backupFrequencyType) => super.noSuchMethod( Invocation.setter(#backupFrequencyType, backupFrequencyType), returnValueForMissingStub: null); @@ -171,20 +187,20 @@ class MockPrefs extends _i1.Mock implements _i2.Prefs { (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool); @override - _i6.Future init() => (super.noSuchMethod(Invocation.method(#init, []), + _i7.Future init() => (super.noSuchMethod(Invocation.method(#init, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i6.Future); + returnValueForMissingStub: Future.value()) as _i7.Future); @override - _i6.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( + _i7.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( Invocation.method(#incrementCurrentNotificationIndex, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i6.Future); + returnValueForMissingStub: Future.value()) as _i7.Future); @override - void addListener(_i7.VoidCallback? listener) => + void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); @override - void removeListener(_i7.VoidCallback? listener) => + void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null); @override @@ -199,57 +215,57 @@ class MockPrefs extends _i1.Mock implements _i2.Prefs { /// A class which mocks [TradesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockTradesService extends _i1.Mock implements _i8.TradesService { +class MockTradesService extends _i1.Mock implements _i9.TradesService { MockTradesService() { _i1.throwOnMissingStub(this); } @override - List<_i9.ExchangeTransaction> get trades => + List<_i10.ExchangeTransaction> get trades => (super.noSuchMethod(Invocation.getter(#trades), - returnValue: <_i9.ExchangeTransaction>[]) - as List<_i9.ExchangeTransaction>); + returnValue: <_i10.ExchangeTransaction>[]) + as List<_i10.ExchangeTransaction>); @override bool get hasListeners => (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool); @override - _i6.Future add( - {_i9.ExchangeTransaction? trade, bool? shouldNotifyListeners}) => + _i7.Future add( + {_i10.ExchangeTransaction? trade, bool? shouldNotifyListeners}) => (super.noSuchMethod( Invocation.method(#add, [], {#trade: trade, #shouldNotifyListeners: shouldNotifyListeners}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i6.Future); + returnValueForMissingStub: Future.value()) as _i7.Future); @override - _i6.Future edit( - {_i9.ExchangeTransaction? trade, bool? shouldNotifyListeners}) => + _i7.Future edit( + {_i10.ExchangeTransaction? trade, bool? shouldNotifyListeners}) => (super.noSuchMethod( Invocation.method(#edit, [], {#trade: trade, #shouldNotifyListeners: shouldNotifyListeners}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i6.Future); + returnValueForMissingStub: Future.value()) as _i7.Future); @override - _i6.Future delete( - {_i9.ExchangeTransaction? trade, bool? shouldNotifyListeners}) => + _i7.Future delete( + {_i10.ExchangeTransaction? trade, bool? shouldNotifyListeners}) => (super.noSuchMethod( Invocation.method(#delete, [], {#trade: trade, #shouldNotifyListeners: shouldNotifyListeners}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i6.Future); + returnValueForMissingStub: Future.value()) as _i7.Future); @override - _i6.Future deleteByUuid({String? uuid, bool? shouldNotifyListeners}) => + _i7.Future deleteByUuid({String? uuid, bool? shouldNotifyListeners}) => (super.noSuchMethod( Invocation.method(#deleteByUuid, [], {#uuid: uuid, #shouldNotifyListeners: shouldNotifyListeners}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i6.Future); + returnValueForMissingStub: Future.value()) as _i7.Future); @override - void addListener(_i7.VoidCallback? listener) => + void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); @override - void removeListener(_i7.VoidCallback? listener) => + void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null); @override @@ -264,7 +280,7 @@ class MockTradesService extends _i1.Mock implements _i8.TradesService { /// A class which mocks [TradeNotesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockTradeNotesService extends _i1.Mock implements _i10.TradeNotesService { +class MockTradeNotesService extends _i1.Mock implements _i11.TradeNotesService { MockTradeNotesService() { _i1.throwOnMissingStub(this); } @@ -281,21 +297,21 @@ class MockTradeNotesService extends _i1.Mock implements _i10.TradeNotesService { (super.noSuchMethod(Invocation.method(#getNote, [], {#tradeId: tradeId}), returnValue: '') as String); @override - _i6.Future set({String? tradeId, String? note}) => (super.noSuchMethod( + _i7.Future set({String? tradeId, String? note}) => (super.noSuchMethod( Invocation.method(#set, [], {#tradeId: tradeId, #note: note}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i6.Future); + returnValueForMissingStub: Future.value()) as _i7.Future); @override - _i6.Future delete({String? tradeId}) => + _i7.Future delete({String? tradeId}) => (super.noSuchMethod(Invocation.method(#delete, [], {#tradeId: tradeId}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i6.Future); + returnValueForMissingStub: Future.value()) as _i7.Future); @override - void addListener(_i7.VoidCallback? listener) => + void addListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); @override - void removeListener(_i7.VoidCallback? listener) => + void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod(Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null); @override @@ -310,8 +326,175 @@ class MockTradeNotesService extends _i1.Mock implements _i10.TradeNotesService { /// A class which mocks [ChangeNow]. /// /// See the documentation for Mockito's code generation for more information. -class MockChangeNow extends _i1.Mock implements _i11.ChangeNow { +class MockChangeNow extends _i1.Mock implements _i12.ChangeNow { MockChangeNow() { _i1.throwOnMissingStub(this); } + + @override + set client(_i13.Client? _client) => + super.noSuchMethod(Invocation.setter(#client, _client), + returnValueForMissingStub: null); + @override + _i7.Future<_i2.ChangeNowResponse>> getAvailableCurrencies( + {bool? fixedRate, bool? active}) => + (super.noSuchMethod( + Invocation.method(#getAvailableCurrencies, [], + {#fixedRate: fixedRate, #active: active}), + returnValue: Future<_i2.ChangeNowResponse>>.value( + _FakeChangeNowResponse_0>())) as _i7 + .Future<_i2.ChangeNowResponse>>); + @override + _i7.Future<_i2.ChangeNowResponse>> getPairedCurrencies( + {String? ticker, bool? fixedRate}) => + (super.noSuchMethod( + Invocation.method(#getPairedCurrencies, [], + {#ticker: ticker, #fixedRate: fixedRate}), + returnValue: Future<_i2.ChangeNowResponse>>.value( + _FakeChangeNowResponse_0>())) as _i7 + .Future<_i2.ChangeNowResponse>>); + @override + _i7.Future<_i2.ChangeNowResponse<_i15.Decimal>> getMinimalExchangeAmount( + {String? fromTicker, String? toTicker, String? apiKey}) => + (super.noSuchMethod( + Invocation.method(#getMinimalExchangeAmount, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #apiKey: apiKey + }), + returnValue: Future<_i2.ChangeNowResponse<_i15.Decimal>>.value( + _FakeChangeNowResponse_0<_i15.Decimal>())) + as _i7.Future<_i2.ChangeNowResponse<_i15.Decimal>>); + @override + _i7.Future<_i2.ChangeNowResponse<_i16.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.ChangeNowResponse< + _i16.EstimatedExchangeAmount>>.value( + _FakeChangeNowResponse_0<_i16.EstimatedExchangeAmount>())) + as _i7 + .Future<_i2.ChangeNowResponse<_i16.EstimatedExchangeAmount>>); + @override + _i7.Future<_i2.ChangeNowResponse<_i16.EstimatedExchangeAmount>> + getEstimatedFixedRateExchangeAmount( + {String? fromTicker, + String? toTicker, + _i15.Decimal? fromAmount, + bool? useRateId = true, + String? apiKey}) => + (super.noSuchMethod( + Invocation.method(#getEstimatedFixedRateExchangeAmount, [], { + #fromTicker: fromTicker, + #toTicker: toTicker, + #fromAmount: fromAmount, + #useRateId: useRateId, + #apiKey: apiKey + }), + returnValue: Future< + _i2.ChangeNowResponse< + _i16.EstimatedExchangeAmount>>.value( + _FakeChangeNowResponse_0<_i16.EstimatedExchangeAmount>())) + as _i7 + .Future<_i2.ChangeNowResponse<_i16.EstimatedExchangeAmount>>); + @override + _i7.Future<_i2.ChangeNowResponse>> + getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod( + Invocation.method( + #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}), + returnValue: + Future<_i2.ChangeNowResponse>>.value( + _FakeChangeNowResponse_0>())) as _i7 + .Future<_i2.ChangeNowResponse>>); + @override + _i7.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>> + createStandardExchangeTransaction( + {String? fromTicker, + String? toTicker, + String? receivingAddress, + _i15.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.ChangeNowResponse<_i10.ExchangeTransaction>>.value( + _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i7 + .Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>); + @override + _i7.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>> + createFixedRateExchangeTransaction( + {String? fromTicker, + String? toTicker, + String? receivingAddress, + _i15.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.ChangeNowResponse<_i10.ExchangeTransaction>>.value( + _FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i7 + .Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>); + @override + _i7.Future<_i2.ChangeNowResponse<_i18.ExchangeTransactionStatus>> + getTransactionStatus({String? id, String? apiKey}) => (super.noSuchMethod( + Invocation.method( + #getTransactionStatus, [], {#id: id, #apiKey: apiKey}), + returnValue: + Future<_i2.ChangeNowResponse<_i18.ExchangeTransactionStatus>>.value( + _FakeChangeNowResponse_0<_i18.ExchangeTransactionStatus>())) as _i7 + .Future<_i2.ChangeNowResponse<_i18.ExchangeTransactionStatus>>); + @override + _i7.Future<_i2.ChangeNowResponse>> + getAvailableFloatingRatePairs({bool? includePartners = false}) => (super + .noSuchMethod( + Invocation.method(#getAvailableFloatingRatePairs, [], + {#includePartners: includePartners}), + returnValue: + Future<_i2.ChangeNowResponse>>.value( + _FakeChangeNowResponse_0>())) as _i7 + .Future<_i2.ChangeNowResponse>>); } From eb63b1828c5daffc39ad416c4c30138252938eae Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 3 Sep 2022 10:31:20 -0600 Subject: [PATCH 04/11] explicitly declare empty lists --- .../exchange/available_currencies_state_provider.dart | 2 +- .../exchange/available_floating_rate_pairs_state_provider.dart | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/providers/exchange/available_currencies_state_provider.dart b/lib/providers/exchange/available_currencies_state_provider.dart index dee8cfa60..5b8395201 100644 --- a/lib/providers/exchange/available_currencies_state_provider.dart +++ b/lib/providers/exchange/available_currencies_state_provider.dart @@ -2,4 +2,4 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/exchange/change_now/currency.dart'; final availableChangeNowCurrenciesStateProvider = - StateProvider>((ref) => []); + 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 index 5620d1d91..a157b0727 100644 --- a/lib/providers/exchange/available_floating_rate_pairs_state_provider.dart +++ b/lib/providers/exchange/available_floating_rate_pairs_state_provider.dart @@ -2,4 +2,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'; final availableFloatingRatePairsStateProvider = - StateProvider>((ref) => []); + StateProvider>( + (ref) => []); From e97ccf812e612287a6b7899ce591eb9b0cbf30c1 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 3 Sep 2022 11:41:00 -0600 Subject: [PATCH 05/11] estimated/floating rate fixes and optimizations --- .../estimated_rate_exchange_form_state.dart | 310 ++++++------------ lib/pages/exchange_view/exchange_view.dart | 91 ++++- .../wallet_initiated_exchange_view.dart | 20 +- 3 files changed, 198 insertions(+), 223 deletions(-) diff --git a/lib/models/exchange/estimated_rate_exchange_form_state.dart b/lib/models/exchange/estimated_rate_exchange_form_state.dart index 210df8c95..4e63bbe20 100644 --- a/lib/models/exchange/estimated_rate_exchange_form_state.dart +++ b/lib/models/exchange/estimated_rate_exchange_form_state.dart @@ -1,10 +1,14 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:stackwallet/models/exchange/change_now/currency.dart'; import 'package:stackwallet/services/change_now/change_now.dart'; import 'package:stackwallet/utilities/logger.dart'; class EstimatedRateExchangeFormState extends ChangeNotifier { + /// used in testing to inject mock + ChangeNow? cnTesting; + Decimal? _fromAmount; Decimal? _toAmount; @@ -16,9 +20,43 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { 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; @@ -43,10 +81,6 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { final Decimal? newMinFromAmount = _minToAmount; final Decimal? newMinToAmount = _minFromAmount; - // final Decimal? newRate = rate == null - // ? rate - // : (Decimal.one / rate!).toDecimal(scaleOnInfinitePrecision: 12); - final Currency? newTo = from; final Currency? newFrom = to; @@ -63,48 +97,11 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { await _updateMinFromAmount(shouldNotifyListeners: false); - rate = null; - - if (_fromAmount != null) { - Decimal? amt; - if (_minFromAmount != null) { - if (_minFromAmount! > _fromAmount!) { - amt = await getStandardEstimatedToAmount( - fromAmount: _minFromAmount!, from: _from!, to: _to!); - if (amt != null) { - rate = - (amt / _minFromAmount!).toDecimal(scaleOnInfinitePrecision: 12); - } - } else { - amt = await getStandardEstimatedToAmount( - fromAmount: _fromAmount!, from: _from!, to: _to!); - if (amt != null) { - rate = (amt / _fromAmount!).toDecimal(scaleOnInfinitePrecision: 12); - } - } - } - if (rate != null) { - _toAmount = (_fromAmount! * rate!); - } - } else { - if (_minFromAmount != null) { - Decimal? amt = await getStandardEstimatedToAmount( - fromAmount: _minFromAmount!, from: _from!, to: _to!); - if (amt != null) { - rate = - (amt / _minFromAmount!).toDecimal(scaleOnInfinitePrecision: 12); - } - } - } + await updateRate(); notifyListeners(); } - String get fromAmountString => - _fromAmount == null ? "" : _fromAmount!.toStringAsFixed(8); - String get toAmountString => - _toAmount == null ? "" : _toAmount!.toStringAsFixed(8); - Future updateTo(Currency to, bool shouldNotifyListeners) async { try { _to = to; @@ -115,46 +112,8 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { } await _updateMinFromAmount(shouldNotifyListeners: shouldNotifyListeners); - // await _updateMinToAmount(shouldNotifyListeners: shouldNotifyListeners); - rate = null; - - if (_fromAmount != null) { - Decimal? amt; - if (_minFromAmount != null) { - if (_minFromAmount! > _fromAmount!) { - amt = await getStandardEstimatedToAmount( - fromAmount: _minFromAmount!, from: _from!, to: _to!); - if (amt != null) { - rate = (amt / _minFromAmount!) - .toDecimal(scaleOnInfinitePrecision: 12); - } - debugPrint("A"); - } else { - amt = await getStandardEstimatedToAmount( - fromAmount: _fromAmount!, from: _from!, to: _to!); - if (amt != null) { - rate = - (amt / _fromAmount!).toDecimal(scaleOnInfinitePrecision: 12); - } - debugPrint("B"); - } - } - if (rate != null) { - _toAmount = (_fromAmount! * rate!); - } - debugPrint("C"); - } else { - if (_minFromAmount != null) { - Decimal? amt = await getStandardEstimatedToAmount( - fromAmount: _minFromAmount!, from: _from!, to: _to!); - if (amt != null) { - rate = - (amt / _minFromAmount!).toDecimal(scaleOnInfinitePrecision: 12); - } - debugPrint("D"); - } - } + await updateRate(); debugPrint( "_updated TO: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$_fromAmount _toAmount=$_toAmount rate:$rate"); @@ -163,7 +122,7 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { notifyListeners(); } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Fatal); + Logging.instance.log("$e\n$s", level: LogLevel.Error); } } @@ -179,40 +138,7 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { await _updateMinFromAmount(shouldNotifyListeners: shouldNotifyListeners); - rate = null; - - if (_fromAmount != null) { - Decimal? amt; - if (_minFromAmount != null) { - if (_minFromAmount! > _fromAmount!) { - amt = await getStandardEstimatedToAmount( - fromAmount: _minFromAmount!, from: _from!, to: _to!); - if (amt != null) { - rate = (amt / _minFromAmount!) - .toDecimal(scaleOnInfinitePrecision: 12); - } - } else { - amt = await getStandardEstimatedToAmount( - fromAmount: _fromAmount!, from: _from!, to: _to!); - if (amt != null) { - rate = - (amt / _fromAmount!).toDecimal(scaleOnInfinitePrecision: 12); - } - } - } - if (rate != null) { - _toAmount = (_fromAmount! * rate!); - } - } else { - if (_minFromAmount != null) { - Decimal? amt = await getStandardEstimatedToAmount( - fromAmount: _minFromAmount!, from: _from!, to: _to!); - if (amt != null) { - rate = - (amt / _minFromAmount!).toDecimal(scaleOnInfinitePrecision: 12); - } - } - } + await updateRate(); debugPrint( "_updated FROM: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$_fromAmount _toAmount=$_toAmount rate:$rate"); @@ -220,55 +146,10 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { notifyListeners(); } } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Fatal); + Logging.instance.log("$e\n$s", level: LogLevel.Error); } } - 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 && - minimumReceiveWarning.isEmpty && - minimumSendWarning.isEmpty; - } - - String get minimumSendWarning { - if (_from != null && - _fromAmount != null && - _minFromAmount != null && - _fromAmount! < _minFromAmount!) { - return "Minimum amount ${_minFromAmount!.toString()} ${from!.ticker.toUpperCase()}"; - } - - return ""; - } - - String get minimumReceiveWarning { - // TODO not sure this is needed - // if (_toAmount != null && - // _minToAmount != null && - // _toAmount! < _minToAmount!) { - // return "Minimum amount ${_minToAmount!.toString()} ${to.ticker.toUpperCase()}"; - // } - return ""; - } - - // Future _updateMinToAmount({required bool shouldNotifyListeners}) async { - // _minToAmount = await getStandardMinExchangeAmount(from: to!, to: from!); - // if (shouldNotifyListeners) { - // notifyListeners(); - // } - // } - Future _updateMinFromAmount( {required bool shouldNotifyListeners}) async { _minFromAmount = await getStandardMinExchangeAmount(from: from!, to: to!); @@ -277,48 +158,32 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { } } - Future setToAmountAndCalculateFromAmount( - Decimal newToAmount, - bool shouldNotifyListeners, - ) async { - // if (newToAmount == Decimal.zero) { - // _fromAmount = Decimal.zero; - // _toAmount = Decimal.zero; - // if (shouldNotifyListeners) { - // notifyListeners(); - // } - // return; - // } - - if (rate != null) { - _fromAmount = - (newToAmount / rate!).toDecimal(scaleOnInfinitePrecision: 12); - } - - _toAmount = newToAmount; - 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) { - // _fromAmount = Decimal.zero; - // _toAmount = Decimal.zero; - // if (shouldNotifyListeners) { - // notifyListeners(); - // } - // return; - // } - - if (rate != null) { - _toAmount = (newFromAmount * rate!); + if (newFromAmount == Decimal.zero) { + _toAmount = Decimal.zero; } _fromAmount = newFromAmount; + await updateRate(); + if (shouldNotifyListeners) { notifyListeners(); } @@ -329,8 +194,12 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { required Currency from, required Currency to, }) async { - final response = await ChangeNow.instance.getEstimatedExchangeAmount( - fromTicker: from.ticker, toTicker: to.ticker, fromAmount: fromAmount); + final response = + await (cnTesting ?? ChangeNow.instance).getEstimatedExchangeAmount( + fromTicker: from.ticker, + toTicker: to.ticker, + fromAmount: fromAmount, + ); if (response.value != null) { return response.value!.estimatedAmount; @@ -341,11 +210,31 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { } } + // 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 ChangeNow.instance + final response = await (cnTesting ?? ChangeNow.instance) .getMinimalExchangeAmount(fromTicker: from.ticker, toTicker: to.ticker); if (response.value != null) { @@ -357,8 +246,6 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { } } - void Function(String)? _onError; - void setOnError({ required void Function(String)? onError, bool shouldNotifyListeners = false, @@ -368,4 +255,25 @@ class EstimatedRateExchangeFormState extends ChangeNotifier { notifyListeners(); } } + + Future updateRate() 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; + } + } + } } diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart index df659616f..b47ad3bf6 100644 --- a/lib/pages/exchange_view/exchange_view.dart +++ b/lib/pages/exchange_view/exchange_view.dart @@ -231,6 +231,65 @@ class _ExchangeViewState extends ConsumerState { ? 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 = ""; + } + } + }); + _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(); } @@ -332,12 +391,12 @@ class _ExchangeViewState extends ConsumerState { await ref .read(estimatedRateExchangeFormProvider) .setFromAmountAndCalculateToAmount( - newFromAmount, true); + newFromAmount, false); } else { await ref .read(fixedRateExchangeFormProvider) .setFromAmountAndCalculateToAmount( - newFromAmount, true); + newFromAmount, false); } } else { if (ref @@ -347,12 +406,12 @@ class _ExchangeViewState extends ConsumerState { await ref .read(estimatedRateExchangeFormProvider) .setFromAmountAndCalculateToAmount( - Decimal.zero, true); + Decimal.zero, false); } else { await ref .read(fixedRateExchangeFormProvider) .setFromAmountAndCalculateToAmount( - Decimal.zero, true); + Decimal.zero, false); } _receiveController.text = ""; } @@ -631,6 +690,10 @@ class _ExchangeViewState extends ConsumerState { TextFormField( focusNode: _receiveFocusNode, controller: _receiveController, + readOnly: ref + .read(prefsChangeNotifierProvider) + .exchangeRateType == + ExchangeRateType.estimated, onTap: () { if (_receiveController.text == "-") { _receiveController.text = ""; @@ -643,30 +706,30 @@ class _ExchangeViewState extends ConsumerState { .read(prefsChangeNotifierProvider) .exchangeRateType == ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount( - newToAmount, true); + // await ref + // .read(estimatedRateExchangeFormProvider) + // .setToAmountAndCalculateFromAmount( + // newToAmount, false); } else { await ref .read(fixedRateExchangeFormProvider) .setToAmountAndCalculateFromAmount( - newToAmount, true); + newToAmount, false); } } else { if (ref .read(prefsChangeNotifierProvider) .exchangeRateType == ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount( - Decimal.zero, true); + // await ref + // .read(estimatedRateExchangeFormProvider) + // .setToAmountAndCalculateFromAmount( + // Decimal.zero, false); } else { await ref .read(fixedRateExchangeFormProvider) .setToAmountAndCalculateFromAmount( - Decimal.zero, true); + Decimal.zero, false); } _sendController.text = ""; } diff --git a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart index 152826345..8a952fd8d 100644 --- a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart +++ b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart @@ -716,6 +716,10 @@ class _WalletInitiatedExchangeViewState TextFormField( focusNode: _receiveFocusNode, controller: _receiveController, + readOnly: ref + .read(prefsChangeNotifierProvider) + .exchangeRateType == + ExchangeRateType.estimated, onTap: () { if (_receiveController.text == "-") { _receiveController.text = ""; @@ -728,10 +732,10 @@ class _WalletInitiatedExchangeViewState .read(prefsChangeNotifierProvider) .exchangeRateType == ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount( - newToAmount, true); + // await ref + // .read(estimatedRateExchangeFormProvider) + // .setToAmountAndCalculateFromAmount( + // newToAmount, true); } else { await ref .read(fixedRateExchangeFormProvider) @@ -743,10 +747,10 @@ class _WalletInitiatedExchangeViewState .read(prefsChangeNotifierProvider) .exchangeRateType == ExchangeRateType.estimated) { - await ref - .read(estimatedRateExchangeFormProvider) - .setToAmountAndCalculateFromAmount( - Decimal.zero, true); + // await ref + // .read(estimatedRateExchangeFormProvider) + // .setToAmountAndCalculateFromAmount( + // Decimal.zero, true); } else { await ref .read(fixedRateExchangeFormProvider) From 57814fab4b8af0f55b5a85ddbc27e1cf5e96d118 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 3 Sep 2022 11:51:08 -0600 Subject: [PATCH 06/11] remove redundant null check operator --- .../coins/epiccash/epiccash_wallet.dart | 78 ++++++++----------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index b57244009..a99718dd3 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -77,11 +77,8 @@ Future executeNative(Map arguments) async { final startHeight = arguments['startHeight'] as int?; final numberOfBlocks = arguments['numberOfBlocks'] as int?; Map result = {}; - if (!(wallet == null || - startHeight == null || - numberOfBlocks == null)) { - var outputs = - await scanOutPuts(wallet, startHeight, numberOfBlocks); + if (!(wallet == null || startHeight == null || numberOfBlocks == null)) { + var outputs = await scanOutPuts(wallet, startHeight, numberOfBlocks); result['outputs'] = outputs; sendPort.send(result); return; @@ -111,8 +108,8 @@ Future executeNative(Map arguments) async { epicboxConfig == null)) { Logging.instance .log("SECRET_KEY_INDEX_IS $secretKeyIndex", level: LogLevel.Info); - result['result'] = await getSubscribeRequest( - wallet, secretKeyIndex, epicboxConfig); + result['result'] = + await getSubscribeRequest(wallet, secretKeyIndex, epicboxConfig); sendPort.send(result); return; } @@ -122,8 +119,7 @@ Future executeNative(Map arguments) async { Map result = {}; if (!(wallet == null || slates == null)) { - result['result'] = - await processSlates(wallet, slates.toString()); + result['result'] = await processSlates(wallet, slates.toString()); sendPort.send(result); return; } @@ -135,8 +131,8 @@ Future executeNative(Map arguments) async { if (!(wallet == null || refreshFromNode == null || minimumConfirmations == null)) { - var res = await getWalletInfo( - wallet, refreshFromNode, minimumConfirmations); + var res = + await getWalletInfo(wallet, refreshFromNode, minimumConfirmations); result['result'] = res; sendPort.send(result); return; @@ -166,11 +162,9 @@ Future executeNative(Map arguments) async { final amount = arguments['amount'] as int?; final minimumConfirmations = arguments['minimumConfirmations'] as int?; Map result = {}; - if (!(wallet == null || - amount == null || - minimumConfirmations == null)) { - var res = await getTransactionFees( - wallet, amount, minimumConfirmations); + if (!(wallet == null || amount == null || minimumConfirmations == null)) { + var res = + await getTransactionFees(wallet, amount, minimumConfirmations); result['result'] = res; sendPort.send(result); return; @@ -198,7 +192,8 @@ Future executeNative(Map arguments) async { } } else if (function == "txHttpSend") { final wallet = arguments['wallet'] as String?; - final selectionStrategyIsAll = arguments['selectionStrategyIsAll'] as int?; + final selectionStrategyIsAll = + arguments['selectionStrategyIsAll'] as int?; final minimumConfirmations = arguments['minimumConfirmations'] as int?; final message = arguments['message'] as String?; final amount = arguments['amount'] as int?; @@ -218,7 +213,6 @@ Future executeNative(Map arguments) async { sendPort.send(result); return; } - } Logging.instance.log( "Error Arguments for $function not formatted correctly", @@ -245,8 +239,7 @@ void stop(ReceivePort port) { // Keep Wrapper functions outside of the class to avoid memory leaks and errors about receive ports and illegal arguments. // TODO: Can get rid of this wrapper and call it in a full isolate instead of compute() if we want more control over this -Future _cancelTransactionWrapper( - Tuple2 data) async { +Future _cancelTransactionWrapper(Tuple2 data) async { // assuming this returns an empty string on success // or an error message string on failure return cancelTransaction(data.item1, data.item2); @@ -290,8 +283,7 @@ Future _initWalletWrapper( Future _initGetAddressInfoWrapper( Tuple3 data) async { - String walletAddress = - getAddressInfo(data.item1, data.item2, data.item3); + String walletAddress = getAddressInfo(data.item1, data.item2, data.item3); return walletAddress; } @@ -727,7 +719,7 @@ class EpicCashWallet extends CoinServiceAPI { /// returns an empty String on success, error message on failure Future cancelPendingTransaction(String tx_slate_id) async { final String wallet = - (await _secureStore.read(key: '${_walletId}_wallet'))!; + (await _secureStore.read(key: '${_walletId}_wallet'))!; String? result; await m.protect(() async { @@ -754,7 +746,8 @@ class EpicCashWallet extends CoinServiceAPI { String receiverAddress = txData['addresss'] as String; await m.protect(() async { - if (receiverAddress.startsWith("http://") || receiverAddress.startsWith("https://")) { + if (receiverAddress.startsWith("http://") || + receiverAddress.startsWith("https://")) { const int selectionStrategyIsAll = 0; ReceivePort receivePort = await getIsolate({ "function": "txHttpSend", @@ -774,9 +767,8 @@ class EpicCashWallet extends CoinServiceAPI { throw Exception("txHttpSend isolate failed"); } stop(receivePort); - Logging.instance.log('Closing txHttpSend!\n $message', - level: LogLevel.Info); - + Logging.instance + .log('Closing txHttpSend!\n $message', level: LogLevel.Info); } else { ReceivePort receivePort = await getIsolate({ "function": "createTransaction", @@ -809,7 +801,6 @@ class EpicCashWallet extends CoinServiceAPI { await putSendToAddresses(sendTx); - Logging.instance.log("CONFIRM_RESULT_IS $sendTx", level: LogLevel.Info); final decodeData = json.decode(sendTx); @@ -818,9 +809,9 @@ class EpicCashWallet extends CoinServiceAPI { String errorMessage = decodeData[1] as String; throw Exception("Transaction failed with error code $errorMessage"); } else { - //If it's HTTP send no need to post to epicbox - if (!(receiverAddress.startsWith("http://") || receiverAddress.startsWith("https://"))) { + if (!(receiverAddress.startsWith("http://") || + receiverAddress.startsWith("https://"))) { final postSlateRequest = decodeData[1]; final postToServer = await postSlate( txData['addresss'] as String, postSlateRequest as String); @@ -969,10 +960,9 @@ class EpicCashWallet extends CoinServiceAPI { level: LogLevel.Info); final config = await getRealConfig(); - final password = - await _secureStore.read(key: '${_walletId}_password'); + final password = await _secureStore.read(key: '${_walletId}_password'); - final walletOpen = openWallet(config!, password!); + final walletOpen = openWallet(config, password!); await _secureStore.write(key: '${_walletId}_wallet', value: walletOpen); if ((DB.instance.get(boxName: walletId, key: "id")) == null) { @@ -1297,7 +1287,6 @@ class EpicCashWallet extends CoinServiceAPI { Future startScans() async { try { - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); var restoreHeight = @@ -1445,7 +1434,6 @@ class EpicCashWallet extends CoinServiceAPI { //Store Epic box address info await storeEpicboxInfo(); - } catch (e, s) { Logging.instance .log("Error recovering wallet $e\n$s", level: LogLevel.Error); @@ -1724,11 +1712,13 @@ class EpicCashWallet extends CoinServiceAPI { subscribeRequest['signature'] as String, slate as String); } - if (response.contains("Error Wallet store error: DB Not Found Error")) { + if (response + .contains("Error Wallet store error: DB Not Found Error")) { //Already processed - to be deleted - Logging.instance.log("DELETING_PROCESSED_SLATE", - level: LogLevel.Info); - final slateDelete = await deleteSlate(currentAddress, subscribeRequest['signature'] as String, slate as String); + Logging.instance + .log("DELETING_PROCESSED_SLATE", level: LogLevel.Info); + final slateDelete = await deleteSlate(currentAddress, + subscribeRequest['signature'] as String, slate as String); Logging.instance.log("DELETE_SLATE_RESPONSE $slateDelete", level: LogLevel.Info); } else { @@ -1738,14 +1728,14 @@ class EpicCashWallet extends CoinServiceAPI { if (slateStatus == "PendingProcessing") { //Encrypt slate String encryptedSlate = await getEncryptedSlate( - wallet!, + wallet, slateSender, currentReceivingIndex, epicboxConfig!, decodedResponse[1] as String); final postSlateToServer = - await postSlate(slateSender, encryptedSlate); + await postSlate(slateSender, encryptedSlate); await deleteSlate(currentAddress, subscribeRequest['signature'] as String, slate as String); @@ -1753,7 +1743,8 @@ class EpicCashWallet extends CoinServiceAPI { level: LogLevel.Info); } else { //Finalise Slate - final processSlate = json.decode(decodedResponse[1] as String); + final processSlate = + json.decode(decodedResponse[1] as String); Logging.instance.log( "PROCESSED_SLATE_TO_FINALIZE $processSlate", level: LogLevel.Info); @@ -1762,8 +1753,7 @@ class EpicCashWallet extends CoinServiceAPI { String txSlateId = tx[0]['tx_slate_id'] as String; Logging.instance .log("TX_SLATE_ID_IS $txSlateId", level: LogLevel.Info); - final postToNode = await postSlateToNode( - wallet!, txSlateId); + final postToNode = await postSlateToNode(wallet, txSlateId); await deleteSlate(currentAddress, subscribeRequest['signature'] as String, slate as String); Logging.instance.log("POST_SLATE_RESPONSE $postToNode", From 7dc44eb7daa3daf8e2fa31474402bb2379247f06 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 3 Sep 2022 12:37:54 -0600 Subject: [PATCH 07/11] trade details view bug fixes --- .../exchange_view/trade_details_view.dart | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index 16c34891a..68b1f49a6 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -10,6 +12,7 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/edit_trade_note_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'; @@ -63,6 +66,26 @@ class _TradeDetailsViewState extends ConsumerState { clipboard = widget.clipboard; transactionIfSentFromStack = widget.transactionIfSentFromStack; walletId = widget.walletId; + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + final trade = ref + .read(tradesServiceProvider) + .trades + .firstWhere((e) => e.id == tradeId); + + if (mounted && trade.statusObject == null || + trade.statusObject!.amountSendDecimal.isEmpty) { + final status = await ref + .read(changeNowProvider) + .getTransactionStatus(id: trade.id); + + if (mounted && status.value != null) { + await ref.read(tradesServiceProvider).edit( + trade: trade.copyWith(statusObject: status.value), + shouldNotifyListeners: true); + } + } + }); super.initState(); } @@ -77,8 +100,6 @@ class _TradeDetailsViewState extends ConsumerState { status = ChangeNowTransactionStatus.Failed; } - debugPrint("statusstatusstatusstatus: $status"); - debugPrint("statusstatusstatusstatusSTRING: $statusString"); switch (status) { case ChangeNowTransactionStatus.New: case ChangeNowTransactionStatus.Waiting: @@ -113,6 +134,11 @@ class _TradeDetailsViewState extends ConsumerState { debugPrint("hasTx: $hasTx"); debugPrint("trade: ${trade.toString()}"); + final sendAmount = Decimal.tryParse( + trade.statusObject?.amountSendDecimal ?? "") ?? + Decimal.tryParse(trade.statusObject?.expectedSendAmountDecimal ?? "") ?? + Decimal.parse("-1"); + return Scaffold( backgroundColor: CFColors.almostWhite, appBar: AppBar( @@ -150,7 +176,7 @@ class _TradeDetailsViewState extends ConsumerState { height: 4, ), SelectableText( - "${Format.localizedStringAsFixed(value: Decimal.parse(trade.statusObject?.amountSendDecimal ?? trade.amount), locale: ref.watch( + "${Format.localizedStringAsFixed(value: sendAmount, locale: ref.watch( localeServiceChangeNotifierProvider .select((value) => value.locale), ), decimalPlaces: trade.fromCurrency.toLowerCase() == "xmr" ? 12 : 8)} ${trade.fromCurrency.toUpperCase()}", @@ -205,7 +231,7 @@ class _TradeDetailsViewState extends ConsumerState { ], ), ), - if (!sentFromStack && hasTx) + if (!sentFromStack && !hasTx) const SizedBox( height: 12, ), @@ -214,9 +240,8 @@ class _TradeDetailsViewState extends ConsumerState { color: CFColors.warningBackground, child: RichText( text: TextSpan( - text: "You must send at least ${Decimal.parse( - trade.statusObject!.amountSendDecimal, - ).toStringAsFixed( + text: + "You must send at least ${sendAmount.toStringAsFixed( trade.fromCurrency.toLowerCase() == "xmr" ? 12 : 8, )} ${trade.fromCurrency.toUpperCase()}. ", style: STextStyles.label.copyWith( @@ -225,9 +250,8 @@ class _TradeDetailsViewState extends ConsumerState { ), children: [ TextSpan( - text: "If you send less than ${Decimal.parse( - trade.statusObject!.amountSendDecimal, - ).toStringAsFixed( + text: + "If you send less than ${sendAmount.toStringAsFixed( trade.fromCurrency.toLowerCase() == "xmr" ? 12 : 8, @@ -623,11 +647,11 @@ class _TradeDetailsViewState extends ConsumerState { onTap: () async { final data = ClipboardData(text: trade.id); await clipboard.setData(data); - showFloatingFlushBar( + unawaited(showFloatingFlushBar( type: FlushBarType.info, message: "Copied to clipboard", context: context, - ); + )); }, child: SvgPicture.asset( Assets.svg.copy, From c366e4a9c53dc2ef6095f7ad3266a46e246bae3c Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 5 Sep 2022 17:31:24 -0600 Subject: [PATCH 08/11] wrap swb open from file in platform check --- lib/main.dart | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 5ceb47e1a..8345bd021 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -344,20 +344,23 @@ class _MaterialAppWithThemeState extends ConsumerState _prefs = ref.read(prefsChangeNotifierProvider); _wallets = ref.read(walletsChangeNotifierProvider); - WidgetsBinding.instance.addPostFrameCallback((_) async { - // fetch open file if it exists - await getOpenFile(); + if (Platform.isAndroid) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + // fetch open file if it exists + await getOpenFile(); - if (ref.read(openedFromSWBFileStringStateProvider.state).state != null) { - // waiting for loading to complete before going straight to restore if the app was opened via file - await loadingCompleter.future; + if (ref.read(openedFromSWBFileStringStateProvider.state).state != + null) { + // waiting for loading to complete before going straight to restore if the app was opened via file + await loadingCompleter.future; - await goToRestoreSWB( - ref.read(openedFromSWBFileStringStateProvider.state).state!); - ref.read(openedFromSWBFileStringStateProvider.state).state = null; - } - // ref.read(shouldShowLockscreenOnResumeStateProvider.state).state = false; - }); + await goToRestoreSWB( + ref.read(openedFromSWBFileStringStateProvider.state).state!); + ref.read(openedFromSWBFileStringStateProvider.state).state = null; + } + // ref.read(shouldShowLockscreenOnResumeStateProvider.state).state = false; + }); + } super.initState(); } @@ -378,14 +381,16 @@ class _MaterialAppWithThemeState extends ConsumerState case AppLifecycleState.paused: break; case AppLifecycleState.resumed: - // fetch open file if it exists - await getOpenFile(); - // go straight to restore if the app was resumed via file - if (ref.read(openedFromSWBFileStringStateProvider.state).state != - null) { - await goToRestoreSWB( - ref.read(openedFromSWBFileStringStateProvider.state).state!); - ref.read(openedFromSWBFileStringStateProvider.state).state = null; + if (Platform.isAndroid) { + // fetch open file if it exists + await getOpenFile(); + // go straight to restore if the app was resumed via file + if (ref.read(openedFromSWBFileStringStateProvider.state).state != + null) { + await goToRestoreSWB( + ref.read(openedFromSWBFileStringStateProvider.state).state!); + ref.read(openedFromSWBFileStringStateProvider.state).state = null; + } } // if (ref.read(hasAuthenticatedOnStartStateProvider.state).state && // ref.read(shouldShowLockscreenOnResumeStateProvider.state).state) { @@ -419,6 +424,7 @@ class _MaterialAppWithThemeState extends ConsumerState } } + /// should only be called on android currently Future getOpenFile() async { // update provider with new file content state ref.read(openedFromSWBFileStringStateProvider.state).state = @@ -432,6 +438,7 @@ class _MaterialAppWithThemeState extends ConsumerState level: LogLevel.Info); } + /// should only be called on android currently Future resetOpenPath() async { await platform.invokeMethod("resetOpenPath"); } From d6618518ad130a935080db38206a2c8c045fd2f5 Mon Sep 17 00:00:00 2001 From: julian Date: Mon, 5 Sep 2022 19:18:45 -0600 Subject: [PATCH 09/11] receive screen redesign and added generate new address button --- assets/svg/share-2.svg | 14 + .../generate_receiving_uri_qr_code_view.dart | 45 +++- lib/pages/receive_view/receive_view.dart | 242 ++++++++++++------ lib/pages/wallet_view/wallet_view.dart | 10 +- lib/route_generator.dart | 2 +- .../coins/bitcoin/bitcoin_wallet.dart | 30 +++ lib/services/coins/coin_service.dart | 2 + .../coins/dogecoin/dogecoin_wallet.dart | 29 +++ .../coins/epiccash/epiccash_wallet.dart | 25 ++ lib/services/coins/firo/firo_wallet.dart | 24 ++ lib/services/coins/manager.dart | 8 + lib/services/coins/monero/monero_wallet.dart | 29 +++ lib/utilities/assets.dart | 1 + pubspec.yaml | 1 + 14 files changed, 368 insertions(+), 94 deletions(-) create mode 100644 assets/svg/share-2.svg diff --git a/assets/svg/share-2.svg b/assets/svg/share-2.svg new file mode 100644 index 000000000..d002d2bff --- /dev/null +++ b/assets/svg/share-2.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index a473d0d45..037677a40 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -6,10 +6,12 @@ import 'dart:ui' as ui; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:path_provider/path_provider.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:share_plus/share_plus.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/cfcolors.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -318,8 +320,7 @@ class _GenerateUriQrCodeViewState extends State { child: QrImage( data: uriString, size: width, - backgroundColor: - CFColors.almostWhite, + backgroundColor: CFColors.white, foregroundColor: CFColors.stackAccent, ), @@ -344,12 +345,40 @@ class _GenerateUriQrCodeViewState extends State { CFColors.buttonGray, ), ), - child: Text( - "Share", - style: - STextStyles.button.copyWith( - color: CFColors.stackAccent, - ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Center( + child: SvgPicture.asset( + Assets.svg.share, + width: 14, + height: 14, + ), + ), + const SizedBox( + width: 4, + ), + Column( + children: [ + Text( + "Share", + textAlign: + TextAlign.center, + style: STextStyles.button + .copyWith( + color: CFColors + .stackAccent, + ), + ), + const SizedBox( + height: 2, + ), + ], + ), + ], ), ), ), diff --git a/lib/pages/receive_view/receive_view.dart b/lib/pages/receive_view/receive_view.dart index 4662abac2..b0f49682b 100644 --- a/lib/pages/receive_view/receive_view.dart +++ b/lib/pages/receive_view/receive_view.dart @@ -1,36 +1,114 @@ +import 'dart:async'; + 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:qr_flutter/qr_flutter.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart'; +import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/cfcolors.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; -import 'package:stackwallet/utilities/constants.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/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/custom_loading_overlay.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; -class ReceiveView extends StatelessWidget { +class ReceiveView extends ConsumerStatefulWidget { const ReceiveView({ Key? key, required this.coin, - required this.receivingAddress, + required this.walletId, this.clipboard = const ClipboardWrapper(), }) : super(key: key); static const String routeName = "/receiveView"; final Coin coin; - final String receivingAddress; + final String walletId; final ClipboardInterface clipboard; + @override + ConsumerState createState() => _ReceiveViewState(); +} + +class _ReceiveViewState extends ConsumerState { + late final Coin coin; + late final String walletId; + late final ClipboardInterface clipboard; + + Future generateNewAddress() async { + bool shouldPop = false; + unawaited( + showDialog( + context: context, + builder: (_) { + return WillPopScope( + onWillPop: () async => shouldPop, + child: const CustomLoadingOverlay( + message: "Generating address", + eventBus: null, + ), + ); + }, + ), + ); + + await ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .generateNewAddress(); + + shouldPop = true; + + if (mounted) { + Navigator.of(context) + .popUntil(ModalRoute.withName(ReceiveView.routeName)); + } + } + + String receivingAddress = ""; + + @override + void initState() { + walletId = widget.walletId; + coin = widget.coin; + clipboard = widget.clipboard; + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + final address = await ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .currentReceivingAddress; + setState(() { + receivingAddress = address; + }); + }); + + super.initState(); + } + @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); + + ref.listen( + ref + .read(walletsChangeNotifierProvider) + .getManagerProvider(walletId) + .select((value) => value.currentReceivingAddress), + (previous, next) { + if (next is Future) { + next.then((value) => setState(() => receivingAddress = value)); + } + }); + return Scaffold( backgroundColor: CFColors.almostWhite, appBar: AppBar( @@ -52,15 +130,19 @@ class ReceiveView extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Container( - decoration: BoxDecoration( - color: CFColors.white, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - child: Padding( - padding: const EdgeInsets.all(12.0), + GestureDetector( + onTap: () { + clipboard.setData( + ClipboardData(text: receivingAddress), + ); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + }, + child: RoundedWhiteContainer( child: Column( children: [ Row( @@ -70,35 +152,22 @@ class ReceiveView extends StatelessWidget { style: STextStyles.itemSubtitle, ), const Spacer(), - GestureDetector( - onTap: () { - clipboard.setData( - ClipboardData(text: receivingAddress), - ); - showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - iconAsset: Assets.svg.copy, - context: context, - ); - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - width: 10, - height: 10, - color: CFColors.link2, - ), - const SizedBox( - width: 4, - ), - Text( - "Copy", - style: STextStyles.link2, - ), - ], - ), + Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + width: 10, + height: 10, + color: CFColors.link2, + ), + const SizedBox( + width: 4, + ), + Text( + "Copy", + style: STextStyles.link2, + ), + ], ), ], ), @@ -119,47 +188,62 @@ class ReceiveView extends StatelessWidget { ), ), ), - const SizedBox( - height: 30, - ), - Center( - child: QrImage( - data: "${coin.uriScheme}:$receivingAddress", - size: MediaQuery.of(context).size.width / 2, - foregroundColor: CFColors.stackAccent, + if (coin != Coin.epicCash) + const SizedBox( + height: 12, ), - ), - const SizedBox( - height: 30, - ), - // Spacer( - // flex: 7, - // ), - TextButton( - onPressed: () { - Navigator.of(context).push( - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator.useMaterialPageRoute, - builder: (_) => GenerateUriQrCodeView( - coin: coin, - receivingAddress: receivingAddress, - ), - settings: const RouteSettings( - name: GenerateUriQrCodeView.routeName, - ), + if (coin != Coin.epicCash) + TextButton( + onPressed: generateNewAddress, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + CFColors.buttonGray, + ), + ), + child: Text( + "Generate new address", + style: STextStyles.button.copyWith( + color: CFColors.stackAccent, ), - ); - }, - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - CFColors.buttonGray, ), ), - child: Text( - "Generate QR Code", - style: STextStyles.button.copyWith( - color: CFColors.stackAccent, + const SizedBox( + height: 30, + ), + RoundedWhiteContainer( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Column( + children: [ + QrImage( + data: "${coin.uriScheme}:$receivingAddress", + size: MediaQuery.of(context).size.width / 2, + foregroundColor: CFColors.stackAccent, + ), + const SizedBox( + height: 20, + ), + BlueTextButton( + text: "Create new QR code", + onTap: () async { + unawaited(Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => GenerateUriQrCodeView( + coin: coin, + receivingAddress: receivingAddress, + ), + settings: const RouteSettings( + name: GenerateUriQrCodeView.routeName, + ), + ), + )); + }, + ), + ], + ), ), ), ), diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 3f097ddf3..198e30904 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -532,19 +532,17 @@ class _WalletViewState extends ConsumerState { onExchangePressed: () => _onExchangePressed(context), onReceivePressed: () async { - final address = await ref - .read(managerProvider) - .currentReceivingAddress; final coin = ref.read(managerProvider).coin; if (mounted) { - Navigator.of(context).pushNamed( + unawaited( + Navigator.of(context).pushNamed( ReceiveView.routeName, arguments: Tuple2( - address, + walletId, coin, ), - ); + )); } }, onSendPressed: () { diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 329018d9a..001c21b0d 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -681,7 +681,7 @@ class RouteGenerator { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => ReceiveView( - receivingAddress: args.item1, + walletId: args.item1, coin: args.item2, ), settings: RouteSettings( diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index 0750e9b93..342fb7b91 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -3825,4 +3825,34 @@ class BitcoinWallet extends CoinServiceAPI { return available - estimatedFee; } + + @override + Future generateNewAddress() async { + try { + await _incrementAddressIndexForChain( + 0, DerivePathType.bip84); // First increment the receiving index + final newReceivingIndex = DB.instance.get( + boxName: walletId, + key: 'receivingIndexP2WPKH') as int; // Check the new receiving index + final newReceivingAddress = await _generateAddressForChain( + 0, + newReceivingIndex, + DerivePathType + .bip84); // Use new index to derive a new receiving address + await _addToAddressesArrayForChain( + newReceivingAddress, + 0, + DerivePathType + .bip84); // Add that new receiving address to the array of receiving addresses + _currentReceivingAddress = Future(() => + newReceivingAddress); // Set the new receiving address that the service + + return true; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from generateNewAddress(): $e\n$s", + level: LogLevel.Error); + return false; + } + } } diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 3d8b1bd10..bc0e4be28 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -212,4 +212,6 @@ abstract class CoinServiceAPI { bool get isConnected; Future estimateFeeFor(int satoshiAmount, int feeRate); + + Future generateNewAddress(); } diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index 7a3a70a96..a7b2132ad 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -3011,6 +3011,35 @@ class DogecoinWallet extends CoinServiceAPI { return available - estimatedFee; } + + Future generateNewAddress() async { + try { + await _incrementAddressIndexForChain( + 0, DerivePathType.bip44); // First increment the receiving index + final newReceivingIndex = DB.instance.get( + boxName: walletId, + key: 'receivingIndexP2PKH') as int; // Check the new receiving index + final newReceivingAddress = await _generateAddressForChain( + 0, + newReceivingIndex, + DerivePathType + .bip44); // Use new index to derive a new receiving address + await _addToAddressesArrayForChain( + newReceivingAddress, + 0, + DerivePathType + .bip44); // Add that new receiving address to the array of receiving addresses + _currentReceivingAddressP2PKH = Future(() => + newReceivingAddress); // Set the new receiving address that the service + + return true; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from generateNewAddress(): $e\n$s", + level: LogLevel.Error); + return false; + } + } } // Dogecoin Network diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index a99718dd3..514ddb79f 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -2306,4 +2306,29 @@ class EpicCashWallet extends CoinServiceAPI { // TODO: implement this return currentFee; } + + // not used in epic currently + @override + Future generateNewAddress() async { + try { + // await incrementAddressIndexForChain( + // 0); // First increment the receiving index + // final newReceivingIndex = + // DB.instance.get(boxName: walletId, key: 'receivingIndex') + // as int; // Check the new receiving index + // final newReceivingAddress = await _generateAddressForChain(0, + // newReceivingIndex); // Use new index to derive a new receiving address + // await addToAddressesArrayForChain(newReceivingAddress, + // 0); // Add that new receiving address to the array of receiving addresses + // _currentReceivingAddress = Future(() => + // newReceivingAddress); // Set the new receiving address that the service + + return true; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from generateNewAddress(): $e\n$s", + level: LogLevel.Error); + return false; + } + } } diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index c8bd9be24..4108cce39 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -3857,4 +3857,28 @@ class FiroWallet extends CoinServiceAPI { rethrow; } } + + @override + Future generateNewAddress() async { + try { + await incrementAddressIndexForChain( + 0); // First increment the receiving index + final newReceivingIndex = + DB.instance.get(boxName: walletId, key: 'receivingIndex') + as int; // Check the new receiving index + final newReceivingAddress = await _generateAddressForChain(0, + newReceivingIndex); // Use new index to derive a new receiving address + await addToAddressesArrayForChain(newReceivingAddress, + 0); // Add that new receiving address to the array of receiving addresses + _currentReceivingAddress = Future(() => + newReceivingAddress); // Set the new receiving address that the service + + return true; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from generateNewAddress(): $e\n$s", + level: LogLevel.Error); + return false; + } + } } diff --git a/lib/services/coins/manager.dart b/lib/services/coins/manager.dart index 659db73bd..c8329ec28 100644 --- a/lib/services/coins/manager.dart +++ b/lib/services/coins/manager.dart @@ -266,4 +266,12 @@ class Manager with ChangeNotifier { Future estimateFeeFor(int satoshiAmount, int feeRate) async { return _currentWallet.estimateFeeFor(satoshiAmount, feeRate); } + + Future generateNewAddress() async { + final success = await _currentWallet.generateNewAddress(); + if (success) { + notifyListeners(); + } + return success; + } } diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 95794a356..b63e711a4 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -1521,4 +1521,33 @@ class MoneroWallet extends CoinServiceAPI { 10000; return fee; } + + @override + Future generateNewAddress() async { + try { + const String indexKey = "receivingIndex"; + // First increment the receiving index + await _incrementAddressIndexForChain(0); + final newReceivingIndex = + DB.instance.get(boxName: walletId, key: indexKey) as int; + + // Use new index to derive a new receiving address + final newReceivingAddress = + await _generateAddressForChain(0, newReceivingIndex); + + // Add that new receiving address to the array of receiving addresses + await _addToAddressesArrayForChain(newReceivingAddress, 0); + + // Set the new receiving address that the service + + _currentReceivingAddress = Future(() => newReceivingAddress); + + return true; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from generateNewAddress(): $e\n$s", + level: LogLevel.Error); + return false; + } + } } diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 9adc50e59..906dd509c 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -84,6 +84,7 @@ class _SVG { String get solidSliders => "assets/svg/sliders-solid.svg"; String get questionMessage => "assets/svg/message-question.svg"; String get envelope => "assets/svg/envelope.svg"; + String get share => "assets/svg/share-2.svg"; String get receive => "assets/svg/tx-icon-receive.svg"; String get receivePending => "assets/svg/tx-icon-receive-pending.svg"; diff --git a/pubspec.yaml b/pubspec.yaml index f2faf1497..7f8bd4507 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -255,6 +255,7 @@ flutter: - assets/svg/sliders-solid.svg - assets/svg/message-question.svg - assets/svg/envelope.svg + - assets/svg/share-2.svg # coin icons - assets/svg/coin_icons/Bitcoin.svg - assets/svg/coin_icons/Dogecoin.svg From 8fc2e5ee1b01d1585b893f127f77766f4fafd896 Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 6 Sep 2022 09:12:23 -0600 Subject: [PATCH 10/11] hive data migrate to v1 for lelantus coins update --- lib/hive/db.dart | 46 +++++- lib/main.dart | 19 ++- lib/utilities/constants.dart | 2 +- lib/utilities/db_version_migration.dart | 191 ++++++++++++++---------- 4 files changed, 163 insertions(+), 95 deletions(-) diff --git a/lib/hive/db.dart b/lib/hive/db.dart index da161ce2d..79e98050b 100644 --- a/lib/hive/db.dart +++ b/lib/hive/db.dart @@ -27,6 +27,7 @@ class DB { static const String boxNamePrefs = "prefs"; static const String boxNameWalletsToDeleteOnStart = "walletsToDeleteOnStart"; static const String boxNamePriceCache = "priceAPIPrice24hCache"; + static const String boxNameDBInfo = "dbInfo"; String boxNameTxCache({required Coin coin}) => "${coin.name}_txCache"; String boxNameSetCache({required Coin coin}) => @@ -50,6 +51,7 @@ class DB { late final Box _walletInfoSource; late final Box _boxPrefs; late final Box _boxTradeLookup; + late final Box _boxDBInfo; final Map> _walletBoxes = {}; @@ -80,13 +82,40 @@ class DB { // open hive boxes Future init() async { if (!_initialized) { + if (Hive.isBoxOpen(boxNameDBInfo)) { + _boxDBInfo = Hive.box(boxNameDBInfo); + } else { + _boxDBInfo = await Hive.openBox(boxNameDBInfo); + } await Hive.openBox(boxNameWalletsToDeleteOnStart); - _boxPrefs = await Hive.openBox(boxNamePrefs); + + if (Hive.isBoxOpen(boxNamePrefs)) { + _boxPrefs = Hive.box(boxNamePrefs); + } else { + _boxPrefs = await Hive.openBox(boxNamePrefs); + } + _boxAddressBook = await Hive.openBox(boxNameAddressBook); _boxDebugInfo = await Hive.openBox(boxNameDebugInfo); - _boxNodeModels = await Hive.openBox(boxNameNodeModels); - _boxPrimaryNodes = await Hive.openBox(boxNamePrimaryNodes); - _boxAllWalletsData = await Hive.openBox(boxNameAllWalletsData); + + if (Hive.isBoxOpen(boxNameNodeModels)) { + _boxNodeModels = Hive.box(boxNameNodeModels); + } else { + _boxNodeModels = await Hive.openBox(boxNameNodeModels); + } + + if (Hive.isBoxOpen(boxNamePrimaryNodes)) { + _boxPrimaryNodes = Hive.box(boxNamePrimaryNodes); + } else { + _boxPrimaryNodes = await Hive.openBox(boxNamePrimaryNodes); + } + + if (Hive.isBoxOpen(boxNameAllWalletsData)) { + _boxAllWalletsData = Hive.box(boxNameAllWalletsData); + } else { + _boxAllWalletsData = await Hive.openBox(boxNameAllWalletsData); + } + _boxNotifications = await Hive.openBox(boxNameNotifications); _boxWatchedTransactions = @@ -116,8 +145,13 @@ class DB { name, WalletInfo.fromJson(Map.from(dyn as Map)))); for (final entry in mapped.entries) { - _walletBoxes[entry.value.walletId] = - await Hive.openBox(entry.value.walletId); + if (Hive.isBoxOpen(entry.value.walletId)) { + _walletBoxes[entry.value.walletId] = + Hive.box(entry.value.walletId); + } else { + _walletBoxes[entry.value.walletId] = + await Hive.openBox(entry.value.walletId); + } } } diff --git a/lib/main.dart b/lib/main.dart index 8345bd021..10067ac19 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -51,6 +51,7 @@ import 'package:stackwallet/services/trade_service.dart'; import 'package:stackwallet/services/wallets.dart'; import 'package:stackwallet/utilities/cfcolors.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/db_version_migration.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; @@ -119,18 +120,16 @@ void main() async { Hive.registerAdapter(UnspentCoinsInfoAdapter()); + await Hive.openBox(DB.boxNameDBInfo); + int dbVersion = DB.instance.get( + boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ?? + 0; + if (dbVersion < Constants.currentHiveDbVersion) { + await DbVersionMigrator().migrate(dbVersion); + } + monero.onStartup(); - // final wallets = await Hive.openBox('wallets'); - // await wallets.put('currentWalletName', ""); - - // NOT USED YET - // int dbVersion = await wallets.get("db_version"); - // if (dbVersion == null || dbVersion < Constants.currentDbVersion) { - // if (dbVersion == null) dbVersion = 0; - // await DbVersionMigrator().migrate(dbVersion); - // } - // SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, // overlays: [SystemUiOverlay.bottom]); await NotificationApi.init(); diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index b8e5b5bce..3b82cde40 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -33,7 +33,7 @@ abstract class Constants { // Enable Logger.print statements static const bool disableLogger = false; - static const int currentDbVersion = 0; + static const int currentHiveDbVersion = 1; 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 ad92b5a59..ec26c5c13 100644 --- a/lib/utilities/db_version_migration.dart +++ b/lib/utilities/db_version_migration.dart @@ -1,5 +1,16 @@ 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/lelantus_coin.dart'; +import 'package:stackwallet/models/node_model.dart'; +import 'package:stackwallet/services/node_service.dart'; +import 'package:stackwallet/services/wallets_service.dart'; +import 'package:stackwallet/utilities/default_nodes.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; class DbVersionMigrator { Future migrate( @@ -8,83 +19,107 @@ class DbVersionMigrator { FlutterSecureStorage(), ), }) async { - // final wallets = await Hive.openBox('wallets'); - // final names = Map.from((await wallets.get("names")) ?? {}); - // - // switch (fromVersion) { - // case 0: - // // migrate each - // for (final entry in names.entries) { - // final walletId = entry.value; - // final walletName = entry.key; - // - // // move main/test network to walletId based - // final network = await wallets.get("${entry.key}_network"); - // await wallets.put("${walletId}_network", network); - // await wallets.delete("${walletName}_network"); - // - // final old = await Hive.openBox(walletName); - // final wallet = await Hive.openBox(walletId); - // - // // notes - // final oldNotes = await old.get("notes"); - // await wallet.put("notes", oldNotes); - // await old.delete("notes"); - // - // // address book - // final addressBook = await old.get("addressBookEntries"); - // await wallet.put("addressBookEntries", addressBook); - // await old.put("addressBookEntries", null); - // - // // receiveDerivations - // Map newReceiveDerivations = {}; - // final receiveDerivations = - // Map.from(await old.get("receiveDerivations") ?? {}); - // - // for (int i = 0; i < receiveDerivations.length; i++) { - // receiveDerivations[i].remove("fingerprint"); - // receiveDerivations[i].remove("identifier"); - // receiveDerivations[i].remove("privateKey"); - // newReceiveDerivations["$i"] = receiveDerivations[i]; - // } - // final receiveDerivationsString = jsonEncode(newReceiveDerivations); - // - // await secureStore.write( - // key: "${walletId}_receiveDerivations", - // value: receiveDerivationsString); - // await old.delete("receiveDerivations"); - // - // // changeDerivations - // Map newChangeDerivations = {}; - // final changeDerivations = - // Map.from(await old.get("changeDerivations") ?? {}); - // - // for (int i = 0; i < changeDerivations.length; i++) { - // changeDerivations[i].remove("fingerprint"); - // changeDerivations[i].remove("identifier"); - // changeDerivations[i].remove("privateKey"); - // newChangeDerivations["$i"] = changeDerivations[i]; - // } - // final changeDerivationsString = jsonEncode(newChangeDerivations); - // - // await secureStore.write( - // key: "${walletId}_changeDerivations", - // value: changeDerivationsString); - // await old.delete("changeDerivations"); - // } - // - // // finally update version - // await wallets.put("db_version", 1); - // - // return; - // // not needed yet - // // return migrate(1); - // - // // case 1: - // // return migrate(2); - // - // default: - // return; - // } + switch (fromVersion) { + case 0: + await Hive.openBox(DB.boxNameAllWalletsData); + await Hive.openBox(DB.boxNamePrefs); + final walletsService = WalletsService(); + final nodeService = NodeService(); + final prefs = Prefs.instance; + final walletInfoList = await walletsService.walletNames; + await prefs.init(); + + ElectrumX? client; + int? latestSetId; + + // only instantiate client if there are firo wallets + if (walletInfoList.values.any((element) => element.coin == Coin.firo)) { + await Hive.openBox(DB.boxNameNodeModels); + await Hive.openBox(DB.boxNamePrimaryNodes); + final node = nodeService.getPrimaryNodeFor(coin: Coin.firo) ?? + DefaultNodes.firo; + List failovers = nodeService + .failoverNodesFor(coin: Coin.firo) + .map( + (e) => ElectrumXNode( + address: e.host, + port: e.port, + name: e.name, + id: e.id, + useSSL: e.useSSL, + ), + ) + .toList(); + + client = ElectrumX.from( + node: ElectrumXNode( + address: node.host, + port: node.port, + name: node.name, + id: node.id, + useSSL: node.useSSL), + prefs: prefs, + failovers: failovers, + ); + + try { + latestSetId = await client.getLatestCoinId(); + } catch (e) { + // default to 2 for now + latestSetId = 2; + Logging.instance.log( + "Failed to fetch latest coin id during firo db migrate: $e \nUsing a default value of 2", + level: LogLevel.Warning); + } + } + + for (final walletInfo in walletInfoList.values) { + // migrate each firo wallet's lelantus coins + if (walletInfo.coin == Coin.firo) { + await Hive.openBox(walletInfo.walletId); + final _lelantusCoins = DB.instance.get( + boxName: walletInfo.walletId, key: '_lelantus_coins') as List?; + final List> lelantusCoins = []; + for (var lCoin in _lelantusCoins ?? []) { + lelantusCoins + .add({lCoin.keys.first: lCoin.values.first as LelantusCoin}); + } + + List> coins = []; + for (final element in lelantusCoins) { + LelantusCoin coin = element.values.first; + int anonSetId = coin.anonymitySetId; + if (coin.anonymitySetId == 1 && + (coin.publicCoin == '' || + coin.publicCoin == "jmintData.publicCoin")) { + anonSetId = latestSetId!; + } + coins.add({ + element.keys.first: LelantusCoin(coin.index, coin.value, + coin.publicCoin, coin.txId, anonSetId, coin.isUsed) + }); + } + Logger.print("newcoins $coins", normalLength: false); + await DB.instance.put( + boxName: walletInfo.walletId, + key: '_lelantus_coins', + value: coins); + } + } + + // finally update version + await DB.instance.put( + boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 1); + + return; + // not needed yet + // return migrate(1); + + // case 1: + // return migrate(2); + + default: + return; + } } } From 2fb96f4a96a8ebab95d71ccc439a7cc6225f13ea Mon Sep 17 00:00:00 2001 From: julian Date: Tue, 6 Sep 2022 09:13:10 -0600 Subject: [PATCH 11/11] added missing override --- test/services/coins/fake_coin_service_api.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/services/coins/fake_coin_service_api.dart b/test/services/coins/fake_coin_service_api.dart index c368db129..a3ae28a4b 100644 --- a/test/services/coins/fake_coin_service_api.dart +++ b/test/services/coins/fake_coin_service_api.dart @@ -176,4 +176,10 @@ class FakeCoinServiceAPI extends CoinServiceAPI { // TODO: implement testNetworkConnection throw UnimplementedError(); } + + @override + Future generateNewAddress() { + // TODO: implement generateNewAddress + throw UnimplementedError(); + } }