refactor: extract message interactions

This commit is contained in:
Codex
2026-05-14 20:48:50 +03:00
parent f6c6ed59f3
commit 68fff3ebee
2 changed files with 272 additions and 221 deletions
@@ -0,0 +1,246 @@
package dev.meloda.fast.messageshistory
import android.os.Bundle
import dev.meloda.fast.common.extensions.getParcelableCompat
import dev.meloda.fast.common.extensions.setValue
import dev.meloda.fast.messageshistory.model.MessageDialog
import dev.meloda.fast.messageshistory.model.MessageOption
import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState
import dev.meloda.fast.model.api.domain.VkMessage
import kotlinx.coroutines.flow.MutableStateFlow
internal class MessagesHistoryInteractionHandler(
private val screenState: MutableStateFlow<MessagesHistoryScreenState>,
private val messages: MutableStateFlow<List<VkMessage>>,
private val dialog: MutableStateFlow<MessageDialog?>,
private val selectedMessages: MutableStateFlow<List<VkMessage>>,
private val messageActions: MessagesHistoryMessageActions,
private val messageTransportActions: MessagesHistoryMessageTransportActions,
private val syncUiMessages: () -> Unit
) {
fun onDialogConfirmed(dialog: MessageDialog, bundle: Bundle) {
onDialogDismissed(dialog)
when (dialog) {
is MessageDialog.MessageOptions -> Unit
is MessageDialog.MessageDelete -> {
val deleteForEveryone = bundle.getBoolean("everyone")
if (dialog.message.id <= 0) {
val newMessages = messages.value.toMutableList()
newMessages.remove(dialog.message)
messages.setValue { newMessages }
syncUiMessages()
return
}
messageTransportActions.deleteMessage(
messageIds = listOf(dialog.message.id),
deleteForAll = deleteForEveryone
)
}
is MessageDialog.MessagesDelete -> {
val deleteForEveryone = bundle.getBoolean("everyone")
val failedMessages = dialog.messages.filter { it.id <= 0 }
val messageIdsToDelete =
dialog.messages
.filter { it.id > 0 }
.map(VkMessage::id)
messageTransportActions.deleteMessage(
messageIds = messageIdsToDelete,
deleteForAll = deleteForEveryone,
onSuccess = {
val newMessages = messages.value.toMutableList()
newMessages.removeAll(failedMessages)
messages.setValue { newMessages }
selectedMessages.setValue { emptyList() }
syncUiMessages()
}
)
}
is MessageDialog.MessagePin -> {
messageTransportActions.pinMessage(dialog.messageId)
}
is MessageDialog.MessageUnpin -> {
messageTransportActions.unpinMessage(dialog.messageId)
}
is MessageDialog.MessageMarkImportance -> {
messageTransportActions.markAsImportant(
messageIds = listOf(dialog.message.id),
important = dialog.isImportant
)
}
is MessageDialog.MessageSpam -> {
if (dialog.isSpam) {
messageTransportActions.deleteMessage(
messageIds = listOf(dialog.message.id),
spam = true
)
} else {
// TODO: 29-Mar-25, Danil Nikolaev: report as not spam
}
}
}
}
fun onDialogDismissed(dialog: MessageDialog) {
this.dialog.setValue { null }
}
fun onDialogItemPicked(dialog: MessageDialog, bundle: Bundle) {
when (dialog) {
is MessageDialog.MessageOptions -> {
val cmId = bundle.getLong("cmId")
when (val option = bundle.getParcelableCompat("option", MessageOption::class)) {
null -> Unit
MessageOption.Retry -> {
// TODO: 28-Mar-25, Danil Nikolaev: retry sending
}
MessageOption.Reply -> messageActions.replyToMessage(cmId)
MessageOption.ForwardHere -> {
}
MessageOption.Forward -> {
}
MessageOption.Pin -> {
this.dialog.setValue {
MessageDialog.MessagePin(dialog.message.id)
}
}
MessageOption.Unpin -> {
this.dialog.setValue {
MessageDialog.MessageUnpin(dialog.message.id)
}
}
MessageOption.Read -> {
messageTransportActions.readMessage(dialog.message)
}
MessageOption.Copy -> {
messageTransportActions.copyMessage(dialog.message)
}
MessageOption.MarkAsImportant,
MessageOption.UnmarkAsImportant -> {
this.dialog.setValue {
MessageDialog.MessageMarkImportance(
message = dialog.message,
isImportant = option is MessageOption.MarkAsImportant
)
}
}
MessageOption.MarkAsSpam,
MessageOption.UnmarkAsSpam -> {
this.dialog.setValue {
MessageDialog.MessageSpam(
message = dialog.message,
isSpam = option is MessageOption.MarkAsSpam
)
}
}
MessageOption.Edit -> {
messageActions.editMessage(cmId)
syncUiMessages()
}
MessageOption.Delete -> {
this.dialog.setValue {
MessageDialog.MessageDelete(dialog.message)
}
}
}
}
is MessageDialog.MessageDelete -> Unit
is MessageDialog.MessageUnpin -> Unit
is MessageDialog.MessageMarkImportance -> Unit
is MessageDialog.MessageSpam -> Unit
is MessageDialog.MessagePin -> Unit
is MessageDialog.MessagesDelete -> Unit
}
}
fun onCloseButtonClicked() {
if (selectedMessages.value.isNotEmpty()) {
selectedMessages.setValue { emptyList() }
}
if (screenState.value.editCmId != null) {
messageActions.stopEditMessage()
}
syncUiMessages()
}
fun onMessageClicked(messageId: Long) {
val currentMessage = messages.value.firstOrNull { it.id == messageId } ?: return
if (selectedMessages.value.isNotEmpty()) {
val isSelected = selectedMessages.value.contains(currentMessage)
selectedMessages.setValue { old ->
old.toMutableList().also {
if (isSelected) {
it.remove(currentMessage)
} else {
it.add(currentMessage)
}
}
}
syncUiMessages()
} else {
dialog.setValue {
MessageDialog.MessageOptions(currentMessage)
}
}
}
fun onMessageLongClicked(messageId: Long) {
val currentMessage = messages.value.firstOrNull { it.id == messageId } ?: return
val isSelected = selectedMessages.value.contains(currentMessage)
if (isSelected) return
selectedMessages.setValue { old ->
old.toMutableList().also {
it.add(currentMessage)
}
}
syncUiMessages()
}
fun onEditSelectedMessageClicked() {
val cmId = selectedMessages.value.firstOrNull()?.cmId ?: return
selectedMessages.setValue { emptyList() }
messageActions.editMessage(cmId)
syncUiMessages()
}
fun onDeleteSelectedMessagesClicked() {
dialog.setValue {
MessageDialog.MessagesDelete(selectedMessages.value)
}
}
}
@@ -8,7 +8,6 @@ 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.extensions.getParcelableCompat
import dev.meloda.fast.common.extensions.listenValue import dev.meloda.fast.common.extensions.listenValue
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
@@ -21,7 +20,6 @@ import dev.meloda.fast.domain.MessagesUseCase
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
import dev.meloda.fast.messageshistory.model.MessageOption
import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState
import dev.meloda.fast.messageshistory.navigation.MessagesHistory import dev.meloda.fast.messageshistory.navigation.MessagesHistory
import dev.meloda.fast.model.BaseError import dev.meloda.fast.model.BaseError
@@ -116,6 +114,16 @@ class MessagesHistoryViewModelImpl(
onPinnedMessageChanged = pinnedMessageHandler::update onPinnedMessageChanged = pinnedMessageHandler::update
) )
private val interactionHandler = MessagesHistoryInteractionHandler(
screenState = screenState,
messages = messages,
dialog = dialog,
selectedMessages = selectedMessages,
messageActions = messageActions,
messageTransportActions = messageTransportActions,
syncUiMessages = ::syncUiMessages
)
init { init {
val arguments = MessagesHistory.from(savedStateHandle).arguments val arguments = MessagesHistory.from(savedStateHandle).arguments
@@ -150,183 +158,21 @@ class MessagesHistoryViewModelImpl(
} }
} }
override fun onDialogConfirmed(dialog: MessageDialog, bundle: Bundle) { override fun onDialogConfirmed(dialog: MessageDialog, bundle: Bundle) =
onDialogDismissed(dialog) interactionHandler.onDialogConfirmed(dialog, bundle)
when (dialog) { override fun onDialogDismissed(dialog: MessageDialog) =
is MessageDialog.MessageOptions -> Unit interactionHandler.onDialogDismissed(dialog)
is MessageDialog.MessageDelete -> { override fun onDialogItemPicked(dialog: MessageDialog, bundle: Bundle) =
val deleteForEveryone = bundle.getBoolean("everyone") interactionHandler.onDialogItemPicked(dialog, bundle)
if (dialog.message.id <= 0) {
val newMessages = messages.value.toMutableList()
newMessages.remove(dialog.message)
messages.setValue { newMessages }
syncUiMessages()
return
}
messageTransportActions.deleteMessage(
messageIds = listOf(dialog.message.id),
deleteForAll = deleteForEveryone
)
}
is MessageDialog.MessagesDelete -> {
val deleteForEveryone = bundle.getBoolean("everyone")
val failedMessages = dialog.messages.filter { it.id <= 0 }
val messageIdsToDelete =
dialog.messages
.filter { it.id > 0 }
.map(VkMessage::id)
messageTransportActions.deleteMessage(
messageIds = messageIdsToDelete,
deleteForAll = deleteForEveryone,
onSuccess = {
val newMessages = messages.value.toMutableList()
newMessages.removeAll(failedMessages)
messages.setValue { newMessages }
selectedMessages.setValue { emptyList() }
syncUiMessages()
}
)
}
is MessageDialog.MessagePin -> {
messageTransportActions.pinMessage(dialog.messageId)
}
is MessageDialog.MessageUnpin -> {
messageTransportActions.unpinMessage(dialog.messageId)
}
is MessageDialog.MessageMarkImportance -> {
messageTransportActions.markAsImportant(
messageIds = listOf(dialog.message.id),
important = dialog.isImportant
)
}
is MessageDialog.MessageSpam -> {
if (dialog.isSpam) {
messageTransportActions.deleteMessage(
messageIds = listOf(dialog.message.id),
spam = true
)
} else {
// TODO: 29-Mar-25, Danil Nikolaev: report as not spam
}
}
}
}
override fun onDialogDismissed(dialog: MessageDialog) {
this.dialog.setValue { null }
}
override fun onDialogItemPicked(dialog: MessageDialog, bundle: Bundle) {
when (dialog) {
is MessageDialog.MessageOptions -> {
// val messageId = bundle.getLong("messageId")
val cmId = bundle.getLong("cmId")
when (val option = bundle.getParcelableCompat("option", MessageOption::class)) {
null -> Unit
MessageOption.Retry -> {
// TODO: 28-Mar-25, Danil Nikolaev: retry sending
}
MessageOption.Reply -> messageActions.replyToMessage(cmId)
MessageOption.ForwardHere -> {
}
MessageOption.Forward -> {
}
MessageOption.Pin -> {
this.dialog.setValue {
MessageDialog.MessagePin(dialog.message.id)
}
}
MessageOption.Unpin -> {
this.dialog.setValue {
MessageDialog.MessageUnpin(dialog.message.id)
}
}
MessageOption.Read -> {
messageTransportActions.readMessage(dialog.message)
}
MessageOption.Copy -> {
messageTransportActions.copyMessage(dialog.message)
}
MessageOption.MarkAsImportant,
MessageOption.UnmarkAsImportant -> {
this.dialog.setValue {
MessageDialog.MessageMarkImportance(
message = dialog.message,
isImportant = option is MessageOption.MarkAsImportant
)
}
}
MessageOption.MarkAsSpam,
MessageOption.UnmarkAsSpam -> {
this.dialog.setValue {
MessageDialog.MessageSpam(
message = dialog.message,
isSpam = option is MessageOption.MarkAsSpam
)
}
}
MessageOption.Edit -> {
messageActions.editMessage(cmId)
syncUiMessages()
}
MessageOption.Delete -> {
this.dialog.setValue {
MessageDialog.MessageDelete(dialog.message)
}
}
}
}
is MessageDialog.MessageDelete -> Unit
is MessageDialog.MessageUnpin -> Unit
is MessageDialog.MessageMarkImportance -> Unit
is MessageDialog.MessageSpam -> Unit
is MessageDialog.MessagePin -> Unit
is MessageDialog.MessagesDelete -> Unit
}
}
override fun onScrolledToIndex() { override fun onScrolledToIndex() {
isNeedToScrollToIndex.setValue { null } isNeedToScrollToIndex.setValue { null }
} }
override fun onCloseButtonClicked() { override fun onCloseButtonClicked() =
if (selectedMessages.value.isNotEmpty()) { interactionHandler.onCloseButtonClicked()
selectedMessages.setValue { emptyList() }
}
if (screenState.value.editCmId != null) {
messageActions.stopEditMessage()
}
syncUiMessages()
}
override fun onRefresh() { override fun onRefresh() {
loaders.loadMessagesHistory(offset = 0) loaders.loadMessagesHistory(offset = 0)
@@ -391,42 +237,11 @@ class MessagesHistoryViewModelImpl(
loaders.loadMessagesHistory(currentOffset.value) loaders.loadMessagesHistory(currentOffset.value)
} }
override fun onMessageClicked(messageId: Long) { override fun onMessageClicked(messageId: Long) =
val currentMessage = messages.value.firstOrNull { it.id == messageId } ?: return interactionHandler.onMessageClicked(messageId)
if (selectedMessages.value.isNotEmpty()) { override fun onMessageLongClicked(messageId: Long) =
val isSelected = selectedMessages.value.contains(currentMessage) interactionHandler.onMessageLongClicked(messageId)
selectedMessages.setValue { old ->
old.toMutableList().also {
if (isSelected) {
it.remove(currentMessage)
} else {
it.add(currentMessage)
}
}
}
syncUiMessages()
} else {
dialog.setValue {
MessageDialog.MessageOptions(currentMessage)
}
}
}
override fun onMessageLongClicked(messageId: Long) {
val currentMessage = messages.value.firstOrNull { it.id == messageId } ?: return
val isSelected = selectedMessages.value.contains(currentMessage)
if (isSelected) return
selectedMessages.setValue { old ->
old.toMutableList().also {
it.add(currentMessage)
}
}
syncUiMessages()
}
override fun onPinnedMessageClicked(messageId: Long) { override fun onPinnedMessageClicked(messageId: Long) {
val uiMessages = uiMessages.value val uiMessages = uiMessages.value
@@ -448,21 +263,11 @@ class MessagesHistoryViewModelImpl(
} }
} }
override fun onEditSelectedMessageClicked() { override fun onEditSelectedMessageClicked() =
val cmId = selectedMessages.value.firstOrNull()?.cmId ?: return interactionHandler.onEditSelectedMessageClicked()
selectedMessages.setValue { emptyList() } override fun onDeleteSelectedMessagesClicked() =
interactionHandler.onDeleteSelectedMessagesClicked()
messageActions.editMessage(cmId)
syncUiMessages()
}
override fun onDeleteSelectedMessagesClicked() {
dialog.setValue {
MessageDialog.MessagesDelete(selectedMessages.value)
}
}
override fun onBoldClicked() { override fun onBoldClicked() {
messageActions.onBoldClicked() messageActions.onBoldClicked()