forked from melod1n/fast-messenger
refactor: extract message composer actions
This commit is contained in:
+21
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+51
@@ -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
|
||||||
|
|
||||||
|
|||||||
+20
-84
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user