refactor: extract message composer actions

This commit is contained in:
Codex
2026-05-14 20:53:34 +03:00
parent 68fff3ebee
commit 0ffd92b875
3 changed files with 92 additions and 84 deletions
@@ -1,12 +1,14 @@
package dev.meloda.fast.messageshistory package dev.meloda.fast.messageshistory
import android.os.Bundle import android.os.Bundle
import com.conena.nanokt.collections.indexOfFirstOrNull
import dev.meloda.fast.common.extensions.getParcelableCompat import dev.meloda.fast.common.extensions.getParcelableCompat
import dev.meloda.fast.common.extensions.setValue import dev.meloda.fast.common.extensions.setValue
import dev.meloda.fast.messageshistory.model.MessageDialog import dev.meloda.fast.messageshistory.model.MessageDialog
import dev.meloda.fast.messageshistory.model.MessageOption 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.model.api.domain.VkMessage 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.MutableStateFlow
internal class MessagesHistoryInteractionHandler( internal class MessagesHistoryInteractionHandler(
@@ -14,6 +16,8 @@ internal class MessagesHistoryInteractionHandler(
private val messages: MutableStateFlow<List<VkMessage>>, private val messages: MutableStateFlow<List<VkMessage>>,
private val dialog: MutableStateFlow<MessageDialog?>, private val dialog: MutableStateFlow<MessageDialog?>,
private val selectedMessages: MutableStateFlow<List<VkMessage>>, private val selectedMessages: MutableStateFlow<List<VkMessage>>,
private val uiMessages: MutableStateFlow<List<MessageUiItem>>,
private val isNeedToScrollToIndex: MutableStateFlow<Int?>,
private val messageActions: MessagesHistoryMessageActions, private val messageActions: MessagesHistoryMessageActions,
private val messageTransportActions: MessagesHistoryMessageTransportActions, private val messageTransportActions: MessagesHistoryMessageTransportActions,
private val syncUiMessages: () -> Unit private val syncUiMessages: () -> Unit
@@ -243,4 +247,21 @@ internal class MessagesHistoryInteractionHandler(
MessageDialog.MessagesDelete(selectedMessages.value) MessageDialog.MessagesDelete(selectedMessages.value)
} }
} }
fun onPinnedMessageClicked(messageId: Long) {
val messageIndex = uiMessages.value.indexOfFirstOrNull {
it is MessageUiItem.Message && it.id == messageId
}
if (messageIndex != null) {
isNeedToScrollToIndex.setValue { messageIndex }
}
}
fun onUnpinMessageClicked() {
val pinnedMessageId = screenState.value.pinnedMessage?.id ?: return
dialog.setValue {
MessageDialog.MessageUnpin(pinnedMessageId)
}
}
} }
@@ -16,6 +16,7 @@ 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.data.UserConfig import dev.meloda.fast.data.UserConfig
import dev.meloda.fast.data.VkMemoryCache import dev.meloda.fast.data.VkMemoryCache
import dev.meloda.fast.datastore.AppSettings
import dev.meloda.fast.domain.MessagesUseCase import dev.meloda.fast.domain.MessagesUseCase
import dev.meloda.fast.domain.util.extractReplySummary import dev.meloda.fast.domain.util.extractReplySummary
import dev.meloda.fast.domain.util.extractReplyTitle import dev.meloda.fast.domain.util.extractReplyTitle
@@ -64,6 +65,38 @@ internal class MessagesHistoryMessageActions(
} }
} }
fun onMessageInputChanged(newText: TextFieldValue) {
screenState.setValue { old ->
old.copy(
message = newText,
actionMode =
when {
screenState.value.editCmId != null -> {
// TODO: 13/03/2026, Danil Nikolaev: also check if attachments is empty
if (newText.text.trim().isEmpty()) {
ActionMode.DELETE
} else {
ActionMode.EDIT
}
}
newText.text.trim().isEmpty() -> ActionMode.RECORD_AUDIO
else -> ActionMode.SEND
}
)
}
updateStyles()
}
fun onEmojiButtonLongClicked() {
AppSettings.Features.fastText.takeIf { it.isNotBlank() }?.let { text ->
val newText = "${screenState.value.message.text}$text"
onMessageInputChanged(
TextFieldValue(text = newText, selection = TextRange(newText.length))
)
}
}
fun editMessage(cmId: Long) { fun editMessage(cmId: Long) {
screenState.setValue { old -> old.copy(editCmId = cmId) } screenState.setValue { old -> old.copy(editCmId = cmId) }
@@ -118,6 +151,20 @@ internal class MessagesHistoryMessageActions(
updateStyles() updateStyles()
} }
fun onActionButtonClicked() {
when (screenState.value.actionMode) {
ActionMode.DELETE -> confirmDeleteCurrentEditMessage()
ActionMode.EDIT -> editCurrentEditMessage()
ActionMode.RECORD_AUDIO -> {
screenState.setValue { it.copy(actionMode = ActionMode.RECORD_VIDEO) }
}
ActionMode.RECORD_VIDEO -> {
screenState.setValue { it.copy(actionMode = ActionMode.RECORD_AUDIO) }
}
ActionMode.SEND -> sendMessage()
}
}
fun onReplyCloseClicked() { fun onReplyCloseClicked() {
replyToCmId = null replyToCmId = null
screenState.setValue { old -> screenState.setValue { old ->
@@ -128,6 +175,10 @@ internal class MessagesHistoryMessageActions(
} }
} }
fun onKeyboardShown() {
showKeyboard.setValue { false }
}
fun sendMessage() { fun sendMessage() {
lastMessageText = screenState.value.message.text lastMessageText = screenState.value.message.text
@@ -2,22 +2,18 @@ package dev.meloda.fast.messageshistory
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
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 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
import dev.meloda.fast.data.processState 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.LongPollUpdatesReducer import dev.meloda.fast.domain.LongPollUpdatesReducer
import dev.meloda.fast.domain.MessagesUseCase import dev.meloda.fast.domain.MessagesUseCase
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.MessagesHistoryScreenState import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState
@@ -119,6 +115,8 @@ class MessagesHistoryViewModelImpl(
messages = messages, messages = messages,
dialog = dialog, dialog = dialog,
selectedMessages = selectedMessages, selectedMessages = selectedMessages,
uiMessages = uiMessages,
isNeedToScrollToIndex = isNeedToScrollToIndex,
messageActions = messageActions, messageActions = messageActions,
messageTransportActions = messageTransportActions, messageTransportActions = messageTransportActions,
syncUiMessages = ::syncUiMessages syncUiMessages = ::syncUiMessages
@@ -182,55 +180,14 @@ class MessagesHistoryViewModelImpl(
} }
override fun onMessageInputChanged(newText: TextFieldValue) { override fun onMessageInputChanged(newText: TextFieldValue) =
screenState.setValue { old -> messageActions.onMessageInputChanged(newText)
old.copy(
message = newText,
actionMode =
when {
screenState.value.editCmId != null -> {
// TODO: 13/03/2026, Danil Nikolaev: also check if attachments is empty
if (newText.text.trim().isEmpty()) {
ActionMode.DELETE
} else {
ActionMode.EDIT
}
}
newText.text.trim().isEmpty() -> ActionMode.RECORD_AUDIO override fun onEmojiButtonLongClicked() =
else -> ActionMode.SEND messageActions.onEmojiButtonLongClicked()
}
)
}
updateStyles()
}
override fun onEmojiButtonLongClicked() { override fun onActionButtonClicked() =
AppSettings.Features.fastText.takeIf { it.isNotBlank() }?.let { text -> messageActions.onActionButtonClicked()
val newText = "${screenState.value.message.text}$text"
onMessageInputChanged(
TextFieldValue(text = newText, selection = TextRange(newText.length))
)
}
}
override fun onActionButtonClicked() {
when (screenState.value.actionMode) {
ActionMode.DELETE -> messageActions.confirmDeleteCurrentEditMessage()
ActionMode.EDIT -> messageActions.editCurrentEditMessage()
ActionMode.RECORD_AUDIO -> {
screenState.setValue { it.copy(actionMode = ActionMode.RECORD_VIDEO) }
}
ActionMode.RECORD_VIDEO -> {
screenState.setValue { it.copy(actionMode = ActionMode.RECORD_AUDIO) }
}
ActionMode.SEND -> messageActions.sendMessage()
}
}
override fun onPaginationConditionsMet() { override fun onPaginationConditionsMet() {
currentOffset.update { messages.value.size } currentOffset.update { messages.value.size }
@@ -243,25 +200,11 @@ class MessagesHistoryViewModelImpl(
override fun onMessageLongClicked(messageId: Long) = override fun onMessageLongClicked(messageId: Long) =
interactionHandler.onMessageLongClicked(messageId) interactionHandler.onMessageLongClicked(messageId)
override fun onPinnedMessageClicked(messageId: Long) { override fun onPinnedMessageClicked(messageId: Long) =
val uiMessages = uiMessages.value interactionHandler.onPinnedMessageClicked(messageId)
val messageIndex = uiMessages.indexOfFirstOrNull {
it is MessageUiItem.Message && it.id == messageId
}
if (messageIndex == null) { // сообщения нет в списке override fun onUnpinMessageClicked() =
// pizdets interactionHandler.onUnpinMessageClicked()
} else {
isNeedToScrollToIndex.setValue { messageIndex }
}
}
override fun onUnpinMessageClicked() {
val pinnedMessageId = screenState.value.pinnedMessage?.id ?: return
dialog.setValue {
MessageDialog.MessageUnpin(pinnedMessageId)
}
}
override fun onEditSelectedMessageClicked() = override fun onEditSelectedMessageClicked() =
interactionHandler.onEditSelectedMessageClicked() interactionHandler.onEditSelectedMessageClicked()
@@ -269,37 +212,30 @@ class MessagesHistoryViewModelImpl(
override fun onDeleteSelectedMessagesClicked() = override fun onDeleteSelectedMessagesClicked() =
interactionHandler.onDeleteSelectedMessagesClicked() interactionHandler.onDeleteSelectedMessagesClicked()
override fun onBoldClicked() { override fun onBoldClicked() =
messageActions.onBoldClicked() messageActions.onBoldClicked()
}
override fun onItalicClicked() { override fun onItalicClicked() =
messageActions.onItalicClicked() messageActions.onItalicClicked()
}
override fun onUnderlineClicked() { override fun onUnderlineClicked() =
messageActions.onUnderlineClicked() messageActions.onUnderlineClicked()
}
override fun onLinkClicked() { override fun onLinkClicked() {
} }
override fun onRegularClicked() { override fun onRegularClicked() =
messageActions.onRegularClicked() messageActions.onRegularClicked()
}
override fun onReplyCloseClicked() { override fun onReplyCloseClicked() =
messageActions.onReplyCloseClicked() messageActions.onReplyCloseClicked()
}
override fun onRequestReplyToMessage(cmId: Long) { override fun onRequestReplyToMessage(cmId: Long) =
messageActions.replyToMessage(cmId) messageActions.replyToMessage(cmId)
}
override fun onKeyboardShown() { override fun onKeyboardShown() =
showKeyboard.setValue { false } messageActions.onKeyboardShown()
}
override suspend fun loadMessageReadPeers(peerId: Long, cmId: Long): Int = override suspend fun loadMessageReadPeers(peerId: Long, cmId: Long): Int =
suspendCancellableCoroutine { suspendCancellableCoroutine {