diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingDeckCardViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingDeckCardViewHolder.kt index 503a7b54c..f3e9bf3f3 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingDeckCardViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingDeckCardViewHolder.kt @@ -182,7 +182,7 @@ class IncomingDeckCardViewHolder(incomingView: View, payload: Any) : viewThemeUtils.talk.themeIncomingMessageBubble(bubble, message.isGrouped, message.isDeleted) } - @Suppress("Detekt.TooGenericExceptionCaught") + @Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod") private fun setParentMessageDataOnMessageItem(message: ChatMessage) { if (message.parentMessageId != null && !message.isDeleted) { CoroutineScope(Dispatchers.Main).launch { @@ -232,7 +232,15 @@ class IncomingDeckCardViewHolder(incomingView: View, payload: Any) : binding.messageQuote.quoteColoredView ) - binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE + binding.messageQuote.quotedChatMessageView.visibility = + if (!message.isDeleted && + message.parentMessageId != null && + message.parentMessageId != chatActivity.conversationThreadId + ) { + View.VISIBLE + } else { + View.GONE + } } catch (e: Exception) { Log.d(TAG, "Error when processing parent message in view holder", e) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt index b19931726..0a65894de 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt @@ -157,7 +157,7 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) : viewThemeUtils.talk.themeIncomingMessageBubble(bubble, message.isGrouped, message.isDeleted) } - @Suppress("Detekt.TooGenericExceptionCaught") + @Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod") private fun setParentMessageDataOnMessageItem(message: ChatMessage) { if (message.parentMessageId != null && !message.isDeleted) { CoroutineScope(Dispatchers.Main).launch { @@ -207,7 +207,15 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) : binding.messageQuote.quoteColoredView ) - binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE + binding.messageQuote.quotedChatMessageView.visibility = + if (!message.isDeleted && + message.parentMessageId != null && + message.parentMessageId != chatActivity.conversationThreadId + ) { + View.VISIBLE + } else { + View.GONE + } } catch (e: Exception) { Log.d(TAG, "Error when processing parent message in view holder", e) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt index 3983098f3..9441effbd 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt @@ -142,7 +142,7 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : viewThemeUtils.talk.themeIncomingMessageBubble(bubble, message.isGrouped, message.isDeleted) } - @Suppress("Detekt.TooGenericExceptionCaught") + @Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod") private fun setParentMessageDataOnMessageItem(message: ChatMessage) { if (message.parentMessageId != null && !message.isDeleted) { CoroutineScope(Dispatchers.Main).launch { @@ -192,7 +192,15 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : binding.messageQuote.quoteColoredView ) - binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE + binding.messageQuote.quotedChatMessageView.visibility = + if (!message.isDeleted && + message.parentMessageId != null && + message.parentMessageId != chatActivity.conversationThreadId + ) { + View.VISIBLE + } else { + View.GONE + } } catch (e: Exception) { Log.d(TAG, "Error when processing parent message in view holder", e) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt index 60d078579..1b66bb198 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt @@ -164,7 +164,7 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : viewThemeUtils.talk.themeIncomingMessageBubble(bubble, message.isGrouped, message.isDeleted) } - @Suppress("Detekt.TooGenericExceptionCaught") + @Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod") private fun setParentMessageDataOnMessageItem(message: ChatMessage) { if (message.parentMessageId != null && !message.isDeleted) { CoroutineScope(Dispatchers.Main).launch { @@ -213,7 +213,15 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : message, binding.messageQuote.quoteColoredView ) - binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE + binding.messageQuote.quotedChatMessageView.visibility = + if (!message.isDeleted && + message.parentMessageId != null && + message.parentMessageId != chatActivity.conversationThreadId + ) { + View.VISIBLE + } else { + View.GONE + } } catch (e: Exception) { Log.d(TAG, "Error when processing parent message in view holder", e) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt index 89a0a2e30..1cdeab177 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt @@ -304,7 +304,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : ) } - @Suppress("Detekt.TooGenericExceptionCaught") + @Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod") private fun setParentMessageDataOnMessageItem(message: ChatMessage) { if (message.parentMessageId != null && !message.isDeleted) { CoroutineScope(Dispatchers.Main).launch { @@ -355,6 +355,16 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) : ) binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE + + binding.messageQuote.quotedChatMessageView.visibility = + if (!message.isDeleted && + message.parentMessageId != null && + message.parentMessageId != chatActivity.conversationThreadId + ) { + View.VISIBLE + } else { + View.GONE + } } catch (e: Exception) { Log.d(TAG, "Error when processing parent message in view holder", e) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingDeckCardViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingDeckCardViewHolder.kt index 2aec3b1b4..25d40fb6e 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingDeckCardViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingDeckCardViewHolder.kt @@ -170,7 +170,7 @@ class OutcomingDeckCardViewHolder(outcomingView: View) : commonMessageInterface.onClickReaction(chatMessage, emoji) } - @Suppress("Detekt.TooGenericExceptionCaught") + @Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod") private fun setParentMessageDataOnMessageItem(message: ChatMessage) { if (message.parentMessageId != null && !message.isDeleted) { CoroutineScope(Dispatchers.Main).launch { @@ -220,7 +220,15 @@ class OutcomingDeckCardViewHolder(outcomingView: View) : binding.messageQuote.quoteColoredView ) - binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE + binding.messageQuote.quotedChatMessageView.visibility = + if (!message.isDeleted && + message.parentMessageId != null && + message.parentMessageId != chatActivity.conversationThreadId + ) { + View.VISIBLE + } else { + View.GONE + } } catch (e: Exception) { Log.d(TAG, "Error when processing parent message in view holder", e) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt index 8d553dae3..4c4cc72c6 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt @@ -146,7 +146,7 @@ class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) : commonMessageInterface.onClickReaction(chatMessage, emoji) } - @Suppress("Detekt.TooGenericExceptionCaught") + @Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod") private fun setParentMessageDataOnMessageItem(message: ChatMessage) { if (message.parentMessageId != null && !message.isDeleted) { CoroutineScope(Dispatchers.Main).launch { @@ -190,7 +190,15 @@ class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) : viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor) viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView) - binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE + binding.messageQuote.quotedChatMessageView.visibility = + if (!message.isDeleted && + message.parentMessageId != null && + message.parentMessageId != chatActivity.conversationThreadId + ) { + View.VISIBLE + } else { + View.GONE + } } catch (e: Exception) { Log.d(TAG, "Error when processing parent message in view holder", e) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt index d0d38c891..e16129af8 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt @@ -196,7 +196,7 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : }) } - @Suppress("Detekt.TooGenericExceptionCaught") + @Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod") private fun setParentMessageDataOnMessageItem(message: ChatMessage) { if (message.parentMessageId != null && !message.isDeleted) { CoroutineScope(Dispatchers.Main).launch { @@ -240,7 +240,15 @@ class OutcomingLocationMessageViewHolder(incomingView: View) : viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor) viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView) - binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE + binding.messageQuote.quotedChatMessageView.visibility = + if (!message.isDeleted && + message.parentMessageId != null && + message.parentMessageId != chatActivity.conversationThreadId + ) { + View.VISIBLE + } else { + View.GONE + } } catch (e: Exception) { Log.d(TAG, "Error when processing parent message in view holder", e) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt index 450f36a82..e34e2fbf8 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt @@ -158,7 +158,7 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : } } - @Suppress("Detekt.TooGenericExceptionCaught") + @Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod") private fun setParentMessageDataOnMessageItem(message: ChatMessage) { if (message.parentMessageId != null && !message.isDeleted) { CoroutineScope(Dispatchers.Main).launch { @@ -202,7 +202,15 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor) viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView) - binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE + binding.messageQuote.quotedChatMessageView.visibility = + if (!message.isDeleted && + message.parentMessageId != null && + message.parentMessageId != chatActivity.conversationThreadId + ) { + View.VISIBLE + } else { + View.GONE + } } catch (e: Exception) { Log.d(TAG, "Error when processing parent message in view holder", e) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt index 184c09664..94dd8ca56 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt @@ -307,7 +307,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : binding.progressBar.visibility = View.VISIBLE } - @Suppress("Detekt.TooGenericExceptionCaught") + @Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod") private fun setParentMessageDataOnMessageItem(message: ChatMessage) { if (message.parentMessageId != null && !message.isDeleted) { CoroutineScope(Dispatchers.Main).launch { @@ -351,7 +351,15 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) : viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor) viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView) - binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE + binding.messageQuote.quotedChatMessageView.visibility = + if (!message.isDeleted && + message.parentMessageId != null && + message.parentMessageId != chatActivity.conversationThreadId + ) { + View.VISIBLE + } else { + View.GONE + } } catch (e: Exception) { Log.d(TAG, "Error when processing parent message in view holder", e) } 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 905bc9b37..7c89fe228 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -227,6 +227,7 @@ import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import java.io.File import java.io.IOException +import java.lang.Exception import java.net.HttpURLConnection import java.text.SimpleDateFormat import java.time.Instant @@ -2339,11 +2340,26 @@ class ChatActivity : BuildConfig.APPLICATION_ID, File(file.absolutePath) ) - uploadFile(shareUri.toString(), false) + uploadFile( + fileUri = shareUri.toString(), + isVoiceMessage = false, + caption = "", + roomToken = roomToken, + replyToMessageId = getReplyToMessageId(), + displayName = currentConversation?.displayName ?: "" + ) } cursor?.close() } + fun getReplyToMessageId(): Int { + var replyMessageId = messageInputViewModel.getReplyChatMessage.value?.id?.toInt() + if (replyMessageId == null || replyMessageId == 0) { + replyMessageId = conversationThreadInfo?.thread?.id ?: 0 + } + return replyMessageId + } + @Throws(IllegalStateException::class) private fun onPickCameraResult(intent: Intent?) { try { @@ -2515,35 +2531,27 @@ class ChatActivity : private fun uploadFiles(files: MutableList, caption: String = "") { for (i in 0 until files.size) { if (i == files.size - 1) { - uploadFile(files[i], false, caption) + uploadFile( + fileUri = files[i], + isVoiceMessage = false, + caption = caption, + roomToken = roomToken, + replyToMessageId = getReplyToMessageId(), + displayName = currentConversation?.displayName!! + ) } else { - uploadFile(files[i], false) + uploadFile( + fileUri = files[i], + isVoiceMessage = false, + caption = "", + roomToken = roomToken, + replyToMessageId = getReplyToMessageId(), + displayName = currentConversation?.displayName!! + ) } } } - private fun uploadFile(fileUri: String, isVoiceMessage: Boolean, caption: String = "", token: String = "") { - var metaData = "" - var room = "" - - if (!participantPermissions.hasChatPermission()) { - Log.w(TAG, "uploading file(s) is forbidden because of missing attendee permissions") - return - } - - if (isVoiceMessage) { - metaData = VOICE_MESSAGE_META_DATA - } - - if (caption != "") { - metaData = "{\"caption\":\"$caption\"}" - } - - if (token == "") room = roomToken else room = token - - chatViewModel.uploadFile(fileUri, room, currentConversation?.displayName!!, metaData) - } - fun showGalleryPicker() { pickMultipleMedia.launch(PickVisualMediaRequest(PickVisualMedia.ImageAndVideo)) } @@ -3872,7 +3880,14 @@ class ChatActivity : val type = message.getCalculateMessageType() when (type) { ChatMessage.MessageType.VOICE_MESSAGE -> { - uploadFile(shareUri.toString(), true, token = roomToken) + uploadFile( + shareUri.toString(), + true, + roomToken = roomToken, + caption = "", + replyToMessageId = getReplyToMessageId(), + displayName = currentConversation?.displayName ?: "" + ) showSnackBar(roomToken) } @@ -3881,12 +3896,26 @@ class ChatActivity : if (null != shareUri) { try { context.contentResolver.openInputStream(shareUri)?.close() - uploadFile(shareUri.toString(), false, caption!!, roomToken) + uploadFile( + fileUri = shareUri.toString(), + isVoiceMessage = false, + caption = caption!!, + roomToken = roomToken, + replyToMessageId = getReplyToMessageId(), + displayName = currentConversation?.displayName ?: "" + ) showSnackBar(roomToken) - } catch (e: java.lang.Exception) { - Log.w(TAG, "File corresponding to the uri does not exist $shareUri") + } catch (e: Exception) { + Log.w(TAG, "File corresponding to the uri does not exist $shareUri", e) downloadFileToCache(message, false) { - uploadFile(shareUri.toString(), false, caption!!, roomToken) + uploadFile( + fileUri = shareUri.toString(), + isVoiceMessage = false, + caption = caption!!, + roomToken = roomToken, + replyToMessageId = getReplyToMessageId(), + displayName = currentConversation?.displayName ?: "" + ) showSnackBar(roomToken) } } @@ -4300,6 +4329,33 @@ class ChatActivity : ) } + fun uploadFile( + fileUri: String, + isVoiceMessage: Boolean, + caption: String = "", + roomToken: String = "", + replyToMessageId: Int? = null, + displayName: String + ) { + chatViewModel.uploadFile( + fileUri, + isVoiceMessage, + caption, + roomToken, + replyToMessageId, + displayName + ) + cancelReply() + } + + fun cancelReply() { + messageInputViewModel.reply(null) + chatViewModel.messageDraft.quotedMessageText = null + chatViewModel.messageDraft.quotedDisplayName = null + chatViewModel.messageDraft.quotedImageUrl = null + chatViewModel.messageDraft.quotedJsonId = null + } + companion object { val TAG = ChatActivity::class.simpleName private const val CONTENT_TYPE_CALL_STARTED: Byte = 1 @@ -4319,7 +4375,6 @@ class ChatActivity : private const val REQUEST_RECORD_AUDIO_PERMISSION = 222 private const val REQUEST_READ_CONTACT_PERMISSION = 234 private const val REQUEST_CAMERA_PERMISSION = 223 - private const val VOICE_MESSAGE_META_DATA = "{\"messageType\":\"voice-message\"}" private const val FILE_DATE_PATTERN = "yyyy-MM-dd HH-mm-ss" private const val VIDEO_SUFFIX = ".mp4" private const val FULLY_OPAQUE_INT: Int = 255 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 7157608fc..8ed6aa76d 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -101,7 +101,6 @@ class MessageInputFragment : Fragment() { private const val TYPING_INTERVAL_TO_SEND_NEXT_TYPING_MESSAGE = 1000L private const val TYPING_STARTED_SIGNALING_MESSAGE_TYPE = "startedTyping" private const val TYPING_STOPPED_SIGNALING_MESSAGE_TYPE = "stoppedTyping" - const val VOICE_MESSAGE_META_DATA = "{\"messageType\":\"voice-message\"}" private const val QUOTED_MESSAGE_IMAGE_MAX_HEIGHT = 96f private const val MENTION_AUTO_COMPLETE_ELEVATION = 6f private const val MINIMUM_VOICE_RECORD_DURATION: Int = 1000 @@ -178,13 +177,17 @@ class MessageInputFragment : Fragment() { private fun initObservers() { Log.d(TAG, "LifeCyclerOwner is: ${viewLifecycleOwner.lifecycle}") chatActivity.messageInputViewModel.getReplyChatMessage.observe(viewLifecycleOwner) { message -> - (message as ChatMessage?)?.let { + message?.let { chatActivity.chatViewModel.messageDraft.quotedMessageText = message.text chatActivity.chatViewModel.messageDraft.quotedDisplayName = message.actorDisplayName chatActivity.chatViewModel.messageDraft.quotedImageUrl = message.imageUrl chatActivity.chatViewModel.messageDraft.quotedJsonId = message.jsonMessageId - replyToMessage(message.text, message.actorDisplayName, message.imageUrl, message.jsonMessageId) - } + replyToMessage( + message.text, + message.actorDisplayName, + message.imageUrl + ) + } ?: clearReplyUi() } chatActivity.messageInputViewModel.getEditChatMessage.observe(viewLifecycleOwner) { message -> @@ -315,8 +318,7 @@ class MessageInputFragment : Fragment() { replyToMessage( chatActivity.chatViewModel.messageDraft.quotedMessageText, chatActivity.chatViewModel.messageDraft.quotedDisplayName, - chatActivity.chatViewModel.messageDraft.quotedImageUrl, - chatActivity.chatViewModel.messageDraft.quotedJsonId ?: 0 + chatActivity.chatViewModel.messageDraft.quotedImageUrl ) } } @@ -392,7 +394,14 @@ class MessageInputFragment : Fragment() { // See: https://developer.android.com/guide/topics/text/image-keyboard (binding.fragmentMessageInputView.inputEditText as ImageEmojiEditText).onCommitContentListener = { - uploadFile(it.toString(), false) + chatActivity.chatViewModel.uploadFile( + fileUri = it.toString(), + isVoiceMessage = false, + caption = "", + roomToken = chatActivity.roomToken, + replyToMessageId = chatActivity.getReplyToMessageId(), + displayName = chatActivity.currentConversation?.displayName!! + ) } if (chatActivity.sharedText.isNotEmpty()) { @@ -462,6 +471,10 @@ class MessageInputFragment : Fragment() { binding.fragmentCallStarted.callStartedSecondaryText.visibility = if (collapsed) View.VISIBLE else View.GONE setDropDown(collapsed) } + + binding.fragmentMessageInputView.findViewById(R.id.cancelReplyButton)?.setOnClickListener { + cancelReply() + } } private fun setDropDown(collapsed: Boolean) { @@ -564,9 +577,9 @@ class MessageInputFragment : Fragment() { return@setOnTouchListener false } else { chatActivity.chatViewModel.stopAndSendAudioRecording( - chatActivity.roomToken, - chatActivity.currentConversation!!.displayName, - VOICE_MESSAGE_META_DATA + roomToken = chatActivity.roomToken, + replyToMessageId = chatActivity.getReplyToMessageId(), + displayName = chatActivity.currentConversation!!.displayName ) } resetSlider() @@ -713,16 +726,9 @@ class MessageInputFragment : Fragment() { } } - private fun replyToMessage( - quotedMessageText: String?, - quotedActorDisplayName: String?, - quotedImageUrl: String?, - quotedJsonId: Int - ) { + private fun replyToMessage(quotedMessageText: String?, quotedActorDisplayName: String?, quotedImageUrl: String?) { Log.d(TAG, "Reply") val view = binding.fragmentMessageInputView - view.findViewById(R.id.attachmentButton)?.visibility = - View.GONE view.findViewById(R.id.cancelReplyButton)?.visibility = View.VISIBLE @@ -757,9 +763,7 @@ class MessageInputFragment : Fragment() { } } - val quotedChatMessageView = - view.findViewById(R.id.quotedChatMessageView) - quotedChatMessageView?.tag = quotedJsonId + val quotedChatMessageView = view.findViewById(R.id.quotedChatMessageView) quotedChatMessageView?.visibility = View.VISIBLE } @@ -827,28 +831,6 @@ class MessageInputFragment : Fragment() { private fun isTypingStatusEnabled(): Boolean = !CapabilitiesUtil.isTypingStatusPrivate(chatActivity.conversationUser!!) - private fun uploadFile(fileUri: String, isVoiceMessage: Boolean, caption: String = "", token: String = "") { - var metaData = "" - val room: String - - if (!chatActivity.participantPermissions.hasChatPermission()) { - Log.w(ChatActivity.TAG, "uploading file(s) is forbidden because of missing attendee permissions") - return - } - - if (isVoiceMessage) { - metaData = VOICE_MESSAGE_META_DATA - } - - if (caption != "") { - metaData = "{\"caption\":\"$caption\"}" - } - - if (token == "") room = chatActivity.roomToken else room = token - - chatActivity.chatViewModel.uploadFile(fileUri, room, chatActivity.currentConversation!!.displayName, metaData) - } - private fun submitMessage(sendWithoutNotification: Boolean) { if (binding.fragmentMessageInputView.inputEditText != null) { val editable = binding.fragmentMessageInputView.inputEditText!!.editableText @@ -856,23 +838,15 @@ class MessageInputFragment : Fragment() { binding.fragmentMessageInputView.inputEditText?.setText("") sendStopTypingMessage() - var replyMessageId = binding.fragmentMessageInputView - .findViewById(R.id.quotedChatMessageView)?.tag as Int? ?: 0 - - if (replyMessageId == 0) { - replyMessageId = chatActivity.conversationThreadInfo?.thread?.id ?: 0 - } - sendMessage( editable.toString(), - replyMessageId, sendWithoutNotification ) cancelReply() } } - private fun sendMessage(message: String, replyTo: Int?, sendWithoutNotification: Boolean) { + private fun sendMessage(message: String, sendWithoutNotification: Boolean) { chatActivity.messageInputViewModel.sendChatMessage( chatActivity.conversationUser!!.getCredentials(), ApiUtils.getUrlForChat( @@ -882,7 +856,7 @@ class MessageInputFragment : Fragment() { ), message, chatActivity.conversationUser!!.displayName ?: "", - replyTo ?: 0, + chatActivity.getReplyToMessageId(), sendWithoutNotification ) } @@ -983,10 +957,6 @@ class MessageInputFragment : Fragment() { private fun themeMessageInputView() { binding.fragmentMessageInputView.button?.let { viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) } - binding.fragmentMessageInputView.findViewById(R.id.cancelReplyButton)?.setOnClickListener { - cancelReply() - } - binding.fragmentMessageInputView.findViewById(R.id.cancelReplyButton)?.let { viewThemeUtils.platform .themeImageButton(it) @@ -1043,17 +1013,14 @@ class MessageInputFragment : Fragment() { } private fun cancelReply() { - val quote = binding.fragmentMessageInputView - .findViewById(R.id.quotedChatMessageView) - quote.visibility = View.GONE - quote.tag = null - binding.fragmentMessageInputView.findViewById(R.id.attachmentButton)?.visibility = View.VISIBLE - chatActivity.messageInputViewModel.reply(null) + chatActivity.cancelReply() + clearReplyUi() + } - chatActivity.chatViewModel.messageDraft.quotedMessageText = null - chatActivity.chatViewModel.messageDraft.quotedDisplayName = null - chatActivity.chatViewModel.messageDraft.quotedImageUrl = null - chatActivity.chatViewModel.messageDraft.quotedJsonId = null + private fun clearReplyUi() { + val quote = binding.fragmentMessageInputView.findViewById(R.id.quotedChatMessageView) + quote.visibility = View.GONE + binding.fragmentMessageInputView.findViewById(R.id.attachmentButton)?.visibility = View.VISIBLE } private fun isInReplyState(): Boolean { diff --git a/app/src/main/java/com/nextcloud/talk/chat/MessageInputVoiceRecordingFragment.kt b/app/src/main/java/com/nextcloud/talk/chat/MessageInputVoiceRecordingFragment.kt index 945622eec..98f529f18 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputVoiceRecordingFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputVoiceRecordingFragment.kt @@ -114,9 +114,9 @@ class MessageInputVoiceRecordingFragment : Fragment() { binding.sendVoiceRecording.setOnClickListener { chatActivity.chatViewModel.stopAndSendAudioRecording( - chatActivity.roomToken, - chatActivity.currentConversation!!.displayName, - MessageInputFragment.VOICE_MESSAGE_META_DATA + roomToken = chatActivity.roomToken, + replyToMessageId = chatActivity.getReplyToMessageId(), + displayName = chatActivity.currentConversation!!.displayName ) clear() } 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 796b74fb2..03654dd0a 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 @@ -445,6 +445,7 @@ class OfflineFirstChatRepository @Inject constructor( return loadFromServer } + @Suppress("LongParameterList") private fun getFieldMap( lookIntoFuture: Boolean, timeout: Int, diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt index e0c97381b..e5e41684a 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt @@ -17,6 +17,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.google.gson.Gson import com.nextcloud.talk.chat.data.ChatMessageRepository import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager import com.nextcloud.talk.chat.data.io.MediaPlayerManager @@ -43,6 +44,7 @@ import com.nextcloud.talk.models.json.userAbsence.UserAbsenceData import com.nextcloud.talk.repositories.reactions.ReactionsRepository import com.nextcloud.talk.threadsoverview.data.ThreadsRepository import com.nextcloud.talk.ui.PlaybackSpeed +import com.nextcloud.talk.utils.ParticipantPermissions import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew import com.nextcloud.talk.utils.preferences.AppPreferences @@ -92,6 +94,7 @@ class ChatViewModel @Inject constructor( val mediaPlayerPosition = mediaPlayerManager.mediaPlayerPosition var chatRoomToken: String = "" var messageDraft: MessageDraft = MessageDraft() + lateinit var participantPermissions: ParticipantPermissions fun getChatRepository(): ChatMessageRepository = chatRepository @@ -316,6 +319,10 @@ class ChatViewModel @Inject constructor( } else { _getCapabilitiesViewState.value = GetCapabilitiesUpdateState(user.capabilities!!.spreedCapability!!) } + participantPermissions = ParticipantPermissions( + user.capabilities!!.spreedCapability!!, + conversationModel + ) } else { chatNetworkDataSource.getCapabilities(user, token) .subscribeOn(Schedulers.io()) @@ -331,6 +338,10 @@ class ChatViewModel @Inject constructor( } else { _getCapabilitiesViewState.value = GetCapabilitiesUpdateState(spreedCapabilities) } + participantPermissions = ParticipantPermissions( + spreedCapabilities, + conversationModel + ) } override fun onError(e: Throwable) { @@ -680,13 +691,20 @@ class ChatViewModel @Inject constructor( } } - fun stopAndSendAudioRecording(room: String, displayName: String, metaData: String) { + fun stopAndSendAudioRecording(roomToken: String = "", replyToMessageId: Int? = null, displayName: String) { stopAudioRecording() if (mediaRecorderManager.mediaRecorderState != MediaRecorderManager.MediaRecorderState.ERROR) { val uri = Uri.fromFile(File(mediaRecorderManager.currentVoiceRecordFile)) Log.d(TAG, "File uploaded") - uploadFile(uri.toString(), room, displayName, metaData) + uploadFile( + fileUri = uri.toString(), + isVoiceMessage = true, + caption = "", + roomToken = roomToken, + replyToMessageId = replyToMessageId, + displayName = displayName + ) } } @@ -699,7 +717,38 @@ class ChatViewModel @Inject constructor( fun getCurrentVoiceRecordFile(): String = mediaRecorderManager.currentVoiceRecordFile - fun uploadFile(fileUri: String, room: String, displayName: String, metaData: String) { + fun uploadFile( + fileUri: String, + isVoiceMessage: Boolean, + caption: String = "", + roomToken: String = "", + replyToMessageId: Int? = null, + displayName: String + ) { + val metaDataMap = mutableMapOf() + var room = "" + + if (!participantPermissions.hasChatPermission()) { + Log.w(TAG, "uploading file(s) is forbidden because of missing attendee permissions") + return + } + + if (replyToMessageId != 0) { + metaDataMap["replyTo"] = replyToMessageId.toString() + } + + if (isVoiceMessage) { + metaDataMap["messageType"] = "voice-message" + } + + if (caption != "") { + metaDataMap["caption"] = caption + } + + val metaData = Gson().toJson(metaDataMap) + + room = if (roomToken == "") chatRoomToken else roomToken + try { require(fileUri.isNotEmpty()) UploadAndShareFilesWorker.upload( 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 7c081b9d2..a31129210 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 @@ -90,8 +90,8 @@ class MessageInputViewModel @Inject constructor( val getEditChatMessage: LiveData get() = _getEditChatMessage - private val _getReplyChatMessage: MutableLiveData = MutableLiveData() - val getReplyChatMessage: LiveData + private val _getReplyChatMessage: MutableLiveData = MutableLiveData() + val getReplyChatMessage: LiveData get() = _getReplyChatMessage sealed interface ViewState @@ -203,7 +203,7 @@ class MessageInputViewModel @Inject constructor( } } - fun reply(message: IMessage?) { + fun reply(message: ChatMessage?) { _getReplyChatMessage.postValue(message) }