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.
This commit is contained in:
AbronStudio 2025-08-11 12:07:02 +03:30
parent 072fcbf5eb
commit e121fc5774
1 changed files with 70 additions and 17 deletions

View File

@ -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++
}
}
}
}
}
// </editor-fold>