Fixes scroll to newest when a new message arrives

This commit is contained in:
Vitor Pamplona
2026-04-28 15:19:08 -04:00
parent ee860b92a1
commit 24336cb940
@@ -29,17 +29,16 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -51,6 +50,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.ChatroomMessageCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.ChatroomMessageCompose
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import androidx.lifecycle.viewmodel.compose.viewModel as composeViewModel import androidx.lifecycle.viewmodel.compose.viewModel as composeViewModel
/** /**
@@ -94,8 +95,15 @@ internal fun ColumnScope.NestChatPanel(
nestScreenModel.init(accountViewModel) nestScreenModel.init(accountViewModel)
nestScreenModel.load(event) nestScreenModel.load(event)
val listState = rememberLazyListState()
Column(modifier = modifier.fillMaxWidth()) { Column(modifier = modifier.fillMaxWidth()) {
Box(modifier = Modifier.fillMaxWidth().weight(1f, fill = true)) { Box(
modifier =
Modifier
.fillMaxWidth()
.weight(1f, fill = true),
) {
if (messages.isEmpty()) { if (messages.isEmpty()) {
Text( Text(
text = stringRes(R.string.nest_chat_empty), text = stringRes(R.string.nest_chat_empty),
@@ -106,6 +114,7 @@ internal fun ColumnScope.NestChatPanel(
} else { } else {
NestChatMessageList( NestChatMessageList(
messages = messages, messages = messages,
listState = listState,
routeForLastRead = routeForLastRead, routeForLastRead = routeForLastRead,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
@@ -120,7 +129,12 @@ internal fun ColumnScope.NestChatPanel(
NestEditFieldRow( NestEditFieldRow(
nestScreenModel = nestScreenModel, nestScreenModel = nestScreenModel,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
onSendNewMessage = {}, onSendNewMessage = {
scope.launch {
delay(100)
listState.animateScrollToItem(0)
}
},
nav = nav, nav = nav,
) )
} }
@@ -130,26 +144,12 @@ internal fun ColumnScope.NestChatPanel(
private fun NestChatMessageList( private fun NestChatMessageList(
messages: List<Note>, messages: List<Note>,
routeForLastRead: String, routeForLastRead: String,
listState: LazyListState,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: BouncingIntentNav, nav: BouncingIntentNav,
onWantsToReply: (Note) -> Unit, onWantsToReply: (Note) -> Unit,
onWantsToEditDraft: (Note) -> Unit, onWantsToEditDraft: (Note) -> Unit,
) { ) {
val listState = rememberLazyListState()
// Auto-scroll to the newest message ONLY when the user is already
// pinned near the bottom — otherwise a new message yanks them away
// from the history they're scrolling. With reverseLayout=true the
// bottom of the screen is index 0, so "pinned" = the first visible
// item is index 0 or 1 (one row of tolerance for partial scroll).
LaunchedEffect(listState) {
snapshotFlow { messages.size }.collect { size ->
if (size > 0 && listState.firstVisibleItemIndex <= 1) {
listState.animateScrollToItem(0)
}
}
}
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
state = listState, state = listState,