From 569be55395981143e8db8c2b9bd7e0ff0638b994 Mon Sep 17 00:00:00 2001 From: rapterjet2004 Date: Tue, 10 Sep 2024 07:38:12 -0500 Subject: [PATCH] Implement queued messages for offline support Signed-off-by: rapterjet2004 --- .../talk/chat/MessageInputFragment.kt | 22 ++++++++++++++----- .../chat/viewmodels/MessageInputViewModel.kt | 15 ++++++++++++- .../utils/preferences/AppPreferencesImpl.kt | 22 ++++++++++--------- app/src/main/res/values/strings.xml | 1 + 4 files changed, 43 insertions(+), 17 deletions(-) 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 4a49ea8d6..5bd77d9b1 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -73,6 +73,7 @@ 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 @@ -141,6 +142,11 @@ class MessageInputFragment : Fragment() { saveState() } + override fun onResume() { + super.onResume() + chatActivity.messageInputViewModel.restoreMessageQueue(chatActivity.roomToken) + } + override fun onDestroyView() { super.onDestroyView() if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) { @@ -178,12 +184,19 @@ class MessageInputFragment : Fragment() { val connectionGained = (!wasOnline && isOnline) wasOnline = !binding.fragmentMessageInputView.isShown Log.d(TAG, "isOnline: $isOnline\nwasOnline: $wasOnline\nconnectionGained: $connectionGained") - - // FIXME timeout exception - maybe something to do with the room? - // handleMessageQueue(isOnline) + delay(500) + handleMessageQueue(isOnline) handleUI(isOnline, connectionGained) }.collect() } + + chatActivity.messageInputViewModel.messageQueueSizeFlow.observe(viewLifecycleOwner) { size -> + if (size > 0) { + binding.fragmentConnectionLost.text = getString(R.string.connection_lost_queued, size) + } else { + binding.fragmentConnectionLost.text = getString(R.string.connection_lost_sent_messages_are_queued) + } + } } private fun handleUI(isOnline: Boolean, connectionGained: Boolean) { @@ -220,12 +233,9 @@ 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.visibility = View.VISIBLE binding.fragmentMessageInputView.attachmentButton.isEnabled = false binding.fragmentMessageInputView.recordAudioButton.isEnabled = false - binding.fragmentMessageInputView.messageInput.isEnabled = false } } 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 4489ca830..a3e641b52 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 @@ -14,6 +14,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager import com.nextcloud.talk.chat.data.io.AudioRecorderManager import com.nextcloud.talk.chat.data.io.MediaPlayerManager @@ -26,6 +27,8 @@ import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update import javax.inject.Inject class MessageInputViewModel @Inject constructor( @@ -51,7 +54,7 @@ class MessageInputViewModel @Inject constructor( ) private var isQueueing: Boolean = false - private val messageQueue: MutableList = mutableListOf() + private var messageQueue: MutableList = mutableListOf() override fun onResume(owner: LifecycleOwner) { super.onResume(owner) @@ -119,6 +122,10 @@ class MessageInputViewModel @Inject constructor( val isVoicePreviewPlaying: LiveData get() = _isVoicePreviewPlaying + private val _messageQueueSizeFlow = MutableStateFlow(messageQueue.size) + val messageQueueSizeFlow: LiveData + get() = _messageQueueSizeFlow.asLiveData() + @Suppress("LongParameterList") fun sendChatMessage( roomToken: String, @@ -132,6 +139,7 @@ class MessageInputViewModel @Inject constructor( if (isQueueing) { messageQueue.add(QueuedMessage(message, displayName, replyTo, sendWithoutNotification)) dataStore.saveMessageQueue(roomToken, messageQueue) + _messageQueueSizeFlow.update { messageQueue.size } return } @@ -259,4 +267,9 @@ class MessageInputViewModel @Inject constructor( fun switchToMessageQueue(shouldQueue: Boolean) { isQueueing = shouldQueue } + + fun restoreMessageQueue(roomToken: String) { + messageQueue = dataStore.getMessageQueue(roomToken) + _messageQueueSizeFlow.tryEmit(messageQueue.size) + } } 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 267c86eb4..b1b948bbd 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,7 @@ 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.message},${msg.replyTo},${msg.displayName},${msg.sendWithoutNotification}^" queueStr += msgStr } } @@ -500,18 +500,20 @@ class AppPreferencesImpl(val context: Context) : AppPreferences { val queue: MutableList = mutableListOf() if (queueStr.isEmpty()) return queue - for (msgStr in queueStr.split("]")) { + for (msgStr in queueStr.split("^")) { try { - val msgArray = msgStr.replace("[", "").split(",") - val message = msgArray[MESSAGE_INDEX] - val replyTo = msgArray[REPLY_TO_INDEX].toInt() - val displayName = msgArray[DISPLY_NAME_INDEX] - val silent = msgArray[SILENT_INDEX].toBoolean() + if (msgStr.isNotEmpty()) { + val msgArray = msgStr.split(",") + val message = msgArray[MESSAGE_INDEX] + val replyTo = msgArray[REPLY_TO_INDEX].toInt() + val displayName = msgArray[DISPLY_NAME_INDEX] + val silent = msgArray[SILENT_INDEX].toBoolean() - val qMsg = MessageInputViewModel.QueuedMessage(message, displayName, replyTo, silent) - queue.add(qMsg) + val qMsg = MessageInputViewModel.QueuedMessage(message, displayName, replyTo, silent) + queue.add(qMsg) + } } catch (e: IndexOutOfBoundsException) { - Log.e(TAG, "Message string: $msgStr\n $e") + Log.e(TAG, "Message string: $msgStr\n Queue String: $queueStr \n$e") } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 13ed2b442..c23acb5fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -802,6 +802,7 @@ How to translate with transifex: Show banned participants Bans list Connection lost - Sent messages are queued + Connection lost - %1$d are queued Connection established Message deleted by you Unban