refactor: unify db refresh flows

This commit is contained in:
Codex
2026-05-14 20:45:24 +03:00
parent f24eae8209
commit f6c6ed59f3
32 changed files with 882 additions and 408 deletions
@@ -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()
)
}