Merge pull request #5188 from nextcloud/feature/3074/answerWithFile

answer with files
This commit is contained in:
Marcel Hibbe 2025-07-31 18:20:45 +02:00 committed by GitHub
commit 1a4c4bed75
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 280 additions and 126 deletions

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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<String>, 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

View File

@ -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<ImageButton>(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<ImageButton>(R.id.attachmentButton)?.visibility =
View.GONE
view.findViewById<ImageButton>(R.id.cancelReplyButton)?.visibility =
View.VISIBLE
@ -757,9 +763,7 @@ class MessageInputFragment : Fragment() {
}
}
val quotedChatMessageView =
view.findViewById<RelativeLayout>(R.id.quotedChatMessageView)
quotedChatMessageView?.tag = quotedJsonId
val quotedChatMessageView = view.findViewById<RelativeLayout>(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<RelativeLayout>(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<ImageButton>(R.id.cancelReplyButton)?.setOnClickListener {
cancelReply()
}
binding.fragmentMessageInputView.findViewById<ImageButton>(R.id.cancelReplyButton)?.let {
viewThemeUtils.platform
.themeImageButton(it)
@ -1043,17 +1013,14 @@ class MessageInputFragment : Fragment() {
}
private fun cancelReply() {
val quote = binding.fragmentMessageInputView
.findViewById<RelativeLayout>(R.id.quotedChatMessageView)
quote.visibility = View.GONE
quote.tag = null
binding.fragmentMessageInputView.findViewById<ImageButton>(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<RelativeLayout>(R.id.quotedChatMessageView)
quote.visibility = View.GONE
binding.fragmentMessageInputView.findViewById<ImageButton>(R.id.attachmentButton)?.visibility = View.VISIBLE
}
private fun isInReplyState(): Boolean {

View File

@ -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()
}

View File

@ -445,6 +445,7 @@ class OfflineFirstChatRepository @Inject constructor(
return loadFromServer
}
@Suppress("LongParameterList")
private fun getFieldMap(
lookIntoFuture: Boolean,
timeout: Int,

View File

@ -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<String, Any>()
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(

View File

@ -90,8 +90,8 @@ class MessageInputViewModel @Inject constructor(
val getEditChatMessage: LiveData<IMessage?>
get() = _getEditChatMessage
private val _getReplyChatMessage: MutableLiveData<IMessage?> = MutableLiveData()
val getReplyChatMessage: LiveData<IMessage?>
private val _getReplyChatMessage: MutableLiveData<ChatMessage?> = MutableLiveData()
val getReplyChatMessage: LiveData<ChatMessage?>
get() = _getReplyChatMessage
sealed interface ViewState
@ -203,7 +203,7 @@ class MessageInputViewModel @Inject constructor(
}
}
fun reply(message: IMessage?) {
fun reply(message: ChatMessage?) {
_getReplyChatMessage.postValue(message)
}