experimental

This commit is contained in:
Matthew Fosse 2025-01-09 20:58:38 -08:00
parent a9f7dec112
commit 582830de49
4 changed files with 119 additions and 69 deletions
cw_core/lib
cw_monero/lib
lib
entities
src/screens/root

View file

@ -60,6 +60,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
bool get isHardwareWallet => walletInfo.isHardwareWallet; bool get isHardwareWallet => walletInfo.isHardwareWallet;
Future<void> init();
Future<void> connectToNode({required Node node}); Future<void> connectToNode({required Node node});
// there is a default definition here because only coins with a pow node (nano based) need to override this // there is a default definition here because only coins with a pow node (nano based) need to override this

View file

@ -158,10 +158,7 @@ abstract class MoneroWalletBase
} }
} }
// TODO: this doesn't work, need to find a better way to check if we are on the main thread _autoSaveTimer?.cancel();
bool isMainThread = Isolate.current.debugName == "main";
printV("isMainThread: $isMainThread");
_autoSaveTimer = _autoSaveTimer =
Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save()); Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save());
@ -275,6 +272,8 @@ abstract class MoneroWalletBase
return; return;
} }
monero_wallet.stopSync(); monero_wallet.stopSync();
_autoSaveTimer?.cancel();
monero_wallet.closeCurrentWallet();
} }
@override @override
@ -737,13 +736,17 @@ abstract class MoneroWalletBase
if (walletInfo.isRecovery) { if (walletInfo.isRecovery) {
await _askForUpdateTransactionHistory(); await _askForUpdateTransactionHistory();
_askForUpdateBalance(); _askForUpdateBalance();
walletAddresses.accountList.update(); if (!isBackgroundSyncing) {
walletAddresses.accountList.update();
}
} }
if (blocksLeft < 100) { if (blocksLeft < 100) {
await _askForUpdateTransactionHistory(); await _askForUpdateTransactionHistory();
_askForUpdateBalance(); _askForUpdateBalance();
walletAddresses.accountList.update(); if (!isBackgroundSyncing) {
walletAddresses.accountList.update();
}
syncStatus = SyncedSyncStatus(); syncStatus = SyncedSyncStatus();
if (!_hasSyncAfterStartup) { if (!_hasSyncAfterStartup) {

View file

@ -118,6 +118,22 @@ void setWalletNotification(FlutterLocalNotificationsPlugin flutterLocalNotificat
); );
} }
AppLifecycleState appStateFromString(String state) {
switch (state) {
case "AppLifecycleState.paused":
return AppLifecycleState.paused;
case "AppLifecycleState.resumed":
return AppLifecycleState.resumed;
case "AppLifecycleState.hidden":
return AppLifecycleState.hidden;
case "AppLifecycleState.detached":
return AppLifecycleState.detached;
case "AppLifecycleState.inactive":
return AppLifecycleState.inactive;
}
throw Exception("unknown app state: $state");
}
@pragma("vm:entry-point") @pragma("vm:entry-point")
Future<void> onStart(ServiceInstance service) async { Future<void> onStart(ServiceInstance service) async {
printV("BACKGROUND SERVICE STARTED"); printV("BACKGROUND SERVICE STARTED");
@ -125,10 +141,13 @@ Future<void> onStart(ServiceInstance service) async {
Timer? _syncTimer; Timer? _syncTimer;
Timer? _stuckSyncTimer; Timer? _stuckSyncTimer;
Timer? _queueTimer; Timer? _queueTimer;
Timer? _appStateTimer;
List<WalletBase> syncingWallets = []; List<WalletBase> syncingWallets = [];
List<WalletBase> standbyWallets = []; List<WalletBase> standbyWallets = [];
Timer? _bgTimer; Timer? _bgTimer;
int fgCount = 0; AppLifecycleState lastAppState = AppLifecycleState.resumed;
final List<AppLifecycleState> lastAppStates = [];
String serviceState = "NOT_RUNNING";
// commented because the behavior appears to be bugged: // commented because the behavior appears to be bugged:
// DartPluginRegistrant.ensureInitialized(); // DartPluginRegistrant.ensureInitialized();
@ -136,8 +155,7 @@ Future<void> onStart(ServiceInstance service) async {
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin(); FlutterLocalNotificationsPlugin();
service.on("stopService").listen((event) async { Future<void> stopAllSyncing() async {
printV("STOPPING BACKGROUND SERVICE");
_syncTimer?.cancel(); _syncTimer?.cancel();
_stuckSyncTimer?.cancel(); _stuckSyncTimer?.cancel();
_queueTimer?.cancel(); _queueTimer?.cancel();
@ -157,6 +175,11 @@ Future<void> onStart(ServiceInstance service) async {
} catch (e) { } catch (e) {
printV("error stopping sync: $e"); printV("error stopping sync: $e");
} }
}
service.on("stopService").listen((event) async {
printV("STOPPING BACKGROUND SERVICE");
await stopAllSyncing();
// stop the service itself: // stop the service itself:
service.invoke("serviceState", {"state": "NOT_RUNNING"}); service.invoke("serviceState", {"state": "NOT_RUNNING"});
await service.stopSelf(); await service.stopSelf();
@ -166,36 +189,39 @@ Future<void> onStart(ServiceInstance service) async {
printV(event); printV(event);
}); });
void setForeground() { Future<void> setForeground() async {
serviceState = "FOREGROUND";
bgSyncStarted = false; bgSyncStarted = false;
_syncTimer?.cancel();
setNotificationStandby(flutterLocalNotificationsPlugin); setNotificationStandby(flutterLocalNotificationsPlugin);
} }
service.on("setForeground").listen((event) async { service.on("setForeground").listen((event) async {
setForeground(); await setForeground();
service.invoke("serviceState", {"state": "FOREGROUND"}); service.invoke("serviceState", {"state": "FOREGROUND"});
}); });
void setReady() { void setReady() {
setNotificationReady(flutterLocalNotificationsPlugin); if (serviceState != "READY" && serviceState != "BACKGROUND") {
serviceState = "READY";
setNotificationReady(flutterLocalNotificationsPlugin);
}
} }
service.on("setReady").listen((event) async { service.on("setReady").listen((event) async {
setReady(); setReady();
service.invoke("serviceState", {"state": "READY"});
}); });
service.on("foregroundPing").listen((event) async { service.on("appState").listen((event) async {
fgCount = 0; printV("APP STATE: ${event?["state"]}");
lastAppState = appStateFromString(event?["state"] as String);
}); });
// we have entered the background, start the sync: // we have entered the background, start the sync:
void setBackground() async { void setBackground() async {
if (bgSyncStarted) { // only runs once per service instance:
return; if (bgSyncStarted) return;
}
bgSyncStarted = true; bgSyncStarted = true;
serviceState = "BACKGROUND";
await Future.delayed(const Duration(seconds: DELAY_SECONDS_BEFORE_SYNC_START)); await Future.delayed(const Duration(seconds: DELAY_SECONDS_BEFORE_SYNC_START));
printV("STARTING SYNC FROM BG"); printV("STARTING SYNC FROM BG");
@ -482,23 +508,24 @@ Future<void> onStart(ServiceInstance service) async {
setBackground(); setBackground();
}); });
// this is a backup timer to trigger in case the user fully closes the app, so that we still // if the app state changes to paused, setReady()
// start the background sync process: // if the app state has been paused for more than 10 seconds, setBackground()
// annoyingly foreground code still runs in the background on android for some time, so we still use the original method _appStateTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
// to detect if we are in the background since it's much faster lastAppStates.add(lastAppState);
_bgTimer = Timer.periodic(const Duration(seconds: 1), (timer) async { if (lastAppStates.length > 10) {
fgCount++; lastAppStates.removeAt(0);
// we haven't been pinged in a while, so we are likely in the background:
if (fgCount == 4) {
setReady();
return;
} }
// printV(lastAppStates);
if (fgCount > 10) { // if (lastAppState == AppLifecycleState.resumed && serviceState != "FOREGROUND") {
fgCount = 0; // setForeground();
// }
if (lastAppStates.length < 5) return;
if (lastAppState == AppLifecycleState.paused && serviceState != "READY") {
setReady();
}
// if all 10 states are paused, setBackground()
if (lastAppStates.every((state) => state == AppLifecycleState.paused) && !bgSyncStarted) {
setBackground(); setBackground();
service.invoke("serviceState", {"state": "BACKGROUND"});
_bgTimer?.cancel();
} }
}); });
} }
@ -592,16 +619,22 @@ class BackgroundTasks {
bgService.invoke("foregroundPing"); bgService.invoke("foregroundPing");
} }
Future<void> serviceForeground() async { void lastAppState(AppLifecycleState state) {
if (serviceState == "FOREGROUND") { bgService.invoke("appState", {"state": state.toString()});
return; }
}
Future<void> serviceForeground() async {
printV("SERVICE FOREGROUNDED");
final settingsStore = getIt.get<SettingsStore>(); final settingsStore = getIt.get<SettingsStore>();
bool showNotifications = settingsStore.showSyncNotification; bool showNotifications = settingsStore.showSyncNotification;
bgService.invoke("stopService"); bgService.invoke("stopService");
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 3));
initializeService(bgService, showNotifications); initializeService(bgService, showNotifications);
bgService.invoke("setForeground");
}
Future<bool> isServiceRunning() async {
return await bgService.isRunning();
} }
void serviceReady() { void serviceReady() {

View file

@ -133,7 +133,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
} }
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) async {
final syncingWalletTypes = [WalletType.litecoin, WalletType.monero, WalletType.bitcoin]; final syncingWalletTypes = [WalletType.litecoin, WalletType.monero, WalletType.bitcoin];
switch (state) { switch (state) {
case AppLifecycleState.paused: case AppLifecycleState.paused:
@ -145,10 +145,6 @@ class RootState extends State<Root> with WidgetsBindingObserver {
setState(() => _setInactive(true)); setState(() => _setInactive(true));
} }
if (FeatureFlag.isBackgroundSyncEnabled && syncingWalletTypes.contains(widget.appStore.wallet?.type)) {
widget.appStore.wallet?.stopSync();
}
break; break;
case AppLifecycleState.resumed: case AppLifecycleState.resumed:
widget.authService.requireAuth().then((value) { widget.authService.requireAuth().then((value) {
@ -163,37 +159,53 @@ class RootState extends State<Root> with WidgetsBindingObserver {
break; break;
} }
// _stateTimer?.cancel();
// _stateTimer = Timer(const Duration(seconds: 1), () async {
// getIt.get<BackgroundTasks>().lastAppState(state);
// });
getIt.get<BackgroundTasks>().lastAppState(state);
// background service handling: // background service handling:
printV("state: $state");
switch (state) { switch (state) {
case AppLifecycleState.resumed: case AppLifecycleState.resumed:
// restart the background service if it was running before: // // restart the background service if it was running before:
getIt.get<BackgroundTasks>().serviceForeground(); await getIt.get<BackgroundTasks>().serviceForeground();
_stateTimer?.cancel(); // _stateTimer?.cancel();
if (!wasInBackground) { // if (!wasInBackground) {
return; // return;
} // }
wasInBackground = false; // wasInBackground = false;
if (syncingWalletTypes.contains(widget.appStore.wallet?.type)) { // final appStore = widget.appStore;
// wait a few seconds before starting the sync make sure the background service is fully exited: // final wallet = appStore.wallet;
Future.delayed(const Duration(seconds: 3), () { // if (syncingWalletTypes.contains(wallet?.type)) {
widget.appStore.wallet?.startSync(); // // wait a few seconds before starting the sync to make sure the background service is fully exited:
}); // Future.delayed(const Duration(seconds: 50), () async {
} // final node = appStore.settingsStore.getCurrentNode(wallet!.type);
// await wallet.stopSync();
// await wallet.init();
// wallet.connectToNode(node: node);
// wallet.startSync();
// });
// }
break; break;
case AppLifecycleState.paused: case AppLifecycleState.hidden:
getIt.get<BackgroundTasks>().serviceReady();
case AppLifecycleState.inactive: case AppLifecycleState.inactive:
case AppLifecycleState.detached: case AppLifecycleState.detached:
default: break;
// anything other than resumed update the notification to say we're in the "ready" state: case AppLifecycleState.paused:
// if we enter any state other than resumed start a timer for 30 seconds widget.appStore.wallet?.stopSync();
// after which we'll consider the app to be in the background // getIt.get<BackgroundTasks>().serviceReady();
_stateTimer?.cancel(); // // if (FeatureFlag.isBackgroundSyncEnabled &&
// TODO: bump this to > 30 seconds when testing is done: // // syncingWalletTypes.contains(widget.appStore.wallet?.type)) {
_stateTimer = Timer(const Duration(seconds: 10), () async { // // widget.appStore.wallet?.stopSync();
wasInBackground = true; // // }
getIt.get<BackgroundTasks>().serviceBackground(); // _stateTimer?.cancel();
}); // _stateTimer = Timer(const Duration(seconds: 10), () async {
// wasInBackground = true;
// getIt.get<BackgroundTasks>().serviceBackground();
// });
break; break;
} }
_previousState = state; _previousState = state;