feat(cache): wire State classes on DesktopIAccount for GC retention

- Add BookmarkListState, Kind3FollowListState, Nip65RelayListState
  to DesktopIAccount — pins important AddressableNotes via strong refs
- Create stub Kind3FollowListRepository + Nip65RelayListRepository
  (no persistence yet, null backups)
- Update followingKeySet() to read from Kind3FollowListState.flow
- Revert accidental long-form branch changes in DeckColumnContainer
This commit is contained in:
nrobi144
2026-03-24 09:42:59 +02:00
parent 2b95cc013a
commit 072e437277
4 changed files with 90 additions and 8 deletions
@@ -153,6 +153,7 @@ sealed class DesktopScreen {
) : DesktopScreen()
data object Drafts : DesktopScreen()
data object Settings : DesktopScreen()
}
@@ -24,6 +24,11 @@ import com.vitorpamplona.amethyst.commons.model.IAccount
import com.vitorpamplona.amethyst.commons.model.INwcSignerState
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.model.User
import com.vitorpamplona.amethyst.commons.model.nip02FollowList.Kind3FollowListRepository
import com.vitorpamplona.amethyst.commons.model.nip02FollowList.Kind3FollowListState
import com.vitorpamplona.amethyst.commons.model.nip51Lists.BookmarkListState
import com.vitorpamplona.amethyst.commons.model.nip65RelayList.Nip65RelayListRepository
import com.vitorpamplona.amethyst.commons.model.nip65RelayList.Nip65RelayListState
import com.vitorpamplona.amethyst.commons.model.privateChats.ChatroomList
import com.vitorpamplona.amethyst.desktop.account.AccountState
import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache
@@ -31,6 +36,7 @@ import com.vitorpamplona.amethyst.desktop.network.RelayConnectionManager
import com.vitorpamplona.amethyst.desktop.ui.chats.DmSendTracker
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
import com.vitorpamplona.quartz.nip17Dm.NIP17Factory
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
@@ -42,6 +48,7 @@ import com.vitorpamplona.quartz.nip47WalletConnect.rpc.Response
import com.vitorpamplona.quartz.nip57Zaps.IPrivateZapsDecryptionCache
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip89AppHandlers.clientTag.NostrSignerWithClientTag
import com.vitorpamplona.quartz.utils.DualCase
import kotlinx.coroutines.CoroutineScope
@@ -67,6 +74,39 @@ class DesktopIAccount(
override val pubKey: String = accountState.pubKeyHex
// ----- State Classes (pin important notes via strong refs for GC retention) -----
val bookmarkState = BookmarkListState(signer, localCache, scope)
val kind3FollowList =
Kind3FollowListState(
signer,
localCache,
scope,
object : Kind3FollowListRepository {
override val backupContactList: ContactListEvent? = null
override fun updateContactListTo(event: ContactListEvent) { /* no persistence yet */ }
},
)
val nip65RelayList =
Nip65RelayListState(
signer,
localCache,
scope,
object : Nip65RelayListRepository {
override val backupNIP65RelayList: AdvertisedRelayListEvent? = null
override fun updateNIP65RelayList(event: AdvertisedRelayListEvent) { /* no persistence yet */ }
override val defaultOutboxRelays = relayManager.connectedRelays.value
override val defaultInboxRelays = relayManager.connectedRelays.value
},
)
// ---------------------------------------------------------------------------------
override val showSensitiveContent: Boolean? = null
override val hiddenWordsCase: List<DualCase> = emptyList()
@@ -97,7 +137,7 @@ class DesktopIAccount(
override fun isWriteable(): Boolean = !accountState.isReadOnly
override fun followingKeySet(): Set<String> = emptySet()
override fun followingKeySet(): Set<String> = kind3FollowList.flow.value.authors
override fun isHidden(user: User): Boolean = false
@@ -45,6 +45,7 @@ import com.vitorpamplona.amethyst.desktop.chess.ChessScreen
import com.vitorpamplona.amethyst.desktop.model.DesktopIAccount
import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager
import com.vitorpamplona.amethyst.desktop.service.drafts.DesktopDraftStore
import com.vitorpamplona.amethyst.desktop.service.highlights.DesktopHighlightStore
import com.vitorpamplona.amethyst.desktop.subscriptions.DesktopRelaySubscriptionsCoordinator
import com.vitorpamplona.amethyst.desktop.subscriptions.FeedMode
import com.vitorpamplona.amethyst.desktop.ui.ArticleEditorScreen
@@ -96,6 +97,8 @@ fun DeckColumnContainer(
iAccount: DesktopIAccount,
nwcConnection: Nip47URINorm?,
subscriptionsCoordinator: DesktopRelaySubscriptionsCoordinator,
highlightStore: DesktopHighlightStore,
draftStore: DesktopDraftStore,
appScope: CoroutineScope,
onShowComposeDialog: () -> Unit,
onShowReplyDialog: (com.vitorpamplona.quartz.nip01Core.core.Event) -> Unit,
@@ -136,6 +139,8 @@ fun DeckColumnContainer(
iAccount = iAccount,
nwcConnection = nwcConnection,
subscriptionsCoordinator = subscriptionsCoordinator,
highlightStore = highlightStore,
draftStore = draftStore,
appScope = appScope,
compactMode = true,
onShowComposeDialog = onShowComposeDialog,
@@ -144,6 +149,7 @@ fun DeckColumnContainer(
onNavigateToProfile = { navState.push(DesktopScreen.UserProfile(it)) },
onNavigateToThread = { navState.push(DesktopScreen.Thread(it)) },
onNavigateToArticle = { navState.push(DesktopScreen.Article(it)) },
onNavigateToEditor = { navState.push(DesktopScreen.Editor(it)) },
)
if (currentOverlay != null) {
Surface(
@@ -157,11 +163,14 @@ fun DeckColumnContainer(
account = account,
nwcConnection = nwcConnection,
subscriptionsCoordinator = subscriptionsCoordinator,
highlightStore = highlightStore,
draftStore = draftStore,
onShowComposeDialog = onShowComposeDialog,
onShowReplyDialog = onShowReplyDialog,
onZapFeedback = onZapFeedback,
onNavigateToProfile = { navState.push(DesktopScreen.UserProfile(it)) },
onNavigateToThread = { navState.push(DesktopScreen.Thread(it)) },
onNavigateToArticle = { navState.push(DesktopScreen.Article(it)) },
onBack = { navState.pop() },
)
}
@@ -180,6 +189,8 @@ internal fun RootContent(
iAccount: DesktopIAccount,
nwcConnection: Nip47URINorm?,
subscriptionsCoordinator: DesktopRelaySubscriptionsCoordinator,
highlightStore: DesktopHighlightStore? = null,
draftStore: DesktopDraftStore? = null,
appScope: CoroutineScope,
compactMode: Boolean = false,
onShowComposeDialog: () -> Unit,
@@ -188,6 +199,7 @@ internal fun RootContent(
onNavigateToProfile: (String) -> Unit,
onNavigateToThread: (String) -> Unit,
onNavigateToArticle: (String) -> Unit = {},
onNavigateToEditor: (String?) -> Unit = {},
) {
val scope = rememberCoroutineScope()
@@ -237,8 +249,11 @@ internal fun RootContent(
relayManager = relayManager,
localCache = localCache,
account = account,
nwcConnection = nwcConnection,
onNavigateToProfile = onNavigateToProfile,
onNavigateToArticle = onNavigateToArticle,
onNavigateToThread = onNavigateToThread,
onZapFeedback = onZapFeedback,
)
}
@@ -281,6 +296,7 @@ internal fun RootContent(
onBack = {},
onCompose = onShowComposeDialog,
onNavigateToProfile = onNavigateToProfile,
onNavigateToArticle = onNavigateToArticle,
onZapFeedback = onZapFeedback,
)
}
@@ -336,16 +352,16 @@ internal fun RootContent(
localCache = localCache,
account = account,
subscriptionsCoordinator = subscriptionsCoordinator,
highlightStore = highlightStore,
onBack = {},
onNavigateToProfile = onNavigateToProfile,
)
}
DeckColumnType.Editor -> {
val draftStore = remember { DesktopDraftStore(scope) }
is DeckColumnType.Editor -> {
ArticleEditorScreen(
draftSlug = null,
draftStore = draftStore,
draftSlug = columnType.draftSlug,
draftStore = draftStore ?: remember { DesktopDraftStore(scope) },
account = account,
relayManager = relayManager,
onBack = {},
@@ -354,10 +370,16 @@ internal fun RootContent(
}
DeckColumnType.Drafts -> {
val draftStore = remember { DesktopDraftStore(scope) }
DraftsScreen(
draftStore = draftStore,
onOpenEditor = {},
draftStore = draftStore ?: remember { DesktopDraftStore(scope) },
onOpenEditor = { slug -> onNavigateToEditor(slug) },
)
}
DeckColumnType.MyHighlights -> {
com.vitorpamplona.amethyst.desktop.ui.highlights.MyHighlightsScreen(
highlightStore = highlightStore ?: remember { DesktopHighlightStore(scope) },
onNavigateToArticle = onNavigateToArticle,
)
}
@@ -382,11 +404,14 @@ internal fun OverlayContent(
account: AccountState.LoggedIn,
nwcConnection: Nip47URINorm?,
subscriptionsCoordinator: DesktopRelaySubscriptionsCoordinator,
highlightStore: DesktopHighlightStore? = null,
draftStore: DesktopDraftStore? = null,
onShowComposeDialog: () -> Unit,
onShowReplyDialog: (com.vitorpamplona.quartz.nip01Core.core.Event) -> Unit,
onZapFeedback: (ZapFeedback) -> Unit,
onNavigateToProfile: (String) -> Unit,
onNavigateToThread: (String) -> Unit,
onNavigateToArticle: (String) -> Unit = {},
onBack: () -> Unit,
) {
when (screen) {
@@ -401,6 +426,7 @@ internal fun OverlayContent(
onBack = onBack,
onCompose = onShowComposeDialog,
onNavigateToProfile = onNavigateToProfile,
onNavigateToArticle = onNavigateToArticle,
onZapFeedback = onZapFeedback,
)
}
@@ -428,11 +454,24 @@ internal fun OverlayContent(
localCache = localCache,
account = account,
subscriptionsCoordinator = subscriptionsCoordinator,
highlightStore = highlightStore,
onBack = onBack,
onNavigateToProfile = onNavigateToProfile,
)
}
is DesktopScreen.Editor -> {
val overlayScope = androidx.compose.runtime.rememberCoroutineScope()
ArticleEditorScreen(
draftSlug = screen.draftSlug,
draftStore = draftStore ?: remember { DesktopDraftStore(overlayScope) },
account = account,
relayManager = relayManager,
onBack = onBack,
onPublished = onBack,
)
}
else -> {
androidx.compose.material3.Text(
"Unsupported screen type",
@@ -79,6 +79,8 @@ class CoordinatorPipelineTest {
override fun disconnect() {}
override fun close() {}
override fun reconnect(
onlyIfChanged: Boolean,
ignoreRetryDelays: Boolean,