diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 2254da21f..6fcfc444b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -923,6 +923,12 @@ object LocalCache : ILocalCache, ICacheProvider { event.taggedAddresses().map { getOrCreateAddressableNote(it) } } + is WakeUpEvent -> { + // Link the referenced events so filterMissingEvents will query + // for them when the WakeUp note is in an EventFinder subscription. + event.eventIds().mapNotNull { checkGetOrCreateNote(it) } + } + is ChannelMessageEvent -> { event.tagsWithoutCitations().filter { it != event.channelId() }.mapNotNull { checkGetOrCreateNote(it) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt index e40adf983..ca76dbec0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt @@ -89,6 +89,7 @@ class EventNotificationConsumer( ) { companion object { private const val WAKELOCK_TIMEOUT_MS = 10 * 60 * 1000L // 10 minutes + private const val WAKEUP_WINDOW_MS = 30_000L } /** @@ -162,7 +163,7 @@ class EventNotificationConsumer( } is WakeUpEvent -> { - wakeUpFor(event, LocalCache.getOrCreateNote(event.id), account) + wakeUpFor(event, account) return } } @@ -185,40 +186,68 @@ class EventNotificationConsumer( suspend fun wakeUpFor( event: WakeUpEvent, - note: Note, account: Account, ) { + // A WakeUp's whole purpose is the events it references. If it carries + // none, there's nothing to fetch — skip the 30s subscription window. + val referencedTags = event.events().distinctBy { it.eventId } + if (referencedTags.isEmpty()) { + Log.d(TAG) { "WakeUp ${event.id} has no referenced events — skipping" } + return + } + + // Per spec, p-tags on a WakeUp are the authors of the referenced + // events; those are whose metadata we need to render the notification. + // Fall back to e-tag author hints and finally to the WakeUp signer. + val referencedNotes = referencedTags.map { LocalCache.getOrCreateNote(it.eventId) } + val authorCandidates = + (event.authorKeys() + referencedTags.mapNotNull { it.author }) + .distinct() + .ifEmpty { listOf(event.pubKey) } + .map { LocalCache.getOrCreateUser(it) } + coroutineScope { // keeps the relay connection active for 30 seconds. launch { try { - withTimeout(30_000L) { + withTimeout(WAKEUP_WINDOW_MS) { Amethyst.instance.relayProxyClientConnector.relayServices .collect() } } catch (_: TimeoutCancellationException) { + Log.d(TAG) { "WakeUp ${event.id} — ${WAKEUP_WINDOW_MS}ms relay window elapsed" } } } - // keeps the subscription to download this event active for 30 seconds. + // keeps subscriptions active for 30 seconds so EventFinder can pull + // the referenced events from relays and UserFinder can resolve the + // referenced authors' metadata. launch { val accountState = ScreenAuthAccount(account) - val eventState = EventFinderQueryState(note, account) - val authorState = UserFinderQueryState(note.author ?: LocalCache.getOrCreateUser(event.pubKey), account) + val eventStates = referencedNotes.map { EventFinderQueryState(it, account) } + val authorStates = authorCandidates.map { UserFinderQueryState(it, account) } try { Amethyst.instance.authCoordinator.subscribe(accountState) - Amethyst.instance.sources.eventFinder - .subscribe(eventState) - Amethyst.instance.sources.userFinder - .subscribe(authorState) - delay(30_000) + eventStates.forEach { + Amethyst.instance.sources.eventFinder + .subscribe(it) + } + authorStates.forEach { + Amethyst.instance.sources.userFinder + .subscribe(it) + } + delay(WAKEUP_WINDOW_MS) } finally { Amethyst.instance.authCoordinator.unsubscribe(accountState) - Amethyst.instance.sources.eventFinder - .unsubscribe(eventState) - Amethyst.instance.sources.userFinder - .unsubscribe(authorState) + eventStates.forEach { + Amethyst.instance.sources.eventFinder + .unsubscribe(it) + } + authorStates.forEach { + Amethyst.instance.sources.userFinder + .unsubscribe(it) + } } } } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/icons/symbols/MaterialSymbolsDefaults.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/icons/symbols/MaterialSymbolsDefaults.kt index a41303cea..b6774a7be 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/icons/symbols/MaterialSymbolsDefaults.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/icons/symbols/MaterialSymbolsDefaults.kt @@ -23,7 +23,7 @@ package com.vitorpamplona.amethyst.commons.icons.symbols // Single source of truth for Material Symbols visual axes. Change WEIGHT here to globally // re-tune line thickness (100 = Thin, 200 = ExtraLight, 300 = Light, 400 = Regular, …, 700 = Bold). object MaterialSymbolsDefaults { - const val WEIGHT: Int = 100 + const val WEIGHT: Int = 300 const val FILL: Float = 0f const val OPTICAL_SIZE: Float = 24f const val GRADE: Float = 0f diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/notifications/wake/WakeUpEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/notifications/wake/WakeUpEvent.kt index 7019d3355..5fc6ee712 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/notifications/wake/WakeUpEvent.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/experimental/notifications/wake/WakeUpEvent.kt @@ -68,6 +68,10 @@ class WakeUpEvent( const val KIND = 23903 const val ALT_DESCRIPTION = "WakeUp" + // p-tags on a WakeUp identify the AUTHORS of the referenced events — + // the people whose events are the subject of the wake-up and who should + // come online to handle new activity on them. Callers can add extra + // e/p tags via [initializer] when waking up about multiple events. fun build( about: EventHintBundle, createdAt: Long = TimeUtils.now(),