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

View file

@ -60,6 +60,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
bool get isHardwareWallet => walletInfo.isHardwareWallet;
Future<void> init();
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

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
bool isMainThread = Isolate.current.debugName == "main";
printV("isMainThread: $isMainThread");
_autoSaveTimer?.cancel();
_autoSaveTimer =
Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save());
@ -275,6 +272,8 @@ abstract class MoneroWalletBase
return;
}
monero_wallet.stopSync();
_autoSaveTimer?.cancel();
monero_wallet.closeCurrentWallet();
}
@override
@ -737,13 +736,17 @@ abstract class MoneroWalletBase
if (walletInfo.isRecovery) {
await _askForUpdateTransactionHistory();
_askForUpdateBalance();
walletAddresses.accountList.update();
if (!isBackgroundSyncing) {
walletAddresses.accountList.update();
}
}
if (blocksLeft < 100) {
await _askForUpdateTransactionHistory();
_askForUpdateBalance();
walletAddresses.accountList.update();
if (!isBackgroundSyncing) {
walletAddresses.accountList.update();
}
syncStatus = SyncedSyncStatus();
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")
Future<void> onStart(ServiceInstance service) async {
printV("BACKGROUND SERVICE STARTED");
@ -125,10 +141,13 @@ Future<void> onStart(ServiceInstance service) async {
Timer? _syncTimer;
Timer? _stuckSyncTimer;
Timer? _queueTimer;
Timer? _appStateTimer;
List<WalletBase> syncingWallets = [];
List<WalletBase> standbyWallets = [];
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:
// DartPluginRegistrant.ensureInitialized();
@ -136,8 +155,7 @@ Future<void> onStart(ServiceInstance service) async {
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
service.on("stopService").listen((event) async {
printV("STOPPING BACKGROUND SERVICE");
Future<void> stopAllSyncing() async {
_syncTimer?.cancel();
_stuckSyncTimer?.cancel();
_queueTimer?.cancel();
@ -157,6 +175,11 @@ Future<void> onStart(ServiceInstance service) async {
} catch (e) {
printV("error stopping sync: $e");
}
}
service.on("stopService").listen((event) async {
printV("STOPPING BACKGROUND SERVICE");
await stopAllSyncing();
// stop the service itself:
service.invoke("serviceState", {"state": "NOT_RUNNING"});
await service.stopSelf();
@ -166,36 +189,39 @@ Future<void> onStart(ServiceInstance service) async {
printV(event);
});
void setForeground() {
Future<void> setForeground() async {
serviceState = "FOREGROUND";
bgSyncStarted = false;
_syncTimer?.cancel();
setNotificationStandby(flutterLocalNotificationsPlugin);
}
service.on("setForeground").listen((event) async {
setForeground();
await setForeground();
service.invoke("serviceState", {"state": "FOREGROUND"});
});
void setReady() {
setNotificationReady(flutterLocalNotificationsPlugin);
if (serviceState != "READY" && serviceState != "BACKGROUND") {
serviceState = "READY";
setNotificationReady(flutterLocalNotificationsPlugin);
}
}
service.on("setReady").listen((event) async {
setReady();
service.invoke("serviceState", {"state": "READY"});
});
service.on("foregroundPing").listen((event) async {
fgCount = 0;
service.on("appState").listen((event) async {
printV("APP STATE: ${event?["state"]}");
lastAppState = appStateFromString(event?["state"] as String);
});
// we have entered the background, start the sync:
void setBackground() async {
if (bgSyncStarted) {
return;
}
// only runs once per service instance:
if (bgSyncStarted) return;
bgSyncStarted = true;
serviceState = "BACKGROUND";
await Future.delayed(const Duration(seconds: DELAY_SECONDS_BEFORE_SYNC_START));
printV("STARTING SYNC FROM BG");
@ -482,23 +508,24 @@ Future<void> onStart(ServiceInstance service) async {
setBackground();
});
// this is a backup timer to trigger in case the user fully closes the app, so that we still
// start the background sync process:
// annoyingly foreground code still runs in the background on android for some time, so we still use the original method
// to detect if we are in the background since it's much faster
_bgTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
fgCount++;
// we haven't been pinged in a while, so we are likely in the background:
if (fgCount == 4) {
setReady();
return;
// if the app state changes to paused, setReady()
// if the app state has been paused for more than 10 seconds, setBackground()
_appStateTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
lastAppStates.add(lastAppState);
if (lastAppStates.length > 10) {
lastAppStates.removeAt(0);
}
if (fgCount > 10) {
fgCount = 0;
// printV(lastAppStates);
// if (lastAppState == AppLifecycleState.resumed && serviceState != "FOREGROUND") {
// 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();
service.invoke("serviceState", {"state": "BACKGROUND"});
_bgTimer?.cancel();
}
});
}
@ -592,16 +619,22 @@ class BackgroundTasks {
bgService.invoke("foregroundPing");
}
Future<void> serviceForeground() async {
if (serviceState == "FOREGROUND") {
return;
}
void lastAppState(AppLifecycleState state) {
bgService.invoke("appState", {"state": state.toString()});
}
Future<void> serviceForeground() async {
printV("SERVICE FOREGROUNDED");
final settingsStore = getIt.get<SettingsStore>();
bool showNotifications = settingsStore.showSyncNotification;
bgService.invoke("stopService");
await Future.delayed(const Duration(seconds: 2));
await Future.delayed(const Duration(seconds: 3));
initializeService(bgService, showNotifications);
bgService.invoke("setForeground");
}
Future<bool> isServiceRunning() async {
return await bgService.isRunning();
}
void serviceReady() {

View file

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