android: add message action state machine core
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
This commit is contained in:
@@ -71,15 +71,73 @@ class ChatViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun onSelectMessage(message: MessageItem?) {
|
||||
if (message == null) {
|
||||
onClearSelection()
|
||||
return
|
||||
}
|
||||
val canEdit = canEdit(message)
|
||||
val canDeleteForAll = canDeleteForAll(message)
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
selectedMessage = message,
|
||||
selectedCanEdit = message?.let(::canEdit) == true,
|
||||
selectedCanDeleteForAll = message?.let(::canDeleteForAll) == true,
|
||||
selectedCanEdit = canEdit,
|
||||
selectedCanDeleteForAll = canDeleteForAll,
|
||||
actionState = it.actionState.selectSingle(
|
||||
message = message,
|
||||
canEdit = canEdit,
|
||||
canDeleteForAll = canDeleteForAll,
|
||||
),
|
||||
)
|
||||
}
|
||||
if (message != null) {
|
||||
loadReactions(messageId = message.id)
|
||||
loadReactions(messageId = message.id)
|
||||
}
|
||||
|
||||
fun onEnterMultiSelect(message: MessageItem) {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
selectedMessage = message,
|
||||
selectedCanEdit = false,
|
||||
selectedCanDeleteForAll = false,
|
||||
actionState = it.actionState.selectMulti(
|
||||
selectedIds = setOf(message.id),
|
||||
focusedMessageId = message.id,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onToggleMessageMultiSelection(message: MessageItem) {
|
||||
val current = uiState.value.actionState
|
||||
val nextState = if (current.mode != MessageSelectionMode.MULTI) {
|
||||
current.selectMulti(selectedIds = setOf(message.id), focusedMessageId = message.id)
|
||||
} else {
|
||||
val toggledIds = current.selectedMessageIds.toMutableSet().apply {
|
||||
if (contains(message.id)) remove(message.id) else add(message.id)
|
||||
}.toSet()
|
||||
current.selectMulti(
|
||||
selectedIds = toggledIds,
|
||||
focusedMessageId = message.id,
|
||||
)
|
||||
}
|
||||
val focused = nextState.focusedMessageId?.let(::findMessageById)
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
selectedMessage = focused,
|
||||
selectedCanEdit = focused?.let(::canEdit) == true,
|
||||
selectedCanDeleteForAll = focused?.let(::canDeleteForAll) == true,
|
||||
actionState = nextState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onClearSelection() {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
selectedMessage = null,
|
||||
selectedCanEdit = false,
|
||||
selectedCanDeleteForAll = false,
|
||||
actionState = it.actionState.clearSelection(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +149,7 @@ class ChatViewModel @Inject constructor(
|
||||
selectedMessage = null,
|
||||
selectedCanEdit = false,
|
||||
selectedCanDeleteForAll = false,
|
||||
actionState = it.actionState.clearSelection(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -103,6 +162,7 @@ class ChatViewModel @Inject constructor(
|
||||
selectedMessage = null,
|
||||
selectedCanEdit = false,
|
||||
selectedCanDeleteForAll = false,
|
||||
actionState = it.actionState.clearSelection(),
|
||||
inputText = message.text.orEmpty(),
|
||||
)
|
||||
}
|
||||
@@ -116,12 +176,13 @@ class ChatViewModel @Inject constructor(
|
||||
selectedMessage = null,
|
||||
selectedCanEdit = false,
|
||||
selectedCanDeleteForAll = false,
|
||||
actionState = it.actionState.clearSelection(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onDeleteSelected(forAll: Boolean = false) {
|
||||
val selected = uiState.value.selectedMessage ?: return
|
||||
val selected = getFocusedSelectedMessage() ?: return
|
||||
if (forAll && !canDeleteForAll(selected)) {
|
||||
_uiState.update { it.copy(errorMessage = "Delete for all is available only for your own messages.") }
|
||||
return
|
||||
@@ -133,6 +194,7 @@ class ChatViewModel @Inject constructor(
|
||||
selectedMessage = null,
|
||||
selectedCanEdit = false,
|
||||
selectedCanDeleteForAll = false,
|
||||
actionState = it.actionState.clearSelection(),
|
||||
)
|
||||
}
|
||||
is AppResult.Error -> _uiState.update { it.copy(errorMessage = result.reason.toUiMessage()) }
|
||||
@@ -141,7 +203,7 @@ class ChatViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun onForwardSelected() {
|
||||
val message = uiState.value.selectedMessage ?: return
|
||||
val message = getFocusedSelectedMessage() ?: return
|
||||
viewModelScope.launch {
|
||||
val targets = observeChatsUseCase(archived = false)
|
||||
.first()
|
||||
@@ -157,6 +219,7 @@ class ChatViewModel @Inject constructor(
|
||||
selectedMessage = null,
|
||||
selectedCanEdit = false,
|
||||
selectedCanDeleteForAll = false,
|
||||
actionState = it.actionState.clearSelection(),
|
||||
errorMessage = null,
|
||||
)
|
||||
}
|
||||
@@ -200,7 +263,7 @@ class ChatViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun onToggleReaction(emoji: String) {
|
||||
val selected = uiState.value.selectedMessage ?: return
|
||||
val selected = getFocusedSelectedMessage() ?: return
|
||||
viewModelScope.launch {
|
||||
when (val result = toggleMessageReactionUseCase(messageId = selected.id, emoji = emoji)) {
|
||||
is AppResult.Success -> {
|
||||
@@ -424,6 +487,19 @@ class ChatViewModel @Inject constructor(
|
||||
return message.isOutgoing
|
||||
}
|
||||
|
||||
private fun getFocusedSelectedMessage(): MessageItem? {
|
||||
val focusId = uiState.value.actionState.focusedMessageId
|
||||
return if (focusId != null) {
|
||||
findMessageById(focusId)
|
||||
} else {
|
||||
uiState.value.selectedMessage
|
||||
}
|
||||
}
|
||||
|
||||
private fun findMessageById(messageId: Long): MessageItem? {
|
||||
return uiState.value.messages.firstOrNull { it.id == messageId }
|
||||
}
|
||||
|
||||
private fun AppError.toUiMessage(): String {
|
||||
return when (this) {
|
||||
AppError.Network -> "Network error."
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package ru.daemonlord.messenger.ui.chat
|
||||
|
||||
import ru.daemonlord.messenger.domain.message.model.MessageItem
|
||||
|
||||
enum class MessageSelectionMode {
|
||||
NONE,
|
||||
SINGLE,
|
||||
MULTI,
|
||||
}
|
||||
|
||||
enum class MessageActionIntent {
|
||||
REPLY,
|
||||
EDIT,
|
||||
FORWARD,
|
||||
FORWARD_BULK,
|
||||
DELETE_FOR_ME,
|
||||
DELETE_FOR_ALL,
|
||||
TOGGLE_REACTION,
|
||||
CLEAR_SELECTION,
|
||||
}
|
||||
|
||||
data class MessageActionState(
|
||||
val mode: MessageSelectionMode = MessageSelectionMode.NONE,
|
||||
val selectedMessageIds: Set<Long> = emptySet(),
|
||||
val focusedMessageId: Long? = null,
|
||||
val availableIntents: Set<MessageActionIntent> = emptySet(),
|
||||
) {
|
||||
val hasSelection: Boolean
|
||||
get() = selectedMessageIds.isNotEmpty()
|
||||
|
||||
val selectedCount: Int
|
||||
get() = selectedMessageIds.size
|
||||
}
|
||||
|
||||
fun MessageActionState.selectSingle(
|
||||
message: MessageItem,
|
||||
canEdit: Boolean,
|
||||
canDeleteForAll: Boolean,
|
||||
): MessageActionState {
|
||||
val intents = buildSet {
|
||||
add(MessageActionIntent.REPLY)
|
||||
add(MessageActionIntent.FORWARD)
|
||||
add(MessageActionIntent.DELETE_FOR_ME)
|
||||
add(MessageActionIntent.TOGGLE_REACTION)
|
||||
add(MessageActionIntent.CLEAR_SELECTION)
|
||||
if (canEdit) add(MessageActionIntent.EDIT)
|
||||
if (canDeleteForAll) add(MessageActionIntent.DELETE_FOR_ALL)
|
||||
}
|
||||
return copy(
|
||||
mode = MessageSelectionMode.SINGLE,
|
||||
selectedMessageIds = setOf(message.id),
|
||||
focusedMessageId = message.id,
|
||||
availableIntents = intents,
|
||||
)
|
||||
}
|
||||
|
||||
fun MessageActionState.selectMulti(
|
||||
selectedIds: Set<Long>,
|
||||
focusedMessageId: Long? = null,
|
||||
): MessageActionState {
|
||||
if (selectedIds.isEmpty()) return clearSelection()
|
||||
return copy(
|
||||
mode = MessageSelectionMode.MULTI,
|
||||
selectedMessageIds = selectedIds,
|
||||
focusedMessageId = focusedMessageId ?: this.focusedMessageId ?: selectedIds.first(),
|
||||
availableIntents = setOf(
|
||||
MessageActionIntent.FORWARD_BULK,
|
||||
MessageActionIntent.DELETE_FOR_ME,
|
||||
MessageActionIntent.CLEAR_SELECTION,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun MessageActionState.clearSelection(): MessageActionState {
|
||||
return copy(
|
||||
mode = MessageSelectionMode.NONE,
|
||||
selectedMessageIds = emptySet(),
|
||||
focusedMessageId = null,
|
||||
availableIntents = emptySet(),
|
||||
)
|
||||
}
|
||||
@@ -23,6 +23,7 @@ data class MessageUiState(
|
||||
val isForwarding: Boolean = false,
|
||||
val canSendMessages: Boolean = true,
|
||||
val sendRestrictionText: String? = null,
|
||||
val actionState: MessageActionState = MessageActionState(),
|
||||
)
|
||||
|
||||
data class ForwardTargetUiModel(
|
||||
|
||||
Reference in New Issue
Block a user