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:
parent
d18b7c8a24
commit
96f559abbb
2 changed files with 22 additions and 0 deletions
|
|
@ -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.ObserveConnectionUseCase
|
||||||
import io.picoclaw.android.core.domain.usecase.ObserveMessagesUseCase
|
import io.picoclaw.android.core.domain.usecase.ObserveMessagesUseCase
|
||||||
import io.picoclaw.android.core.domain.usecase.SendMessageUseCase
|
import io.picoclaw.android.core.domain.usecase.SendMessageUseCase
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -26,6 +28,9 @@ class ChatViewModel(
|
||||||
private val _uiState = MutableStateFlow(ChatUiState())
|
private val _uiState = MutableStateFlow(ChatUiState())
|
||||||
val uiState: StateFlow<ChatUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<ChatUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
private val _scrollToBottom = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||||
|
val scrollToBottom = _scrollToBottom.asSharedFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
connectChat()
|
connectChat()
|
||||||
|
|
||||||
|
|
@ -51,6 +56,7 @@ class ChatViewModel(
|
||||||
val state = _uiState.value
|
val state = _uiState.value
|
||||||
val text = state.inputText.trim()
|
val text = state.inputText.trim()
|
||||||
if (text.isEmpty() && state.pendingImages.isEmpty()) return
|
if (text.isEmpty() && state.pendingImages.isEmpty()) return
|
||||||
|
_scrollToBottom.tryEmit(Unit)
|
||||||
_uiState.update { it.copy(inputText = "", pendingImages = emptyList()) }
|
_uiState.update { it.copy(inputText = "", pendingImages = emptyList()) }
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,22 @@ fun ChatScreen(
|
||||||
if (shouldLoadMore) viewModel.onEvent(ChatEvent.OnLoadMore)
|
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(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(title = { Text("PicoClaw") })
|
TopAppBar(title = { Text("PicoClaw") })
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue