From 764461a8b3e0b936294d9b2b4c52bb5965ceb61e Mon Sep 17 00:00:00 2001 From: naezith Date: Mon, 9 Oct 2023 13:22:59 +0300 Subject: [PATCH 1/7] zhtlc allow resync on app resume --- lib/blocs/coins_bloc.dart | 5 ++++- lib/services/mm_service.dart | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/blocs/coins_bloc.dart b/lib/blocs/coins_bloc.dart index 19c7edeca..52aa4afd2 100644 --- a/lib/blocs/coins_bloc.dart +++ b/lib/blocs/coins_bloc.dart @@ -461,7 +461,7 @@ class CoinsBloc implements BlocBase { /// Handle the coins user has picked for activation. /// Also used for coin activations during the application startup. - Future enableCoins(List coins) async { + Future enableCoins(List coins, {initialization = false}) async { await pauseUntil(() => !_coinsLock, maxMs: 3000); _coinsLock = true; @@ -483,6 +483,9 @@ class CoinsBloc implements BlocBase { .map((c) => c.abbr) .toList(); + // Allow for resyncing of existing coins at app launch or resuming + _zCoinRepository.willInitialize = initialization; + await _zCoinRepository.addRequestedActivatedCoins(requestedZCoins); // await _zCoinRepository.setRequestedActivatedCoins(requestedZCoins); diff --git a/lib/services/mm_service.dart b/lib/services/mm_service.dart index eefb85aa1..520d8ea7c 100644 --- a/lib/services/mm_service.dart +++ b/lib/services/mm_service.dart @@ -467,7 +467,7 @@ class MMService { await coinsBloc.activateCoinKickStart(); final active = await coinsBloc.electrumCoins(); - await coinsBloc.enableCoins(active); + await coinsBloc.enableCoins(active, initialization: true); for (int i = 0; i < 2; i++) { await coinsBloc.retryActivatingSuspendedCoins(); From 06c162cdd2e638e3b603df4f6259e0ae8890d327 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:23:15 +0200 Subject: [PATCH 2/7] Fix iOS warning text style and re-organise form order Fix iOS warning text style and re-organise form order so that the date picker is below the associated option to indicate that the date select is associated with the second option. --- .../widgets/z_coin_status_list_tile.dart | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart b/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart index 81ac8c241..2cd37ae9b 100644 --- a/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart +++ b/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart @@ -292,6 +292,41 @@ Future> _showConfirmationDialog(BuildContext context) { }); }, ), + + // Date Picker shown if sync type is specified date + AnimatedContainer( + height: _syncType == SyncType.specifiedDate ? 80 : 0, + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: ClipRRect( + child: Column( + children: [ + ElevatedButton( + onPressed: () async { + final DateTime pickedDate = await showDatePicker( + context: context, + initialDate: _selectedDate, + firstDate: DateTime(2000), + lastDate: DateTime.now(), + ); + if (pickedDate != null && + pickedDate != _selectedDate) + setState(() { + _selectedDate = pickedDate; + }); + }, + child: Text(localisations.selectDate), + ), + // Display the selected date + Text( + '${localisations.startDate}: ' + "${DateFormat('yyyy-MM-dd').format(_selectedDate)}", + ), + ], + ), + ), + ), + RadioListTile( title: Text(localisations.syncFromSaplingActivation), value: SyncType.fullSync, @@ -302,35 +337,6 @@ Future> _showConfirmationDialog(BuildContext context) { }); }, ), - // Date Picker - _syncType == SyncType.specifiedDate - ? Column( - children: [ - ElevatedButton( - onPressed: () async { - final DateTime pickedDate = - await showDatePicker( - context: context, - initialDate: _selectedDate, - firstDate: DateTime(2000), - lastDate: DateTime.now(), - ); - if (pickedDate != null && - pickedDate != _selectedDate) - setState(() { - _selectedDate = pickedDate; - }); - }, - child: Text(localisations.selectDate), - ), - // Display the selected date - Text( - '${localisations.startDate}: ' - "${DateFormat('yyyy-MM-dd').format(_selectedDate)}", - ), - ], - ) - : SizedBox.shrink(), SizedBox(height: 16), // Sync Type Description @@ -345,19 +351,13 @@ Future> _showConfirmationDialog(BuildContext context) { if (Platform.isIOS) ...[ SizedBox(height: 16), - ListTile( - leading: Icon( - Icons.warning, - color: Colors.amber, - ), - dense: true, - title: Text( - localisations.minimizingWillTerminate, - style: DefaultTextStyle.of(context) - .style - .apply(color: Colors.amber), - ), - ) + Text( + localisations.minimizingWillTerminate, + style: Theme.of(context) + .textTheme + .bodyText2 + .copyWith(color: Colors.amber), + ), ], if (_syncType == SyncType.fullSync || _syncType == SyncType.specifiedDate) From d31b04cea2adaceea2ac0843be4f05316396b5ed Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:36:34 +0200 Subject: [PATCH 3/7] Minor bug fixes and improved error handling --- lib/blocs/coins_bloc.dart | 5 +---- lib/model/cex_provider.dart | 12 ++++++++++-- lib/model/transaction_data.dart | 16 ++++++++++++---- lib/model/transactions.dart | 26 ++++++++++++++------------ lib/screens/portfolio/coins_page.dart | 15 ++++++++------- lib/services/mm_service.dart | 16 ++++++++++++---- lib/services/notif_service.dart | 2 +- 7 files changed, 58 insertions(+), 34 deletions(-) diff --git a/lib/blocs/coins_bloc.dart b/lib/blocs/coins_bloc.dart index 52aa4afd2..d6b76f953 100644 --- a/lib/blocs/coins_bloc.dart +++ b/lib/blocs/coins_bloc.dart @@ -483,9 +483,6 @@ class CoinsBloc implements BlocBase { .map((c) => c.abbr) .toList(); - // Allow for resyncing of existing coins at app launch or resuming - _zCoinRepository.willInitialize = initialization; - await _zCoinRepository.addRequestedActivatedCoins(requestedZCoins); // await _zCoinRepository.setRequestedActivatedCoins(requestedZCoins); @@ -827,7 +824,7 @@ class CoinsBloc implements BlocBase { if (transactions is Transactions) { transactions.camouflageIfNeeded(); - if (transactions.result.transactions.isNotEmpty) { + if ((transactions.result?.transactions ?? []).isNotEmpty) { return transactions.result.transactions[0]; } return null; diff --git a/lib/model/cex_provider.dart b/lib/model/cex_provider.dart index 4a43be6a1..33723b880 100644 --- a/lib/model/cex_provider.dart +++ b/lib/model/cex_provider.dart @@ -377,7 +377,10 @@ class CexPrices { _init(); } + bool isInitialized = false; + Future _init() async { + if (isInitialized) return; prefs = await SharedPreferences.getInstance(); activeCurrency = prefs.getInt('activeCurrency') ?? 0; _selectedFiat = prefs.getString('selectedFiat') ?? 'USD'; @@ -389,6 +392,8 @@ class CexPrices { updatePrices(); updateRates(); }); + + isInitialized = true; } List currencies; @@ -617,7 +622,8 @@ class CexPrices { Map json; try { - json = jsonDecode(_body); + final isJsonString = _body.startsWith('{'); + json = isJsonString ? jsonDecode(_body) : null; } catch (e) { Log('cex_provider', 'Failed to parse prices json: $e'); } @@ -696,14 +702,16 @@ class CexPrices { _fetchingPrices = false; + if (_body == null) return false; + Map json; try { json = jsonDecode(_body); } catch (e) { Log('cex_provider', 'Failed to parse prices json: $e'); } - if (json == null) return false; + if (json['error'] != null) { Log('cex_provider', 'Prices endpoint error: ${json['error']}'); return false; diff --git a/lib/model/transaction_data.dart b/lib/model/transaction_data.dart index e9d5d73d7..b622c2bbc 100644 --- a/lib/model/transaction_data.dart +++ b/lib/model/transaction_data.dart @@ -126,10 +126,18 @@ class FeeDetails { ); try { - // QRC20 tokens - feeDetails.totalFee = cutTrailingZeros(formatPrice( - double.parse(json['miner_fee']) + - double.parse(json['total_gas_fee']))); + final minerFee = + json['miner_fee'] == null ? null : double.tryParse(json['miner_fee']); + + final totalGasFee = json['total_gas_fee'] == null + ? null + : double.tryParse(json['total_gas_fee']); + + if (minerFee != null || totalGasFee != null) { + final total = minerFee ?? 0.0 + totalGasFee ?? 0.0; + // QRC20 tokens + feeDetails.totalFee = cutTrailingZeros(formatPrice(total)); + } } catch (_) {} return feeDetails; diff --git a/lib/model/transactions.dart b/lib/model/transactions.dart index 3d909daf2..8d37c8312 100644 --- a/lib/model/transactions.dart +++ b/lib/model/transactions.dart @@ -45,18 +45,20 @@ class Result { this.transactions, }); - factory Result.fromJson(Map json) => Result( - fromId: json['from_id'] ?? '', - limit: json['limit'] ?? 0, - skipped: json['skipped'] ?? 0, - total: json['total'] ?? 0, - currentBlock: json['current_block'] ?? 0, - syncStatus: json['sync_status'] == null - ? SyncStatus() - : SyncStatus.fromJson(json['sync_status']), - transactions: List.from( - json['transactions'].map((dynamic x) => Transaction.fromJson(x))), - ); + static Result fromJson(Map json) => json == null + ? null + : Result( + fromId: json['from_id'] ?? '', + limit: json['limit'] ?? 0, + skipped: json['skipped'] ?? 0, + total: json['total'] ?? 0, + currentBlock: json['current_block'] ?? 0, + syncStatus: json['sync_status'] == null + ? SyncStatus() + : SyncStatus.fromJson(json['sync_status']), + transactions: List.from( + json['transactions'].map((dynamic x) => Transaction.fromJson(x))), + ); String fromId; int currentBlock; diff --git a/lib/screens/portfolio/coins_page.dart b/lib/screens/portfolio/coins_page.dart index 5c608e5d8..3847df456 100644 --- a/lib/screens/portfolio/coins_page.dart +++ b/lib/screens/portfolio/coins_page.dart @@ -13,6 +13,7 @@ import 'package:komodo_dex/packages/z_coin_activation/bloc/z_coin_activation_eve import 'package:komodo_dex/packages/z_coin_activation/bloc/z_coin_activation_state.dart'; import 'package:komodo_dex/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart'; import 'package:komodo_dex/screens/portfolio/animated_asset_proportions_graph.dart'; +import 'package:komodo_dex/services/mm.dart'; import 'package:provider/provider.dart'; import '../../../../blocs/coins_bloc.dart'; @@ -39,6 +40,8 @@ class _CoinsPageState extends State { StreamSubscription _loginSubscription; + Timer _timer; + // Rebranding Future showRebrandingDialog(BuildContext context) async { showDialog( @@ -65,12 +68,9 @@ class _CoinsPageState extends State { // Check every 5 seconds if mmSe is running. When it is running, emit the // event [ZCoinActivationStatusRequested] and kill the timer. - Timer.periodic(const Duration(seconds: 5), (timer) { - if (mmSe.running) { - bloc.add(ZCoinActivationStatusRequested()); - timer.cancel(); - } - }); + MM.untilRpcIsUp().then( + (_) => bloc.add(ZCoinActivationStatusRequested()), + ); // Subscribe to the outIsLogin stream _loginSubscription = authBloc.outIsLogin.listen((isLogin) async { @@ -93,7 +93,8 @@ class _CoinsPageState extends State { @override void dispose() { - _loginSubscription.cancel(); + _loginSubscription?.cancel()?.ignore(); + _timer?.cancel(); super.dispose(); } diff --git a/lib/services/mm_service.dart b/lib/services/mm_service.dart index 520d8ea7c..185dfe43a 100644 --- a/lib/services/mm_service.dart +++ b/lib/services/mm_service.dart @@ -38,7 +38,15 @@ class MMService { Process mm2Process; List coins = []; - /// Switched on when we hear from MM. + /// Represents wether mm2 has been started or not for this session even if + /// it is not currently running. + /// + /// On iOS, the RPC server is killed when the app goes to background. This + /// will remain true when the app is restored. + /// + /// Use [MM.isRpcUp()] to get the current status of the RPC server. + /// + /// Use [MM.untilRpcIsUp()] to efficiently await until RPC is up. bool get running => _running; bool _running = false; @@ -148,7 +156,7 @@ class MMService { jobService.install('updateMm2VersionInfo', 3.14, (j) async { if (!mmSe.running) return; - if (mmVersion == null && mmDate == null) { + if (mmVersion == null && mmDate == null && await MM.pingMm2()) { await initializeMmVersion(); } }); @@ -528,12 +536,12 @@ class MMService { if (!running) return; /// Wait until mm2 is up, in case it was restarted from Swift - await pauseUntil(() async => await MM.isRpcUp()); + await MM.untilRpcIsUp(); /// If [running], but enabled coins list is empty, /// it means that mm2 was restarted from Swift, and we /// should reenable active coins ones again - if ((await MM.getEnabledCoins()).isEmpty) initCoinsAndLoad(); + if ((await MM.getEnabledCoins()).isEmpty) await initCoinsAndLoad(); } Future> getAllBalances(bool forceUpdate) async { diff --git a/lib/services/notif_service.dart b/lib/services/notif_service.dart index 989bd4f7b..9874a5fc4 100644 --- a/lib/services/notif_service.dart +++ b/lib/services/notif_service.dart @@ -112,7 +112,7 @@ class NotifService { )); if (res is! Transactions) continue; - for (Transaction tx in res.result.transactions) { + for (Transaction tx in res.result?.transactions ?? []) { if (tx.to.contains(address)) { if (double.parse(tx.myBalanceChange) < 0) continue; transactions.add(tx); From 633c954e439b2e13731a095a5bb240e2942997e3 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:37:08 +0200 Subject: [PATCH 4/7] Improve performance of RPC status checking --- lib/services/mm.dart | 80 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/lib/services/mm.dart b/lib/services/mm.dart index 7f764debf..de9108fd4 100644 --- a/lib/services/mm.dart +++ b/lib/services/mm.dart @@ -1,4 +1,6 @@ +import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:http/http.dart' show Response; @@ -720,6 +722,26 @@ class ApiProvider { ), ); + /// Ping mm2 endpoint to check if the host up. Use [isRpcUp] to verify + /// that the API is running. [pingMm2] is useful to check if the host is + /// running without trying to authenticate since the IP will be banned if + /// an invalid RPC password is used multiple times. + Future pingMm2({http.Client client}) async { + client ??= mmSe.client; + try { + final r = await client.post( + Uri.parse(url), + body: json.encode({'method': 'ping'}), + ); + + Log('ApiProvider:pingMm2', r.toString()); + + return true; + } catch (e) { + return false; + } + } + Future getVersionMM2(BaseService body, {http.Client client}) async { client ??= mmSe.client; @@ -750,15 +772,67 @@ class ApiProvider { return value; } + Completer _completerRpcIsUp; + Timer _recheckIfRpcIsUpTimer; + + void _completeRpcUpCheck({bool isError = false}) { + if (isError) throw UnimplementedError(); + + _completerRpcIsUp?.complete(); + _recheckIfRpcIsUpTimer?.cancel(); + + _completerRpcIsUp = null; + _recheckIfRpcIsUpTimer = null; + } + + Future untilRpcIsUp() async { + const MAX_RETRIES = null; + const retryWarningInterval = Duration(seconds: 100); + + _completerRpcIsUp ??= Completer(); + + if (await isRpcUp()) { + return _completerRpcIsUp.future.then((_) => _completeRpcUpCheck()); + } + + if (!(_recheckIfRpcIsUpTimer?.isActive ?? false)) { + int retries = 0; + + _recheckIfRpcIsUpTimer = + Timer.periodic(Duration(seconds: 1), (Timer t) async { + final isUp = mmSe.running && await pingMm2() && await isRpcUp(); + + final exceededMaxRetries = + (MAX_RETRIES != null && retries >= MAX_RETRIES); + + if (isUp || exceededMaxRetries) { + _completeRpcUpCheck(); + } + + retries++; + + // Every [retryWarningInterval] seconds, log a warning + if (retries % retryWarningInterval.inSeconds == 0) { + Log( + 'ApiProvider:untilRpcIsUp', + ': Waiting a long time for RPC to be up', + ); + } + }); + } + + return _completerRpcIsUp.future; + } + Future isRpcUp([http.Client client]) async { client ??= mmSe.client; bool isUp = false; try { - final VersionMm2 versionmm2 = + final VersionMm2 versionMm2 = await MM.getVersionMM2(BaseService(method: 'version')); - isUp = versionmm2 is VersionMm2 && versionmm2 != null; + isUp = versionMm2 is VersionMm2 && versionMm2 != null; } catch (e) { Log('mm', 'isRpcUp: $e'); } @@ -1013,7 +1087,7 @@ class ApiProvider { .replaceFirstMapped(RegExp('^(.{0,99}).*'), (Match m) => m[1]); } throw ErrorString( - 'HTTP ${r.statusCode}: Could not assert 200 in response.', + 'HTTP ${r.statusCode}: Could not assert 200 in response. $emsg', ); } } From 81ebdd3d3c542804228bcc4ed790ad607512a2cd Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:41:35 +0200 Subject: [PATCH 5/7] Only show sapling activation in developer mode --- .../widgets/z_coin_status_list_tile.dart | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart b/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart index 2cd37ae9b..bad69fc15 100644 --- a/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart +++ b/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:intl/intl.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:komodo_dex/blocs/settings_bloc.dart'; import 'package:komodo_dex/localizations.dart'; import 'package:komodo_dex/packages/z_coin_activation/bloc/z_coin_activation_bloc.dart'; import 'package:komodo_dex/packages/z_coin_activation/bloc/z_coin_activation_event.dart'; @@ -326,17 +327,17 @@ Future> _showConfirmationDialog(BuildContext context) { ), ), ), - - RadioListTile( - title: Text(localisations.syncFromSaplingActivation), - value: SyncType.fullSync, - groupValue: _syncType, - onChanged: (SyncType value) { - setState(() { - _syncType = value; - }); - }, - ), + if (settingsBloc.enableTestCoins) + RadioListTile( + title: Text(localisations.syncFromSaplingActivation), + value: SyncType.fullSync, + groupValue: _syncType, + onChanged: (SyncType value) { + setState(() { + _syncType = value; + }); + }, + ), SizedBox(height: 16), // Sync Type Description From 7f193e7c5c8e888c1242677c6e89ccaa99db33b3 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:41:56 +0200 Subject: [PATCH 6/7] Make ZHTLC activation banner less scary --- .../widgets/z_coin_status_list_tile.dart | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart b/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart index bad69fc15..96c003da0 100644 --- a/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart +++ b/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart @@ -64,13 +64,11 @@ class _ZCoinStatusWidgetState extends State { if (isActivationInProgress) { final progressState = state as ZCoinActivationInProgess; return ListTile( - onTap: () => _showInProgressDialog(context), title: Text(localisations.activating(protocolTag)), + dense: true, + onTap: () => _showInProgressDialog(context), subtitle: Text(localisations.doNotCloseTheAppTapForMoreInfo), - leading: Icon( - Icons.warning, - color: Colors.red, - ), + leading: Icon(Icons.hourglass_full_rounded), tileColor: Theme.of(context).primaryColor, trailing: SizedBox( child: RotatingCircularProgressIndicator( @@ -119,7 +117,14 @@ class _ZCoinStatusWidgetState extends State { } static void listener(BuildContext context, ZCoinActivationState state) { - final scaffold = ScaffoldMessenger.maybeOf(context); + ScaffoldMessengerState scaffold; + + try { + ScaffoldMessenger.maybeOf(context); + } catch (e) { + return; + } + if (scaffold == null) return; final localisations = AppLocalizations.of(context); From 8df0d4451962609e36edf127b390a3307520561d Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:55:44 +0200 Subject: [PATCH 7/7] Bug: Tweak ZHTLC resync logic to fix iOS error --- lib/main.dart | 15 +++++++- .../bloc/z_coin_activation_api.dart | 20 +++------- .../bloc/z_coin_activation_bloc.dart | 38 ++++++++++--------- .../bloc/z_coin_activation_event.dart | 6 ++- .../bloc/z_coin_activation_repository.dart | 18 +++++---- .../models/z_coin_status.dart | 6 ++- .../widgets/z_coin_status_list_tile.dart | 10 +++-- lib/services/mm.dart | 1 - 8 files changed, 65 insertions(+), 49 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 1f58b82c3..67019da1e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,9 +5,11 @@ import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_bloc/flutter_bloc.dart' as real_bloc; import 'package:komodo_dex/packages/z_coin_activation/bloc/z_coin_activation_bloc.dart'; +import 'package:komodo_dex/packages/z_coin_activation/bloc/z_coin_activation_event.dart'; import 'package:komodo_dex/packages/z_coin_activation/bloc/z_coin_activation_state.dart'; import 'package:komodo_dex/packages/z_coin_activation/bloc/z_coin_notifications.dart'; import 'package:komodo_dex/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart'; +import 'package:komodo_dex/services/mm.dart'; import '../app_config/app_config.dart'; import '../blocs/authenticate_bloc.dart'; import '../blocs/coins_bloc.dart'; @@ -178,6 +180,8 @@ class _MyAppState extends State with WidgetsBindingObserver { super.initState(); _initCheckNetworkStatus(); WidgetsBinding.instance.addObserver(this); + + MM.untilRpcIsUp().then((_) => _requestResync()); } @override @@ -207,11 +211,20 @@ class _MyAppState extends State with WidgetsBindingObserver { Log('main', 'lifecycle: resumed'); mainBloc.isInBackground = false; lockService.lockSignal(context); - await mmSe.handleWakeUp(); + await mmSe.handleWakeUp().whenComplete(() { + if (mmSe.running) _requestResync(); + }); + break; } } + void _requestResync() { + context + .read() + .add(ZCoinActivationRequested(resync: true)); + } + @override Widget build(BuildContext context) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [ diff --git a/lib/packages/z_coin_activation/bloc/z_coin_activation_api.dart b/lib/packages/z_coin_activation/bloc/z_coin_activation_api.dart index 6e2e78613..4c51049e4 100644 --- a/lib/packages/z_coin_activation/bloc/z_coin_activation_api.dart +++ b/lib/packages/z_coin_activation/bloc/z_coin_activation_api.dart @@ -190,11 +190,11 @@ class ZCoinActivationApi { Stream activateCoin( String ticker, { - bool firstLaunch = false, + bool resync = false, }) async* { int coinTaskId = await getTaskId(ticker); ZCoinStatus taskStatus; - if (!firstLaunch) { + if (!resync) { final isAlreadyActivated = (await activatedZCoins()).contains(ticker); taskStatus = coinTaskId == null @@ -225,7 +225,7 @@ class ZCoinActivationApi { ZCoinStatus lastEmittedStatus; - coinTaskId = await initiateActivation(ticker, noSyncParams: firstLaunch); + coinTaskId = await initiateActivation(ticker, noSyncParams: resync); lastEmittedStatus = await activationTaskStatus(coinTaskId, ticker: ticker); @@ -410,8 +410,8 @@ class ZCoinActivationApi { Map responseBody, String ticker, }) async { - int _progress = 100; - String _messageDetails = ''; + int _progress = 5; + String _messageDetails = 'Activating $ticker'; if (!responseBody.containsKey('result')) return null; final result = responseBody['result'] is Map @@ -420,13 +420,6 @@ class ZCoinActivationApi { String status = result['status']; dynamic details = result['details']; - // checkPointBlock will be removed - //Coin coin = coinsBloc.getKnownCoinByAbbr(ticker); - // int blockOffset = 0; - // if (coin.type == CoinType.zhtlc) { - // blockOffset = coin.protocol.protocolData.checkPointBlock?.height ?? 0; - // } - // use range from checkpoint block to present if (status == 'Ok') { if (details.containsKey('error')) { @@ -487,9 +480,6 @@ class ZCoinActivationApi { _messageDetails = isBuildingPhase ? 'Building $ticker wallet database' : 'Updating $ticker blocks cache'; - } else { - _progress = 5; - _messageDetails = 'Activating $ticker'; } return ZCoinStatus( diff --git a/lib/packages/z_coin_activation/bloc/z_coin_activation_bloc.dart b/lib/packages/z_coin_activation/bloc/z_coin_activation_bloc.dart index 21fd563ae..243b409b5 100644 --- a/lib/packages/z_coin_activation/bloc/z_coin_activation_bloc.dart +++ b/lib/packages/z_coin_activation/bloc/z_coin_activation_bloc.dart @@ -41,16 +41,22 @@ class ZCoinActivationBloc ZCoinActivationRequested event, Emitter emit, ) async { - final toActivate = await _repository.outstandingZCoinActivations(); + final hasRequestedCoins = + (await _repository.getRequestedActivatedCoins()).isNotEmpty; + + if (!hasRequestedCoins) { + emit(ZCoinActivationInitial()); + return; + } + + final isResync = event.resync; + + final toActivate = isResync + ? await _repository.getEnabledZCoins() + : await _repository.outstandingZCoinActivations(); final toActivateInitalCount = toActivate.length; try { - final isAllCoinsEnabled = await _repository.isAllRequestedZCoinsEnabled(); - if (isAllCoinsEnabled) { - add(ZCoinActivationStatusRequested()); - return; - } - final zhtlcActivationPrefs = await loadZhtlcActivationPrefs(); SyncType zhtlcSyncType = zhtlcActivationPrefs['zhtlcSyncType']; @@ -63,7 +69,9 @@ class ZCoinActivationBloc ), ); await emit.forEach( - _repository.activateRequestedZCoins(), + event.resync + ? _repository.resyncEnabledZCoins() + : _repository.activateRequestedZCoins(), onData: (coinStatus) { if (coinStatus.isFailed) { return ZCoinActivationFailure( @@ -108,7 +116,7 @@ class ZCoinActivationBloc progress: shouldShowNewProgress ? overallProgress : lastProgress, message: 'Activating ${coinStatus.coin}', eta: eta, - startTime: previousInProgressState.startTime, + startTime: previousInProgressState?.startTime ?? DateTime.now(), ); }, onError: (e, s) { @@ -165,6 +173,8 @@ class ZCoinActivationBloc try { final isAllCoinsEnabled = await _repository.isAllRequestedZCoinsEnabled(); + final mustResync = await isResyncing(); + // TODO? Consider if better to base the "in progress" state on the // API task status instead of the existing bloc state. final isActivationInProgress = state is ZCoinActivationInProgess; @@ -174,10 +184,10 @@ class ZCoinActivationBloc } ZCoinActivationState newState; - if (isAllCoinsEnabled) { + if (isAllCoinsEnabled && !mustResync) { newState = isActivationInProgress ? ZCoinActivationSuccess() - : ZCoinActivationKnownState(isAllCoinsEnabled); + : ZCoinActivationKnownState(false); } else if (isActivationInProgress) { newState = state as ZCoinActivationInProgess; } else { @@ -185,12 +195,6 @@ class ZCoinActivationBloc } emit(newState); - - if (!isAllCoinsEnabled && !isActivationInProgress) { - add(ZCoinActivationRequested()); - } else { - _repository.willInitialize = false; - } } catch (e) { debugPrint('Failed to get activation status: $e'); emit( diff --git a/lib/packages/z_coin_activation/bloc/z_coin_activation_event.dart b/lib/packages/z_coin_activation/bloc/z_coin_activation_event.dart index 79e4e8cae..839428a0c 100644 --- a/lib/packages/z_coin_activation/bloc/z_coin_activation_event.dart +++ b/lib/packages/z_coin_activation/bloc/z_coin_activation_event.dart @@ -3,7 +3,11 @@ abstract class ZCoinActivationEvent { } /// Activates any requested ZCoins not already activated -class ZCoinActivationRequested extends ZCoinActivationEvent {} +class ZCoinActivationRequested extends ZCoinActivationEvent { + const ZCoinActivationRequested({this.resync = false}); + + final bool resync; +} /// Sets the list of requested ZCoins to activate. /// Must call [ZCoinActivationRequested] to activate the coins. diff --git a/lib/packages/z_coin_activation/bloc/z_coin_activation_repository.dart b/lib/packages/z_coin_activation/bloc/z_coin_activation_repository.dart index abecddb7a..82abe2a46 100644 --- a/lib/packages/z_coin_activation/bloc/z_coin_activation_repository.dart +++ b/lib/packages/z_coin_activation/bloc/z_coin_activation_repository.dart @@ -18,19 +18,23 @@ class ZCoinActivationRepository with RequestedZCoinsStorage { static Future get taskIdKey async => 'activationTaskId_${(await Db.getCurrentWallet()).id}'; - bool willInitialize = true; + Stream resyncEnabledZCoins() async* { + final enabledZCoins = await getEnabledZCoins(); - Stream _activateZCoins(List zCoins) async* { - try { - bool firstLaunch = willInitialize; - willInitialize = false; + yield* _activateZCoins(enabledZCoins, resyncOnly: true); + } + Stream _activateZCoins( + List zCoins, { + bool resyncOnly = false, + }) async* { + try { if (zCoins.isEmpty) return; while (zCoins.isNotEmpty) { final currentCoinTicker = zCoins.first; await for (final update - in api.activateCoin(currentCoinTicker, firstLaunch: firstLaunch)) { + in api.activateCoin(currentCoinTicker, resync: resyncOnly)) { Log( 'ZCoinActivationRepository:activateZCoins', 'Update received: ${update.toJson()}', @@ -94,8 +98,6 @@ class ZCoinActivationRepository with RequestedZCoinsStorage { Future> outstandingZCoinActivations() async { final requestedCoins = (await getRequestedActivatedCoins()).toSet(); - if (willInitialize) return requestedCoins.toList(); - final activatedZCoins = (await getEnabledZCoins()).toSet(); final coinsAlreadyActivated = activatedZCoins.intersection(requestedCoins); diff --git a/lib/packages/z_coin_activation/models/z_coin_status.dart b/lib/packages/z_coin_activation/models/z_coin_status.dart index f0392c34e..e6448e6bd 100644 --- a/lib/packages/z_coin_activation/models/z_coin_status.dart +++ b/lib/packages/z_coin_activation/models/z_coin_status.dart @@ -29,9 +29,11 @@ class ZCoinStatus { final String message; final double progress; - bool get isActivated => status == ActivationTaskStatus.active; + bool get isActivated => + status == ActivationTaskStatus.active || + message.contains('is activated already'); - bool get isFailed => status == ActivationTaskStatus.failed; + bool get isFailed => status == ActivationTaskStatus.failed && !isActivated; bool get isInProgress => status == ActivationTaskStatus.inProgress; diff --git a/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart b/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart index 96c003da0..920b80be7 100644 --- a/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart +++ b/lib/packages/z_coin_activation/widgets/z_coin_status_list_tile.dart @@ -443,7 +443,7 @@ Future _showInProgressDialog(BuildContext context) async { final localisations = AppLocalizations.of(context); final etaString = state?.eta?.inMinutes == null - ? localisations.loading + ? null : '${state.eta.inMinutes}${localisations.minutes}'; return AlertDialog( title: Text( @@ -462,12 +462,14 @@ Future _showInProgressDialog(BuildContext context) async { ], Text(localisations.willTakeTime), SizedBox(height: 16), - Text('${localisations.rewardsTableTime}: $etaString'), - SizedBox(height: 16), + if (etaString != null) ...[ + Text('${localisations.rewardsTableTime}: $etaString'), + SizedBox(height: 16), + ], Text( '${localisations.swapProgress}: ${(state.progress * 100).round()}%', ), - SizedBox(height: 4), + SizedBox(height: 8), LinearProgressIndicator(value: state.progress), ], ), diff --git a/lib/services/mm.dart b/lib/services/mm.dart index de9108fd4..222e9db2f 100644 --- a/lib/services/mm.dart +++ b/lib/services/mm.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:http/http.dart' show Response;