From e121fc5774829ca3dbdcd46643df8370ab9a1fef Mon Sep 17 00:00:00 2001 From: AbronStudio Date: Mon, 11 Aug 2025 12:07:02 +0330 Subject: [PATCH] feat: Implement pagination improvements in ChannelScreen This commit enhances the pagination logic in the ChannelScreen to improve message loading behavior. - Introduced a new `isNearBottom` state to detect when the user is near the bottom of the message list, triggering loading of newer messages. - Refactored the existing pagination logic to handle both loading older messages when near the top and newer messages when near the bottom. - Added logic to preserve the scroll position when new messages are loaded, ensuring a smoother user experience. - Improved logging for better debugging and tracking of pagination events. --- .../chat/views/channel/ChannelScreen.kt | 87 +++++++++++++++---- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreen.kt b/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreen.kt index 2f757f33..a188a634 100644 --- a/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreen.kt +++ b/app/src/main/java/chat/peptide/screens/chat/views/channel/ChannelScreen.kt @@ -424,6 +424,13 @@ fun ChannelScreen( } } + // Near bottom detection (reverseLayout = true): firstVisibleItemIndex small means near bottom/latest + val isNearBottom = remember(lazyListState) { + derivedStateOf { + lazyListState.firstVisibleItemIndex < 3 + } + } + val scrollDownFABPadding by animateDpAsState( if (viewModel.typingUsers.isNotEmpty()) 25.dp else 0.dp, animationSpec = RevoltTweenDp, @@ -467,25 +474,71 @@ fun ChannelScreen( Log.w("ChannelScreen", "Target message not found after loading around window") } } - } ?: run { - LaunchedEffect(isNearTop) { - snapshotFlow { isNearTop.value } - .distinctUntilChanged() - .collect { isNearTop -> - if (isNearTop) { - Log.d("ChannelScreen", "Loading more messages") - viewModel.loadMessages(before = viewModel.items.lastOrNull { - it is ChannelScreenItem.RegularMessage || it is ChannelScreenItem.SystemMessage - }?.let { - when (it) { - is ChannelScreenItem.RegularMessage -> it.message.id - is ChannelScreenItem.SystemMessage -> it.message.id - else -> null - } - }, amount = 50) + } + + // Always-on pagination collectors (work for both default and around modes) + LaunchedEffect(isNearTop) { + snapshotFlow { isNearTop.value } + .distinctUntilChanged() + .collect { nearTop -> + if (nearTop && !viewModel.endOfChannel) { + val oldestId = viewModel.items.lastOrNull { + it is ChannelScreenItem.RegularMessage || it is ChannelScreenItem.SystemMessage + }?.let { + when (it) { + is ChannelScreenItem.RegularMessage -> it.message.id + is ChannelScreenItem.SystemMessage -> it.message.id + else -> null + } + } + if (oldestId != null) { + Log.d("ChannelScreen", "Pagination: loading older before=$oldestId") + viewModel.loadMessages(amount = 50, before = oldestId, ignoreExisting = true) } } - } + } + } + + LaunchedEffect(isNearBottom) { + snapshotFlow { isNearBottom.value } + .distinctUntilChanged() + .collect { nearBottom -> + if (nearBottom) { + val newestId = viewModel.items.firstOrNull { + it is ChannelScreenItem.RegularMessage || it is ChannelScreenItem.SystemMessage + }?.let { + when (it) { + is ChannelScreenItem.RegularMessage -> it.message.id + is ChannelScreenItem.SystemMessage -> it.message.id + else -> null + } + } + if (newestId != null) { + // Preserve anchor: remember current first visible item and offset + val beforeSize = viewModel.items.size + val anchorIndex = lazyListState.firstVisibleItemIndex + val anchorOffset = lazyListState.firstVisibleItemScrollOffset + Log.d("ChannelScreen", "Pagination: loading newer after=$newestId (anchor index=$anchorIndex, offset=$anchorOffset, size=$beforeSize)") + + viewModel.loadMessages(amount = 50, after = newestId, ignoreExisting = true) + + // Wait until items are appended to the front (after-insert path adds to front) + var attempts = 0 + while (attempts < 30) { // ~1.5s max + kotlinx.coroutines.delay(50) + val added = viewModel.items.size - beforeSize + if (added > 0) { + // Shift anchor forward by number of inserted items to keep viewport stable + try { + lazyListState.scrollToItem(anchorIndex + added, anchorOffset) + } catch (_: Exception) {} + break + } + attempts++ + } + } + } + } } //