feat(cache): Phase 3 — route all screen events through cache

All screens now pipe relay events into DesktopLocalCache via
coordinator.consumeEvent(). Existing per-screen EventCollectionState
kept for rendering (incremental migration), but data also enters
cache for persistence across navigation.

- FeedScreen: route global + following feed events
- ThreadScreen: route root note + reply events
- UserProfileScreen: route posts + add cache hydration on mount
  (LaunchedEffect queries cache for existing posts → instant display)
- BookmarksScreen: route public + private bookmark events
- ReadsScreen: route long-form feed events (add coordinator param)
- NotificationsScreen: route notification events

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
nrobi144
2026-03-19 15:22:38 +02:00
parent c198df57fd
commit e80473a7d9
6 changed files with 32 additions and 9 deletions
@@ -188,7 +188,8 @@ fun BookmarksScreen(
FilterBuilders.byIds(publicBookmarkIds),
),
relays = connectedRelays,
onEvent = { event, _, _, _ ->
onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
publicEventState.addItem(event)
},
onEose = { _, _ -> },
@@ -209,7 +210,8 @@ fun BookmarksScreen(
FilterBuilders.byIds(privateBookmarkIds),
),
relays = connectedRelays,
onEvent = { event, _, _, _ ->
onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
privateEventState.addItem(event)
},
onEose = { _, _ -> },
@@ -279,7 +279,8 @@ fun FeedScreen(
FeedMode.GLOBAL -> {
createGlobalFeedSubscription(
relays = configuredRelays,
onEvent = { event, _, _, _ ->
onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
// Store metadata events in cache
if (event is MetadataEvent) {
localCache.consumeMetadata(event)
@@ -143,7 +143,8 @@ fun NotificationsScreen(
createNotificationsSubscription(
relays = connectedRelays,
pubKeyHex = account.pubKeyHex,
onEvent = { event, _, _, _ ->
onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
// Skip events from the user themselves (except zaps)
if (event.pubKey == account.pubKeyHex && event !is LnZapEvent) {
return@createNotificationsSubscription
@@ -62,6 +62,7 @@ import com.vitorpamplona.amethyst.commons.ui.components.LoadingState
import com.vitorpamplona.amethyst.desktop.account.AccountState
import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache
import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager
import com.vitorpamplona.amethyst.desktop.subscriptions.DesktopRelaySubscriptionsCoordinator
import com.vitorpamplona.amethyst.desktop.subscriptions.FeedMode
import com.vitorpamplona.amethyst.desktop.subscriptions.createContactListSubscription
import com.vitorpamplona.amethyst.desktop.subscriptions.createFollowingLongFormFeedSubscription
@@ -180,6 +181,7 @@ fun ReadsScreen(
localCache: DesktopLocalCache,
account: AccountState.LoggedIn? = null,
nwcConnection: Nip47WalletConnect.Nip47URINorm? = null,
subscriptionsCoordinator: DesktopRelaySubscriptionsCoordinator? = null,
onNavigateToProfile: (String) -> Unit = {},
onNavigateToArticle: (String) -> Unit = {},
onNavigateToThread: (String) -> Unit = {},
@@ -239,7 +241,8 @@ fun ReadsScreen(
FeedMode.GLOBAL -> {
createLongFormFeedSubscription(
relays = connectedRelays,
onEvent = { event, _, _, _ ->
onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
if (event is LongTextNoteEvent) {
eventState.addItem(event)
}
@@ -203,7 +203,8 @@ fun ThreadScreen(
createNoteSubscription(
relays = connectedRelays,
noteId = noteId,
onEvent = { event, _, _, _ ->
onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
if (event.id == noteId) {
rootNote = event
levelCache[event.id] = 0
@@ -224,7 +225,8 @@ fun ThreadScreen(
createThreadRepliesSubscription(
relays = connectedRelays,
noteId = noteId,
onEvent = { event, _, _, _ ->
onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
replyEventState.addItem(event)
},
onEose = { _, _ ->
@@ -206,13 +206,26 @@ fun UserProfileScreen(
}
}
// Clear posts when profile changes
// Clear posts when profile changes, then hydrate from cache
remember(pubKeyHex, retryTrigger) {
eventState.clear()
postsLoading = true
postsError = null
}
// Hydrate from cache — show previously loaded posts instantly
LaunchedEffect(pubKeyHex) {
val cachedNotes =
localCache.notes.filterIntoSet { _, note ->
note.event?.kind == 1 && note.author?.pubkeyHex == pubKeyHex
}
if (cachedNotes.isNotEmpty()) {
val events = cachedNotes.mapNotNull { it.event }
eventState.addItems(events)
postsLoading = false
}
}
// Subscribe to user metadata
rememberSubscription(connectedRelays, pubKeyHex, retryTrigger, relayManager = relayManager) {
if (connectedRelays.isNotEmpty()) {
@@ -308,7 +321,8 @@ fun UserProfileScreen(
createUserPostsSubscription(
relays = connectedRelays,
pubKeyHex = pubKeyHex,
onEvent = { event, _, _, _ ->
onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
eventState.addItem(event)
},
onEose = { _, _ ->