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