forked from melod1n/fast-messenger
refactor: unify db refresh flows
This commit is contained in:
@@ -8,11 +8,11 @@ import androidx.lifecycle.viewModelScope
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import com.conena.nanokt.collections.indexOfFirstOrNull
|
||||
import dev.meloda.fast.common.VkConstants
|
||||
import dev.meloda.fast.common.extensions.createTimerFlow
|
||||
import dev.meloda.fast.common.extensions.findWithIndex
|
||||
import dev.meloda.fast.common.extensions.listenValue
|
||||
import dev.meloda.fast.common.extensions.setValue
|
||||
import dev.meloda.fast.common.extensions.launchDbRefresh
|
||||
import dev.meloda.fast.common.extensions.updateValue
|
||||
import dev.meloda.fast.common.paging.canPaginate as canPaginatePage
|
||||
import dev.meloda.fast.common.paging.isPaginationExhausted as isPaginationExhaustedPage
|
||||
@@ -27,8 +27,7 @@ import dev.meloda.fast.data.VkUtils
|
||||
import dev.meloda.fast.data.processState
|
||||
import dev.meloda.fast.datastore.UserSettings
|
||||
import dev.meloda.fast.domain.ConvoUseCase
|
||||
import dev.meloda.fast.domain.LoadConvosByIdUseCase
|
||||
import dev.meloda.fast.domain.LongPollUpdatesParser
|
||||
import dev.meloda.fast.domain.LongPollUpdatesReducer
|
||||
import dev.meloda.fast.domain.MessagesUseCase
|
||||
import dev.meloda.fast.domain.util.asPresentation
|
||||
import dev.meloda.fast.domain.util.extractAvatar
|
||||
@@ -44,20 +43,21 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ConvosViewModel(
|
||||
updatesParser: LongPollUpdatesParser,
|
||||
updatesReducer: LongPollUpdatesReducer,
|
||||
private val filter: ConvosFilter,
|
||||
private val convoUseCase: ConvoUseCase,
|
||||
private val messagesUseCase: MessagesUseCase,
|
||||
private val resources: Resources,
|
||||
private val userSettings: UserSettings,
|
||||
private val imageLoader: ImageLoader,
|
||||
private val applicationContext: Context,
|
||||
private val loadConvosByIdUseCase: LoadConvosByIdUseCase
|
||||
private val applicationContext: Context
|
||||
) : ViewModel() {
|
||||
private val _screenState = MutableStateFlow(ConvosScreenState.EMPTY)
|
||||
val screenState = _screenState.asStateFlow()
|
||||
@@ -98,15 +98,15 @@ class ConvosViewModel(
|
||||
|
||||
loadConvos()
|
||||
|
||||
updatesParser.onNewMessage(::handleNewMessage)
|
||||
updatesParser.onMessageEdited(::handleEditedMessage)
|
||||
updatesParser.onMessageIncomingRead(::handleReadIncomingMessage)
|
||||
updatesParser.onMessageOutgoingRead(::handleReadOutgoingMessage)
|
||||
updatesParser.onInteractions(::handleInteraction)
|
||||
updatesParser.onChatMajorChanged(::handleChatMajorChanged)
|
||||
updatesParser.onChatMinorChanged(::handleChatMinorChanged)
|
||||
updatesParser.onChatCleared(::handleChatClearing)
|
||||
updatesParser.onChatArchived(::handleChatArchived)
|
||||
updatesReducer.newMessages.onEach(::handleNewMessage).launchIn(viewModelScope)
|
||||
updatesReducer.messageEdited.onEach(::handleEditedMessage).launchIn(viewModelScope)
|
||||
updatesReducer.messageIncomingRead.onEach(::handleReadIncomingMessage).launchIn(viewModelScope)
|
||||
updatesReducer.messageOutgoingRead.onEach(::handleReadOutgoingMessage).launchIn(viewModelScope)
|
||||
updatesReducer.interactions.onEach(::handleInteraction).launchIn(viewModelScope)
|
||||
updatesReducer.chatMajorChanged.onEach(::handleChatMajorChanged).launchIn(viewModelScope)
|
||||
updatesReducer.chatMinorChanged.onEach(::handleChatMinorChanged).launchIn(viewModelScope)
|
||||
updatesReducer.chatCleared.onEach(::handleChatClearing).launchIn(viewModelScope)
|
||||
updatesReducer.chatArchived.onEach(::handleChatArchived).launchIn(viewModelScope)
|
||||
|
||||
userSettings.useContactNames.listenValue(viewModelScope) {
|
||||
syncUiConvos()
|
||||
@@ -257,6 +257,10 @@ class ConvosViewModel(
|
||||
private fun loadConvos(
|
||||
offset: Int = currentOffset.value
|
||||
) {
|
||||
if (offset == 0) {
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
|
||||
convoUseCase.getConvos(
|
||||
count = LOAD_COUNT,
|
||||
offset = offset,
|
||||
@@ -313,14 +317,10 @@ class ConvosViewModel(
|
||||
state.processState(
|
||||
error = {},
|
||||
success = {
|
||||
val newConvos = convos.value.toMutableList()
|
||||
val convoIndex =
|
||||
newConvos.indexOfFirstOrNull { it.id == peerId }
|
||||
?: return@processState
|
||||
|
||||
newConvos.removeAt(convoIndex)
|
||||
_convos.update { newConvos.sorted() }
|
||||
syncUiConvos()
|
||||
viewModelScope.launch {
|
||||
convoUseCase.deleteLocalConvo(peerId)
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
}
|
||||
)
|
||||
_screenState.emit(screenState.value.copy(isLoading = state.isLoading()))
|
||||
@@ -333,16 +333,22 @@ class ConvosViewModel(
|
||||
state.processState(
|
||||
error = {},
|
||||
success = {
|
||||
handleChatMajorChanged(
|
||||
LongPollParsedEvent.ChatMajorChanged(
|
||||
peerId = peerId,
|
||||
majorId = if (pin) {
|
||||
pinnedConvosCount.value.plus(1) * 16
|
||||
} else {
|
||||
0
|
||||
}
|
||||
)
|
||||
)
|
||||
viewModelScope.launch {
|
||||
convoUseCase.getLocalConvoById(peerId)?.let { convo ->
|
||||
convoUseCase.storeConvos(
|
||||
listOf(
|
||||
convo.copy(
|
||||
majorId = if (pin) {
|
||||
pinnedConvosCount.value.plus(1) * 16
|
||||
} else {
|
||||
0
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -356,145 +362,35 @@ class ConvosViewModel(
|
||||
state.processState(
|
||||
error = {},
|
||||
success = {
|
||||
convos.value.find { it.id == peerId }?.let { convo ->
|
||||
handleChatArchived(
|
||||
LongPollParsedEvent.ChatArchived(
|
||||
convo = convo,
|
||||
archived = archive
|
||||
viewModelScope.launch {
|
||||
convoUseCase.getLocalConvoById(peerId)?.let { convo ->
|
||||
convoUseCase.storeConvos(
|
||||
listOf(
|
||||
convo.copy(isArchived = archive)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 03-Apr-25, Danil Nikolaev: handle business messages
|
||||
private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) {
|
||||
val message = event.message
|
||||
|
||||
val newConvos = convos.value.toMutableList()
|
||||
val convoIndex =
|
||||
newConvos.indexOfFirstOrNull { it.id == message.peerId }
|
||||
|
||||
if (convoIndex == null) {
|
||||
if (event.inArchive != (filter == ConvosFilter.ARCHIVE)) return
|
||||
|
||||
loadConvosByIdUseCase(
|
||||
peerIds = listOf(message.peerId),
|
||||
extended = true,
|
||||
fields = VkConstants.ALL_FIELDS
|
||||
).listenValue(viewModelScope) { state ->
|
||||
state.processState(
|
||||
error = {},
|
||||
success = { response ->
|
||||
val convo = (response.firstOrNull() ?: return@listenValue)
|
||||
.copy(lastMessage = message)
|
||||
|
||||
newConvos.add(pinnedConvosCount.value, convo)
|
||||
_convos.update { newConvos.sorted() }
|
||||
syncUiConvos()
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val convo = newConvos[convoIndex]
|
||||
var newConvo = convo.copy(
|
||||
lastMessage = message,
|
||||
lastMessageId = message.id,
|
||||
lastCmId = message.cmId,
|
||||
unreadCount = if (message.isOut) convo.unreadCount
|
||||
else convo.unreadCount + 1
|
||||
)
|
||||
|
||||
interactionsTimers[convo.id]?.let { job ->
|
||||
if (job.interactionType == InteractionType.Typing
|
||||
&& message.fromId in convo.interactionIds
|
||||
) {
|
||||
val newInteractionIds = newConvo.interactionIds.filter { id ->
|
||||
id != message.fromId
|
||||
}
|
||||
|
||||
newConvo = newConvo.copy(
|
||||
interactionType = if (newInteractionIds.isEmpty()) -1 else {
|
||||
newConvo.interactionType
|
||||
},
|
||||
interactionIds = newInteractionIds
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (convo.isPinned()) {
|
||||
newConvos[convoIndex] = newConvo
|
||||
} else {
|
||||
newConvos.removeAt(convoIndex)
|
||||
|
||||
val toPosition = pinnedConvosCount.value
|
||||
newConvos.add(toPosition, newConvo)
|
||||
}
|
||||
|
||||
_convos.update { newConvos.sorted() }
|
||||
syncUiConvos()
|
||||
}
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
|
||||
private fun handleEditedMessage(event: LongPollParsedEvent.MessageEdited) {
|
||||
val message = event.message
|
||||
val newConvos = convos.value.toMutableList()
|
||||
|
||||
val convoIndex = newConvos.indexOfFirstOrNull { it.id == message.peerId }
|
||||
if (convoIndex == null) { // диалога нет в списке
|
||||
// pizdets
|
||||
} else {
|
||||
val convo = newConvos[convoIndex]
|
||||
newConvos[convoIndex] = convo.copy(
|
||||
lastMessage = message,
|
||||
lastMessageId = message.id,
|
||||
lastCmId = message.cmId
|
||||
)
|
||||
_convos.update { newConvos }
|
||||
syncUiConvos()
|
||||
}
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
|
||||
private fun handleReadIncomingMessage(event: LongPollParsedEvent.IncomingMessageRead) {
|
||||
val newConvos = convos.value.toMutableList()
|
||||
|
||||
val convoIndex =
|
||||
newConvos.indexOfFirstOrNull { it.id == event.peerId }
|
||||
|
||||
if (convoIndex == null) { // диалога нет в списке
|
||||
// pizdets
|
||||
} else {
|
||||
newConvos[convoIndex] =
|
||||
newConvos[convoIndex].copy(
|
||||
inReadCmId = event.cmId,
|
||||
unreadCount = event.unreadCount
|
||||
)
|
||||
|
||||
_convos.update { newConvos }
|
||||
syncUiConvos()
|
||||
}
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
|
||||
private fun handleReadOutgoingMessage(event: LongPollParsedEvent.OutgoingMessageRead) {
|
||||
val newConvos = convos.value.toMutableList()
|
||||
|
||||
val convoIndex =
|
||||
newConvos.indexOfFirstOrNull { it.id == event.peerId }
|
||||
|
||||
if (convoIndex == null) { // диалога нет в списке
|
||||
// pizdets
|
||||
} else {
|
||||
newConvos[convoIndex] =
|
||||
newConvos[convoIndex].copy(
|
||||
outReadCmId = event.cmId,
|
||||
unreadCount = event.unreadCount
|
||||
)
|
||||
|
||||
_convos.update { newConvos }
|
||||
syncUiConvos()
|
||||
}
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
|
||||
private fun handleInteraction(event: LongPollParsedEvent.Interaction) {
|
||||
@@ -563,88 +459,19 @@ class ConvosViewModel(
|
||||
}
|
||||
|
||||
private fun handleChatMajorChanged(event: LongPollParsedEvent.ChatMajorChanged) {
|
||||
val newConvos = convos.value.toMutableList()
|
||||
val convoIndex =
|
||||
newConvos.indexOfFirstOrNull { it.id == event.peerId }
|
||||
|
||||
if (convoIndex == null) { // диалога нет в списке
|
||||
// pizdets
|
||||
} else {
|
||||
newConvos[convoIndex] =
|
||||
newConvos[convoIndex].copy(majorId = event.majorId)
|
||||
|
||||
_convos.setValue { newConvos.sorted() }
|
||||
syncUiConvos()
|
||||
}
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
|
||||
private fun handleChatMinorChanged(event: LongPollParsedEvent.ChatMinorChanged) {
|
||||
val newConvos = convos.value.toMutableList()
|
||||
val convoIndex =
|
||||
newConvos.indexOfFirstOrNull { it.id == event.peerId }
|
||||
|
||||
if (convoIndex == null) { // диалога нет в списке
|
||||
// pizdets
|
||||
} else {
|
||||
newConvos[convoIndex] =
|
||||
newConvos[convoIndex].copy(minorId = event.minorId)
|
||||
|
||||
_convos.setValue { newConvos.sorted() }
|
||||
syncUiConvos()
|
||||
}
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
|
||||
private fun handleChatClearing(event: LongPollParsedEvent.ChatCleared) {
|
||||
val newConvos = convos.value.toMutableList()
|
||||
|
||||
val convoIndex = newConvos.indexOfFirstOrNull { it.id == event.peerId }
|
||||
|
||||
if (convoIndex == null) { // диалога нет в списке
|
||||
// pizdets
|
||||
} else {
|
||||
newConvos.removeAt(convoIndex)
|
||||
|
||||
_convos.setValue { newConvos.sorted() }
|
||||
syncUiConvos()
|
||||
}
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
|
||||
private fun handleChatArchived(event: LongPollParsedEvent.ChatArchived) {
|
||||
val convo = event.convo
|
||||
|
||||
val newConvos = convos.value.toMutableList()
|
||||
|
||||
when (filter) {
|
||||
ConvosFilter.BUSINESS_NOTIFY -> Unit
|
||||
|
||||
ConvosFilter.ARCHIVE -> {
|
||||
if (event.archived) {
|
||||
newConvos.add(0, convo)
|
||||
} else {
|
||||
val index = newConvos.indexOfFirstOrNull { it.id == convo.id }
|
||||
if (index == null) return
|
||||
|
||||
newConvos.removeAt(index)
|
||||
}
|
||||
|
||||
_convos.update { newConvos }
|
||||
syncUiConvos()
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (event.archived) {
|
||||
val index = newConvos.indexOfFirstOrNull { it.id == convo.id }
|
||||
if (index == null) return
|
||||
|
||||
newConvos.removeAt(index)
|
||||
} else {
|
||||
newConvos.add(pinnedConvosCount.value, convo)
|
||||
}
|
||||
|
||||
_convos.update { newConvos.sorted() }
|
||||
syncUiConvos()
|
||||
}
|
||||
}
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
|
||||
private fun readConvo(peerId: Long, startMessageId: Long) {
|
||||
@@ -655,21 +482,42 @@ class ConvosViewModel(
|
||||
state.processState(
|
||||
error = {},
|
||||
success = {
|
||||
val newConvos = convos.value.toMutableList()
|
||||
val convoIndex =
|
||||
newConvos.indexOfFirstOrNull { it.id == peerId }
|
||||
?: return@listenValue
|
||||
|
||||
newConvos[convoIndex] =
|
||||
newConvos[convoIndex].copy(inRead = startMessageId)
|
||||
|
||||
_convos.update { newConvos }
|
||||
syncUiConvos()
|
||||
viewModelScope.launch {
|
||||
convoUseCase.getLocalConvoById(peerId)?.let { convo ->
|
||||
convoUseCase.storeConvos(
|
||||
listOf(
|
||||
convo.copy(
|
||||
inRead = startMessageId,
|
||||
unreadCount = 0
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
refreshConvosFromDb()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshConvosFromDb() {
|
||||
viewModelScope.launchDbRefresh(
|
||||
load = {
|
||||
val localConvos = convoUseCase.getLocalConvos()
|
||||
|
||||
val filteredConvos = when (filter) {
|
||||
ConvosFilter.ARCHIVE -> localConvos.filter(VkConvo::isArchived)
|
||||
ConvosFilter.UNREAD -> localConvos.filter { !it.isArchived && it.unreadCount > 0 }
|
||||
ConvosFilter.ALL -> localConvos.filterNot(VkConvo::isArchived)
|
||||
ConvosFilter.BUSINESS_NOTIFY -> localConvos
|
||||
}
|
||||
|
||||
_convos.emit(filteredConvos)
|
||||
},
|
||||
after = ::syncUiConvos
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<VkConvo>.sorted(): List<VkConvo> {
|
||||
val newConvos = toMutableList()
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ private fun Scope.createConvosViewModel(filter: ConvosFilter): ConvosViewModel {
|
||||
resources = get(),
|
||||
userSettings = get(),
|
||||
imageLoader = get(),
|
||||
applicationContext = get(),
|
||||
loadConvosByIdUseCase = get()
|
||||
applicationContext = get()
|
||||
)
|
||||
}
|
||||
|
||||
+40
@@ -3,6 +3,7 @@ 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.launchDbRefresh
|
||||
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
|
||||
@@ -39,6 +40,29 @@ internal class MessagesHistoryLoaders(
|
||||
fun loadConvo() {
|
||||
Log.d("MessagesHistoryViewModelImpl", "loadConvo()")
|
||||
|
||||
scope.launchDbRefresh(
|
||||
load = {
|
||||
convoUseCase.getLocalConvoById(screenState.value.convoId)?.let { convo ->
|
||||
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)
|
||||
}
|
||||
},
|
||||
after = {}
|
||||
)
|
||||
|
||||
convoUseCase.getById(
|
||||
peerIds = listOf(screenState.value.convoId),
|
||||
extended = true,
|
||||
@@ -71,6 +95,22 @@ internal class MessagesHistoryLoaders(
|
||||
fun loadMessagesHistory(offset: Int) {
|
||||
Log.d("MessagesHistoryViewModel", "loadMessagesHistory: $offset")
|
||||
|
||||
if (offset == 0) {
|
||||
scope.launchDbRefresh(
|
||||
load = {
|
||||
val cachedMessages = messagesUseCase.getLocalMessages(screenState.value.convoId)
|
||||
if (cachedMessages.isNotEmpty()) {
|
||||
messages.emit(cachedMessages.sorted())
|
||||
}
|
||||
},
|
||||
after = {
|
||||
if (messages.value.isNotEmpty()) {
|
||||
syncUiMessages()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
messagesUseCase.getMessagesHistory(
|
||||
convoId = screenState.value.convoId,
|
||||
count = MessagesHistoryViewModelImpl.MESSAGES_LOAD_COUNT,
|
||||
|
||||
+35
-94
@@ -1,18 +1,24 @@
|
||||
package dev.meloda.fast.messageshistory
|
||||
|
||||
import android.util.Log
|
||||
import com.conena.nanokt.collections.indexOfFirstOrNull
|
||||
import dev.meloda.fast.common.extensions.launchDbRefresh
|
||||
import dev.meloda.fast.common.extensions.setValue
|
||||
import dev.meloda.fast.domain.ConvoUseCase
|
||||
import dev.meloda.fast.domain.MessagesUseCase
|
||||
import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState
|
||||
import dev.meloda.fast.model.LongPollParsedEvent
|
||||
import dev.meloda.fast.model.api.domain.VkMessage
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlin.math.abs
|
||||
|
||||
internal class MessagesHistoryLongPollEventHandler(
|
||||
private val scope: CoroutineScope,
|
||||
private val convoUseCase: ConvoUseCase,
|
||||
private val messagesUseCase: MessagesUseCase,
|
||||
private val screenState: MutableStateFlow<MessagesHistoryScreenState>,
|
||||
private val messages: MutableStateFlow<List<VkMessage>>,
|
||||
private val syncUiMessages: () -> Unit
|
||||
private val syncUiMessages: () -> Unit,
|
||||
private val onPinnedMessageChanged: (VkMessage?) -> Unit
|
||||
) {
|
||||
fun onNewMessage(event: LongPollParsedEvent.NewMessage) {
|
||||
val message = event.message
|
||||
@@ -20,139 +26,74 @@ internal class MessagesHistoryLongPollEventHandler(
|
||||
Log.d("MessagesHistoryViewModel", "handleNewMessage: $message")
|
||||
|
||||
if (message.peerId != screenState.value.convoId) return
|
||||
if (messages.value.indexOfFirstOrNull { it.id == message.id } != null) return
|
||||
|
||||
val randomIds = messages.value.map(VkMessage::randomId)
|
||||
if (message.randomId != 0L && message.randomId in randomIds) return
|
||||
|
||||
val newMessages = messages.value.toMutableList()
|
||||
newMessages.add(0, message)
|
||||
|
||||
messages.setValue { newMessages }
|
||||
syncUiMessages()
|
||||
refreshFromDb(refreshMessages = true)
|
||||
}
|
||||
|
||||
fun onMessageEdited(event: LongPollParsedEvent.MessageEdited) {
|
||||
val message = event.message
|
||||
if (message.peerId != screenState.value.convoId) return
|
||||
|
||||
val newMessages = messages.value.toMutableList()
|
||||
val index = newMessages.indexOfFirstOrNull { it.id == message.id }
|
||||
if (index == null) {
|
||||
return
|
||||
}
|
||||
|
||||
newMessages[index] = message
|
||||
messages.setValue { newMessages }
|
||||
syncUiMessages()
|
||||
refreshFromDb(refreshMessages = true)
|
||||
}
|
||||
|
||||
fun onReadIncoming(event: LongPollParsedEvent.IncomingMessageRead) {
|
||||
if (event.peerId != screenState.value.convoId) return
|
||||
|
||||
val index = messages.value.indexOfFirstOrNull { it.cmId == event.cmId }
|
||||
if (index == null) return
|
||||
|
||||
val newConvo = screenState.value.convo.copy(
|
||||
inReadCmId = event.cmId
|
||||
)
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(convo = newConvo)
|
||||
}
|
||||
|
||||
syncUiMessages()
|
||||
refreshFromDb(refreshMessages = false)
|
||||
}
|
||||
|
||||
fun onReadOutgoing(event: LongPollParsedEvent.OutgoingMessageRead) {
|
||||
if (event.peerId != screenState.value.convoId) return
|
||||
|
||||
val index = messages.value.indexOfFirstOrNull { it.cmId == event.cmId }
|
||||
if (index == null) return
|
||||
|
||||
val newConvo = screenState.value.convo.copy(
|
||||
outReadCmId = event.cmId
|
||||
)
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(convo = newConvo)
|
||||
}
|
||||
|
||||
syncUiMessages()
|
||||
refreshFromDb(refreshMessages = false)
|
||||
}
|
||||
|
||||
fun onMessageDeleted(event: LongPollParsedEvent.MessageDeleted) {
|
||||
if (event.peerId != screenState.value.convoId) return
|
||||
|
||||
val newMessages = messages.value.toMutableList()
|
||||
val index = newMessages.indexOfFirstOrNull { it.cmId == event.cmId }
|
||||
if (index == null) return
|
||||
|
||||
newMessages.removeAt(index)
|
||||
messages.setValue { newMessages }
|
||||
syncUiMessages()
|
||||
refreshFromDb(refreshMessages = true)
|
||||
}
|
||||
|
||||
fun onMessageRestored(event: LongPollParsedEvent.MessageRestored) {
|
||||
if (event.message.peerId != screenState.value.convoId) return
|
||||
|
||||
val newMessages = messages.value.toMutableList()
|
||||
val minDate = newMessages.minOf(VkMessage::date)
|
||||
|
||||
if (event.message.date < minDate) return
|
||||
|
||||
newMessages.add(event.message)
|
||||
messages.setValue { newMessages.sorted() }
|
||||
syncUiMessages()
|
||||
refreshFromDb(refreshMessages = true)
|
||||
}
|
||||
|
||||
fun onMessageMarkedAsImportant(event: LongPollParsedEvent.MessageMarkedAsImportant) {
|
||||
if (event.peerId != screenState.value.convoId) return
|
||||
|
||||
val newMessages = messages.value.toMutableList()
|
||||
val index = newMessages.indexOfFirstOrNull { it.cmId == event.cmId }
|
||||
if (index == null) return
|
||||
|
||||
newMessages[index] = newMessages[index].copy(isImportant = event.marked)
|
||||
messages.setValue { newMessages }
|
||||
syncUiMessages()
|
||||
refreshFromDb(refreshMessages = true)
|
||||
}
|
||||
|
||||
fun onMessageMarkedAsSpam(event: LongPollParsedEvent.MessageMarkedAsSpam) {
|
||||
if (event.peerId != screenState.value.convoId) return
|
||||
|
||||
val newMessages = messages.value.toMutableList()
|
||||
val index = newMessages.indexOfFirstOrNull { it.cmId == event.cmId }
|
||||
if (index == null) return
|
||||
|
||||
newMessages.removeAt(index)
|
||||
messages.setValue { newMessages }
|
||||
syncUiMessages()
|
||||
refreshFromDb(refreshMessages = true)
|
||||
}
|
||||
|
||||
fun onMessageMarkedAsNotSpam(event: LongPollParsedEvent.MessageMarkedAsNotSpam) {
|
||||
if (event.message.peerId != screenState.value.convoId) return
|
||||
|
||||
val newMessages = messages.value.toMutableList()
|
||||
val maxDate = newMessages.maxOf(VkMessage::date)
|
||||
val minDate = newMessages.minOf(VkMessage::date)
|
||||
|
||||
if (event.message.date !in minDate..maxDate) return
|
||||
|
||||
newMessages.add(event.message)
|
||||
messages.setValue { newMessages.sorted() }
|
||||
syncUiMessages()
|
||||
refreshFromDb(refreshMessages = true)
|
||||
}
|
||||
|
||||
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 refreshFromDb(refreshMessages: Boolean) {
|
||||
scope.launchDbRefresh(
|
||||
load = {
|
||||
convoUseCase.getLocalConvoById(screenState.value.convoId)?.let { convo ->
|
||||
screenState.setValue { old ->
|
||||
old.copy(convo = convo)
|
||||
}
|
||||
onPinnedMessageChanged(convo.pinnedMessage)
|
||||
}
|
||||
|
||||
if (refreshMessages) {
|
||||
val localMessages = messagesUseCase.getLocalMessages(screenState.value.convoId)
|
||||
messages.setValue { localMessages }
|
||||
}
|
||||
},
|
||||
after = ::syncUiMessages
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+44
-10
@@ -10,6 +10,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import dev.meloda.fast.common.extensions.listenValue
|
||||
import dev.meloda.fast.common.extensions.launchDbRefresh
|
||||
import dev.meloda.fast.common.extensions.removeIfCompat
|
||||
import dev.meloda.fast.common.extensions.setValue
|
||||
import dev.meloda.fast.common.provider.ResourceProvider
|
||||
@@ -214,13 +215,15 @@ internal class MessagesHistoryMessageActions(
|
||||
syncUiMessages()
|
||||
},
|
||||
success = { response ->
|
||||
val newMessages = messages.value.toMutableList()
|
||||
newMessages[newMessages.indexOf(newMessage)] = newMessage.copy(
|
||||
id = response.messageId,
|
||||
cmId = response.cmId
|
||||
)
|
||||
messages.setValue { newMessages }
|
||||
syncUiMessages()
|
||||
viewModelScope.launch {
|
||||
messagesUseCase.storeMessage(
|
||||
newMessage.copy(
|
||||
id = response.messageId,
|
||||
cmId = response.cmId
|
||||
)
|
||||
)
|
||||
refreshMessagesFromDb()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -232,10 +235,9 @@ internal class MessagesHistoryMessageActions(
|
||||
}
|
||||
|
||||
fun editCurrentEditMessage() {
|
||||
replyToCmId = null
|
||||
|
||||
val newText = screenState.value.message.text
|
||||
val lastText = lastMessageText.orEmpty().trim()
|
||||
val currentReplyToCmId = replyToCmId
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
@@ -253,11 +255,33 @@ internal class MessagesHistoryMessageActions(
|
||||
syncUiMessages()
|
||||
|
||||
val newMessage = editMessage?.copy(
|
||||
replyMessage = if (replyToCmId == null) null else editMessage?.replyMessage,
|
||||
replyMessage = if (currentReplyToCmId == null) null else editMessage?.replyMessage,
|
||||
text = newText
|
||||
) ?: return
|
||||
|
||||
Log.d("MessagesHistoryViewModelImpl", "editMessage: $newMessage")
|
||||
|
||||
messagesUseCase.edit(
|
||||
peerId = screenState.value.convoId,
|
||||
cmId = newMessage.cmId,
|
||||
message = newMessage.text,
|
||||
attachments = null,
|
||||
formatData = newMessage.formatData
|
||||
).listenValue(viewModelScope) { state ->
|
||||
state.processState(
|
||||
error = { error ->
|
||||
Log.d("MessagesHistoryViewModelImpl", "editMessage: ERROR: $error")
|
||||
},
|
||||
success = {
|
||||
viewModelScope.launch {
|
||||
messagesUseCase.storeMessage(newMessage)
|
||||
refreshMessagesFromDb()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
replyToCmId = null
|
||||
}
|
||||
|
||||
private fun updateFormatting(type: FormatDataType) {
|
||||
@@ -313,4 +337,14 @@ internal class MessagesHistoryMessageActions(
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshMessagesFromDb() {
|
||||
viewModelScope.launchDbRefresh(
|
||||
load = {
|
||||
val localMessages = messagesUseCase.getLocalMessages(screenState.value.convoId)
|
||||
messages.setValue { localMessages }
|
||||
},
|
||||
after = ::syncUiMessages
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+55
-45
@@ -10,12 +10,13 @@ import androidx.core.content.FileProvider
|
||||
import androidx.core.graphics.drawable.toBitmapOrNull
|
||||
import coil.imageLoader
|
||||
import coil.request.ImageRequest
|
||||
import com.conena.nanokt.collections.indexOfFirstOrNull
|
||||
import dev.meloda.fast.common.extensions.launchDbRefresh
|
||||
import dev.meloda.fast.common.extensions.listenValue
|
||||
import dev.meloda.fast.common.extensions.setValue
|
||||
import dev.meloda.fast.data.State
|
||||
import dev.meloda.fast.data.VkUtils
|
||||
import dev.meloda.fast.data.processState
|
||||
import dev.meloda.fast.domain.ConvoUseCase
|
||||
import dev.meloda.fast.domain.MessagesUseCase
|
||||
import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState
|
||||
import dev.meloda.fast.model.BaseError
|
||||
@@ -33,6 +34,7 @@ import java.io.FileOutputStream
|
||||
internal class MessagesHistoryMessageTransportActions(
|
||||
private val applicationContext: Context,
|
||||
private val viewModelScope: CoroutineScope,
|
||||
private val convoUseCase: ConvoUseCase,
|
||||
private val messagesUseCase: MessagesUseCase,
|
||||
private val screenState: MutableStateFlow<MessagesHistoryScreenState>,
|
||||
private val messages: MutableStateFlow<List<VkMessage>>,
|
||||
@@ -49,17 +51,14 @@ internal class MessagesHistoryMessageTransportActions(
|
||||
state.processState(
|
||||
error = ::handleError,
|
||||
success = {
|
||||
val newMessages = messages.value
|
||||
.toMutableList()
|
||||
.map { message ->
|
||||
if (message.id in messageIds) {
|
||||
message.copy(isImportant = important)
|
||||
} else {
|
||||
message
|
||||
viewModelScope.launch {
|
||||
messageIds.forEach { messageId ->
|
||||
messagesUseCase.getLocalMessageById(messageId)?.let { localMessage ->
|
||||
messagesUseCase.storeMessage(localMessage.copy(isImportant = important))
|
||||
}
|
||||
}
|
||||
messages.setValue { newMessages }
|
||||
syncUiMessages()
|
||||
refreshMessagesFromDb()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -80,12 +79,17 @@ internal class MessagesHistoryMessageTransportActions(
|
||||
state.processState(
|
||||
error = ::handleError,
|
||||
success = {
|
||||
onSuccess()
|
||||
val newMessages = messages.value.toMutableList()
|
||||
val messagesToDelete = newMessages.filter { it.id in messageIds }
|
||||
newMessages.removeAll(messagesToDelete)
|
||||
messages.setValue { newMessages }
|
||||
syncUiMessages()
|
||||
viewModelScope.launch {
|
||||
onSuccess()
|
||||
val localMessageIds = mutableListOf<Long>()
|
||||
messageIds.forEach { messageId ->
|
||||
messagesUseCase.getLocalMessageById(messageId)?.let { localMessage ->
|
||||
localMessageIds += localMessage.id
|
||||
}
|
||||
}
|
||||
messagesUseCase.deleteLocalMessages(localMessageIds)
|
||||
refreshMessagesFromDb()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -100,14 +104,10 @@ internal class MessagesHistoryMessageTransportActions(
|
||||
state.processState(
|
||||
error = ::handleError,
|
||||
success = { pinnedMessage ->
|
||||
onPinnedMessageChanged(pinnedMessage)
|
||||
|
||||
val newMessages = messages.value.toMutableList()
|
||||
val index = newMessages.indexOfFirstOrNull { it.id == messageId }
|
||||
if (index != null) {
|
||||
newMessages[index] = pinnedMessage
|
||||
messages.setValue { newMessages }
|
||||
syncUiMessages()
|
||||
viewModelScope.launch {
|
||||
onPinnedMessageChanged(pinnedMessage)
|
||||
messagesUseCase.storeMessage(pinnedMessage)
|
||||
refreshMessagesFromDb()
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -118,20 +118,18 @@ internal class MessagesHistoryMessageTransportActions(
|
||||
messagesUseCase.unpin(screenState.value.convoId)
|
||||
.listenValue(viewModelScope) { state ->
|
||||
state.processState(
|
||||
error = ::handleError,
|
||||
success = {
|
||||
val newMessages = messages.value.toMutableList()
|
||||
val index = newMessages.indexOfFirstOrNull { it.id == messageId }
|
||||
if (index != null) {
|
||||
newMessages[index] = newMessages[index].copy(isPinned = false)
|
||||
messages.setValue { newMessages }
|
||||
syncUiMessages()
|
||||
error = ::handleError,
|
||||
success = {
|
||||
viewModelScope.launch {
|
||||
messagesUseCase.getLocalMessageById(messageId)?.let { localMessage ->
|
||||
messagesUseCase.storeMessage(localMessage.copy(isPinned = false))
|
||||
}
|
||||
|
||||
onPinnedMessageChanged(null)
|
||||
refreshMessagesFromDb()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun readMessage(message: VkMessage) {
|
||||
@@ -142,17 +140,19 @@ internal class MessagesHistoryMessageTransportActions(
|
||||
state.processState(
|
||||
error = ::handleError,
|
||||
success = {
|
||||
val oldConvo = screenState.value.convo
|
||||
val newConvo = oldConvo.copy(
|
||||
inRead = if (!message.isOut) message.id else oldConvo.inRead,
|
||||
outRead = if (message.isOut) message.id else oldConvo.outRead
|
||||
)
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(convo = newConvo)
|
||||
viewModelScope.launch {
|
||||
convoUseCase.getLocalConvoById(screenState.value.convoId)?.let { localConvo ->
|
||||
convoUseCase.storeConvos(
|
||||
listOf(
|
||||
localConvo.copy(
|
||||
inRead = if (!message.isOut) message.id else localConvo.inRead,
|
||||
outRead = if (message.isOut) message.id else localConvo.outRead
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
refreshMessagesFromDb()
|
||||
}
|
||||
|
||||
syncUiMessages()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -219,4 +219,14 @@ internal class MessagesHistoryMessageTransportActions(
|
||||
baseError.setValue { newBaseError }
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshMessagesFromDb() {
|
||||
viewModelScope.launchDbRefresh(
|
||||
load = {
|
||||
val localMessages = messagesUseCase.getLocalMessages(screenState.value.convoId)
|
||||
messages.setValue { localMessages }
|
||||
},
|
||||
after = ::syncUiMessages
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+22
-15
@@ -12,10 +12,11 @@ import dev.meloda.fast.common.extensions.getParcelableCompat
|
||||
import dev.meloda.fast.common.extensions.listenValue
|
||||
import dev.meloda.fast.common.extensions.setValue
|
||||
import dev.meloda.fast.common.provider.ResourceProvider
|
||||
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.LongPollUpdatesParser
|
||||
import dev.meloda.fast.domain.LongPollUpdatesReducer
|
||||
import dev.meloda.fast.domain.MessagesUseCase
|
||||
import dev.meloda.fast.messageshistory.model.ActionMode
|
||||
import dev.meloda.fast.messageshistory.model.MessageDialog
|
||||
@@ -27,6 +28,8 @@ import dev.meloda.fast.model.BaseError
|
||||
import dev.meloda.fast.model.api.domain.VkMessage
|
||||
import dev.meloda.fast.ui.model.vk.MessageUiItem
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
@@ -38,7 +41,7 @@ class MessagesHistoryViewModelImpl(
|
||||
private val convoUseCase: ConvoUseCase,
|
||||
private val resourceProvider: ResourceProvider,
|
||||
private val getMessageReadPeersUseCase: GetMessageReadPeersUseCase,
|
||||
updatesParser: LongPollUpdatesParser,
|
||||
updatesReducer: LongPollUpdatesReducer,
|
||||
savedStateHandle: SavedStateHandle
|
||||
) : MessagesHistoryViewModel, ViewModel() {
|
||||
|
||||
@@ -80,6 +83,7 @@ class MessagesHistoryViewModelImpl(
|
||||
private val messageTransportActions = MessagesHistoryMessageTransportActions(
|
||||
applicationContext = applicationContext,
|
||||
viewModelScope = viewModelScope,
|
||||
convoUseCase = convoUseCase,
|
||||
messagesUseCase = messagesUseCase,
|
||||
screenState = screenState,
|
||||
messages = messages,
|
||||
@@ -103,11 +107,14 @@ class MessagesHistoryViewModelImpl(
|
||||
)
|
||||
|
||||
private val longPollEventHandler = MessagesHistoryLongPollEventHandler(
|
||||
scope = viewModelScope,
|
||||
convoUseCase = convoUseCase,
|
||||
messagesUseCase = messagesUseCase,
|
||||
screenState = screenState,
|
||||
messages = messages
|
||||
) {
|
||||
syncUiMessages()
|
||||
}
|
||||
messages = messages,
|
||||
syncUiMessages = ::syncUiMessages,
|
||||
onPinnedMessageChanged = pinnedMessageHandler::update
|
||||
)
|
||||
|
||||
init {
|
||||
val arguments = MessagesHistory.from(savedStateHandle).arguments
|
||||
@@ -117,15 +124,15 @@ class MessagesHistoryViewModelImpl(
|
||||
loaders.loadConvo()
|
||||
loaders.loadMessagesHistory(currentOffset.value)
|
||||
|
||||
updatesParser.onNewMessage(longPollEventHandler::onNewMessage)
|
||||
updatesParser.onMessageEdited(longPollEventHandler::onMessageEdited)
|
||||
updatesParser.onMessageIncomingRead(longPollEventHandler::onReadIncoming)
|
||||
updatesParser.onMessageOutgoingRead(longPollEventHandler::onReadOutgoing)
|
||||
updatesParser.onMessageDeleted(longPollEventHandler::onMessageDeleted)
|
||||
updatesParser.onMessageRestored(longPollEventHandler::onMessageRestored)
|
||||
updatesParser.onMessageMarkedAsImportant(longPollEventHandler::onMessageMarkedAsImportant)
|
||||
updatesParser.onMessageMarkedAsSpam(longPollEventHandler::onMessageMarkedAsSpam)
|
||||
updatesParser.onMessageMarkedAsNotSpam(longPollEventHandler::onMessageMarkedAsNotSpam)
|
||||
updatesReducer.newMessages.onEach(longPollEventHandler::onNewMessage).launchIn(viewModelScope)
|
||||
updatesReducer.messageEdited.onEach(longPollEventHandler::onMessageEdited).launchIn(viewModelScope)
|
||||
updatesReducer.messageIncomingRead.onEach(longPollEventHandler::onReadIncoming).launchIn(viewModelScope)
|
||||
updatesReducer.messageOutgoingRead.onEach(longPollEventHandler::onReadOutgoing).launchIn(viewModelScope)
|
||||
updatesReducer.messageDeleted.onEach(longPollEventHandler::onMessageDeleted).launchIn(viewModelScope)
|
||||
updatesReducer.messageRestored.onEach(longPollEventHandler::onMessageRestored).launchIn(viewModelScope)
|
||||
updatesReducer.messageMarkedAsImportant.onEach(longPollEventHandler::onMessageMarkedAsImportant).launchIn(viewModelScope)
|
||||
updatesReducer.messageMarkedAsSpam.onEach(longPollEventHandler::onMessageMarkedAsSpam).launchIn(viewModelScope)
|
||||
updatesReducer.messageMarkedAsNotSpam.onEach(longPollEventHandler::onMessageMarkedAsNotSpam).launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun onNavigationConsumed() {
|
||||
|
||||
Reference in New Issue
Block a user