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
import android.os.Bundle
import com.conena.nanokt.collections.indexOfFirstOrNull
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 dev.meloda.fast.ui.model.vk.MessageUiItem
import kotlinx.coroutines.flow.MutableStateFlow
internal class MessagesHistoryInteractionHandler(
@@ -14,6 +16,8 @@ internal class MessagesHistoryInteractionHandler(
private val messages: MutableStateFlow<List<VkMessage>>,
private val dialog: MutableStateFlow<MessageDialog?>,
private val selectedMessages: MutableStateFlow<List<VkMessage>>,
private val uiMessages: MutableStateFlow<List<MessageUiItem>>,
private val isNeedToScrollToIndex: MutableStateFlow<Int?>,
private val messageActions: MessagesHistoryMessageActions,
private val messageTransportActions: MessagesHistoryMessageTransportActions,
private val syncUiMessages: () -> Unit
@@ -243,4 +247,21 @@ internal class MessagesHistoryInteractionHandler(
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.data.UserConfig
import dev.meloda.fast.data.VkMemoryCache
import dev.meloda.fast.datastore.AppSettings
import dev.meloda.fast.domain.MessagesUseCase
import dev.meloda.fast.domain.util.extractReplySummary
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) {
screenState.setValue { old -> old.copy(editCmId = cmId) }
@@ -118,6 +151,20 @@ internal class MessagesHistoryMessageActions(
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() {
replyToCmId = null
screenState.setValue { old ->
@@ -128,6 +175,10 @@ internal class MessagesHistoryMessageActions(
}
}
fun onKeyboardShown() {
showKeyboard.setValue { false }
}
fun sendMessage() {
lastMessageText = screenState.value.message.text
@@ -2,22 +2,18 @@ package dev.meloda.fast.messageshistory
import android.content.Context
import android.os.Bundle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.conena.nanokt.collections.indexOfFirstOrNull
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.LongPollUpdatesReducer
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.MessageNavigation
import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState
@@ -119,6 +115,8 @@ class MessagesHistoryViewModelImpl(
messages = messages,
dialog = dialog,
selectedMessages = selectedMessages,
uiMessages = uiMessages,
isNeedToScrollToIndex = isNeedToScrollToIndex,
messageActions = messageActions,
messageTransportActions = messageTransportActions,
syncUiMessages = ::syncUiMessages
@@ -182,55 +180,14 @@ class MessagesHistoryViewModelImpl(
}
override 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
}
}
override fun onMessageInputChanged(newText: TextFieldValue) =
messageActions.onMessageInputChanged(newText)
newText.text.trim().isEmpty() -> ActionMode.RECORD_AUDIO
else -> ActionMode.SEND
}
)
}
updateStyles()
}
override fun onEmojiButtonLongClicked() =
messageActions.onEmojiButtonLongClicked()
override 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))
)
}
}
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 onActionButtonClicked() =
messageActions.onActionButtonClicked()
override fun onPaginationConditionsMet() {
currentOffset.update { messages.value.size }
@@ -243,25 +200,11 @@ class MessagesHistoryViewModelImpl(
override fun onMessageLongClicked(messageId: Long) =
interactionHandler.onMessageLongClicked(messageId)
override fun onPinnedMessageClicked(messageId: Long) {
val uiMessages = uiMessages.value
val messageIndex = uiMessages.indexOfFirstOrNull {
it is MessageUiItem.Message && it.id == messageId
}
override fun onPinnedMessageClicked(messageId: Long) =
interactionHandler.onPinnedMessageClicked(messageId)
if (messageIndex == null) { // сообщения нет в списке
// pizdets
} else {
isNeedToScrollToIndex.setValue { messageIndex }
}
}
override fun onUnpinMessageClicked() {
val pinnedMessageId = screenState.value.pinnedMessage?.id ?: return
dialog.setValue {
MessageDialog.MessageUnpin(pinnedMessageId)
}
}
override fun onUnpinMessageClicked() =
interactionHandler.onUnpinMessageClicked()
override fun onEditSelectedMessageClicked() =
interactionHandler.onEditSelectedMessageClicked()
@@ -269,37 +212,30 @@ class MessagesHistoryViewModelImpl(
override fun onDeleteSelectedMessagesClicked() =
interactionHandler.onDeleteSelectedMessagesClicked()
override fun onBoldClicked() {
override fun onBoldClicked() =
messageActions.onBoldClicked()
}
override fun onItalicClicked() {
override fun onItalicClicked() =
messageActions.onItalicClicked()
}
override fun onUnderlineClicked() {
override fun onUnderlineClicked() =
messageActions.onUnderlineClicked()
}
override fun onLinkClicked() {
}
override fun onRegularClicked() {
override fun onRegularClicked() =
messageActions.onRegularClicked()
}
override fun onReplyCloseClicked() {
override fun onReplyCloseClicked() =
messageActions.onReplyCloseClicked()
}
override fun onRequestReplyToMessage(cmId: Long) {
override fun onRequestReplyToMessage(cmId: Long) =
messageActions.replyToMessage(cmId)
}
override fun onKeyboardShown() {
showKeyboard.setValue { false }
}
override fun onKeyboardShown() =
messageActions.onKeyboardShown()
override suspend fun loadMessageReadPeers(peerId: Long, cmId: Long): Int =
suspendCancellableCoroutine {