mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 08:27:04 +00:00
feat(amethyst): keep the signer answering in the background
The signer's relay subscription lives in the account scope and needs the shared NostrClient connected and the process alive to keep working while Amethyst is backgrounded or closed. Hook it into the existing always-on foreground service (and its five restart layers) instead of building a new one: - AlwaysOnNotificationServiceManager now starts/stops the layers when EITHER the notification service or nip46SignerEnabled is on (combined flow). - NotificationRelayService.isEnabled (the auto-restart guard) honors the signer flag too, so START_STICKY / watchdog / boot restart keep the signer up. - The signer screen notes that a background connection (ongoing notification) is what lets it answer requests while closed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FHr2mu5SiHwYNR7evYUuF
This commit is contained in:
+17
-4
@@ -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 {
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
+6
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -926,6 +926,7 @@
|
||||
<string name="nip46_signer_connect_failed">Could not connect: %1$s</string>
|
||||
<string name="nip46_signer_readonly">This is a read-only account and cannot sign.</string>
|
||||
<string name="nip46_signer_remote_app">Remote signer app</string>
|
||||
<string name="nip46_signer_background_hint">Amethyst keeps a background connection (shown as an ongoing notification) so it can answer signing requests while closed.</string>
|
||||
<string name="nip46_signer_hero_title">Sign for other apps</string>
|
||||
<string name="nip46_signer_turn_on">Turn on signer</string>
|
||||
<string name="nip46_signer_live">Live</string>
|
||||
|
||||
Reference in New Issue
Block a user