fix(desktop): feed loading issues — missing events, broken profile navigation
- Profile feed now includes reposts (kind 6/16), not just text notes - Metadata consume returns false to avoid wasteful null note lookups - Feed subscription limits raised from 50 to 200 for global/following - Thread replies subscription fetches NIP-22 comments (kind 1111) - Cache now handles CommentEvent for thread display - Wire onNavigateToThread through UserProfileScreen so tapping notes opens threads Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vendored
+24
-1
@@ -39,6 +39,7 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.events.LnZapPaymentRequestEvent
|
||||
@@ -173,7 +174,7 @@ class DesktopLocalCache : ICacheProvider {
|
||||
when (event) {
|
||||
is MetadataEvent -> {
|
||||
consumeMetadata(event)
|
||||
true
|
||||
false // metadata updates User, not Note — skip event stream
|
||||
}
|
||||
|
||||
is TextNoteEvent -> {
|
||||
@@ -212,6 +213,10 @@ class DesktopLocalCache : ICacheProvider {
|
||||
consumeBookmarkList(event)
|
||||
}
|
||||
|
||||
is CommentEvent -> {
|
||||
consumeComment(event, relay)
|
||||
}
|
||||
|
||||
else -> {
|
||||
false
|
||||
}
|
||||
@@ -235,6 +240,24 @@ class DesktopLocalCache : ICacheProvider {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes a kind 1111 comment event (NIP-22).
|
||||
* Like text notes but uses BaseThreadedEvent reply structure.
|
||||
*/
|
||||
private fun consumeComment(
|
||||
event: CommentEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
): Boolean {
|
||||
val note = getOrCreateNote(event.id)
|
||||
if (note.event != null) return false
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
val repliesTo = event.tagsWithoutCitations().map { getOrCreateNote(it) }
|
||||
note.loadEvent(event, author, repliesTo)
|
||||
relay?.let { note.addRelay(it) }
|
||||
repliesTo.forEach { it.addReply(note) }
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes a kind 7 reaction event.
|
||||
* Links reaction to target notes via e-tags and a-tags.
|
||||
|
||||
+15
-10
@@ -34,7 +34,10 @@ import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
|
||||
private fun isFeedNote(event: com.vitorpamplona.quartz.nip01Core.core.Event?): Boolean = event is TextNoteEvent || event is RepostEvent || event is GenericRepostEvent
|
||||
private fun isFeedNote(event: com.vitorpamplona.quartz.nip01Core.core.Event?): Boolean =
|
||||
event is TextNoteEvent ||
|
||||
event is RepostEvent ||
|
||||
event is GenericRepostEvent
|
||||
|
||||
private fun List<Note>.deduplicateReposts(): List<Note> =
|
||||
distinctBy { note ->
|
||||
@@ -132,7 +135,7 @@ class DesktopThreadFilter(
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile feed: all kind 1 notes by a specific pubkey.
|
||||
* Profile feed: text notes + reposts by a specific pubkey.
|
||||
*/
|
||||
class DesktopProfileFeedFilter(
|
||||
private val pubkey: HexKey,
|
||||
@@ -140,19 +143,21 @@ class DesktopProfileFeedFilter(
|
||||
) : AdditiveFeedFilter<Note>() {
|
||||
override fun feedKey(): String = "profile-$pubkey"
|
||||
|
||||
private fun isProfileNote(note: Note): Boolean {
|
||||
val event = note.event ?: return false
|
||||
return note.author?.pubkeyHex == pubkey && isFeedNote(event)
|
||||
}
|
||||
|
||||
override fun feed(): List<Note> =
|
||||
cache.notes
|
||||
.filterIntoSet { _, note ->
|
||||
note.event is TextNoteEvent && note.author?.pubkeyHex == pubkey
|
||||
}.sortedWith(DefaultFeedOrder)
|
||||
.filterIntoSet { _, note -> isProfileNote(note) }
|
||||
.sortedWith(DefaultFeedOrder)
|
||||
.deduplicateReposts()
|
||||
.take(limit())
|
||||
|
||||
override fun applyFilter(newItems: Set<Note>): Set<Note> =
|
||||
newItems.filterTo(HashSet()) {
|
||||
it.event is TextNoteEvent && it.author?.pubkeyHex == pubkey
|
||||
}
|
||||
override fun applyFilter(newItems: Set<Note>): Set<Note> = newItems.filterTo(HashSet()) { isProfileNote(it) }
|
||||
|
||||
override fun sort(items: Set<Note>): List<Note> = items.sortedWith(DefaultFeedOrder)
|
||||
override fun sort(items: Set<Note>): List<Note> = items.sortedWith(DefaultFeedOrder).deduplicateReposts()
|
||||
|
||||
override fun limit(): Int = 1000
|
||||
}
|
||||
|
||||
+15
-3
@@ -37,7 +37,7 @@ enum class FeedMode {
|
||||
*/
|
||||
fun createGlobalFeedSubscription(
|
||||
relays: Set<NormalizedRelayUrl>,
|
||||
limit: Int = 50,
|
||||
limit: Int = 200,
|
||||
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
|
||||
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
|
||||
): SubscriptionConfig =
|
||||
@@ -55,7 +55,7 @@ fun createGlobalFeedSubscription(
|
||||
fun createFollowingFeedSubscription(
|
||||
relays: Set<NormalizedRelayUrl>,
|
||||
followedUsers: List<String>,
|
||||
limit: Int = 50,
|
||||
limit: Int = 200,
|
||||
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
|
||||
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
|
||||
): SubscriptionConfig? {
|
||||
@@ -121,9 +121,21 @@ fun createThreadRepliesSubscription(
|
||||
subId = generateSubId("thread-${noteId.take(8)}"),
|
||||
filters =
|
||||
listOf(
|
||||
// Kind 1 replies via lowercase e-tag (NIP-10)
|
||||
FilterBuilders.byETags(
|
||||
eventIds = listOf(noteId),
|
||||
kinds = listOf(1), // TextNoteEvent
|
||||
kinds = listOf(1),
|
||||
limit = limit,
|
||||
),
|
||||
// Kind 1111 comments via lowercase e-tag (reply parent) or uppercase E-tag (root)
|
||||
FilterBuilders.byTags(
|
||||
tags = mapOf("e" to listOf(noteId)),
|
||||
kinds = listOf(1111),
|
||||
limit = limit,
|
||||
),
|
||||
FilterBuilders.byTags(
|
||||
tags = mapOf("E" to listOf(noteId)),
|
||||
kinds = listOf(1111),
|
||||
limit = limit,
|
||||
),
|
||||
),
|
||||
|
||||
+2
@@ -121,6 +121,7 @@ fun UserProfileScreen(
|
||||
onBack: () -> Unit,
|
||||
onCompose: () -> Unit = {},
|
||||
onNavigateToProfile: (String) -> Unit = {},
|
||||
onNavigateToThread: (String) -> Unit = {},
|
||||
onNavigateToArticle: (String) -> Unit = {},
|
||||
onZapFeedback: (ZapFeedback) -> Unit = {},
|
||||
) {
|
||||
@@ -796,6 +797,7 @@ fun UserProfileScreen(
|
||||
onReply = onCompose,
|
||||
onZapFeedback = onZapFeedback,
|
||||
onNavigateToProfile = onNavigateToProfile,
|
||||
onNavigateToThread = onNavigateToThread,
|
||||
onImageClick = { urls, index ->
|
||||
lightboxState = LightboxState(urls, index)
|
||||
},
|
||||
|
||||
+3
@@ -296,6 +296,7 @@ internal fun RootContent(
|
||||
onBack = {},
|
||||
onCompose = onShowComposeDialog,
|
||||
onNavigateToProfile = onNavigateToProfile,
|
||||
onNavigateToThread = onNavigateToThread,
|
||||
onNavigateToArticle = onNavigateToArticle,
|
||||
onZapFeedback = onZapFeedback,
|
||||
)
|
||||
@@ -325,6 +326,7 @@ internal fun RootContent(
|
||||
onBack = {},
|
||||
onCompose = onShowComposeDialog,
|
||||
onNavigateToProfile = onNavigateToProfile,
|
||||
onNavigateToThread = onNavigateToThread,
|
||||
onZapFeedback = onZapFeedback,
|
||||
)
|
||||
}
|
||||
@@ -426,6 +428,7 @@ internal fun OverlayContent(
|
||||
onBack = onBack,
|
||||
onCompose = onShowComposeDialog,
|
||||
onNavigateToProfile = onNavigateToProfile,
|
||||
onNavigateToThread = onNavigateToThread,
|
||||
onNavigateToArticle = onNavigateToArticle,
|
||||
onZapFeedback = onZapFeedback,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user