Реакции, но с багом
This commit is contained in:
@@ -43,7 +43,11 @@ data class ChatDetailState(
|
||||
val isCompressionEnabled: Boolean = true,
|
||||
val inputText: String = "",
|
||||
val replyingMessage: Message? = null,
|
||||
val selectedMessageIds: Set<String> = emptySet()
|
||||
val editingMessage: Message? = null,
|
||||
val forwardingMessages: List<Message> = emptyList(),
|
||||
val availableChatsToForward: List<chats.domain.model.Chat> = emptyList(),
|
||||
val selectedMessageIds: Set<String> = emptySet(),
|
||||
val pinnedMessages: List<Message> = emptyList()
|
||||
)
|
||||
|
||||
@HiltViewModel
|
||||
@@ -187,6 +191,25 @@ class ChatDetailViewModel @Inject constructor(
|
||||
s.copy(messages = updatedMessages)
|
||||
}
|
||||
}
|
||||
|
||||
// For handling reaction removed event
|
||||
private fun removeMessageReaction(messageId: String, userId: String, emoji: String) {
|
||||
_state.update { s ->
|
||||
val updatedMessages = s.messages.map { msg ->
|
||||
if (msg.id == messageId) {
|
||||
val currentReactions = msg.reactions.toMutableMap()
|
||||
val count = currentReactions[emoji] ?: 0
|
||||
if (count > 1) {
|
||||
currentReactions[emoji] = count - 1
|
||||
} else {
|
||||
currentReactions.remove(emoji)
|
||||
}
|
||||
msg.copy(reactions = currentReactions)
|
||||
} else msg
|
||||
}
|
||||
s.copy(messages = updatedMessages)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeSignalREvents(chatId: String) {
|
||||
signalrEventsJob?.cancel()
|
||||
@@ -222,7 +245,24 @@ class ChatDetailViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
is ChatEvent.ReactionUpdated -> {
|
||||
updateMessageReaction(event.messageId, event.userId, event.emoji)
|
||||
if (event.isRemoved) {
|
||||
removeMessageReaction(event.messageId, event.userId, event.emoji)
|
||||
} else {
|
||||
updateMessageReaction(event.messageId, event.userId, event.emoji)
|
||||
}
|
||||
}
|
||||
is ChatEvent.MessageDeleted -> {
|
||||
_state.update { currentState ->
|
||||
currentState.copy(messages = currentState.messages.filter { it.id != event.messageId })
|
||||
}
|
||||
}
|
||||
is ChatEvent.MessageEdited -> {
|
||||
_state.update { currentState ->
|
||||
val updated = currentState.messages.map { msg ->
|
||||
if (msg.id == event.messageId) msg.copy(content = event.content) else msg
|
||||
}
|
||||
currentState.copy(messages = updated)
|
||||
}
|
||||
}
|
||||
is ChatEvent.UserTyping -> {
|
||||
_state.update { it.copy(isTyping = true) }
|
||||
@@ -245,6 +285,18 @@ class ChatDetailViewModel @Inject constructor(
|
||||
currentState.copy(messages = updatedMessages)
|
||||
}
|
||||
}
|
||||
is ChatEvent.MessagePinned -> {
|
||||
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
|
||||
_state.update { s ->
|
||||
val domainMsg = event.message.toDomain(getCurrentUserId(), baseUrl)
|
||||
s.copy(pinnedMessages = (s.pinnedMessages + domainMsg).distinctBy { it.id })
|
||||
}
|
||||
}
|
||||
is ChatEvent.MessageUnpinned -> {
|
||||
_state.update { s ->
|
||||
s.copy(pinnedMessages = s.pinnedMessages.filter { it.id != event.messageId })
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
@@ -313,6 +365,46 @@ class ChatDetailViewModel @Inject constructor(
|
||||
_state.update { it.copy(replyingMessage = message) }
|
||||
}
|
||||
|
||||
fun forwardMessages(targetChatId: String, messages: List<Message>) {
|
||||
viewModelScope.launch {
|
||||
messages.forEach { msg ->
|
||||
repository.sendMessage(
|
||||
chatId = targetChatId,
|
||||
content = msg.content,
|
||||
type = msg.mediaType.name.lowercase(),
|
||||
forwardedFromId = msg.senderId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onForward(message: Message) {
|
||||
_state.update { it.copy(forwardingMessages = listOf(message)) }
|
||||
loadChatsForForwarding()
|
||||
}
|
||||
|
||||
fun loadChatsForForwarding() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val chats = repository.getChats()
|
||||
_state.update { it.copy(availableChatsToForward = chats) }
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelForwarding() {
|
||||
_state.update { it.copy(forwardingMessages = emptyList(), availableChatsToForward = emptyList()) }
|
||||
}
|
||||
|
||||
fun onForwardSelectedMessages() {
|
||||
val selectedIds = _state.value.selectedMessageIds
|
||||
val messages = _state.value.messages.filter { selectedIds.contains(it.id) }
|
||||
_state.update { it.copy(forwardingMessages = messages) }
|
||||
loadChatsForForwarding()
|
||||
}
|
||||
|
||||
fun toggleSelection(messageId: String) {
|
||||
_state.update { s ->
|
||||
val newSelection = if (s.selectedMessageIds.contains(messageId)) {
|
||||
@@ -332,13 +424,70 @@ class ChatDetailViewModel @Inject constructor(
|
||||
_state.update { it.copy(replyingMessage = null) }
|
||||
}
|
||||
|
||||
fun cancelEdit() {
|
||||
_state.update { it.copy(editingMessage = null, inputText = "") }
|
||||
}
|
||||
|
||||
fun deleteMessage(message: Message, forEveryone: Boolean) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
repository.deleteMessage(message.id, forEveryone)
|
||||
// Local update if needed (will also come via SignalR for everyone, but forMe might need local only update)
|
||||
_state.update { currentState ->
|
||||
currentState.copy(messages = currentState.messages.filter { it.id != message.id })
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_state.update { it.copy(error = "Delete failed: ${e.localizedMessage}") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onEdit(message: Message) {
|
||||
_state.update { it.copy(editingMessage = message, inputText = message.content ?: "", replyingMessage = null) }
|
||||
}
|
||||
|
||||
fun pinMessage(messageId: String) {
|
||||
val chatId = currentChatId ?: return
|
||||
signalrClient.pinMessage(messageId, chatId)
|
||||
}
|
||||
|
||||
fun unpinMessage(messageId: String) {
|
||||
val chatId = currentChatId ?: return
|
||||
signalrClient.unpinMessage(messageId, chatId)
|
||||
}
|
||||
|
||||
fun onPin(message: Message) {
|
||||
pinMessage(message.id)
|
||||
}
|
||||
|
||||
fun sendMessage(onFail: (String) -> Unit = {}) {
|
||||
val chatId = currentChatId ?: return
|
||||
val text = _state.value.inputText
|
||||
val pending = _state.value.pendingAttachments
|
||||
val replyToId = _state.value.replyingMessage?.id
|
||||
val editingMsg = _state.value.editingMessage
|
||||
if (text.isBlank() && pending.isEmpty()) return
|
||||
|
||||
// Handle Edit
|
||||
if (editingMsg != null) {
|
||||
_state.update { it.copy(inputText = "", editingMessage = null) }
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val updated = repository.editMessage(editingMsg.id, text)
|
||||
_state.update { currentState ->
|
||||
val updatedList = currentState.messages.map {
|
||||
if (it.id == updated.id) updated else it
|
||||
}
|
||||
currentState.copy(messages = updatedList)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_state.update { it.copy(error = "Edit failed: ${e.localizedMessage}") }
|
||||
onFail(text)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Clear input immediately to avoid double clicks and ensure UI experience
|
||||
_state.update { it.copy(inputText = "", replyingMessage = null) }
|
||||
|
||||
@@ -417,13 +566,11 @@ class ChatDetailViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun addReaction(messageId: String, emoji: String) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
repository.addReaction(messageId, emoji)
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
val chatId = currentChatId ?: return
|
||||
|
||||
// We rely on SignalR event to update the count to avoid double counting
|
||||
// especially since domain Message doesn't track user IDs for reactions yet.
|
||||
signalrClient.addReaction(messageId, chatId, emoji)
|
||||
}
|
||||
|
||||
fun sendVoiceMessage(file: File) {
|
||||
|
||||
Reference in New Issue
Block a user