refactor: extract message loading

This commit is contained in:
Codex
2026-05-14 18:16:43 +03:00
parent 6961ac7240
commit 5dc000341b
2 changed files with 159 additions and 117 deletions
@@ -0,0 +1,140 @@
package dev.meloda.fast.messageshistory
import android.util.Log
import dev.meloda.fast.common.VkConstants
import dev.meloda.fast.common.extensions.listenValue
import dev.meloda.fast.common.extensions.orDots
import dev.meloda.fast.common.extensions.setValue
import dev.meloda.fast.common.paging.canPaginate as canPaginatePage
import dev.meloda.fast.common.paging.isPaginationExhausted as isPaginationExhaustedPage
import dev.meloda.fast.common.paging.loadingFlags
import dev.meloda.fast.common.paging.mergePage
import dev.meloda.fast.common.provider.ResourceProvider
import dev.meloda.fast.data.State
import dev.meloda.fast.data.VkUtils
import dev.meloda.fast.data.processState
import dev.meloda.fast.datastore.AppSettings
import dev.meloda.fast.domain.ConvoUseCase
import dev.meloda.fast.domain.MessagesUseCase
import dev.meloda.fast.domain.util.extractAvatar
import dev.meloda.fast.domain.util.extractTitle
import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.model.api.domain.VkMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
internal class MessagesHistoryLoaders(
private val convoUseCase: ConvoUseCase,
private val messagesUseCase: MessagesUseCase,
private val resourceProvider: ResourceProvider,
private val screenState: MutableStateFlow<MessagesHistoryScreenState>,
private val messages: MutableStateFlow<List<VkMessage>>,
private val imagesToPreload: MutableStateFlow<List<String>>,
private val canPaginate: MutableStateFlow<Boolean>,
private val baseError: MutableStateFlow<BaseError?>,
private val scope: CoroutineScope,
private val syncUiMessages: () -> Unit,
private val onPinnedMessage: (VkMessage?) -> Unit,
) {
fun loadConvo() {
Log.d("MessagesHistoryViewModelImpl", "loadConvo()")
convoUseCase.getById(
peerIds = listOf(screenState.value.convoId),
extended = true,
fields = VkConstants.ALL_FIELDS
).listenValue(scope) { state ->
state.processState(
error = ::handleError,
success = { response ->
val convo = response.firstOrNull() ?: return@listenValue
val title = convo.extractTitle(
useContactName = AppSettings.General.useContactNames,
resources = resourceProvider.resources
)
val avatar = convo.extractAvatar()
screenState.setValue { old ->
old.copy(
convo = convo,
title = title,
avatar = avatar
)
}
onPinnedMessage(convo.pinnedMessage)
}
)
}
}
fun loadMessagesHistory(offset: Int) {
Log.d("MessagesHistoryViewModel", "loadMessagesHistory: $offset")
messagesUseCase.getMessagesHistory(
convoId = screenState.value.convoId,
count = MessagesHistoryViewModelImpl.MESSAGES_LOAD_COUNT,
offset = offset,
).listenValue(scope) { state ->
state.processState(
error = ::handleError,
success = { response ->
val messages = response.messages
val fullMessages = mergePage(this.messages.value, messages, offset)
.sorted()
imagesToPreload.setValue {
messages.mapNotNull { it.extractAvatar().extractUrl() }
}
messagesUseCase.storeMessages(messages)
convoUseCase.storeConvos(response.convos)
val itemsCountSufficient = canPaginatePage(
MessagesHistoryViewModelImpl.MESSAGES_LOAD_COUNT,
messages.size
)
val paginationExhausted = isPaginationExhaustedPage(
pageSize = MessagesHistoryViewModelImpl.MESSAGES_LOAD_COUNT,
loadedCount = messages.size,
hasExistingItems = this.messages.value.isNotEmpty()
)
screenState.setValue { old ->
old.copy(isPaginationExhausted = paginationExhausted)
}
this.messages.emit(fullMessages)
syncUiMessages()
canPaginate.setValue { itemsCountSufficient }
}
)
val flags = loadingFlags(offset, state.isLoading())
screenState.setValue { old ->
old.copy(
isLoading = flags.isLoading,
isPaginating = flags.isPaginating
)
}
}
}
private fun handleError(error: State.Error) {
VkUtils.parseError(error)?.let { newBaseError ->
baseError.setValue { newBaseError }
}
}
private fun List<VkMessage>.sorted(): List<VkMessage> {
return sortedWith { m1, m2 ->
val dateDiff = m2.date - m1.date
if (dateDiff != 0) {
dateDiff
} else {
val idDiff = m2.id - m1.id
idDiff.toInt()
}
}
}
}
@@ -2,35 +2,23 @@ package dev.meloda.fast.messageshistory
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.conena.nanokt.collections.indexOfFirstOrNull
import dev.meloda.fast.common.VkConstants
import dev.meloda.fast.common.extensions.getParcelableCompat
import dev.meloda.fast.common.extensions.listenValue
import dev.meloda.fast.common.extensions.orDots
import dev.meloda.fast.common.extensions.setValue
import dev.meloda.fast.common.provider.ResourceProvider
import dev.meloda.fast.common.paging.canPaginate as canPaginatePage
import dev.meloda.fast.common.paging.isPaginationExhausted as isPaginationExhaustedPage
import dev.meloda.fast.common.paging.loadingFlags
import dev.meloda.fast.common.paging.mergePage
import dev.meloda.fast.data.State
import dev.meloda.fast.data.VkUtils
import dev.meloda.fast.data.VkMemoryCache
import dev.meloda.fast.data.processState
import dev.meloda.fast.datastore.AppSettings
import dev.meloda.fast.domain.ConvoUseCase
import dev.meloda.fast.domain.GetMessageReadPeersUseCase
import dev.meloda.fast.domain.LoadConvosByIdUseCase
import dev.meloda.fast.domain.LongPollUpdatesParser
import dev.meloda.fast.domain.MessagesUseCase
import dev.meloda.fast.domain.util.extractAvatar
import dev.meloda.fast.domain.util.extractTitle
import dev.meloda.fast.messageshistory.model.ActionMode
import dev.meloda.fast.messageshistory.model.MessageDialog
import dev.meloda.fast.messageshistory.model.MessageNavigation
@@ -52,7 +40,6 @@ class MessagesHistoryViewModelImpl(
private val messagesUseCase: MessagesUseCase,
private val convoUseCase: ConvoUseCase,
private val resourceProvider: ResourceProvider,
private val loadConvosByIdUseCase: LoadConvosByIdUseCase,
private val getMessageReadPeersUseCase: GetMessageReadPeersUseCase,
updatesParser: LongPollUpdatesParser,
savedStateHandle: SavedStateHandle
@@ -91,6 +78,20 @@ class MessagesHistoryViewModelImpl(
onPinnedMessageChanged = ::handlePinnedMessage
)
private val loaders = MessagesHistoryLoaders(
convoUseCase = convoUseCase,
messagesUseCase = messagesUseCase,
resourceProvider = resourceProvider,
screenState = screenState,
messages = messages,
imagesToPreload = imagesToPreload,
canPaginate = canPaginate,
baseError = baseError,
scope = viewModelScope,
syncUiMessages = ::syncUiMessages,
onPinnedMessage = ::handlePinnedMessage
)
private val longPollEventHandler = MessagesHistoryLongPollEventHandler(
screenState = screenState,
messages = messages
@@ -103,8 +104,8 @@ class MessagesHistoryViewModelImpl(
screenState.setValue { old -> old.copy(convoId = arguments.convoId) }
loadConvo()
loadMessagesHistory()
loaders.loadConvo()
loaders.loadMessagesHistory(currentOffset.value)
updatesParser.onNewMessage(longPollEventHandler::onNewMessage)
updatesParser.onMessageEdited(longPollEventHandler::onMessageEdited)
@@ -311,7 +312,7 @@ class MessagesHistoryViewModelImpl(
}
override fun onRefresh() {
loadMessagesHistory(offset = 0)
loaders.loadMessagesHistory(offset = 0)
}
override fun onAttachmentButtonClicked() {
@@ -370,7 +371,7 @@ class MessagesHistoryViewModelImpl(
override fun onPaginationConditionsMet() {
currentOffset.update { messages.value.size }
loadMessagesHistory()
loaders.loadMessagesHistory(currentOffset.value)
}
override fun onMessageClicked(messageId: Long) {
@@ -496,38 +497,6 @@ class MessagesHistoryViewModelImpl(
}
}
private fun loadConvo() {
Log.d("MessagesHistoryViewModelImpl", "loadConvo()")
loadConvosByIdUseCase(
peerIds = listOf(screenState.value.convoId),
extended = true,
fields = VkConstants.ALL_FIELDS
).listenValue(viewModelScope) { state ->
state.processState(
error = ::handleError,
success = { response ->
val convo = response.firstOrNull() ?: return@listenValue
val title = convo.extractTitle(
useContactName = AppSettings.General.useContactNames,
resources = resourceProvider.resources
)
val avatar = convo.extractAvatar()
screenState.setValue { old ->
old.copy(
convo = convo,
title = title,
avatar = avatar
)
}
convo.pinnedMessage?.let(::handlePinnedMessage)
}
)
}
}
private fun handlePinnedMessage(pinnedMessage: VkMessage?) {
if (pinnedMessage == null) {
screenState.setValue { old ->
@@ -565,73 +534,6 @@ class MessagesHistoryViewModelImpl(
}
}
private fun loadMessagesHistory(offset: Int = currentOffset.value) {
Log.d("MessagesHistoryViewModel", "loadMessagesHistory: $offset")
messagesUseCase.getMessagesHistory(
convoId = screenState.value.convoId,
count = MESSAGES_LOAD_COUNT,
offset = offset,
).listenValue(viewModelScope) { state ->
state.processState(
error = ::handleError,
success = { response ->
val messages = response.messages
val fullMessages = mergePage(this.messages.value, messages, offset).sorted()
val convos = response.convos
imagesToPreload.setValue {
messages.mapNotNull { it.extractAvatar().extractUrl() }
}
messagesUseCase.storeMessages(messages)
convoUseCase.storeConvos(convos)
val itemsCountSufficient = canPaginatePage(MESSAGES_LOAD_COUNT, messages.size)
val paginationExhausted = isPaginationExhaustedPage(
pageSize = MESSAGES_LOAD_COUNT,
loadedCount = messages.size,
hasExistingItems = this.messages.value.isNotEmpty()
)
screenState.setValue { old ->
old.copy(isPaginationExhausted = paginationExhausted)
}
this.messages.emit(fullMessages)
syncUiMessages()
canPaginate.setValue { itemsCountSufficient }
}
)
val flags = loadingFlags(offset, state.isLoading())
screenState.setValue { old ->
old.copy(
isLoading = flags.isLoading,
isPaginating = flags.isPaginating
)
}
}
}
private fun handleError(error: State.Error) {
VkUtils.parseError(error)?.let { newBaseError ->
baseError.setValue { newBaseError }
}
}
private fun List<VkMessage>.sorted(): List<VkMessage> {
return sortedWith { m1, m2 ->
val dateDiff = m2.date - m1.date
if (dateDiff != 0) {
dateDiff
} else {
val idDiff = m2.id - m1.id
idDiff.toInt()
}
}
}
private fun syncUiMessages(): List<MessageUiItem> {
val newUiMessages = buildMessagesHistoryUiMessages(
messages = messages.value,