Split MAX bot loops and make chats cache-first

This commit is contained in:
sevenhill
2026-07-05 23:20:20 +03:00
parent 0106130744
commit 3bc6456ed7
7 changed files with 157 additions and 16 deletions
@@ -80,7 +80,7 @@ class QMaxApi {
}
suspend fun messages(sessionServerUrl: String, token: String, chatId: String): List<MessageDto> {
return get(sessionServerUrl, "/api/chats/$chatId/messages", token)
return get(sessionServerUrl, "/api/chats/$chatId/messages?sync=false", token)
}
suspend fun markChatRead(sessionServerUrl: String, token: String, chatId: String) {
@@ -23,6 +23,8 @@ import xyz.kusoft.qmax.core.model.QMaxSession
import xyz.kusoft.qmax.core.network.QMaxHttpException
import xyz.kusoft.qmax.core.realtime.QMaxRealtimeClient
import java.io.File
import java.time.OffsetDateTime
import java.util.UUID
data class QMaxUiState(
val serverUrl: String = BuildConfig.QMAX_DEFAULT_SERVER_URL,
@@ -328,7 +330,7 @@ class QMaxViewModel(private val repository: QMaxRepository) : ViewModel() {
}
realtime.joinChat(chat.id)
}
loadMessages(showLoading = true)
loadMessages(showLoading = false)
presencePollJob?.cancel()
presencePollJob = viewModelScope.launch {
while (state.value.selectedChat?.id == chat.id) {
@@ -574,7 +576,45 @@ class QMaxViewModel(private val repository: QMaxRepository) : ViewModel() {
val sentMessages = mutableListOf<MessageDto>()
if (attachmentUris.isEmpty()) {
sentMessages += repository.sendMessage(session, chat.id, text, replyToMessageId)
val pending = pendingTextMessage(chat.id, text, replyToMessageId)
repository.cacheRealtimeMessage(session, pending)
repository.clearDraft(chat.id)
val current = state.value
if (current.selectedChat?.id == chat.id) {
state.value = current.copy(
composerText = "",
replyTarget = null,
messages = upsertMessage(current.messages, pending)
)
}
onSuccess()
try {
val sent = repository.sendMessage(session, chat.id, text, replyToMessageId)
repository.deleteCachedMessage(session, chat.id, pending.id)
repository.cacheRealtimeMessage(session, sent)
val afterSend = state.value
if (afterSend.selectedChat?.id == chat.id) {
state.value = afterSend.copy(
messages = replaceMessage(afterSend.messages, pending.id, sent)
)
}
loadChats()
} catch (error: Throwable) {
val failed = pending.copy(
deliveryState = "Failed",
error = error.message ?: "\u041e\u0448\u0438\u0431\u043a\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438"
)
repository.cacheRealtimeMessage(session, failed)
val afterFailure = state.value
if (afterFailure.selectedChat?.id == chat.id) {
state.value = afterFailure.copy(
messages = replaceMessage(afterFailure.messages, pending.id, failed)
)
}
throw error
}
return@launchLoading
} else {
attachmentUris.forEachIndexed { index, uri ->
val caption = text.takeIf { index == 0 && it.isNotBlank() }
@@ -1101,6 +1141,23 @@ class QMaxViewModel(private val repository: QMaxRepository) : ViewModel() {
return (messages.filterNot { it.id == message.id } + message).sortedBy { it.sentAt }
}
private fun replaceMessage(messages: List<MessageDto>, oldMessageId: String, message: MessageDto): List<MessageDto> {
return (messages.filterNot { it.id == oldMessageId || it.id == message.id } + message).sortedBy { it.sentAt }
}
private fun pendingTextMessage(chatId: String, text: String, replyToMessageId: String?): MessageDto {
return MessageDto(
id = "local-${UUID.randomUUID()}",
chatId = chatId,
senderName = "You",
direction = "Outgoing",
text = text,
sentAt = OffsetDateTime.now().toString(),
replyToMessageId = replyToMessageId,
deliveryState = "Sending"
)
}
private fun normalizeChats(
chats: List<ChatDto>,
selectedChatId: String? = state.value.selectedChat?.id