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), FilterBuilders.byIds(publicBookmarkIds),
), ),
relays = connectedRelays, relays = connectedRelays,
onEvent = { event, _, _, _ -> onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
publicEventState.addItem(event) publicEventState.addItem(event)
}, },
onEose = { _, _ -> }, onEose = { _, _ -> },
@@ -209,7 +210,8 @@ fun BookmarksScreen(
FilterBuilders.byIds(privateBookmarkIds), FilterBuilders.byIds(privateBookmarkIds),
), ),
relays = connectedRelays, relays = connectedRelays,
onEvent = { event, _, _, _ -> onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
privateEventState.addItem(event) privateEventState.addItem(event)
}, },
onEose = { _, _ -> }, onEose = { _, _ -> },
@@ -279,7 +279,8 @@ fun FeedScreen(
FeedMode.GLOBAL -> { FeedMode.GLOBAL -> {
createGlobalFeedSubscription( createGlobalFeedSubscription(
relays = configuredRelays, relays = configuredRelays,
onEvent = { event, _, _, _ -> onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
// Store metadata events in cache // Store metadata events in cache
if (event is MetadataEvent) { if (event is MetadataEvent) {
localCache.consumeMetadata(event) localCache.consumeMetadata(event)
@@ -143,7 +143,8 @@ fun NotificationsScreen(
createNotificationsSubscription( createNotificationsSubscription(
relays = connectedRelays, relays = connectedRelays,
pubKeyHex = account.pubKeyHex, pubKeyHex = account.pubKeyHex,
onEvent = { event, _, _, _ -> onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
// Skip events from the user themselves (except zaps) // Skip events from the user themselves (except zaps)
if (event.pubKey == account.pubKeyHex && event !is LnZapEvent) { if (event.pubKey == account.pubKeyHex && event !is LnZapEvent) {
return@createNotificationsSubscription 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.account.AccountState
import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache
import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager 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.FeedMode
import com.vitorpamplona.amethyst.desktop.subscriptions.createContactListSubscription import com.vitorpamplona.amethyst.desktop.subscriptions.createContactListSubscription
import com.vitorpamplona.amethyst.desktop.subscriptions.createFollowingLongFormFeedSubscription import com.vitorpamplona.amethyst.desktop.subscriptions.createFollowingLongFormFeedSubscription
@@ -180,6 +181,7 @@ fun ReadsScreen(
localCache: DesktopLocalCache, localCache: DesktopLocalCache,
account: AccountState.LoggedIn? = null, account: AccountState.LoggedIn? = null,
nwcConnection: Nip47WalletConnect.Nip47URINorm? = null, nwcConnection: Nip47WalletConnect.Nip47URINorm? = null,
subscriptionsCoordinator: DesktopRelaySubscriptionsCoordinator? = null,
onNavigateToProfile: (String) -> Unit = {}, onNavigateToProfile: (String) -> Unit = {},
onNavigateToArticle: (String) -> Unit = {}, onNavigateToArticle: (String) -> Unit = {},
onNavigateToThread: (String) -> Unit = {}, onNavigateToThread: (String) -> Unit = {},
@@ -239,7 +241,8 @@ fun ReadsScreen(
FeedMode.GLOBAL -> { FeedMode.GLOBAL -> {
createLongFormFeedSubscription( createLongFormFeedSubscription(
relays = connectedRelays, relays = connectedRelays,
onEvent = { event, _, _, _ -> onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
if (event is LongTextNoteEvent) { if (event is LongTextNoteEvent) {
eventState.addItem(event) eventState.addItem(event)
} }
@@ -203,7 +203,8 @@ fun ThreadScreen(
createNoteSubscription( createNoteSubscription(
relays = connectedRelays, relays = connectedRelays,
noteId = noteId, noteId = noteId,
onEvent = { event, _, _, _ -> onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
if (event.id == noteId) { if (event.id == noteId) {
rootNote = event rootNote = event
levelCache[event.id] = 0 levelCache[event.id] = 0
@@ -224,7 +225,8 @@ fun ThreadScreen(
createThreadRepliesSubscription( createThreadRepliesSubscription(
relays = connectedRelays, relays = connectedRelays,
noteId = noteId, noteId = noteId,
onEvent = { event, _, _, _ -> onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
replyEventState.addItem(event) replyEventState.addItem(event)
}, },
onEose = { _, _ -> onEose = { _, _ ->
@@ -206,13 +206,26 @@ fun UserProfileScreen(
} }
} }
// Clear posts when profile changes // Clear posts when profile changes, then hydrate from cache
remember(pubKeyHex, retryTrigger) { remember(pubKeyHex, retryTrigger) {
eventState.clear() eventState.clear()
postsLoading = true postsLoading = true
postsError = null 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 // Subscribe to user metadata
rememberSubscription(connectedRelays, pubKeyHex, retryTrigger, relayManager = relayManager) { rememberSubscription(connectedRelays, pubKeyHex, retryTrigger, relayManager = relayManager) {
if (connectedRelays.isNotEmpty()) { if (connectedRelays.isNotEmpty()) {
@@ -308,7 +321,8 @@ fun UserProfileScreen(
createUserPostsSubscription( createUserPostsSubscription(
relays = connectedRelays, relays = connectedRelays,
pubKeyHex = pubKeyHex, pubKeyHex = pubKeyHex,
onEvent = { event, _, _, _ -> onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
eventState.addItem(event) eventState.addItem(event)
}, },
onEose = { _, _ -> onEose = { _, _ ->