fix: Sync bar ETA (#2515)
* fix: Sync bar ETA * fix: Smooth ETA - WIP * fix: Show disconnected status when sync disconnects * feat: Display blocks remaining for the first five seconds and then switch to percentage * Update * fix: Extend duration for showing blocks remaining during sync from 5 to 15 seconds; update connectivity check to handle multiple connectivity results, and handle disconnected state while syncing * feat: Initially display block if the a reconnection is triggered while syncing * fix: More improvements to syncing
David Adegoke committed
Nov 12, 2025 at 11:40 UTC
9775f7a629b0b6ca0d8ae2ad57f293e7538ff0ca
6 files changed
+45
-21
cw_bitcoin/lib/electrum_wallet.dart
+2
-3
@@ -2657,9 +2657,8 @@ abstract class ElectrumWalletBase
2657
2658
break;
2659
case electrum.ConnectionStatus.disconnected:
2660
- if (syncStatus is! NotConnectedSyncStatus &&
2661
- syncStatus is! ConnectingSyncStatus &&
2662
- syncStatus is! SyncronizingSyncStatus) {
2660
+ // Always show disconnected status when connection is lost, regardless of current sync state
2661
+ if (syncStatus is! NotConnectedSyncStatus) {
2662
syncStatus = NotConnectedSyncStatus();
2663
}
2664
break;
cw_core/lib/sync_status.dart
+15
-13
@@ -21,6 +21,7 @@ class StartingScanSyncStatus extends SyncStatus {
21
class SyncingSyncStatus extends SyncStatus {
22
SyncingSyncStatus(this.blocksLeft, this.ptc) {
23
updateEtaHistory(blocksLeft);
24
+ _globalSyncStartTime ??= DateTime.now();
25
}
26
27
double ptc;
@@ -32,6 +33,19 @@ class SyncingSyncStatus extends SyncStatus {
33
@override
34
String toString() => '$blocksLeft';
35
36
+ /// Returns true if we should show blocks remaining instead of percentage
37
+ /// Shows blocks remaining for the first 15 seconds of syncing
38
+ bool shouldShowBlocksRemaining() {
39
+ if (_globalSyncStartTime == null) return true;
40
+ final elapsed = DateTime.now().difference(_globalSyncStartTime!);
41
+ return elapsed.inSeconds < 15;
42
+ }
43
+
44
+ /// Reset the global sync start time (call when sync completes or fails)
45
+ static void resetSyncStartTime() {
46
+ _globalSyncStartTime = null;
47
+ }
48
+
49
factory SyncingSyncStatus.fromHeightValues(int chainTip, int initialSyncHeight, int syncHeight) {
50
final track = chainTip - initialSyncHeight;
51
final diff = track - (chainTip - syncHeight);
@@ -45,21 +59,14 @@ class SyncingSyncStatus extends SyncStatus {
59
60
static void updateEtaHistory(int blocksLeft) {
61
blockHistory[DateTime.now()] = blocksLeft;
48
-
49
- // keep only the last 30 entries (gives us better statistical accuracy)
50
- while (blockHistory.length > 30) {
51
- blockHistory.remove(blockHistory.keys.first);
52
- }
62
}
63
64
static Map<DateTime, int> blockHistory = {};
65
static Duration? lastEtaDuration;
66
static const int _minDataPoints = 3;
58
- static const int _maxDataAgeMinutes = 2;
67
+ static DateTime? _globalSyncStartTime;
68
69
String? getFormattedEtaWithPlaceholder() {
61
- _cleanOldEntries();
62
-
70
// If we have enough data, show actual ETA
71
if (blockHistory.length >= _minDataPoints) {
72
final eta = getFormattedEta();
@@ -70,11 +77,6 @@ class SyncingSyncStatus extends SyncStatus {
77
return '--:--';
78
}
79
73
- void _cleanOldEntries() {
74
- final cutoffTime = DateTime.now().subtract(Duration(minutes: _maxDataAgeMinutes));
75
- blockHistory.removeWhere((key, value) => key.isBefore(cutoffTime));
76
- }
77
-
80
String? getFormattedEta() {
81
Duration? duration = getEtaDuration();
82
lib/core/sync_status_title.dart
+14
@@ -4,6 +4,20 @@ import 'package:cw_core/sync_status.dart';
4
5
String syncStatusTitle(SyncStatus syncStatus, SyncStatusDisplayMode syncStatusDisplayMode) {
6
if (syncStatus is SyncingSyncStatus) {
7
+ // Show blocks remaining for the first 3 seconds, then switch to percentage
8
+ if (syncStatus.shouldShowBlocksRemaining()) {
9
+ if (syncStatus.blocksLeft == 1) {
10
+ return S.current.block_remaining;
11
+ }
12
+ return S.current.Blocks_remaining('${syncStatus.blocksLeft}');
13
+ }
14
+
15
+ // After 3 seconds, show percentage-based display
16
+ // Don't show ETA for very few blocks (less than 100) to avoid inconsistency
17
+ if (syncStatus.blocksLeft < 100) {
18
+ return S.current.Blocks_remaining('${syncStatus.blocksLeft}');
19
+ }
20
+
21
if (syncStatus.blocksLeft == 1) {
22
return S.current.block_remaining;
23
}
lib/reactions/check_connection.dart
+1
-4
@@ -1,6 +1,5 @@
1
import 'dart:async';
2
3
-import 'package:cake_wallet/src/screens/base_page.dart';
3
import 'package:cake_wallet/utils/tor.dart';
4
import 'package:connectivity_plus/connectivity_plus.dart';
5
import 'package:cw_core/utils/print_verbose.dart';
@@ -8,8 +7,6 @@ import 'package:cw_core/wallet_base.dart';
7
import 'package:cw_core/sync_status.dart';
8
import 'package:cw_core/wallet_type.dart';
9
import 'package:cake_wallet/store/settings_store.dart';
11
-import 'package:flutter/material.dart';
12
-import 'package:get_it/get_it.dart';
10
11
Timer? _checkConnectionTimer;
12
@@ -29,7 +26,7 @@ void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore
26
try {
27
final connectivityResult = await (Connectivity().checkConnectivity());
28
32
- if (connectivityResult == ConnectivityResult.none) {
29
+ if (connectivityResult.contains(ConnectivityResult.none)) {
30
wallet.syncStatus = FailedSyncStatus();
31
return;
32
}
lib/reactions/on_current_wallet_change.dart
+2
@@ -11,6 +11,7 @@ import 'package:cake_wallet/solana/solana.dart';
11
import 'package:cake_wallet/tron/tron.dart';
12
import 'package:cake_wallet/utils/tor.dart';
13
import 'package:cw_core/crypto_currency.dart';
14
+import 'package:cw_core/sync_status.dart';
15
import 'package:cw_core/transaction_history.dart';
16
import 'package:cw_core/balance.dart';
17
import 'package:cw_core/transaction_info.dart';
@@ -92,6 +93,7 @@ void startCurrentWalletChangeReaction(
93
}
94
95
await wallet.connectToNode(node: node);
96
+ SyncingSyncStatus.blockHistory.clear();
97
if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
98
final powNode = settingsStore.getCurrentPowNode(wallet.type);
99
await wallet.connectToPowNode(node: powNode);
lib/reactions/on_wallet_sync_status_change.dart
+11
-1
@@ -19,13 +19,23 @@ void startWalletSyncStatusChangeReaction(
19
_onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async {
20
try {
21
if (status is ConnectedSyncStatus) {
22
+ SyncingSyncStatus.resetSyncStartTime();
23
await wallet.startSync();
24
}
25
+
26
if (status is SyncingSyncStatus || status is ProcessingSyncStatus) {
27
await WakelockPlus.enable();
28
}
27
- if (status is SyncedSyncStatus || status is FailedSyncStatus) {
29
+
30
+ if (status is SyncedSyncStatus) {
31
+ await WakelockPlus.disable();
32
+ SyncingSyncStatus.resetSyncStartTime();
33
+ SyncingSyncStatus.blockHistory.clear();
34
+ }
35
+
36
+ if (status is FailedSyncStatus) {
37
await WakelockPlus.disable();
38
+ SyncingSyncStatus.resetSyncStartTime();
39
}
40
41
if (status is SyncedSyncStatus &&