Merge pull request #609 from barrydeen/fix/profile-notes-feed-pagination

fix(profile): keep notes feed paginating and render inline gallery media
This commit is contained in:
Barry Deen
2026-07-01 12:50:29 -04:00
committed by GitHub
2 changed files with 74 additions and 13 deletions
@@ -545,17 +545,25 @@ fun UserProfileScreen(
val listState = rememberLazyListState()
// Auto-load more media when scrolling near the bottom of the grid
if (selectedTabId == 3 && mediaItems.isNotEmpty()) {
LaunchedEffect(listState) {
// Auto-load more when scrolling near the bottom of the notes/replies/media feeds
val autoPaginates = selectedTabId == 0 || selectedTabId == 1 ||
(selectedTabId == 3 && mediaItems.isNotEmpty())
if (autoPaginates) {
LaunchedEffect(listState, selectedTabId) {
snapshotFlow {
val lastVisible = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
val totalItems = listState.layoutInfo.totalItemsCount
lastVisible to totalItems
}.collect { (lastVisible, totalItems) ->
if (totalItems > 0 && lastVisible >= totalItems - 3) {
viewModel.loadMoreNotes()
viewModel.loadMoreReplies()
when (selectedTabId) {
0 -> if (notesSortMode == ProfileSortMode.RECENCY) viewModel.loadMoreNotes()
1 -> if (repliesSortMode == ProfileSortMode.RECENCY) viewModel.loadMoreReplies()
3 -> {
viewModel.loadMoreNotes()
viewModel.loadMoreReplies()
}
}
}
}
}
@@ -854,6 +862,52 @@ fun UserProfileScreen(
val rootUserZapPollVote = remember(pollVoteVersion, event.id) {
if (event.kind == 6969) eventRepo?.getUserZapPollVote(event.id) else null
}
if (isGalleryEvent(event)) {
GalleryCard(
event = event,
profile = if (repostPubkey != null) eventRepo?.getProfileData(event.pubkey) else profile,
onReply = { onReply(event) },
onProfileClick = { onNavigateToProfile?.invoke(event.pubkey) },
onNavigateToProfile = onNavigateToProfile,
onNoteClick = { onNoteClick(event) },
onQuotedNoteClick = onQuotedNoteClick,
onReact = { emoji -> onReact(event, emoji) },
onRepost = { onRepost(event) },
onQuote = { onQuote(event) },
userReactionEmojis = userEmojis,
hasUserReposted = hasUserReposted,
onZap = { zapTargetEvent = event },
hasUserZapped = hasUserZapped,
likeCount = likeCount,
replyCount = replyCount,
repostCount = repostCount,
zapSats = zapSats,
isZapAnimating = event.id in zapAnimatingIds,
isZapInProgress = event.id in zapInProgressIds,
eventRepo = eventRepo,
repostPubkeys = profileRepostPubkeys,
reactionDetails = reactionDetails,
zapDetails = zapDetails,
reactionEmojiUrls = eventReactionEmojiUrls,
relayIcons = relayIcons,
onNavigateToProfileFromDetails = onNavigateToProfile,
onFollowAuthor = { onToggleFollow?.invoke(event.pubkey) },
onBlockAuthor = { onBlockUser?.invoke() },
isFollowingAuthor = contactRepo.isFollowing(event.pubkey),
isOwnEvent = event.pubkey == userPubkey,
nip05Repo = nip05Repo,
onAddToList = { onAddNoteToList(event.id) },
isInList = event.id in listedIds,
onPin = { onTogglePin(event.id) },
isPinned = event.id in pinnedIds,
onDelete = { onDeleteEvent(event.id, event.kind) },
resolvedEmojis = resolvedEmojis,
unicodeEmojis = unicodeEmojis,
onOpenEmojiLibrary = onOpenEmojiLibrary,
noteActions = invoiceNoteActions
)
return@items
}
PostCard(
event = event,
profile = if (repostPubkey != null) eventRepo?.getProfileData(event.pubkey) else profile,
@@ -340,20 +340,27 @@ class UserProfileViewModel(app: Application) : AndroidViewModel(app) {
}
20, 21, 22 -> {
eventRepo.cacheEvent(event)
if (event.created_at < oldestNoteTimestamp) oldestNoteTimestamp = event.created_at
// Add to gallery posts
// Add to gallery posts (fed by both the notes stream and the
// dedicated, unbounded usergallery sub)
val gallery = _galleryPosts.value.toMutableList()
if (gallery.none { it.id == event.id }) {
gallery.add(event)
gallery.sortByDescending { it.created_at }
_galleryPosts.value = gallery
}
// Also show in root notes feed
val current = _rootNotes.value.toMutableList()
if (current.none { it.id == event.id }) {
current.add(event)
current.sortByDescending { _repostSortTime[it.id] ?: it.created_at }
_rootNotes.value = current
// Only surface gallery kinds in the chronological notes feed when
// they arrive via the time-bounded, paginated posts stream.
// The usergallery sub has no time bound and reaches arbitrarily far
// back — letting it into rootNotes both collapses the feed into
// gallery-only content and poisons oldestNoteTimestamp pagination.
if (subscriptionId == "userposts" || subscriptionId == "userposts-more") {
if (event.created_at < oldestNoteTimestamp) oldestNoteTimestamp = event.created_at
val current = _rootNotes.value.toMutableList()
if (current.none { it.id == event.id }) {
current.add(event)
current.sortByDescending { _repostSortTime[it.id] ?: it.created_at }
_rootNotes.value = current
}
}
}
6 -> {