feat: auto-scroll chat on new messages

Scroll to bottom when sending a message, and conditionally scroll on
received messages when the user is already near the latest messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kohei 2026-02-18 22:17:46 +09:00
parent d18b7c8a24
commit 96f559abbb
2 changed files with 22 additions and 0 deletions

View file

@ -8,8 +8,10 @@ import io.picoclaw.android.core.domain.usecase.LoadMoreMessagesUseCase
import io.picoclaw.android.core.domain.usecase.ObserveConnectionUseCase
import io.picoclaw.android.core.domain.usecase.ObserveMessagesUseCase
import io.picoclaw.android.core.domain.usecase.SendMessageUseCase
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@ -26,6 +28,9 @@ class ChatViewModel(
private val _uiState = MutableStateFlow(ChatUiState())
val uiState: StateFlow<ChatUiState> = _uiState.asStateFlow()
private val _scrollToBottom = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
val scrollToBottom = _scrollToBottom.asSharedFlow()
init {
connectChat()
@ -51,6 +56,7 @@ class ChatViewModel(
val state = _uiState.value
val text = state.inputText.trim()
if (text.isEmpty() && state.pendingImages.isEmpty()) return
_scrollToBottom.tryEmit(Unit)
_uiState.update { it.copy(inputText = "", pendingImages = emptyList()) }
viewModelScope.launch {
try {

View file

@ -99,6 +99,22 @@ fun ChatScreen(
if (shouldLoadMore) viewModel.onEvent(ChatEvent.OnLoadMore)
}
LaunchedEffect(Unit) {
viewModel.scrollToBottom.collect {
listState.animateScrollToItem(0)
}
}
val isNearBottom by remember {
derivedStateOf { listState.firstVisibleItemIndex <= 3 }
}
LaunchedEffect(uiState.messages.size) {
if (isNearBottom) {
listState.animateScrollToItem(0)
}
}
Scaffold(
topBar = {
TopAppBar(title = { Text("PicoClaw") })