From e6f7a543cdeac00865084b85d20926310352d83d Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 4 Feb 2026 15:38:16 -0500 Subject: [PATCH] Migrates Desktop App to the new metadata cache. --- .../amethyst/commons/model/ThreadAssembler.kt | 6 +++--- .../relayClient/preload/MetadataPreloader.kt | 16 ++++++++++++---- .../amethyst/desktop/cache/DesktopLocalCache.kt | 10 +++++----- .../amethyst/desktop/ui/FeedScreen.kt | 12 ++++++++++-- .../amethyst/desktop/ui/NoteActions.kt | 16 +++++----------- .../amethyst/desktop/ui/ReadsScreen.kt | 4 ++-- 6 files changed, 37 insertions(+), 27 deletions(-) diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/ThreadAssembler.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/ThreadAssembler.kt index 0985294fb..7b740190c 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/ThreadAssembler.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/ThreadAssembler.kt @@ -54,7 +54,7 @@ class ThreadAssembler( // Check to see if there is an error in the tag and the root has replies val rootNote = cache.getNoteIfExists(markedAsRoot) as? Note if (rootNote?.replyTo?.isEmpty() == true) { - return cache.checkGetOrCreateNote(markedAsRoot) as? Note + return cache.checkGetOrCreateNote(markedAsRoot) } } @@ -88,7 +88,7 @@ class ThreadAssembler( ) fun findRoot(noteId: String): Note? { - val note = cache.checkGetOrCreateNote(noteId) as? Note ?: return null + val note = cache.checkGetOrCreateNote(noteId) ?: return null return if (note.event != null) { val thread = OnlyLatestVersionSet() @@ -102,7 +102,7 @@ class ThreadAssembler( fun findThreadFor(noteId: String): ThreadInfo? { checkNotInMainThread() - val note = cache.checkGetOrCreateNote(noteId) as? Note ?: return null + val note = cache.checkGetOrCreateNote(noteId) ?: return null return if (note.event != null) { val thread = OnlyLatestVersionSet() diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/preload/MetadataPreloader.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/preload/MetadataPreloader.kt index 14771a607..e98e9244e 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/preload/MetadataPreloader.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/preload/MetadataPreloader.kt @@ -41,7 +41,11 @@ class MetadataPreloader( */ fun preloadForUsers(users: Collection) { users.forEach { user -> - val metadata = user.info + val metadata = + user + .metadata() + .flow.value + ?.info if (metadata != null) { // Already have metadata, prefetch avatar metadata.picture?.let { avatarUrl -> @@ -58,7 +62,11 @@ class MetadataPreloader( * Queue a single user for metadata preloading. */ fun preloadForUser(user: User) { - val metadata = user.info + val metadata = + user + .metadata() + .flow.value + ?.info if (metadata != null) { metadata.picture?.let { avatarUrl -> imagePrefetcher?.prefetch(avatarUrl) @@ -73,7 +81,7 @@ class MetadataPreloader( * Triggers avatar image prefetch. */ fun onMetadataReceived(user: User) { - user.info?.picture?.let { avatarUrl -> + user.metadata().flow.value?.info?.picture?.let { avatarUrl -> imagePrefetcher?.prefetch(avatarUrl) } } @@ -83,7 +91,7 @@ class MetadataPreloader( */ fun prefetchAvatars(users: Collection) { users.forEach { user -> - user.info?.picture?.let { avatarUrl -> + user.metadata().flow.value?.info?.picture?.let { avatarUrl -> imagePrefetcher?.prefetch(avatarUrl) } } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt index 844851ceb..ee005ca78 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt @@ -107,11 +107,11 @@ class DesktopLocalCache : ICacheProvider { fun consumeMetadata(event: MetadataEvent) { val user = getOrCreateUser(event.pubKey) - // Only update if newer - val currentMetadata = user.latestMetadata - if (currentMetadata == null || event.createdAt > currentMetadata.createdAt) { - user.latestMetadata = event - user.info = event.contactMetaData() + if (user.metadata().shouldUpdateWith(event)) { + val newUserMetadata = event.contactMetaData() + if (newUserMetadata != null) { + user.updateUserInfo(newUserMetadata, event, null) + } } } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/FeedScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/FeedScreen.kt index 76261ef03..e28275466 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/FeedScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/FeedScreen.kt @@ -333,7 +333,11 @@ fun FeedScreen( // Only fetch metadata for users we don't have yet val missingPubkeys = zapSenderPubkeys.filter { pubkey -> - localCache.getUserIfExists(pubkey)?.info == null + localCache + .getUserIfExists(pubkey) + ?.metadataOrNull() + ?.flow + ?.value == null } if (missingPubkeys.isEmpty()) { return@rememberSubscription null @@ -449,7 +453,11 @@ fun FeedScreen( // Only fetch metadata for users we don't have yet val missingPubkeys = authorPubkeys.filter { pubkey -> - localCache.getUserIfExists(pubkey)?.info == null + localCache + .getUserIfExists(pubkey) + ?.metadataOrNull() + ?.flow + ?.value == null } if (missingPubkeys.isEmpty()) { return@rememberSubscription null diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/NoteActions.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/NoteActions.kt index ccf6ab061..8f8dcd2af 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/NoteActions.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/NoteActions.kt @@ -70,14 +70,12 @@ import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager import com.vitorpamplona.amethyst.desktop.nwc.NwcPaymentHandler import com.vitorpamplona.quartz.nip01Core.core.Event -import com.vitorpamplona.quartz.nip01Core.core.hexToByteArrayOrNull import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent import com.vitorpamplona.quartz.nip19Bech32.entities.NNote -import com.vitorpamplona.quartz.nip19Bech32.toNpub import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent @@ -149,12 +147,8 @@ fun getDisplayName( pubKey: String, localCache: DesktopLocalCache, ): String { - val user = localCache.getUserIfExists(pubKey) - return user?.info?.bestName() - ?: pubKey.hexToByteArrayOrNull()?.toNpub()?.let { npub -> - npub.take(12) + "..." + npub.takeLast(6) - } - ?: pubKey.take(12) + "..." + val user = localCache.getUserIfExists(pubKey) ?: return pubKey.take(12) + return user.toBestDisplayName() } /** @@ -308,7 +302,7 @@ fun ZapReceiptsDialog( .distinct() .filter { pubKey -> val user = localCache.getUserIfExists(pubKey) - user?.info == null + user?.metadataOrNull()?.flow?.value == null } if (pubKeysNeedingMetadata.isNotEmpty()) { @@ -925,7 +919,7 @@ private suspend fun zapNote( withContext(Dispatchers.IO) { // Get author's lightning address from cache var user = localCache.getUserIfExists(event.pubKey) - var lnAddress = user?.info?.lud16 ?: user?.info?.lud06 + var lnAddress = user?.lnAddress() // TODO: Use UserFinderFilterAssemblerSubscription pattern from Amethyst // to proactively load metadata when zap button is displayed. @@ -1068,7 +1062,7 @@ private suspend fun fetchUserLightningAddress( if (event is MetadataEvent && !resumed) { localCache.consumeMetadata(event) val user = localCache.getUserIfExists(pubKey) - val lnAddress = user?.info?.lud16 ?: user?.info?.lud06 + val lnAddress = user?.lnAddress() if (lnAddress != null && !resumed) { resumed = true timeoutJob.cancel() diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ReadsScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ReadsScreen.kt index 81b415e1f..6efbcc0f6 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ReadsScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ReadsScreen.kt @@ -84,8 +84,8 @@ fun LongFormCard( onAuthorClick: (String) -> Unit = {}, onClick: () -> Unit = {}, ) { - val author = localCache.getUserIfExists(event.pubKey) - val authorName = author?.info?.bestName() ?: event.pubKey.take(8) + val author = localCache.getOrCreateUser(event.pubKey) + val authorName = author.toBestDisplayName() val publishedAt = event.publishedAt() ?: event.createdAt Card(