diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index d289933ba..6e970b799 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -121,21 +121,23 @@ class OutcomingTextMessageViewHolder(itemView: View) : CoroutineScope(Dispatchers.Main).launch { - // if (message.sendingFailed) { - // updateStatus( - // R.drawable.baseline_report_problem_24, - // "failed" - // ) - // } else - - if (message.isTempMessage && !networkMonitor.isOnline.first()) { + if (message.isTemporary && !networkMonitor.isOnline.first()) { updateStatus( R.drawable.ic_signal_wifi_off_white_24dp, "offline" ) - } else if (message.isTempMessage) { + } else if (message.sendingFailed) { + updateStatus( + R.drawable.baseline_report_problem_24, + "failed" + ) + binding.bubble.setOnClickListener { + commonMessageInterface.onOpenMessageActionsDialog(message) + } + + } else if (message.isTemporary) { showSendingSpinner() - } else if(message.readStatus == ReadStatus.READ){ + } else if(message.readStatus == ReadStatus.READ) { updateStatus(R.drawable.ic_check_all, context.resources?.getString(R.string.nc_message_read)) } else if(message.readStatus == ReadStatus.SENT) { updateStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent)) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java index 6eda1ff98..3d7609133 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java @@ -68,9 +68,6 @@ public class TalkMessagesListAdapter extends MessagesListAda } else if (holder instanceof SystemMessageViewHolder holderInstance) { holderInstance.assignSystemMessageInterface(chatActivity); - } else if (holder instanceof TemporaryMessageViewHolder holderInstance) { - holderInstance.assignTemporaryMessageInterface(chatActivity); - } else if (holder instanceof IncomingDeckCardViewHolder holderInstance) { holderInstance.assignCommonMessageInterface(chatActivity); } else if (holder instanceof OutcomingDeckCardViewHolder holderInstance) { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageInterface.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageInterface.kt deleted file mode 100644 index 44dab23af..000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageInterface.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Nextcloud Talk - Android Client - * - * SPDX-FileCopyrightText: 2024 Julius Linus - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -package com.nextcloud.talk.adapters.messages - -interface TemporaryMessageInterface { - fun editTemporaryMessage(id: Int, newMessage: String) - fun deleteTemporaryMessage(id: Int) -} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageViewHolder.kt deleted file mode 100644 index a8e6a8f4f..000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageViewHolder.kt +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Nextcloud Talk - Android Client - * - * SPDX-FileCopyrightText: 2024 Julius Linus - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -package com.nextcloud.talk.adapters.messages - -import android.content.Context -import android.util.Log -import android.view.View -import androidx.core.content.res.ResourcesCompat -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.isVisible -import autodagger.AutoInjector -import coil.load -import com.nextcloud.android.common.ui.theme.utils.ColorRole -import com.nextcloud.talk.R -import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.chat.ChatActivity -import com.nextcloud.talk.chat.data.model.ChatMessage -import com.nextcloud.talk.databinding.ItemTemporaryMessageBinding -import com.nextcloud.talk.ui.theme.ViewThemeUtils -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils -import com.nextcloud.talk.utils.message.MessageUtils -import com.stfalcon.chatkit.messages.MessagesListAdapter -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import javax.inject.Inject - -@AutoInjector(NextcloudTalkApplication::class) -class TemporaryMessageViewHolder(outgoingView: View, payload: Any) : - MessagesListAdapter.OutcomingMessageViewHolder(outgoingView) { - - private val binding: ItemTemporaryMessageBinding = ItemTemporaryMessageBinding.bind(outgoingView) - - @Inject - lateinit var viewThemeUtils: ViewThemeUtils - - @Inject - lateinit var context: Context - - @Inject - lateinit var messageUtils: MessageUtils - - lateinit var temporaryMessageInterface: TemporaryMessageInterface - var isEditing = false - - override fun onBind(message: ChatMessage) { - super.onBind(message) - NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) - - viewThemeUtils.platform.colorImageView(binding.tempMsgEdit, ColorRole.PRIMARY) - viewThemeUtils.platform.colorImageView(binding.tempMsgDelete, ColorRole.PRIMARY) - - binding.bubble.setOnClickListener { - if (binding.tempMsgActions.isVisible) { - binding.tempMsgActions.visibility = View.GONE - } else { - binding.tempMsgActions.visibility = View.VISIBLE - } - } - - binding.tempMsgEdit.setOnClickListener { - isEditing = !isEditing - if (isEditing) { - binding.tempMsgEdit.setImageDrawable( - ResourcesCompat.getDrawable( - context.resources, - R.drawable.ic_check, - null - ) - ) - binding.messageEdit.visibility = View.VISIBLE - binding.messageEdit.requestFocus() - ViewCompat.getWindowInsetsController(binding.root)?.show(WindowInsetsCompat.Type.ime()) - binding.messageEdit.setText(binding.messageText.text) - binding.messageText.visibility = View.GONE - } else { - binding.tempMsgEdit.setImageDrawable( - ResourcesCompat.getDrawable( - context.resources, - R.drawable.ic_edit, - null - ) - ) - binding.messageEdit.visibility = View.GONE - binding.messageText.visibility = View.VISIBLE - val newMessage = binding.messageEdit.text.toString() - message.message = newMessage - temporaryMessageInterface.editTemporaryMessage(message.tempMessageId, newMessage) - } - } - - binding.tempMsgDelete.setOnClickListener { - temporaryMessageInterface.deleteTemporaryMessage(message.tempMessageId) - } - - // parent message handling - if (message.parentMessageId != null && message.parentMessageId!! > 0) { - processParentMessage(message) - binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE - } else { - binding.messageQuote.quotedChatMessageView.visibility = View.GONE - } - - val bgBubbleColor = bubble.resources.getColor(R.color.bg_message_list_incoming_bubble, null) - val layout = R.drawable.shape_outcoming_message - val bubbleDrawable = DisplayUtils.getMessageSelector( - bgBubbleColor, - ResourcesCompat.getColor(bubble.resources, R.color.transparent, null), - bgBubbleColor, - layout - ) - ViewCompat.setBackground(bubble, bubbleDrawable) - } - - @Suppress("Detekt.TooGenericExceptionCaught") - private fun processParentMessage(message: ChatMessage) { - if (message.parentMessageId != null && !message.isDeleted) { - CoroutineScope(Dispatchers.Main).launch { - try { - val chatActivity = temporaryMessageInterface as ChatActivity - val urlForChatting = ApiUtils.getUrlForChat( - chatActivity.chatApiVersion, - chatActivity.conversationUser?.baseUrl, - chatActivity.roomToken - ) - - val parentChatMessage = withContext(Dispatchers.IO) { - chatActivity.chatViewModel.getMessageById( - urlForChatting, - chatActivity.currentConversation!!, - message.parentMessageId!! - ).first() - } - - parentChatMessage.activeUser = message.activeUser - parentChatMessage.imageUrl?.let { - binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE - val placeholder = ResourcesCompat.getDrawable( - context.resources, - R.drawable.ic_mimetype_image, - null - ) - binding.messageQuote.quotedMessageImage.setImageDrawable(placeholder) - binding.messageQuote.quotedMessageImage.load(it) { - addHeader( - "Authorization", - ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!! - ) - } - } ?: run { - binding.messageQuote.quotedMessageImage.visibility = View.GONE - } - binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName - ?: context.getText(R.string.nc_nick_guest) - binding.messageQuote.quotedMessage.text = messageUtils - .enrichChatReplyMessageText( - binding.messageQuote.quotedMessage.context, - parentChatMessage, - false, - viewThemeUtils - ) - - viewThemeUtils.talk.colorOutgoingQuoteText(binding.messageQuote.quotedMessage) - viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor) - viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView) - - binding.messageQuote.quotedChatMessageView.setOnClickListener { - val chatActivity = temporaryMessageInterface as ChatActivity - chatActivity.jumpToQuotedMessage(parentChatMessage) - } - } catch (e: Exception) { - Log.d(TAG, "Error when processing parent message in view holder", e) - } - } - } - } - - fun assignTemporaryMessageInterface(temporaryMessageInterface: TemporaryMessageInterface) { - this.temporaryMessageInterface = temporaryMessageInterface - } - - override fun viewDetached() { - // unused atm - } - - override fun viewAttached() { - // unused atm - } - - override fun viewRecycled() { - // unused atm - } - - companion object { - private val TAG = TemporaryMessageViewHolder::class.java.simpleName - } -} diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 3a73dc130..cfe4b36e9 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -111,8 +111,6 @@ import com.nextcloud.talk.adapters.messages.PreviewMessageViewHolder import com.nextcloud.talk.adapters.messages.SystemMessageInterface import com.nextcloud.talk.adapters.messages.SystemMessageViewHolder import com.nextcloud.talk.adapters.messages.TalkMessagesListAdapter -import com.nextcloud.talk.adapters.messages.TemporaryMessageInterface -import com.nextcloud.talk.adapters.messages.TemporaryMessageViewHolder import com.nextcloud.talk.adapters.messages.UnreadNoticeMessageViewHolder import com.nextcloud.talk.adapters.messages.VoiceMessageInterface import com.nextcloud.talk.api.NcApi @@ -154,6 +152,7 @@ import com.nextcloud.talk.ui.dialog.FileAttachmentPreviewFragment import com.nextcloud.talk.ui.dialog.MessageActionsDialog import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment import com.nextcloud.talk.ui.dialog.ShowReactionsDialog +import com.nextcloud.talk.ui.dialog.TempMessageActionsDialog import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.utils.ApiUtils @@ -230,8 +229,7 @@ class ChatActivity : CommonMessageInterface, PreviewMessageInterface, SystemMessageInterface, - CallStartedMessageInterface, - TemporaryMessageInterface { + CallStartedMessageInterface { var active = false @@ -600,16 +598,16 @@ class ChatActivity : // } messageInputViewModel.messageQueueSizeFlow.observe(this) { size -> - if (size == 0) { - var i = 0 - var pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING) - while (pos != null && pos > -1) { - adapter?.items?.removeAt(pos) - i++ - pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING) - } - adapter?.notifyDataSetChanged() - } + // if (size == 0) { + // var i = 0 + // var pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING) + // while (pos != null && pos > -1) { + // adapter?.items?.removeAt(pos) + // i++ + // pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING) + // } + // adapter?.notifyDataSetChanged() + // } } this.lifecycleScope.launch { @@ -1253,18 +1251,18 @@ class ChatActivity : viewThemeUtils.material.colorToolbarOverflowIcon(binding.chatToolbar) } - private fun getLastAdapterId(): Int { - var lastId = 0 - if (adapter?.items?.size != 0) { - val item = adapter?.items?.get(0)?.item - if (item != null) { - lastId = (item as ChatMessage).jsonMessageId - } else { - lastId = 0 - } - } - return lastId - } + // private fun getLastAdapterId(): Int { + // var lastId = 0 + // if (adapter?.items?.size != 0) { + // val item = adapter?.items?.get(0)?.item + // if (item != null) { + // lastId = (item as ChatMessage).jsonMessageId + // } else { + // lastId = 0 + // } + // } + // return lastId + // } private fun setupActionBar() { setSupportActionBar(binding.chatToolbar) @@ -1384,17 +1382,6 @@ class ChatActivity : R.layout.item_custom_outcoming_preview_message ) - messageHolders.registerContentType( - CONTENT_TYPE_TEMP, - TemporaryMessageViewHolder::class.java, - payload, - R.layout.item_temporary_message, - TemporaryMessageViewHolder::class.java, - payload, - R.layout.item_temporary_message, - this - ) - messageHolders.registerContentType( CONTENT_TYPE_SYSTEM_MESSAGE, SystemMessageViewHolder::class.java, @@ -3437,9 +3424,16 @@ class ChatActivity : private fun openMessageActionsDialog(iMessage: IMessage?) { val message = iMessage as ChatMessage - if (hasVisibleItems(message) && - !isSystemMessage(message) && - message.id != TEMPORARY_MESSAGE_ID_STRING + + if (message.isTemporary) { + TempMessageActionsDialog( + this, + message, + conversationUser, + currentConversation, + ).show() + } else if (hasVisibleItems(message) && + !isSystemMessage(message) ) { MessageActionsDialog( this, @@ -4011,30 +4005,6 @@ class ChatActivity : startACall(false, false) } - override fun editTemporaryMessage(id: Int, newMessage: String) { - // messageInputViewModel.editQueuedMessage(currentConversation!!.internalId, id, newMessage) - // adapter?.notifyDataSetChanged() // TODO optimize this - } - - override fun deleteTemporaryMessage(id: Int) { - // messageInputViewModel.removeFromQueue(currentConversation!!.internalId, id) - // var i = 0 - // val max = messageInputViewModel.messageQueueSizeFlow.value?.plus(1) - // for (item in adapter?.items!!) { - // if (i > max!! && max < 1) break - // if (item.item is ChatMessage && - // (item.item as ChatMessage).isTempMessage && - // (item.item as ChatMessage).tempMessageId == id - // ) { - // val index = adapter?.items!!.indexOf(item) - // adapter?.items!!.removeAt(index) - // adapter?.notifyItemRemoved(index) - // break - // } - // i++ - // } - } - private fun logConversationInfos(methodName: String) { Log.d(TAG, " |-----------------------------------------------") Log.d(TAG, " | method: $methodName") diff --git a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt index 5e8ee1bc1..33725b5cf 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -916,16 +916,23 @@ class MessageInputFragment : Fragment() { // FIXME Fix API checking with guests? val apiVersion: Int = ApiUtils.getChatApiVersion(chatActivity.spreedCapabilities, intArrayOf(1)) - chatActivity.messageInputViewModel.editChatMessage( - chatActivity.credentials!!, - ApiUtils.getUrlForChatMessage( - apiVersion, - chatActivity.conversationUser!!.baseUrl!!, - chatActivity.roomToken, - message.id - ), - editedMessageText - ) + if (message.isTemporary) { + chatActivity.messageInputViewModel.editTempChatMessage( + message, + editedMessageText + ) + } else { + chatActivity.messageInputViewModel.editChatMessage( + chatActivity.credentials!!, + ApiUtils.getUrlForChatMessage( + apiVersion, + chatActivity.conversationUser!!.baseUrl!!, + chatActivity.roomToken, + message.id + ), + editedMessageText + ) + } } private fun setEditUI(message: ChatMessage) { diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt index 630358040..6ad2599ef 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt @@ -14,6 +14,7 @@ import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow interface ChatMessageRepository : LifecycleAwareManager { @@ -97,4 +98,6 @@ interface ChatMessageRepository : LifecycleAwareManager { ): Flow> suspend fun editChatMessage(credentials: String, url: String, text: String): Flow> + + suspend fun editTempChatMessage(message: ChatMessage, editedMessageText: String): Flow } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt index 0e50ef18a..70f1139e6 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt @@ -115,9 +115,9 @@ data class ChatMessage( var openWhenDownloaded: Boolean = true, - var isTempMessage: Boolean = false, // TODO: replace logic from message drafts with logic from temp message sending + var isTemporary: Boolean = false, // TODO: replace logic from message drafts with logic from temp message sending - var tempMessageId: Int = -1, // TODO: replace logic from message drafts with logic from temp message sending + // var tempMessageId: Int = -1, // TODO: replace logic from message drafts with logic from temp message sending var referenceId: String? = null, diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index f08b278b4..25372edc8 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -13,10 +13,6 @@ import android.util.Log import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.data.ChatMessageRepository import com.nextcloud.talk.chat.data.model.ChatMessage -import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel -import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel.Companion -import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel.SendChatMessageErrorState -import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel.SendChatMessageSuccessState import com.nextcloud.talk.data.database.dao.ChatBlocksDao import com.nextcloud.talk.data.database.dao.ChatMessagesDao import com.nextcloud.talk.data.database.mappers.asEntity @@ -41,10 +37,13 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.retryWhen import kotlinx.coroutines.launch +import java.io.IOException import javax.inject.Inject class OfflineFirstChatRepository @Inject constructor( @@ -338,32 +337,6 @@ class OfflineFirstChatRepository @Inject constructor( } } - // TODO replace with WorkManager? - // private suspend fun tryToSendPendingMessages() { - // val tempMessages = chatDao.getTempMessagesForConversation(internalConversationId).first() - // - // tempMessages.forEach { - // Log.d(TAG, "Sending chat message ${it.message} another time!!") - // - // sendChatMessage( - // credentials, - // urlForChatting, - // it.message, - // it.actorDisplayName, - // it.parentMessageId?.toInt() ?: 0, - // false, - // it.referenceId ?: "" - // ).collect { result -> - // if (result.isSuccess) { - // Log.d(TAG, "success. received ref id: " + (result.getOrNull()?.referenceId ?: "none")) - // - // } else { - // Log.d(TAG, "fail. received ref id: " + (result.getOrNull()?.referenceId ?: "none")) - // } - // } - // } - // } - private suspend fun handleNewAndTempMessages( receivedChatMessages : List, lookIntoFuture: Boolean, @@ -829,35 +802,42 @@ class OfflineFirstChatRepository @Inject constructor( referenceId: String ): Flow> = flow { - try { - val response = network.sendChatMessage( - credentials, - url, - message, - displayName, - replyTo, - sendWithoutNotification, - referenceId - ) + val response = network.sendChatMessage( + credentials, + url, + message, + displayName, + replyTo, + sendWithoutNotification, + referenceId + ) - val chatMessageModel = response.ocs?.data?.asModel() + val chatMessageModel = response.ocs?.data?.asModel() - emit(Result.success(chatMessageModel)) - } catch (e: Exception) { - Log.e(TAG, "Error when sending message", e) + emit(Result.success(chatMessageModel)) + } + // .retryWhen { cause, attempt -> + // if (cause is IOException && attempt < 3) { + // delay(2000) + // return@retryWhen true + // } else { + // return@retryWhen false + // } + // } + .catch { e -> + Log.e(TAG, "Error when sending message", e) - val failedMessage = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first() - failedMessage.sendingFailed = true - chatDao.updateChatMessage(failedMessage) + val failedMessage = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first() + failedMessage.sendingFailed = true + chatDao.updateChatMessage(failedMessage) - // val failedMessageModel = failedMessage.asModel() - // _removeMessageFlow.emit(failedMessageModel) - // - // val tripleChatMessages = Triple(true, false, listOf(failedMessageModel)) - // _messageFlow.emit(tripleChatMessages) + val failedMessageModel = failedMessage.asModel() + _removeMessageFlow.emit(failedMessageModel) - emit(Result.failure(e)) - } + val tripleChatMessages = Triple(true, false, listOf(failedMessageModel)) + _messageFlow.emit(tripleChatMessages) + + emit(Result.failure(e)) } override suspend fun editChatMessage( @@ -878,6 +858,30 @@ class OfflineFirstChatRepository @Inject constructor( } } + override suspend fun editTempChatMessage( + message: ChatMessage, editedMessageText: String + ): Flow = + flow { + try { + val messageToEdit = chatDao.getChatMessageForConversation(internalConversationId, message.jsonMessageId + .toLong()).first() + messageToEdit.message = editedMessageText + chatDao.upsertChatMessage(messageToEdit) + + + val editedMessageModel = messageToEdit.asModel() + _removeMessageFlow.emit(editedMessageModel) + + val tripleChatMessages = Triple(true, false, listOf(editedMessageModel)) + _messageFlow.emit(tripleChatMessages) + + + emit(true) + } catch (e: Exception) { + emit(false) + } + } + override suspend fun addTemporaryMessage( message: CharSequence, displayName: String, @@ -917,7 +921,7 @@ class OfflineFirstChatRepository @Inject constructor( val entity = ChatMessageEntity( internalId = internalConversationId + "@_temp_" + currentTimeMillies, internalConversationId = internalConversationId, - id = currentTimeMillies, + id = currentTimeMillies, // TODO: currentTimeMillies fails as id because later on in the model it's not Long but Int!!!! message = message, deleted = false, token = conversationModel.token, diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 238a6f54d..1e2a89e43 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -219,6 +219,21 @@ class MessageInputViewModel @Inject constructor( } } + fun editTempChatMessage(message: ChatMessage, editedMessageText: String) { + viewModelScope.launch { + chatRepository.editTempChatMessage( + message, + editedMessageText + ).collect { result -> + if (true) { + // _editMessageViewState.value = EditMessageSuccessState(result) + } else { + // _editMessageViewState.value = EditMessageErrorState + } + } + } + } + fun reply(message: IMessage?) { _getReplyChatMessage.postValue(message) } diff --git a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt index b3179c149..7fb423767 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt @@ -32,6 +32,7 @@ interface ChatMessagesDao { SELECT * FROM ChatMessages WHERE internalConversationId = :internalConversationId + AND isTemporary = 0 ORDER BY timestamp DESC, id DESC """ ) @@ -78,10 +79,10 @@ interface ChatMessagesDao { @Query( value = """ DELETE FROM ChatMessages - WHERE id in (:messageIds) + WHERE internalId in (:internalIds) """ ) - fun deleteChatMessages(messageIds: List) + fun deleteChatMessages(internalIds: List) @Query( value = """ diff --git a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt index 1392a2920..5b95abe69 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt @@ -66,7 +66,7 @@ fun ChatMessageEntity.asModel() = lastEditTimestamp = lastEditTimestamp, isDeleted = deleted, referenceId = referenceId, - isTempMessage = isTemporary, + isTemporary = isTemporary, sendingFailed = sendingFailed, readStatus = setStatus(isTemporary, sendingFailed) ) diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt new file mode 100644 index 000000000..e9e73228a --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt @@ -0,0 +1,124 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2022 Marcel Hibbe + * SPDX-FileCopyrightText: 2022 Andy Scherzinger + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.talk.ui.dialog + +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import autodagger.AutoInjector +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.nextcloud.talk.R +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.chat.ChatActivity +import com.nextcloud.talk.chat.data.model.ChatMessage +import com.nextcloud.talk.data.network.NetworkMonitor +import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.databinding.DialogMessageActionsBinding +import com.nextcloud.talk.databinding.DialogTempMessageActionsBinding +import com.nextcloud.talk.models.domain.ConversationModel +import com.nextcloud.talk.ui.theme.ViewThemeUtils +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.DateUtils +import kotlinx.coroutines.launch +import javax.inject.Inject + +@AutoInjector(NextcloudTalkApplication::class) +class TempMessageActionsDialog( + private val chatActivity: ChatActivity, + private val message: ChatMessage, + private val user: User?, + private val currentConversation: ConversationModel? +) : BottomSheetDialog(chatActivity) { + + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + @Inject + lateinit var dateUtils: DateUtils + + @Inject + lateinit var networkMonitor: NetworkMonitor + + private lateinit var binding: DialogTempMessageActionsBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this) + + binding = DialogTempMessageActionsBinding.inflate(layoutInflater) + setContentView(binding.root) + window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + + viewThemeUtils.material.colorBottomSheetBackground(binding.root) + viewThemeUtils.material.colorBottomSheetDragHandle(binding.bottomSheetDragHandle) + + initMenuItemCopy(!message.isDeleted) + val apiVersion = ApiUtils.getConversationApiVersion(user!!, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1)) + + + initMenuItems() + } + + private fun initMenuItems() { + this.lifecycleScope.launch { + initMenuEditMessage(true) + initMenuDeleteMessage(true) + } + } + + override fun onStart() { + super.onStart() + val bottomSheet = findViewById(R.id.design_bottom_sheet) + val behavior = BottomSheetBehavior.from(bottomSheet as View) + behavior.state = BottomSheetBehavior.STATE_COLLAPSED + } + + private fun initMenuDeleteMessage(visible: Boolean) { + if (visible) { + binding.menuDeleteMessage.setOnClickListener { + chatActivity.deleteMessage(message) + dismiss() + } + } + binding.menuDeleteMessage.visibility = getVisibility(visible) + } + + private fun initMenuEditMessage(visible: Boolean) { + binding.menuEditMessage.setOnClickListener { + chatActivity.messageInputViewModel.edit(message) + dismiss() + } + + binding.menuEditMessage.visibility = getVisibility(visible) + } + + private fun initMenuItemCopy(visible: Boolean) { + if (visible) { + binding.menuCopyMessage.setOnClickListener { + chatActivity.copyMessage(message) + dismiss() + } + } + + binding.menuCopyMessage.visibility = getVisibility(visible) + } + + private fun getVisibility(visible: Boolean): Int { + return if (visible) { + View.VISIBLE + } else { + View.GONE + } + } + + companion object { + private val TAG = TempMessageActionsDialog::class.java.simpleName + } +} diff --git a/app/src/main/res/layout/dialog_temp_message_actions.xml b/app/src/main/res/layout/dialog_temp_message_actions.xml new file mode 100644 index 000000000..c377fafc8 --- /dev/null +++ b/app/src/main/res/layout/dialog_temp_message_actions.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +