properly update states in chat screen
This commit is contained in:
+26
-7
@@ -1,8 +1,6 @@
|
||||
package ru.shadowsparky.chat.presentation
|
||||
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.value.MutableValue
|
||||
import com.arkivanov.decompose.value.Value
|
||||
import com.arkivanov.essenty.lifecycle.coroutines.repeatOnLifecycle
|
||||
import com.arkivanov.essenty.lifecycle.doOnDestroy
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
@@ -11,6 +9,9 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.shadowsparky.chat.domain.ChatRepository
|
||||
import ru.shadowsparky.chat.domain.ChatRoles
|
||||
@@ -26,7 +27,9 @@ sealed interface ChatState {
|
||||
data class Data(
|
||||
val messages: List<Message>,
|
||||
val isSending: Boolean = false,
|
||||
val sendError: String? = null
|
||||
val sendError: String? = null,
|
||||
val scrollToFirstMessage: Boolean = false,
|
||||
val clearInput: Boolean = false
|
||||
) : ChatState
|
||||
}
|
||||
|
||||
@@ -37,6 +40,7 @@ class ChatComponent(
|
||||
val onLinkClicked: (String) -> Unit,
|
||||
val refreshFlow: Flow<Unit>
|
||||
) : ComponentContext by componentContext {
|
||||
private var needClearInput = false
|
||||
private val errorHandler = CoroutineExceptionHandler { _, throwable ->
|
||||
_state.value = ChatState.Error(throwable.message ?: throwable.toString())
|
||||
}
|
||||
@@ -45,13 +49,16 @@ class ChatComponent(
|
||||
lifecycle.doOnDestroy { ctx.cancel() }
|
||||
}
|
||||
|
||||
private val _state = MutableValue<ChatState>(ChatState.Loading)
|
||||
val state: Value<ChatState> = _state
|
||||
private val _state = MutableStateFlow<ChatState>(ChatState.Loading)
|
||||
val state: StateFlow<ChatState> = _state.asStateFlow()
|
||||
|
||||
|
||||
init {
|
||||
loadInitialMessages()
|
||||
scope.launch {
|
||||
repeatOnLifecycle { refreshFlow.collect { refreshList() } }
|
||||
repeatOnLifecycle {
|
||||
refreshFlow.collect { refreshList() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +79,7 @@ class ChatComponent(
|
||||
if (content.isBlank() || currentState.isSending) return
|
||||
_state.value = currentState.copy(isSending = true, sendError = null)
|
||||
scope.launch {
|
||||
needClearInput = true
|
||||
repository.send(Message(role = ChatRoles.USER, content = content, timestamp = 0L))
|
||||
}
|
||||
}
|
||||
@@ -84,7 +92,9 @@ class ChatComponent(
|
||||
val freshMessages = repository.query(afterId = latestMessageId)
|
||||
_state.value = currentState.copy(
|
||||
messages = freshMessages + currentState.messages,
|
||||
isSending = false
|
||||
isSending = false,
|
||||
scrollToFirstMessage = freshMessages.isNotEmpty(),
|
||||
clearInput = needClearInput
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -93,4 +103,13 @@ class ChatComponent(
|
||||
val currentState = _state.value as? ChatState.Data ?: return
|
||||
_state.value = currentState.copy(sendError = null)
|
||||
}
|
||||
|
||||
fun resetState(needScroll: Boolean, needClearInput: Boolean) {
|
||||
val currentState = _state.value as? ChatState.Data ?: return
|
||||
this.needClearInput = needClearInput
|
||||
_state.value = currentState.copy(
|
||||
scrollToFirstMessage = needScroll,
|
||||
clearInput = needClearInput
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-6
@@ -45,6 +45,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.runtime.withFrameNanos
|
||||
@@ -124,7 +125,10 @@ fun ChatContent(component: ChatComponent, paddingValues: PaddingValues, state: C
|
||||
onParseMessage = { component.parseMessage(it) },
|
||||
onClickLink = { component.onLinkClicked(it) },
|
||||
onSendMessage = { component.sendMessage(it) },
|
||||
onDismissError = { component.clearSendError() }
|
||||
onDismissError = { component.clearSendError() },
|
||||
resetState = { needScroll, needClearInput ->
|
||||
component.resetState(needScroll, needClearInput)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -139,17 +143,24 @@ private fun ChatContent(
|
||||
onParseMessage: (String) -> List<TextToken>,
|
||||
onClickLink: (String) -> Unit,
|
||||
onSendMessage: (String) -> Unit,
|
||||
onDismissError: () -> Unit
|
||||
onDismissError: () -> Unit,
|
||||
resetState: (needScroll: Boolean, needClearInput: Boolean) -> Unit
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
var textInput by remember { mutableStateOf("") }
|
||||
var textInput by rememberSaveable { mutableStateOf("") }
|
||||
val textFieldFocusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(state.messages.size) {
|
||||
if (state.messages.isNotEmpty()) {
|
||||
LaunchedEffect(state.scrollToFirstMessage) {
|
||||
if (state.scrollToFirstMessage) {
|
||||
listState.animateScrollToItem(0)
|
||||
resetState(false, state.clearInput)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(state.clearInput) {
|
||||
if (state.clearInput) {
|
||||
textInput = ""
|
||||
resetState(state.scrollToFirstMessage, false)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(state.messages) { textInput = "" }
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val focusRequesters = remember { mutableMapOf<Any, FocusRequester>() }
|
||||
|
||||
Reference in New Issue
Block a user