refactor: extract message loading
This commit is contained in:
+140
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-117
@@ -2,35 +2,23 @@ package dev.meloda.fast.messageshistory
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
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.TextRange
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.conena.nanokt.collections.indexOfFirstOrNull
|
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.getParcelableCompat
|
||||||
import dev.meloda.fast.common.extensions.listenValue
|
import dev.meloda.fast.common.extensions.listenValue
|
||||||
import dev.meloda.fast.common.extensions.orDots
|
import dev.meloda.fast.common.extensions.orDots
|
||||||
import dev.meloda.fast.common.extensions.setValue
|
import dev.meloda.fast.common.extensions.setValue
|
||||||
import dev.meloda.fast.common.provider.ResourceProvider
|
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.VkMemoryCache
|
||||||
import dev.meloda.fast.data.processState
|
|
||||||
import dev.meloda.fast.datastore.AppSettings
|
|
||||||
import dev.meloda.fast.domain.ConvoUseCase
|
import dev.meloda.fast.domain.ConvoUseCase
|
||||||
import dev.meloda.fast.domain.GetMessageReadPeersUseCase
|
import dev.meloda.fast.domain.GetMessageReadPeersUseCase
|
||||||
import dev.meloda.fast.domain.LoadConvosByIdUseCase
|
|
||||||
import dev.meloda.fast.domain.LongPollUpdatesParser
|
import dev.meloda.fast.domain.LongPollUpdatesParser
|
||||||
import dev.meloda.fast.domain.MessagesUseCase
|
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.ActionMode
|
||||||
import dev.meloda.fast.messageshistory.model.MessageDialog
|
import dev.meloda.fast.messageshistory.model.MessageDialog
|
||||||
import dev.meloda.fast.messageshistory.model.MessageNavigation
|
import dev.meloda.fast.messageshistory.model.MessageNavigation
|
||||||
@@ -52,7 +40,6 @@ class MessagesHistoryViewModelImpl(
|
|||||||
private val messagesUseCase: MessagesUseCase,
|
private val messagesUseCase: MessagesUseCase,
|
||||||
private val convoUseCase: ConvoUseCase,
|
private val convoUseCase: ConvoUseCase,
|
||||||
private val resourceProvider: ResourceProvider,
|
private val resourceProvider: ResourceProvider,
|
||||||
private val loadConvosByIdUseCase: LoadConvosByIdUseCase,
|
|
||||||
private val getMessageReadPeersUseCase: GetMessageReadPeersUseCase,
|
private val getMessageReadPeersUseCase: GetMessageReadPeersUseCase,
|
||||||
updatesParser: LongPollUpdatesParser,
|
updatesParser: LongPollUpdatesParser,
|
||||||
savedStateHandle: SavedStateHandle
|
savedStateHandle: SavedStateHandle
|
||||||
@@ -91,6 +78,20 @@ class MessagesHistoryViewModelImpl(
|
|||||||
onPinnedMessageChanged = ::handlePinnedMessage
|
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(
|
private val longPollEventHandler = MessagesHistoryLongPollEventHandler(
|
||||||
screenState = screenState,
|
screenState = screenState,
|
||||||
messages = messages
|
messages = messages
|
||||||
@@ -103,8 +104,8 @@ class MessagesHistoryViewModelImpl(
|
|||||||
|
|
||||||
screenState.setValue { old -> old.copy(convoId = arguments.convoId) }
|
screenState.setValue { old -> old.copy(convoId = arguments.convoId) }
|
||||||
|
|
||||||
loadConvo()
|
loaders.loadConvo()
|
||||||
loadMessagesHistory()
|
loaders.loadMessagesHistory(currentOffset.value)
|
||||||
|
|
||||||
updatesParser.onNewMessage(longPollEventHandler::onNewMessage)
|
updatesParser.onNewMessage(longPollEventHandler::onNewMessage)
|
||||||
updatesParser.onMessageEdited(longPollEventHandler::onMessageEdited)
|
updatesParser.onMessageEdited(longPollEventHandler::onMessageEdited)
|
||||||
@@ -311,7 +312,7 @@ class MessagesHistoryViewModelImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onRefresh() {
|
override fun onRefresh() {
|
||||||
loadMessagesHistory(offset = 0)
|
loaders.loadMessagesHistory(offset = 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttachmentButtonClicked() {
|
override fun onAttachmentButtonClicked() {
|
||||||
@@ -370,7 +371,7 @@ class MessagesHistoryViewModelImpl(
|
|||||||
|
|
||||||
override fun onPaginationConditionsMet() {
|
override fun onPaginationConditionsMet() {
|
||||||
currentOffset.update { messages.value.size }
|
currentOffset.update { messages.value.size }
|
||||||
loadMessagesHistory()
|
loaders.loadMessagesHistory(currentOffset.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMessageClicked(messageId: Long) {
|
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?) {
|
private fun handlePinnedMessage(pinnedMessage: VkMessage?) {
|
||||||
if (pinnedMessage == null) {
|
if (pinnedMessage == null) {
|
||||||
screenState.setValue { old ->
|
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> {
|
private fun syncUiMessages(): List<MessageUiItem> {
|
||||||
val newUiMessages = buildMessagesHistoryUiMessages(
|
val newUiMessages = buildMessagesHistoryUiMessages(
|
||||||
messages = messages.value,
|
messages = messages.value,
|
||||||
|
|||||||
Reference in New Issue
Block a user