From 072e437277c3cd9b7eac847743a0b0919da91265 Mon Sep 17 00:00:00 2001 From: nrobi144 Date: Tue, 24 Mar 2026 09:42:59 +0200 Subject: [PATCH] feat(cache): wire State classes on DesktopIAccount for GC retention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../vitorpamplona/amethyst/desktop/Main.kt | 1 + .../amethyst/desktop/model/DesktopIAccount.kt | 42 ++++++++++++++- .../desktop/ui/deck/DeckColumnContainer.kt | 53 ++++++++++++++++--- .../desktop/cache/CoordinatorPipelineTest.kt | 2 + 4 files changed, 90 insertions(+), 8 deletions(-) diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt index 23616a926..4c73e002b 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt @@ -153,6 +153,7 @@ sealed class DesktopScreen { ) : DesktopScreen() data object Drafts : DesktopScreen() + data object Settings : DesktopScreen() } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/model/DesktopIAccount.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/model/DesktopIAccount.kt index 6f432f058..9efb98238 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/model/DesktopIAccount.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/model/DesktopIAccount.kt @@ -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 = emptyList() @@ -97,7 +137,7 @@ class DesktopIAccount( override fun isWriteable(): Boolean = !accountState.isReadOnly - override fun followingKeySet(): Set = emptySet() + override fun followingKeySet(): Set = kind3FollowList.flow.value.authors override fun isHidden(user: User): Boolean = false diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/DeckColumnContainer.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/DeckColumnContainer.kt index 9a71b8bb8..1da11dbd6 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/DeckColumnContainer.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/DeckColumnContainer.kt @@ -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", diff --git a/desktopApp/src/jvmTest/kotlin/com/vitorpamplona/amethyst/desktop/cache/CoordinatorPipelineTest.kt b/desktopApp/src/jvmTest/kotlin/com/vitorpamplona/amethyst/desktop/cache/CoordinatorPipelineTest.kt index eb3d04558..b47fa330f 100644 --- a/desktopApp/src/jvmTest/kotlin/com/vitorpamplona/amethyst/desktop/cache/CoordinatorPipelineTest.kt +++ b/desktopApp/src/jvmTest/kotlin/com/vitorpamplona/amethyst/desktop/cache/CoordinatorPipelineTest.kt @@ -79,6 +79,8 @@ class CoordinatorPipelineTest { override fun disconnect() {} + override fun close() {} + override fun reconnect( onlyIfChanged: Boolean, ignoreRetryDelays: Boolean,