From f6f2559d313aefe01565184fb62a9a12536b013b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Apr 2026 23:59:25 +0000 Subject: [PATCH] Auto-scroll nest chat to newest message when at bottom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both NestFullScreen and NestLobbyScreen render the kind-1311 transcript with LazyColumn(reverseLayout = true). When a new message is prepended, LazyColumn preserves visual position by shifting firstVisibleItemIndex from 0 to 1, leaving the new message just below the viewport — the user has to scroll down to see it. If the user is at (or one item from) the bottom when a new message arrives, scroll back to item 0 so the newest message is visible. If they are reading older history, leave them where they are. --- .../loggedIn/nests/room/chat/NestChatPanel.kt | 14 ++++++++++++++ .../loggedIn/nests/room/lobby/NestLobbyScreen.kt | 13 +++++++++++++ 2 files changed, 27 insertions(+) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestChatPanel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestChatPanel.kt index 355f0f263..27334b1ef 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestChatPanel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestChatPanel.kt @@ -35,6 +35,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -100,6 +101,19 @@ internal fun ColumnScope.NestChatPanel( val listState = rememberLazyListState() + // Auto-stick to the newest message. With reverseLayout=true, item 0 + // is the bottom of the viewport (newest). When a new message is + // prepended, LazyColumn preserves visual position by shifting + // firstVisibleItemIndex from 0 → 1, leaving the new message just + // below the viewport. If the user was at (or near) the bottom, + // scroll back to 0 so the new message becomes visible. If they're + // reading older history, leave them where they are. + LaunchedEffect(messages.firstOrNull()?.idHex) { + if (listState.firstVisibleItemIndex <= 1) { + listState.animateScrollToItem(0) + } + } + Column(modifier = modifier.fillMaxWidth()) { Box( modifier = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/lobby/NestLobbyScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/lobby/NestLobbyScreen.kt index 285c30749..32c95ec5b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/lobby/NestLobbyScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/lobby/NestLobbyScreen.kt @@ -164,6 +164,19 @@ private fun NestLobbyContent( val listState = rememberLazyListState() val scope = rememberCoroutineScope() + // Auto-stick to the newest message. With reverseLayout=true, item 0 + // is the bottom of the viewport (newest). When a new message is + // prepended, LazyColumn preserves visual position by shifting + // firstVisibleItemIndex from 0 → 1, leaving the new message just + // below the viewport. If the user was at (or near) the bottom, + // scroll back to 0 so the new message becomes visible. If they're + // reading older history, leave them where they are. + LaunchedEffect(messages.firstOrNull()?.idHex) { + if (listState.firstVisibleItemIndex <= 1) { + listState.animateScrollToItem(0) + } + } + Scaffold( contentWindowInsets = WindowInsets(0), topBar = {