diff --git a/android/app/src/main/java/app/zeusln/zeus/LdkNodeModule.kt b/android/app/src/main/java/app/zeusln/zeus/LdkNodeModule.kt index 2f225391b..8a3f9ac74 100644 --- a/android/app/src/main/java/app/zeusln/zeus/LdkNodeModule.kt +++ b/android/app/src/main/java/app/zeusln/zeus/LdkNodeModule.kt @@ -535,6 +535,22 @@ class LdkNodeModule(reactContext: ReactApplicationContext) : ReactContextBaseJav return false } + @ReactMethod + fun setPersistentMode(enabled: Boolean, promise: Promise) { + try { + if (enabled) { + if (node != null) { + LdkNodeService.startService(reactApplicationContext) + } + } else { + LdkNodeService.stopService(reactApplicationContext) + } + promise.resolve(null) + } catch (e: Exception) { + promise.reject("error", errorMessage(e)) + } + } + // Node Lifecycle Methods @ReactMethod diff --git a/android/app/src/main/java/com/zeus/LndMobileService.java b/android/app/src/main/java/com/zeus/LndMobileService.java index 25835fe56..3de0387e6 100644 --- a/android/app/src/main/java/com/zeus/LndMobileService.java +++ b/android/app/src/main/java/com/zeus/LndMobileService.java @@ -579,6 +579,34 @@ public class LndMobileService extends Service { notificationManager.notify(ONGOING_NOTIFICATION_ID, buildNotification()); } return START_NOT_STICKY; + } else if (intent.getAction().equals("app.zeusln.zeus.android.intent.action.APPLY_PERSISTENT_MODE")) { + if (notificationManager == null) { + notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + } + boolean enabled = intent.hasExtra("enabled") + ? intent.getBooleanExtra("enabled", false) + : getPersistentServicesEnabled(this); + if (enabled) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel chan = new NotificationChannel(BuildConfig.APPLICATION_ID, "ZEUS", NotificationManager.IMPORTANCE_NONE); + chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); + notificationManager.createNotificationChannel(chan); + } + Notification notification = buildNotification(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + startForeground(ONGOING_NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE); + } else { + startForeground(ONGOING_NOTIFICATION_ID, notification); + } + } else { + try { + stopForeground(STOP_FOREGROUND_REMOVE); + } catch (Exception e) { + Log.e(TAG, "Error stopping foreground service", e); + } + notificationManager.cancel(ONGOING_NOTIFICATION_ID); + } + return enabled ? START_STICKY : START_NOT_STICKY; } } diff --git a/android/app/src/main/java/com/zeus/LndMobileTools.java b/android/app/src/main/java/com/zeus/LndMobileTools.java index 9c10f003f..bf79d94d3 100644 --- a/android/app/src/main/java/com/zeus/LndMobileTools.java +++ b/android/app/src/main/java/com/zeus/LndMobileTools.java @@ -613,6 +613,23 @@ class LndMobileTools extends ReactContextBaseJavaModule { ProcessPhoenix.triggerRebirth(getReactApplicationContext()); } + @ReactMethod + public void setPersistentMode(boolean enabled, Promise promise) { + try { + Intent intent = new Intent(getReactApplicationContext(), LndMobileService.class); + intent.setAction("app.zeusln.zeus.android.intent.action.APPLY_PERSISTENT_MODE"); + intent.putExtra("enabled", enabled); + if (enabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + getReactApplicationContext().startForegroundService(intent); + } else { + getReactApplicationContext().startService(intent); + } + promise.resolve(null); + } catch (Exception e) { + promise.reject("error", e.getMessage()); + } + } + private void checkWriteExternalStoragePermission(@NonNull RequestWriteExternalStoragePermissionCallback successCallback, @NonNull Runnable failCallback, @NonNull Runnable failPermissionCheckcallback) { diff --git a/ldknode/LdkNode.d.ts b/ldknode/LdkNode.d.ts index 08b7097f1..b8f926915 100644 --- a/ldknode/LdkNode.d.ts +++ b/ldknode/LdkNode.d.ts @@ -474,6 +474,7 @@ export interface ILdkNodeModule { start(): Promise; stop(): Promise; syncWallets(): Promise; + setPersistentMode(enabled: boolean): Promise; // Node Info Methods nodeId(): Promise; diff --git a/ldknode/LdkNodeInjection.ts b/ldknode/LdkNodeInjection.ts index e639a3c9c..cd233dfd4 100644 --- a/ldknode/LdkNodeInjection.ts +++ b/ldknode/LdkNodeInjection.ts @@ -3,7 +3,7 @@ * Similar pattern to LndMobileInjection */ -import { NativeModules } from 'react-native'; +import { NativeModules, Platform } from 'react-native'; import RNFS from 'react-native-fs'; import { generateVssAuthHeaders } from '../utils/VssAuthUtils'; import type { @@ -171,6 +171,11 @@ const syncWallets = async (): Promise => { return await LdkNodeModule.syncWallets(); }; +const setPersistentMode = async (enabled: boolean): Promise => { + if (Platform.OS !== 'android') return; + return await LdkNodeModule.setPersistentMode(enabled); +}; + // ============================================================================ // Node Info Functions // ============================================================================ @@ -1025,6 +1030,7 @@ export interface ILdkNodeInjections { start: () => Promise; stop: () => Promise; syncWallets: () => Promise; + setPersistentMode: (enabled: boolean) => Promise; nodeId: () => Promise; status: () => Promise; listBalances: () => Promise; @@ -1273,6 +1279,7 @@ const LdkNodeInjection: ILdkNodeInjections = { start, stop, syncWallets, + setPersistentMode, nodeId, status, listBalances, diff --git a/lndmobile/LndMobile.d.ts b/lndmobile/LndMobile.d.ts index 7f4ab91ff..74c88155c 100644 --- a/lndmobile/LndMobile.d.ts +++ b/lndmobile/LndMobile.d.ts @@ -133,6 +133,7 @@ export interface ILndMobileTools { checkLndProcessExist(): Promise; deleteTLSCerts(): Promise; restartApp(): void; + setPersistentMode(enabled: boolean): Promise; // iOS-specific checkICloudEnabled(): Promise; diff --git a/views/PendingHTLCs.tsx b/views/PendingHTLCs.tsx index b3f9ad733..2d2217461 100644 --- a/views/PendingHTLCs.tsx +++ b/views/PendingHTLCs.tsx @@ -1,5 +1,12 @@ import * as React from 'react'; -import { FlatList, Platform, Text, View, StyleSheet } from 'react-native'; +import { + FlatList, + NativeModules, + Platform, + Text, + View, + StyleSheet +} from 'react-native'; import { Button, ListItem } from '@rneui/themed'; import { inject, observer } from 'mobx-react'; import { Route } from '@react-navigation/native'; @@ -13,7 +20,6 @@ import Screen from '../components/Screen'; import Switch from '../components/Switch'; import { localeString } from '../utils/LocaleUtils'; -import { restartNeeded } from '../utils/RestartUtils'; import { themeColor } from '../utils/ThemeUtils'; import { numberWithCommas } from '../utils/UnitsUtils'; @@ -321,21 +327,46 @@ export default class PendingHTLCs extends React.PureComponent< { + const previousValue = + persistentMode; + const newValue = + !previousValue; this.setState({ persistentMode: - !persistentMode + newValue }); await updateSettings({ persistentMode: - !persistentMode + newValue }); - const newValue = - !persistentMode; await AsyncStorage.setItem( PERSISTENT_KEY, newValue.toString() ); - restartNeeded(); + try { + await NativeModules.LndMobileTools.setPersistentMode( + newValue + ); + } catch (e) { + console.error( + 'Failed to toggle LND persistent mode:', + e + ); + this.setState({ + persistentMode: + previousValue + }); + await updateSettings( + { + persistentMode: + previousValue + } + ); + await AsyncStorage.setItem( + PERSISTENT_KEY, + previousValue.toString() + ); + } }} trackEnabledColor={themeColor( 'background' diff --git a/views/Settings/EmbeddedNode/Advanced.tsx b/views/Settings/EmbeddedNode/Advanced.tsx index 9ad494ce6..f49dfa330 100644 --- a/views/Settings/EmbeddedNode/Advanced.tsx +++ b/views/Settings/EmbeddedNode/Advanced.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Platform, ScrollView, Text, View } from 'react-native'; +import { NativeModules, Platform, ScrollView, Text, View } from 'react-native'; import { Icon, ListItem } from '@rneui/themed'; import { inject, observer } from 'mobx-react'; import AsyncStorage from '@react-native-async-storage/async-storage'; @@ -215,21 +215,41 @@ export default class EmbeddedNodeAdvancedSettings extends React.Component< { + const previousValue = + persistentMode; + const newValue = !previousValue; this.setState({ - persistentMode: - !persistentMode + persistentMode: newValue }); await updateSettings({ - persistentMode: - !persistentMode + persistentMode: newValue }); - const newValue = - !persistentMode; await AsyncStorage.setItem( PERSISTENT_KEY, newValue.toString() ); - restartNeeded(); + try { + await NativeModules.LndMobileTools.setPersistentMode( + newValue + ); + } catch (e) { + console.error( + 'Failed to toggle LND persistent mode:', + e + ); + this.setState({ + persistentMode: + previousValue + }); + await updateSettings({ + persistentMode: + previousValue + }); + await AsyncStorage.setItem( + PERSISTENT_KEY, + previousValue.toString() + ); + } }} /> @@ -244,13 +264,11 @@ export default class EmbeddedNodeAdvancedSettings extends React.Component< color: themeColor('secondaryText') }} > - {`${localeString( + {localeString( embeddedTor ? 'views.Settings.EmbeddedNode.persistentMode.subtitleTor' : 'views.Settings.EmbeddedNode.persistentMode.subtitle' - )} ${localeString( - 'views.Settings.EmbeddedNode.restart' - )}`} + )} diff --git a/views/Settings/EmbeddedNode/index.tsx b/views/Settings/EmbeddedNode/index.tsx index 785160142..c0bb2bd83 100644 --- a/views/Settings/EmbeddedNode/index.tsx +++ b/views/Settings/EmbeddedNode/index.tsx @@ -12,9 +12,10 @@ import Switch from '../../../components/Switch'; import SettingsStore from '../../../stores/SettingsStore'; import { localeString } from '../../../utils/LocaleUtils'; -import { restartNeeded } from '../../../utils/RestartUtils'; import { themeColor } from '../../../utils/ThemeUtils'; +import LdkNode from '../../../ldknode/LdkNodeInjection'; + const PERSISTENT_LDK_KEY = 'persistentLdkNodeServicesEnabled'; interface EmbeddedNodeProps { @@ -434,8 +435,9 @@ export default class EmbeddedNode extends React.Component< { - const newValue = - !this.state.persistentMode; + const previousValue = + this.state.persistentMode; + const newValue = !previousValue; this.setState({ persistentMode: newValue }); @@ -443,7 +445,24 @@ export default class EmbeddedNode extends React.Component< PERSISTENT_LDK_KEY, newValue.toString() ); - restartNeeded(); + try { + await LdkNode.node.setPersistentMode( + newValue + ); + } catch (e) { + console.error( + 'Failed to toggle LDK persistent mode:', + e + ); + this.setState({ + persistentMode: + previousValue + }); + await AsyncStorage.setItem( + PERSISTENT_LDK_KEY, + previousValue.toString() + ); + } }} /> @@ -454,11 +473,9 @@ export default class EmbeddedNode extends React.Component< color: themeColor('secondaryText') }} > - {`${localeString( + {localeString( 'views.Settings.EmbeddedNode.persistentModeLdk.subtitle' - )} ${localeString( - 'views.Settings.EmbeddedNode.restart' - )}`} + )}