diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/AlwaysOnNotificationServiceManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/AlwaysOnNotificationServiceManager.kt index 58c11797f9..4af3a66589 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/AlwaysOnNotificationServiceManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/AlwaysOnNotificationServiceManager.kt @@ -53,8 +53,10 @@ import kotlinx.coroutines.launch * this is the battery-saver "airplane mode". Persisted, so an explicit off survives * restarts and crashes. * - The **per-account participation** flag ([com.vitorpamplona.amethyst.model.AccountSettings.alwaysOnNotificationService], - * "Keep this account active in the background"). While the master is on, the service - * runs as long as **at least one** writable account participates. + * "Keep this account active in the background") **or** its NIP-46 signer toggle + * ([com.vitorpamplona.amethyst.model.AccountSettings.nip46SignerEnabled]). While the master is + * on, the service runs as long as **at least one** writable account has either flag on — the + * background signer relies on the same foreground service to keep answering requests. * * While the master is on, every saved writable account is kept loaded in * [AccountCacheState] so (a) its participation flag is observable and (b) GiftWraps @@ -83,6 +85,11 @@ class AlwaysOnNotificationServiceManager( * loaded writable account. The service layers run while the master is on AND at * least one account participates; the master overrides everything when off. * + * An account "participates" when either its always-on setting **or** its NIP-46 + * signer toggle is on: the foreground service (and its restart layers) keep the + * process + shared relay client alive, which is exactly what the background signer + * needs to keep answering requests, so the signer toggle keeps the layers up too. + * * Idempotent: safe to call again on account switch/login — it restarts the watch. */ @OptIn(ExperimentalCoroutinesApi::class) @@ -105,11 +112,17 @@ class AlwaysOnNotificationServiceManager( // Master on: keep every writable account loaded so its participation // flag is observable and its gift wraps can decrypt, then run the - // service only while at least one account is participating. + // service only while at least one account is participating. An account + // participates when its always-on setting OR its NIP-46 signer toggle is + // on — the background signer needs the same foreground service alive. startMultiAccountPreload() accountsCache.accounts .flatMapLatest { accounts -> - val flags = accounts.values.map { it.settings.alwaysOnNotificationService } + val flags = + accounts.values.map { account -> + account.settings.alwaysOnNotificationService + .combine(account.settings.nip46SignerEnabled) { alwaysOn, signer -> alwaysOn || signer } + } if (flags.isEmpty()) { flowOf(false) } else { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationRelayService.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationRelayService.kt index 2f48e5466b..c2fa2eab2d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationRelayService.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationRelayService.kt @@ -126,9 +126,11 @@ class NotificationRelayService : Service() { // the service itself once a participant exists. fun isEnabled(context: Context): Boolean = try { + // The service also backs the NIP-46 signer, so an account with the signer on + // participates just like one with always-on notifications on. LocalPreferences.isNotificationServiceEnabled() && Amethyst.instance.accountsCache.accounts.value.values.any { - it.settings.alwaysOnNotificationService.value + it.settings.alwaysOnNotificationService.value || it.settings.nip46SignerEnabled.value } } catch (e: Exception) { false diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt index c15648de23..15ef1b7b98 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt @@ -198,6 +198,12 @@ fun Nip46SignerScreen( count = connectedCount, onClick = { nav.nav(Route.ConnectedApps) }, ) + + Text( + stringResource(R.string.nip46_signer_background_hint), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) } } } diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 5b43f12ee2..6b81a1e108 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -926,6 +926,7 @@ Could not connect: %1$s This is a read-only account and cannot sign. Remote signer app + Amethyst keeps a background connection (shown as an ongoing notification) so it can answer signing requests while closed. Sign for other apps Turn on signer Live