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.GenericRepostEvent
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||||
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull
|
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull
|
||||||
|
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.events.LnZapPaymentRequestEvent
|
import com.vitorpamplona.quartz.nip47WalletConnect.events.LnZapPaymentRequestEvent
|
||||||
@@ -173,7 +174,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
when (event) {
|
when (event) {
|
||||||
is MetadataEvent -> {
|
is MetadataEvent -> {
|
||||||
consumeMetadata(event)
|
consumeMetadata(event)
|
||||||
true
|
false // metadata updates User, not Note — skip event stream
|
||||||
}
|
}
|
||||||
|
|
||||||
is TextNoteEvent -> {
|
is TextNoteEvent -> {
|
||||||
@@ -212,6 +213,10 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
consumeBookmarkList(event)
|
consumeBookmarkList(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is CommentEvent -> {
|
||||||
|
consumeComment(event, relay)
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -235,6 +240,24 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
return true
|
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.
|
* Consumes a kind 7 reaction event.
|
||||||
* Links reaction to target notes via e-tags and a-tags.
|
* 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.nip25Reactions.ReactionEvent
|
||||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
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> =
|
private fun List<Note>.deduplicateReposts(): List<Note> =
|
||||||
distinctBy { 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(
|
class DesktopProfileFeedFilter(
|
||||||
private val pubkey: HexKey,
|
private val pubkey: HexKey,
|
||||||
@@ -140,19 +143,21 @@ class DesktopProfileFeedFilter(
|
|||||||
) : AdditiveFeedFilter<Note>() {
|
) : AdditiveFeedFilter<Note>() {
|
||||||
override fun feedKey(): String = "profile-$pubkey"
|
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> =
|
override fun feed(): List<Note> =
|
||||||
cache.notes
|
cache.notes
|
||||||
.filterIntoSet { _, note ->
|
.filterIntoSet { _, note -> isProfileNote(note) }
|
||||||
note.event is TextNoteEvent && note.author?.pubkeyHex == pubkey
|
.sortedWith(DefaultFeedOrder)
|
||||||
}.sortedWith(DefaultFeedOrder)
|
.deduplicateReposts()
|
||||||
.take(limit())
|
.take(limit())
|
||||||
|
|
||||||
override fun applyFilter(newItems: Set<Note>): Set<Note> =
|
override fun applyFilter(newItems: Set<Note>): Set<Note> = newItems.filterTo(HashSet()) { isProfileNote(it) }
|
||||||
newItems.filterTo(HashSet()) {
|
|
||||||
it.event is TextNoteEvent && it.author?.pubkeyHex == pubkey
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
override fun limit(): Int = 1000
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-3
@@ -37,7 +37,7 @@ enum class FeedMode {
|
|||||||
*/
|
*/
|
||||||
fun createGlobalFeedSubscription(
|
fun createGlobalFeedSubscription(
|
||||||
relays: Set<NormalizedRelayUrl>,
|
relays: Set<NormalizedRelayUrl>,
|
||||||
limit: Int = 50,
|
limit: Int = 200,
|
||||||
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
|
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
|
||||||
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
|
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
|
||||||
): SubscriptionConfig =
|
): SubscriptionConfig =
|
||||||
@@ -55,7 +55,7 @@ fun createGlobalFeedSubscription(
|
|||||||
fun createFollowingFeedSubscription(
|
fun createFollowingFeedSubscription(
|
||||||
relays: Set<NormalizedRelayUrl>,
|
relays: Set<NormalizedRelayUrl>,
|
||||||
followedUsers: List<String>,
|
followedUsers: List<String>,
|
||||||
limit: Int = 50,
|
limit: Int = 200,
|
||||||
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
|
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
|
||||||
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
|
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
|
||||||
): SubscriptionConfig? {
|
): SubscriptionConfig? {
|
||||||
@@ -121,9 +121,21 @@ fun createThreadRepliesSubscription(
|
|||||||
subId = generateSubId("thread-${noteId.take(8)}"),
|
subId = generateSubId("thread-${noteId.take(8)}"),
|
||||||
filters =
|
filters =
|
||||||
listOf(
|
listOf(
|
||||||
|
// Kind 1 replies via lowercase e-tag (NIP-10)
|
||||||
FilterBuilders.byETags(
|
FilterBuilders.byETags(
|
||||||
eventIds = listOf(noteId),
|
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,
|
limit = limit,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
+2
@@ -121,6 +121,7 @@ fun UserProfileScreen(
|
|||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onCompose: () -> Unit = {},
|
onCompose: () -> Unit = {},
|
||||||
onNavigateToProfile: (String) -> Unit = {},
|
onNavigateToProfile: (String) -> Unit = {},
|
||||||
|
onNavigateToThread: (String) -> Unit = {},
|
||||||
onNavigateToArticle: (String) -> Unit = {},
|
onNavigateToArticle: (String) -> Unit = {},
|
||||||
onZapFeedback: (ZapFeedback) -> Unit = {},
|
onZapFeedback: (ZapFeedback) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
@@ -796,6 +797,7 @@ fun UserProfileScreen(
|
|||||||
onReply = onCompose,
|
onReply = onCompose,
|
||||||
onZapFeedback = onZapFeedback,
|
onZapFeedback = onZapFeedback,
|
||||||
onNavigateToProfile = onNavigateToProfile,
|
onNavigateToProfile = onNavigateToProfile,
|
||||||
|
onNavigateToThread = onNavigateToThread,
|
||||||
onImageClick = { urls, index ->
|
onImageClick = { urls, index ->
|
||||||
lightboxState = LightboxState(urls, index)
|
lightboxState = LightboxState(urls, index)
|
||||||
},
|
},
|
||||||
|
|||||||
+3
@@ -296,6 +296,7 @@ internal fun RootContent(
|
|||||||
onBack = {},
|
onBack = {},
|
||||||
onCompose = onShowComposeDialog,
|
onCompose = onShowComposeDialog,
|
||||||
onNavigateToProfile = onNavigateToProfile,
|
onNavigateToProfile = onNavigateToProfile,
|
||||||
|
onNavigateToThread = onNavigateToThread,
|
||||||
onNavigateToArticle = onNavigateToArticle,
|
onNavigateToArticle = onNavigateToArticle,
|
||||||
onZapFeedback = onZapFeedback,
|
onZapFeedback = onZapFeedback,
|
||||||
)
|
)
|
||||||
@@ -325,6 +326,7 @@ internal fun RootContent(
|
|||||||
onBack = {},
|
onBack = {},
|
||||||
onCompose = onShowComposeDialog,
|
onCompose = onShowComposeDialog,
|
||||||
onNavigateToProfile = onNavigateToProfile,
|
onNavigateToProfile = onNavigateToProfile,
|
||||||
|
onNavigateToThread = onNavigateToThread,
|
||||||
onZapFeedback = onZapFeedback,
|
onZapFeedback = onZapFeedback,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -426,6 +428,7 @@ internal fun OverlayContent(
|
|||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onCompose = onShowComposeDialog,
|
onCompose = onShowComposeDialog,
|
||||||
onNavigateToProfile = onNavigateToProfile,
|
onNavigateToProfile = onNavigateToProfile,
|
||||||
|
onNavigateToThread = onNavigateToThread,
|
||||||
onNavigateToArticle = onNavigateToArticle,
|
onNavigateToArticle = onNavigateToArticle,
|
||||||
onZapFeedback = onZapFeedback,
|
onZapFeedback = onZapFeedback,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user