diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryLoaders.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryLoaders.kt new file mode 100644 index 00000000..caba7999 --- /dev/null +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryLoaders.kt @@ -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, + private val messages: MutableStateFlow>, + private val imagesToPreload: MutableStateFlow>, + private val canPaginate: MutableStateFlow, + private val baseError: MutableStateFlow, + 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.sorted(): List { + return sortedWith { m1, m2 -> + val dateDiff = m2.date - m1.date + if (dateDiff != 0) { + dateDiff + } else { + val idDiff = m2.id - m1.id + idDiff.toInt() + } + } + } +} diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModelImpl.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModelImpl.kt index 48fe91e7..3ff39b8b 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModelImpl.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModelImpl.kt @@ -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.sorted(): List { - 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 { val newUiMessages = buildMessagesHistoryUiMessages( messages = messages.value,