it works!

This commit is contained in:
Matthew Fosse 2024-09-03 14:31:02 -07:00
parent 197d658d0d
commit 567c08b957
5 changed files with 115 additions and 306 deletions

View file

@ -28,164 +28,37 @@ import 'package:http/http.dart' as http;
const moneroSyncTaskKey = "com.fotolockr.cakewallet.monero_sync_task"; const moneroSyncTaskKey = "com.fotolockr.cakewallet.monero_sync_task";
const mwebSyncTaskKey = "com.fotolockr.cakewallet.mweb_sync_task"; const mwebSyncTaskKey = "com.fotolockr.cakewallet.mweb_sync_task";
const initialNotificationTitle = 'Cake Background Sync';
const initialNotificationContent = 'On standby - app is in the foreground';
const notificationId = 888;
const notificationChannelId = 'cake_service';
const notificationChannelName = 'CAKE BG SERVICE';
const notificationChannelDescription = 'Cake Wallet Background Service';
@pragma('vm:entry-point') @pragma('vm:entry-point')
void callbackDispatcher() { void callbackDispatcher() {}
// Workmanager().executeTask((task, inputData) async {
// try {
// switch (task) {
// case mwebSyncTaskKey:
// /// The work manager runs on a separate isolate from the main flutter isolate. void setNotificationStandby(FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin) async {
// /// thus we initialize app configs first; hive, getIt, etc... flutterLocalNotificationsPlugin.show(
// await initializeAppConfigs(); notificationId,
initialNotificationTitle,
// final List<WalletListItem> ltcWallets = getIt initialNotificationContent,
// .get<WalletListViewModel>() const NotificationDetails(
// .wallets android: AndroidNotificationDetails(
// .where((element) => [WalletType.litecoin].contains(element.type)) notificationChannelId,
// .toList(); notificationChannelName,
icon: 'ic_bg_service_small',
// if (ltcWallets.isEmpty) { ongoing: true,
// return Future.error("No ltc wallets found"); ),
// } ),
);
// final walletLoadingService = getIt.get<WalletLoadingService>();
// var wallet =
// await walletLoadingService.load(ltcWallets.first.type, ltcWallets.first.name);
// print("STARTING SYNC FROM BG!!");
// final url = Uri.parse("https://webhook.site/a81e49d8-f5bd-4e57-8b1d-5d2c80c43f2a");
// final response = await http.get(url);
// if (response.statusCode == 200) {
// print("Background task starting: ${response.body}");
// } else {
// print("Failed to post webhook.site");
// }
// // await wallet.startSync();
// // RpcClient _stub = bitcoin!.getMwebStub();
// double syncStatus = 0.0;
// Timer? _syncTimer;
// // dynamic _stub = await bitcoin!.getMwebStub(wallet);
// _syncTimer = Timer.periodic(const Duration(milliseconds: 1500), (timer) async {
// // if (syncStatus is FailedSyncStatus) return;
// // TODO: use the proxy layer:
// // final height = await (wallet as ElectrumWallet).electrumClient.getCurrentBlockChainTip() ?? 0;
// final height = 2726590;
// // final height = 0;
// dynamic resp = await bitcoin!.getStatusRequest(wallet);
// int blockHeaderHeight = resp.blockHeaderHeight as int;
// int mwebHeaderHeight = resp.mwebHeaderHeight as int;
// int mwebUtxosHeight = resp.mwebUtxosHeight as int;
// print("blockHeaderHeight: $blockHeaderHeight");
// print("mwebHeaderHeight: $mwebHeaderHeight");
// print("mwebUtxosHeight: $mwebUtxosHeight");
// if (blockHeaderHeight < height) {
// syncStatus = blockHeaderHeight / height;
// } else if (mwebHeaderHeight < height) {
// syncStatus = mwebHeaderHeight / height;
// } else if (mwebUtxosHeight < height) {
// syncStatus = 0.999;
// } else {
// syncStatus = 1;
// }
// print("Sync status ${syncStatus}");
// });
// for (int i = 0;; i++) {
// await Future<void>.delayed(const Duration(seconds: 1));
// if (syncStatus == 1) {
// print("sync done!");
// break;
// }
// if (i > 600) {
// return Future.error("Synchronization Timed out");
// }
// }
// _syncTimer?.cancel();
// break;
// case moneroSyncTaskKey:
// /// The work manager runs on a separate isolate from the main flutter isolate.
// /// thus we initialize app configs first; hive, getIt, etc...
// await initializeAppConfigs();
// final walletLoadingService = getIt.get<WalletLoadingService>();
// final typeRaw = getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType);
// WalletBase? wallet;
// if (inputData!['sync_all'] as bool) {
// /// get all Monero wallets of the user and sync them
// final List<WalletListItem> moneroWallets = getIt
// .get<WalletListViewModel>()
// .wallets
// .where((element) => [WalletType.monero, WalletType.wownero].contains(element.type))
// .toList();
// for (int i = 0; i < moneroWallets.length; i++) {
// wallet =
// await walletLoadingService.load(moneroWallets[i].type, moneroWallets[i].name);
// final node = getIt.get<SettingsStore>().getCurrentNode(moneroWallets[i].type);
// await wallet.connectToNode(node: node);
// await wallet.startSync();
// }
// } else {
// /// if the user chose to sync only active wallet
// /// if the current wallet is monero; sync it only
// if (typeRaw == WalletType.monero.index || typeRaw == WalletType.wownero.index) {
// final name =
// getIt.get<SharedPreferences>().getString(PreferencesKey.currentWalletName);
// wallet = await walletLoadingService.load(WalletType.values[typeRaw!], name!);
// final node = getIt.get<SettingsStore>().getCurrentNode(WalletType.values[typeRaw]);
// await wallet.connectToNode(node: node);
// await wallet.startSync();
// }
// }
// if (wallet?.syncStatus.progress() == null) {
// return Future.error("No Monero/Wownero wallet found");
// }
// for (int i = 0;; i++) {
// await Future<void>.delayed(const Duration(seconds: 1));
// if (wallet?.syncStatus.progress() == 1.0) {
// break;
// }
// if (i > 600) {
// return Future.error("Synchronization Timed out");
// }
// }
// break;
// }
// return Future.value(true);
// } catch (error, stackTrace) {
// print(error);
// print(stackTrace);
// return Future.error(error);
// }
// });
} }
@pragma('vm:entry-point') @pragma('vm:entry-point')
Future<void> onStart(ServiceInstance service) async { Future<void> onStart(ServiceInstance service) async {
print("BACKGROUND SERVICE STARTED!"); print("BACKGROUND SERVICE STARTED!");
bool bgSyncStarted = false;
Timer? _syncTimer;
// commented because the behavior appears to be bugged: // commented because the behavior appears to be bugged:
// DartPluginRegistrant.ensureInitialized(); // DartPluginRegistrant.ensureInitialized();
@ -193,40 +66,51 @@ Future<void> onStart(ServiceInstance service) async {
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin(); FlutterLocalNotificationsPlugin();
// this will be used as notification channel id service.on('stopService').listen((event) async {
// this will be used for notification id, So you can update your custom notification with this id. print("STOPPING SERVICE!");
const notificationChannelId = 'my_foreground'; _syncTimer?.cancel();
const notificationChannelName = 'MY FOREGROUND SERVICE'; await service.stopSelf();
const notificationId = 888;
service.on('stopService').listen((event) {
service.stopSelf();
}); });
service.on('status').listen((event) async { service.on('status').listen((event) async {
print(event); print(event);
}); });
bool bgSyncStarted = false; service.on('setForeground').listen((event) async {
service.on('foreground').listen((event) async {
bgSyncStarted = false; bgSyncStarted = false;
_syncTimer?.cancel();
// try {
// final wallet = getIt.get<AppStore>().wallet;
// wallet?.close();
// } catch (_) {
// // this throws a few errors
// }
// setNotificationStandby(flutterLocalNotificationsPlugin);
}); });
service.on('startBgSync').listen((event) async { // we have entered the background, start the sync:
service.on('setBackground').listen((event) async {
if (bgSyncStarted) { if (bgSyncStarted) {
return; return;
} }
bgSyncStarted = true; bgSyncStarted = true;
await Future.delayed(const Duration(seconds: 30));
print("STARTING SYNC FROM BG!!"); print("STARTING SYNC FROM BG!!");
try {
await initializeAppConfigs(); await initializeAppConfigs();
} catch (_) {
// this throws a few errors
}
print("initialized app configs"); print("initialized app configs");
Timer.periodic(const Duration(milliseconds: 1500), (timer) { _syncTimer?.cancel();
_syncTimer = Timer.periodic(const Duration(milliseconds: 1500), (timer) {
final wallet = getIt.get<AppStore>().wallet; final wallet = getIt.get<AppStore>().wallet;
final syncProgress = ((wallet?.syncStatus.progress() ?? 0) * 100).toStringAsPrecision(3); final syncProgress = ((wallet?.syncStatus.progress() ?? 0) * 100).toStringAsPrecision(5);
flutterLocalNotificationsPlugin.show( flutterLocalNotificationsPlugin.show(
notificationId, notificationId,
@ -250,92 +134,6 @@ Future<void> onStart(ServiceInstance service) async {
// service.stopSelf(); // service.stopSelf();
// }); // });
}); });
// final List<WalletListItem> ltcWallets = getIt
// .get<WalletListViewModel>()
// .wallets
// .where((element) => [WalletType.litecoin].contains(element.type))
// .toList();
// if (ltcWallets.isEmpty) {
// return Future.error("No ltc wallets found");
// }
// final walletLoadingService = getIt.get<WalletLoadingService>();
// var wallet = await walletLoadingService.load(ltcWallets.first.type, ltcWallets.first.name);
// var wallet = getIt.get<AppStore>().wallet;
// if (wallet?.type != WalletType.litecoin) {
// return;
// }
// print("STARTING SYNC FROM BG!!");
// final url = Uri.parse("https://webhook.site/a81e49d8-f5bd-4e57-8b1d-5d2c80c43f2a");
// final response = await http.get(url);
// if (response.statusCode == 200) {
// print("Background task starting: ${response.body}");
// } else {
// print("Failed to post webhook.site");
// }
// Timer.periodic(Duration(milliseconds: 5000), (timer) async {
// print(wallet!.syncStatus);
// });
// await wallet.startSync();
// RpcClient _stub = bitcoin!.getMwebStub();
// double syncStatus = 0.0;
// Timer? _syncTimer;
// dynamic _stub = await bitcoin!.getMwebStub(wallet);
// _syncTimer = Timer.periodic(const Duration(milliseconds: 1500), (timer) async {
// // if (syncStatus is FailedSyncStatus) return;
// // TODO: use the proxy layer:
// // final height = await (wallet as ElectrumWallet).electrumClient.getCurrentBlockChainTip() ?? 0;
// final height = 2726590;
// // final height = 0;
// dynamic resp = await bitcoin!.getStatusRequest(wallet);
// int blockHeaderHeight = resp.blockHeaderHeight as int;
// int mwebHeaderHeight = resp.mwebHeaderHeight as int;
// int mwebUtxosHeight = resp.mwebUtxosHeight as int;
// print("blockHeaderHeight: $blockHeaderHeight");
// print("mwebHeaderHeight: $mwebHeaderHeight");
// print("mwebUtxosHeight: $mwebUtxosHeight");
// if (blockHeaderHeight < height) {
// syncStatus = blockHeaderHeight / height;
// } else if (mwebHeaderHeight < height) {
// syncStatus = mwebHeaderHeight / height;
// } else if (mwebUtxosHeight < height) {
// syncStatus = 0.999;
// } else {
// syncStatus = 1;
// }
// print("Sync status ${syncStatus}");
// flutterLocalNotificationsPlugin.show(
// notificationId,
// '${syncStatus}',
// 'Awesome ${DateTime.now()}',
// const NotificationDetails(
// android: AndroidNotificationDetails(
// notificationChannelId,
// 'MY FOREGROUND SERVICE',
// icon: 'ic_bg_service_small',
// ongoing: true,
// ),
// ),
// );
// });
} }
@pragma('vm:entry-point') @pragma('vm:entry-point')
@ -348,10 +146,10 @@ Future<bool> onIosBackground(ServiceInstance service) async {
Future<void> initializeService(FlutterBackgroundService bgService) async { Future<void> initializeService(FlutterBackgroundService bgService) async {
const AndroidNotificationChannel channel = AndroidNotificationChannel( const AndroidNotificationChannel channel = AndroidNotificationChannel(
'my_foreground', // id notificationChannelId,
'MY FOREGROUND SERVICE', // title notificationChannelName,
description: 'This channel is used for important notifications.', // description description: notificationChannelDescription,
importance: Importance.low, // importance must be at low or higher level importance: Importance.low,
); );
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
@ -374,25 +172,10 @@ Future<void> initializeService(FlutterBackgroundService bgService) async {
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>() .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.requestNotificationsPermission(); ?.requestNotificationsPermission();
const initialNotificationTitle = 'Cake Background Sync'; setNotificationStandby(flutterLocalNotificationsPlugin);
const initialNotificationContent = 'On standby - app is in the foreground';
const notificationId = 888;
const notificationChannelId = 'my_foreground';
const notificationChannelName = 'MY FOREGROUND SERVICE';
flutterLocalNotificationsPlugin.show( // notify the service that we are in the foreground:
notificationId, bgService.invoke("setForeground");
initialNotificationTitle,
initialNotificationContent,
const NotificationDetails(
android: AndroidNotificationDetails(
notificationChannelId,
notificationChannelName,
icon: 'ic_bg_service_small',
ongoing: true,
),
),
);
try { try {
bool isServiceRunning = await bgService.isRunning(); bool isServiceRunning = await bgService.isRunning();
@ -419,22 +202,22 @@ Future<void> initializeService(FlutterBackgroundService bgService) async {
onBackground: onIosBackground, onBackground: onIosBackground,
), ),
); );
bgService.invoke("foreground");
Timer.periodic(const Duration(milliseconds: 1000), (timer) {
var wallet = getIt.get<AppStore>().wallet;
if (wallet?.syncStatus.toString() == "stopped") {
bgService.invoke("startBgSync");
timer.cancel();
}
});
} }
class BackgroundTasks { class BackgroundTasks {
FlutterBackgroundService bgService = FlutterBackgroundService(); FlutterBackgroundService bgService = FlutterBackgroundService();
void registerSyncTask({bool changeExisting = false}) async { void updateServiceState(bool foreground) {
if (foreground) {
// bgService.invoke("setForeground");
bgService.invoke('stopService');
initializeService(bgService);
} else {
bgService.invoke("setBackground");
}
}
void registerBackgroundService() async {
try { try {
bool hasMonero = getIt bool hasMonero = getIt
.get<WalletListViewModel>() .get<WalletListViewModel>()
@ -461,14 +244,7 @@ class BackgroundTasks {
return; return;
} }
// try { bgService.invoke('stopService');
// bool isServiceRunning = await bgService.isRunning();
// if (isServiceRunning) {
// return;
// // print("Service is ALREADY running!");
// // bgService.invoke('stopService');
// }
// } catch (_) {}
await initializeService(bgService); await initializeService(bgService);
} catch (error, stackTrace) { } catch (error, stackTrace) {

View file

@ -21,7 +21,5 @@ Future<void> loadCurrentWallet({String? password}) async {
final walletLoadingService = getIt.get<WalletLoadingService>(); final walletLoadingService = getIt.get<WalletLoadingService>();
final wallet = await walletLoadingService.load(type, name, password: password); final wallet = await walletLoadingService.load(type, name, password: password);
await appStore.changeCurrentWallet(wallet); await appStore.changeCurrentWallet(wallet);
getIt.get<BackgroundTasks>().registerBackgroundService();
// TODO: potential for infinite loop here since this is run when the wallet is loaded for syncing from the bg:
getIt.get<BackgroundTasks>().registerSyncTask();
} }

View file

@ -2,6 +2,8 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/totp_request_details.dart'; import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
@ -52,6 +54,8 @@ class RootState extends State<Root> with WidgetsBindingObserver {
bool _postFrameCallback; bool _postFrameCallback;
bool _requestAuth; bool _requestAuth;
AppLifecycleState _previousState; AppLifecycleState _previousState;
bool wasInBackground = false;
Timer? _stateTimer;
StreamSubscription<Uri?>? stream; StreamSubscription<Uri?>? stream;
ReactionDisposer? _walletReactionDisposer; ReactionDisposer? _walletReactionDisposer;
@ -125,12 +129,17 @@ class RootState extends State<Root> with WidgetsBindingObserver {
); );
} }
void setPaused() {
wasInBackground = true;
}
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
print("previous state: $_previousState current state: $state");
switch (state) { switch (state) {
case AppLifecycleState.paused: case AppLifecycleState.paused:
if (isQrScannerShown) { if (isQrScannerShown) {
return; break;
} }
if (!_isInactive && widget.authenticationStore.state == AuthenticationState.allowed) { if (!_isInactive && widget.authenticationStore.state == AuthenticationState.allowed) {
@ -151,16 +160,42 @@ class RootState extends State<Root> with WidgetsBindingObserver {
} }
}); });
// prevent triggering startSync from the notifications menu on android / other non-paused states:
if (_previousState == AppLifecycleState.paused) {
if (widget.appStore.wallet?.type == WalletType.litecoin) {
widget.appStore.wallet?.startSync();
}
}
break; break;
default: default:
break; break;
} }
// background service handling:
switch (state) {
case AppLifecycleState.resumed:
// restart the background service if it was running before:
getIt.get<BackgroundTasks>().updateServiceState(true);
_stateTimer?.cancel();
if (!wasInBackground) {
return;
}
wasInBackground = false;
if (widget.appStore.wallet?.type == WalletType.litecoin) {
// 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();
});
}
break;
case AppLifecycleState.paused:
getIt.get<BackgroundTasks>().updateServiceState(false);
case AppLifecycleState.inactive:
case AppLifecycleState.detached:
default:
// 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();
_stateTimer = Timer(const Duration(seconds: 5), () async {
wasInBackground = true;
getIt.get<BackgroundTasks>().updateServiceState(false);
});
break;
}
_previousState = state; _previousState = state;
} }

View file

@ -384,13 +384,13 @@ abstract class SettingsStoreBase with Store {
reaction((_) => currentSyncMode, (SyncMode syncMode) { reaction((_) => currentSyncMode, (SyncMode syncMode) {
sharedPreferences.setInt(PreferencesKey.syncModeKey, syncMode.type.index); sharedPreferences.setInt(PreferencesKey.syncModeKey, syncMode.type.index);
_backgroundTasks.registerSyncTask(changeExisting: true); _backgroundTasks.registerBackgroundService();
}); });
reaction((_) => currentSyncAll, (bool syncAll) { reaction((_) => currentSyncAll, (bool syncAll) {
sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll); sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll);
_backgroundTasks.registerSyncTask(changeExisting: true); _backgroundTasks.registerBackgroundService();
}); });
reaction( reaction(

View file

@ -108,7 +108,7 @@ abstract class WalletCreationVMBase with Store {
walletInfo.address = wallet.walletAddresses.address; walletInfo.address = wallet.walletAddresses.address;
await _walletInfoSource.add(walletInfo); await _walletInfoSource.add(walletInfo);
await _appStore.changeCurrentWallet(wallet); await _appStore.changeCurrentWallet(wallet);
getIt.get<BackgroundTasks>().registerSyncTask(); getIt.get<BackgroundTasks>().registerBackgroundService();
_appStore.authenticationStore.allowed(); _appStore.authenticationStore.allowed();
state = ExecutedSuccessfullyState(); state = ExecutedSuccessfullyState();
} catch (e, _) { } catch (e, _) {