diff --git a/.idea/inspectionProfiles/ktlint.xml b/.idea/inspectionProfiles/ktlint.xml
index e8f89f3ed2..51c63581ed 100644
--- a/.idea/inspectionProfiles/ktlint.xml
+++ b/.idea/inspectionProfiles/ktlint.xml
@@ -3,15 +3,19 @@
+
+
+
+
diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
index 7f9ee8f6b5..74fbfa91ee 100644
--- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
@@ -95,11 +95,14 @@ import com.nextcloud.talk.ui.dialog.AudioOutputDialog
import com.nextcloud.talk.ui.dialog.MoreCallActionsDialog
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
-import com.nextcloud.talk.utils.SpreedFeatures
+import com.nextcloud.talk.utils.CapabilitiesUtil
+import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
+import com.nextcloud.talk.utils.CapabilitiesUtil.isCallRecordingAvailable
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.NotificationUtils.cancelExistingNotificationsForRoom
import com.nextcloud.talk.utils.NotificationUtils.getCallRingtoneUri
import com.nextcloud.talk.utils.ReceiverFlag
+import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.VibrationUtils.vibrateShort
import com.nextcloud.talk.utils.animations.PulseAnimation
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_VOICE_ONLY
@@ -117,9 +120,6 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SWITCH_TO_ROOM
-import com.nextcloud.talk.utils.CapabilitiesUtil
-import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
-import com.nextcloud.talk.utils.CapabilitiesUtil.isCallRecordingAvailable
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
import com.nextcloud.talk.utils.power.PowerManagerUtils
import com.nextcloud.talk.utils.registerPermissionHandlerBroadcastReceiver
@@ -129,9 +129,9 @@ import com.nextcloud.talk.viewmodels.CallRecordingViewModel.RecordingConfirmStop
import com.nextcloud.talk.viewmodels.CallRecordingViewModel.RecordingErrorState
import com.nextcloud.talk.viewmodels.CallRecordingViewModel.RecordingStartedState
import com.nextcloud.talk.viewmodels.CallRecordingViewModel.RecordingStartingState
-import com.nextcloud.talk.webrtc.WebRTCUtils
import com.nextcloud.talk.webrtc.PeerConnectionWrapper
import com.nextcloud.talk.webrtc.PeerConnectionWrapper.PeerConnectionObserver
+import com.nextcloud.talk.webrtc.WebRTCUtils
import com.nextcloud.talk.webrtc.WebRtcAudioManager
import com.nextcloud.talk.webrtc.WebRtcAudioManager.AudioDevice
import com.nextcloud.talk.webrtc.WebSocketConnectionHelper
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 b9185c7aab..581d3ebb77 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
@@ -65,6 +65,8 @@ public void onBindViewHolder(ViewHolder holder, int position) {
((SystemMessageViewHolder) holder).assignSystemMessageInterface(chatActivity);
} else if (holder instanceof CallStartedViewHolder) {
((CallStartedViewHolder) holder).assignCallStartedMessageInterface(chatActivity);
+ } else if (holder instanceof TemporaryMessageViewHolder) {
+ ((TemporaryMessageViewHolder) holder).assignTemporaryMessageInterface(chatActivity);
}
super.onBindViewHolder(holder, position);
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
new file mode 100644
index 0000000000..44dab23af7
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageInterface.kt
@@ -0,0 +1,13 @@
+/*
+ * 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
new file mode 100644
index 0000000000..42f4c693eb
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageViewHolder.kt
@@ -0,0 +1,192 @@
+/*
+ * 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 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.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)
+ }
+
+ 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 = context.resources.getDrawable(R.drawable.ic_mimetype_image)
+ 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 2670aac1f7..80d2417005 100644
--- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
@@ -101,6 +101,8 @@ 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
@@ -213,7 +215,8 @@ class ChatActivity :
CommonMessageInterface,
PreviewMessageInterface,
SystemMessageInterface,
- CallStartedMessageInterface {
+ CallStartedMessageInterface,
+ TemporaryMessageInterface {
var active = false
@@ -534,6 +537,37 @@ class ChatActivity :
private fun initObservers() {
Log.d(TAG, "initObservers Called")
+ messageInputViewModel.messageQueueFlow.observe(this) { list ->
+ list.forEachIndexed { _, qMsg ->
+ Log.d("Julius", "Message recieved: ${qMsg.message}")
+ val temporaryChatMessage = ChatMessage()
+ temporaryChatMessage.jsonMessageId = -3
+ temporaryChatMessage.actorId = "-3"
+ temporaryChatMessage.timestamp = System.currentTimeMillis() / 1000
+ temporaryChatMessage.message = qMsg.message.toString()
+ temporaryChatMessage.tempMessageId = qMsg.id
+ temporaryChatMessage.isTempMessage = true
+ temporaryChatMessage.parentMessageId = qMsg.replyTo!!.toLong()
+ val pos = adapter?.getMessagePositionById(qMsg.replyTo.toString())
+ adapter?.addToStart(temporaryChatMessage, true)
+ adapter?.notifyDataSetChanged()
+ }
+ }
+
+ messageInputViewModel.messageQueueSizeFlow.observe(this) { size ->
+ if (size == 0) {
+ var i = 0
+ var pos = adapter?.getMessagePositionById("-3")
+ while (pos != null && pos > -1) {
+ adapter?.items?.removeAt(pos)
+ i++
+ pos = adapter?.getMessagePositionById("-3")
+ }
+ adapter?.notifyDataSetChanged()
+ Log.d("Julius", "End i: $i")
+ }
+ }
+
this.lifecycleScope.launch {
chatViewModel.getConversationFlow
.onEach { conversationModel ->
@@ -635,6 +669,7 @@ class ChatActivity :
TAG,
"currentConversation was null in observer ChatViewModel.GetCapabilitiesInitialLoadState"
)
+ messageInputViewModel.getTempMessagesFromMessageQueue(currentConversation!!.internalId)
}
}
@@ -1185,6 +1220,17 @@ class ChatActivity :
this
)
+ 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,
@@ -2345,8 +2391,8 @@ class ChatActivity :
try {
EmojiCompat.get().process(currentConversation?.displayName as CharSequence).toString()
} catch (e: java.lang.IllegalStateException) {
+ Log.e(TAG, "setActionBarTitle failed $e")
currentConversation?.displayName
- error(e)
}
} else {
""
@@ -2460,9 +2506,9 @@ class ChatActivity :
if (currentConversation!!.remoteServer != null) {
val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser!!, intArrayOf(ApiUtils.API_V3, 2, 1))
- ncApi!!.getSignalingSettings(
+ ncApi.getSignalingSettings(
credentials,
- ApiUtils.getUrlForSignalingSettings(apiVersion, conversationUser!!.baseUrl, roomToken!!)
+ ApiUtils.getUrlForSignalingSettings(apiVersion, conversationUser!!.baseUrl, roomToken)
).blockingSubscribe(object : Observer {
override fun onSubscribe(d: Disposable) {
// unused atm
@@ -3064,7 +3110,10 @@ class ChatActivity :
private fun openMessageActionsDialog(iMessage: IMessage?) {
val message = iMessage as ChatMessage
- if (hasVisibleItems(message) && !isSystemMessage(message)) {
+ if (hasVisibleItems(message) &&
+ !isSystemMessage(message) &&
+ message.id != "-3"
+ ) {
MessageActionsDialog(
this,
message,
@@ -3467,6 +3516,7 @@ class ChatActivity :
CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage)
CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == "-1"
CONTENT_TYPE_CALL_STARTED -> message.id == "-2"
+ CONTENT_TYPE_TEMP -> message.id == "-3"
else -> false
}
@@ -3618,6 +3668,30 @@ 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")
@@ -3647,6 +3721,7 @@ class ChatActivity :
private const val CONTENT_TYPE_VOICE_MESSAGE: Byte = 5
private const val CONTENT_TYPE_POLL: Byte = 6
private const val CONTENT_TYPE_LINK_PREVIEW: Byte = 7
+ private const val CONTENT_TYPE_TEMP: Byte = 8
private const val NEW_MESSAGES_POPUP_BUBBLE_DELAY: Long = 200
private const val GET_ROOM_INFO_DELAY_NORMAL: Long = 30000
private const val GET_ROOM_INFO_DELAY_LOBBY: Long = 5000
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 03858b4b5f..79af0a4f53 100644
--- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt
+++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt
@@ -73,7 +73,6 @@ import com.nextcloud.talk.utils.text.Spans
import com.otaliastudios.autocomplete.Autocomplete
import com.stfalcon.chatkit.commons.models.IMessage
import com.vanniktech.emoji.EmojiPopup
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -144,7 +143,7 @@ class MessageInputFragment : Fragment() {
override fun onResume() {
super.onResume()
- chatActivity.messageInputViewModel.restoreMessageQueue(chatActivity.roomToken)
+ chatActivity.messageInputViewModel.restoreMessageQueue(chatActivity.currentConversation!!.internalId)
}
override fun onDestroyView() {
@@ -179,18 +178,20 @@ class MessageInputFragment : Fragment() {
}
viewLifecycleOwner.lifecycleScope.launch {
- var wasOnline = true
- networkMonitor.isOnline.onEach { isOnline ->
- val connectionGained = (!wasOnline && isOnline)
- wasOnline = !binding.fragmentMessageInputView.isShown
- Log.d(TAG, "isOnline: $isOnline\nwasOnline: $wasOnline\nconnectionGained: $connectionGained")
- delay(500)
- handleMessageQueue(isOnline)
- handleUI(isOnline, connectionGained)
- }.collect()
+ var wasOnline: Boolean
+ networkMonitor.isOnline
+ .onEach { isOnline ->
+ wasOnline = !binding.fragmentConnectionLost.isShown
+ val connectionGained = (!wasOnline && isOnline)
+ Log.d(TAG, "isOnline: $isOnline\nwasOnline: $wasOnline\nconnectionGained: $connectionGained")
+ handleMessageQueue(isOnline)
+ handleUI(isOnline, connectionGained)
+ }.collect()
}
chatActivity.messageInputViewModel.messageQueueSizeFlow.observe(viewLifecycleOwner) { size ->
+ Log.d("Julius", "MessageQueueSizeFlow recieved: $size")
+
if (size > 0) {
binding.fragmentConnectionLost.text = getString(R.string.connection_lost_queued, size)
} else {
@@ -233,7 +234,7 @@ class MessageInputFragment : Fragment() {
binding.fragmentConnectionLost.clearAnimation()
binding.fragmentConnectionLost.visibility = View.GONE
binding.fragmentConnectionLost.setBackgroundColor(resources.getColor(R.color.hwSecurityRed))
- binding.fragmentConnectionLost.text = getString(R.string.connection_lost_sent_messages_are_queued)
+ // binding.fragmentConnectionLost.text = getString(R.string.connection_lost_sent_messages_are_queued)
binding.fragmentConnectionLost.visibility = View.VISIBLE
binding.fragmentMessageInputView.attachmentButton.isEnabled = false
binding.fragmentMessageInputView.recordAudioButton.isEnabled = false
@@ -244,7 +245,7 @@ class MessageInputFragment : Fragment() {
if (isOnline) {
chatActivity.messageInputViewModel.switchToMessageQueue(false)
chatActivity.messageInputViewModel.sendAndEmptyMessageQueue(
- chatActivity.roomToken,
+ chatActivity.currentConversation!!.internalId,
chatActivity.conversationUser!!.getCredentials(),
ApiUtils.getUrlForChat(
chatActivity.chatApiVersion,
@@ -793,7 +794,7 @@ class MessageInputFragment : Fragment() {
private fun sendMessage(message: CharSequence, replyTo: Int?, sendWithoutNotification: Boolean) {
chatActivity.messageInputViewModel.sendChatMessage(
- chatActivity.roomToken,
+ chatActivity.currentConversation!!.internalId,
chatActivity.conversationUser!!.getCredentials(),
ApiUtils.getUrlForChat(
chatActivity.chatApiVersion,
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 2c3bf7dad1..cc3cbec92b 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
@@ -111,7 +111,11 @@ data class ChatMessage(
var hiddenByCollapse: Boolean = false,
- var openWhenDownloaded: Boolean = true
+ var openWhenDownloaded: Boolean = true,
+
+ var isTempMessage: Boolean = false,
+
+ var tempMessageId: Int = -1
) : MessageContentType, MessageContentType.Image {
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 e74ebfc15b..c553de7d98 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
@@ -48,7 +48,8 @@ class MessageInputViewModel @Inject constructor(
val disposableSet = mutableSetOf()
data class QueuedMessage(
- val message: CharSequence? = null,
+ val id: Int,
+ var message: CharSequence? = null,
val displayName: String? = null,
val replyTo: Int? = null,
val sendWithoutNotification: Boolean? = null
@@ -124,9 +125,13 @@ class MessageInputViewModel @Inject constructor(
val messageQueueSizeFlow: LiveData
get() = _messageQueueSizeFlow.asLiveData()
+ private val _messageQueueFlow: MutableLiveData> = MutableLiveData()
+ val messageQueueFlow: LiveData>
+ get() = _messageQueueFlow
+
@Suppress("LongParameterList")
fun sendChatMessage(
- roomToken: String,
+ internalId: String,
credentials: String,
url: String,
message: CharSequence,
@@ -135,9 +140,13 @@ class MessageInputViewModel @Inject constructor(
sendWithoutNotification: Boolean
) {
if (isQueueing) {
- messageQueue.add(QueuedMessage(message, displayName, replyTo, sendWithoutNotification))
- dataStore.saveMessageQueue(roomToken, messageQueue)
+ val tempID = System.currentTimeMillis().toInt()
+ val qMsg = QueuedMessage(tempID, message, displayName, replyTo, sendWithoutNotification)
+ messageQueue = dataStore.getMessageQueue(internalId)
+ messageQueue.add(qMsg)
+ dataStore.saveMessageQueue(internalId, messageQueue)
_messageQueueSizeFlow.update { messageQueue.size }
+ _messageQueueFlow.postValue(listOf(qMsg))
return
}
@@ -242,17 +251,16 @@ class MessageInputViewModel @Inject constructor(
_getRecordingTime.postValue(time)
}
- fun sendAndEmptyMessageQueue(roomToken: String, credentials: String, url: String) {
+ fun sendAndEmptyMessageQueue(internalId: String, credentials: String, url: String) {
if (isQueueing) return
messageQueue.clear()
- val queue = dataStore.getMessageQueue(roomToken)
- dataStore.saveMessageQueue(roomToken, null) // empties the queue
+ val queue = dataStore.getMessageQueue(internalId)
+ dataStore.saveMessageQueue(internalId, null) // empties the queue
while (queue.size > 0) {
- val msg = queue.removeFirst()
- sleep(DELAY_BETWEEN_QUEUED_MESSAGES)
+ val msg = queue.removeAt(0)
sendChatMessage(
- roomToken,
+ internalId,
credentials,
url,
msg.message!!,
@@ -260,20 +268,55 @@ class MessageInputViewModel @Inject constructor(
msg.replyTo!!,
msg.sendWithoutNotification!!
)
+ sleep(DELAY_BETWEEN_QUEUED_MESSAGES)
+ }
+ _messageQueueSizeFlow.tryEmit(0)
+ }
+
+ fun getTempMessagesFromMessageQueue(internalId: String) {
+ val queue = dataStore.getMessageQueue(internalId)
+ val list = mutableListOf()
+ for (msg in queue) {
+ Log.d("Julius", "Msg: ${msg.message}")
+ list.add(msg)
}
+ _messageQueueFlow.postValue(list)
}
fun switchToMessageQueue(shouldQueue: Boolean) {
isQueueing = shouldQueue
}
- fun restoreMessageQueue(roomToken: String) {
- messageQueue = dataStore.getMessageQueue(roomToken)
+ fun restoreMessageQueue(internalId: String) {
+ messageQueue = dataStore.getMessageQueue(internalId)
_messageQueueSizeFlow.tryEmit(messageQueue.size)
}
+ fun removeFromQueue(internalId: String, id: Int) {
+ val queue = dataStore.getMessageQueue(internalId)
+ for (qMsg in queue) {
+ if (qMsg.id == id) {
+ queue.remove(qMsg)
+ break
+ }
+ }
+ dataStore.saveMessageQueue(internalId, queue)
+ _messageQueueSizeFlow.tryEmit(queue.size)
+ }
+
+ fun editQueuedMessage(internalId: String, id: Int, newMessage: String) {
+ val queue = dataStore.getMessageQueue(internalId)
+ for (qMsg in queue) {
+ if (qMsg.id == id) {
+ qMsg.message = newMessage
+ break
+ }
+ }
+ dataStore.saveMessageQueue(internalId, queue)
+ }
+
companion object {
private val TAG = MessageInputViewModel::class.java.simpleName
- private const val DELAY_BETWEEN_QUEUED_MESSAGES: Long = 100
+ private const val DELAY_BETWEEN_QUEUED_MESSAGES: Long = 1000
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitorImpl.kt b/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitorImpl.kt
index d3a77013d2..3f27c20f94 100644
--- a/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitorImpl.kt
+++ b/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitorImpl.kt
@@ -20,6 +20,7 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import javax.inject.Inject
import javax.inject.Singleton
@@ -73,6 +74,7 @@ class NetworkMonitorImpl @Inject constructor(
}
}
}
+ .distinctUntilChanged()
.flowOn(Dispatchers.IO)
.conflate()
diff --git a/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java
index 18782a5be1..1f39df37d9 100644
--- a/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java
+++ b/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java
@@ -25,6 +25,7 @@
import com.nextcloud.talk.models.json.push.PushConfigurationState;
import com.nextcloud.talk.users.UserManager;
import com.nextcloud.talk.utils.ApiUtils;
+import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.nextcloud.talk.webrtc.WebSocketConnectionHelper;
import java.net.CookieManager;
@@ -53,6 +54,8 @@ public class AccountRemovalWorker extends Worker {
@Inject ArbitraryStorageManager arbitraryStorageManager;
+ @Inject AppPreferences appPreferences;
+
@Inject Retrofit retrofit;
@Inject OkHttpClient okHttpClient;
@@ -193,6 +196,7 @@ private void deleteUser(User user) {
if (user.getId() != null) {
String username = user.getUsername();
try {
+ appPreferences.deleteAllMessageQueuesFor(user.getUserId());
userManager.deleteUser(user.getId());
Log.d(TAG, "deleted user: " + username);
} catch (Throwable e) {
diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
index 1769aa23d0..606614a722 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
+++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
@@ -176,6 +176,8 @@ public interface AppPreferences {
List getMessageQueue(String internalConversationId);
+ void deleteAllMessageQueuesFor(String userId);
+
void clear();
}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt
index b1b948bbd0..c3e2fcc48d 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt
@@ -484,7 +484,10 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
var queueStr = ""
queue?.let {
for (msg in queue) {
- val msgStr = "${msg.message},${msg.replyTo},${msg.displayName},${msg.sendWithoutNotification}^"
+ val msgStr = "${msg.id},${msg.message},${msg.replyTo},${msg.displayName},${
+ msg
+ .sendWithoutNotification
+ }^"
queueStr += msgStr
}
}
@@ -504,12 +507,13 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
try {
if (msgStr.isNotEmpty()) {
val msgArray = msgStr.split(",")
+ val id = msgArray[ID].toInt()
val message = msgArray[MESSAGE_INDEX]
val replyTo = msgArray[REPLY_TO_INDEX].toInt()
- val displayName = msgArray[DISPLY_NAME_INDEX]
+ val displayName = msgArray[DISPLAY_NAME_INDEX]
val silent = msgArray[SILENT_INDEX].toBoolean()
- val qMsg = MessageInputViewModel.QueuedMessage(message, displayName, replyTo, silent)
+ val qMsg = MessageInputViewModel.QueuedMessage(id, message, displayName, replyTo, silent)
queue.add(qMsg)
}
} catch (e: IndexOutOfBoundsException) {
@@ -520,6 +524,26 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
return queue
}
+ override fun deleteAllMessageQueuesFor(userId: String) {
+ runBlocking {
+ async {
+ val keyList = mutableListOf>()
+ val preferencesMap = context.dataStore.data.first().asMap()
+ for (preference in preferencesMap) {
+ if (preference.key.name.contains("$userId@")) {
+ keyList.add(preference.key)
+ }
+ }
+
+ for (key in keyList) {
+ context.dataStore.edit {
+ it.remove(key)
+ }
+ }
+ }
+ }
+ }
+
override fun clear() {}
private suspend fun writeString(key: String, value: String) =
@@ -572,10 +596,11 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
@Suppress("UnusedPrivateProperty")
private val TAG = AppPreferencesImpl::class.simpleName
private val Context.dataStore: DataStore by preferencesDataStore(name = "settings")
- private const val MESSAGE_INDEX: Int = 0
- private const val REPLY_TO_INDEX: Int = 1
- private const val DISPLY_NAME_INDEX: Int = 2
- private const val SILENT_INDEX: Int = 3
+ private const val ID: Int = 0
+ private const val MESSAGE_INDEX: Int = 1
+ private const val REPLY_TO_INDEX: Int = 2
+ private const val DISPLAY_NAME_INDEX: Int = 3
+ private const val SILENT_INDEX: Int = 4
const val PROXY_TYPE = "proxy_type"
const val PROXY_SERVER = "proxy_server"
const val PROXY_HOST = "proxy_host"
diff --git a/app/src/main/res/layout/item_temporary_message.xml b/app/src/main/res/layout/item_temporary_message.xml
new file mode 100644
index 0000000000..144762c203
--- /dev/null
+++ b/app/src/main/res/layout/item_temporary_message.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+