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 0ecc55314..fb15c9ad5 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -35,7 +35,6 @@ import android.widget.LinearLayout import android.widget.PopupMenu import android.widget.RelativeLayout import android.widget.SeekBar -import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat @@ -81,15 +80,15 @@ import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew import com.nextcloud.talk.utils.message.MessageUtils 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.flow.collect import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import java.util.Objects import javax.inject.Inject -@Suppress("LongParameterList", "TooManyFunctions") +@Suppress("LongParameterList", "TooManyFunctions", "LargeClass", "LongMethod") @AutoInjector(NextcloudTalkApplication::class) class MessageInputFragment : Fragment() { @@ -112,6 +111,10 @@ class MessageInputFragment : Fragment() { private const val CONNECTION_ESTABLISHED_ANIM_DURATION: Long = 3000 private const val FULLY_OPAQUE: Float = 1.0f private const val FULLY_TRANSPARENT: Float = 0.0f + const val QUOTED_MESSAGE_TEXT = "QUOTED_MESSAGE_TEXT" + const val QUOTED_MESSAGE_ID = "QUOTED_MESSAGE_ID" + const val QUOTED_MESSAGE_URL = "QUOTED_MESSAGE_URL" + const val QUOTED_MESSAGE_NAME = "QUOTED_MESSAGE_NAME" } @Inject @@ -163,7 +166,6 @@ class MessageInputFragment : Fragment() { override fun onPause() { super.onPause() - saveState() } override fun onDestroyView() { @@ -172,7 +174,6 @@ class MessageInputFragment : Fragment() { mentionAutocomplete?.dismissPopup() } clearEditUI() - cancelReply() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -183,7 +184,13 @@ class MessageInputFragment : Fragment() { private fun initObservers() { Log.d(TAG, "LifeCyclerOwner is: ${viewLifecycleOwner.lifecycle}") chatActivity.messageInputViewModel.getReplyChatMessage.observe(viewLifecycleOwner) { message -> - message?.let { replyToMessage(message) } + (message as ChatMessage?)?.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) + } } chatActivity.messageInputViewModel.getEditChatMessage.observe(viewLifecycleOwner) { message -> @@ -299,34 +306,24 @@ class MessageInputFragment : Fragment() { } private fun restoreState() { - if (binding.fragmentMessageInputView.inputEditText.text.isEmpty()) { - requireContext().getSharedPreferences(chatActivity.localClassName, AppCompatActivity.MODE_PRIVATE).apply { - val text = getString(chatActivity.roomToken, "") - val cursor = getInt(chatActivity.roomToken + CURSOR_KEY, 0) - binding.fragmentMessageInputView.messageInput.setText(text) - binding.fragmentMessageInputView.messageInput.setSelection(cursor) - } + runBlocking { + chatActivity.chatViewModel.updateMessageDraft() } - } - private fun saveState() { - val text = binding.fragmentMessageInputView.messageInput.text.toString() - val cursor = binding.fragmentMessageInputView.messageInput.selectionStart - val previous = requireContext().getSharedPreferences( - chatActivity.localClassName, - AppCompatActivity - .MODE_PRIVATE - ).getString(chatActivity.roomToken, "null") + val draft = chatActivity.chatViewModel.messageDraft + binding.fragmentMessageInputView.messageInput.setText(draft.messageText) + binding.fragmentMessageInputView.messageInput.setSelection(draft.messageCursor) + if (draft.messageText != "") { + binding.fragmentMessageInputView.messageInput.requestFocus() + } - if (text != previous) { - requireContext().getSharedPreferences( - chatActivity.localClassName, - AppCompatActivity.MODE_PRIVATE - ).edit().apply { - putString(chatActivity.roomToken, text) - putInt(chatActivity.roomToken + CURSOR_KEY, cursor) - apply() - } + if (isInReplyState()) { + replyToMessage( + chatActivity.chatViewModel.messageDraft.quotedMessageText, + chatActivity.chatViewModel.messageDraft.quotedDisplayName, + chatActivity.chatViewModel.messageDraft.quotedImageUrl, + chatActivity.chatViewModel.messageDraft.quotedJsonId ?: 0 + ) } } @@ -388,7 +385,10 @@ class MessageInputFragment : Fragment() { } override fun afterTextChanged(s: Editable) { - // unused atm + val cursor = binding.fragmentMessageInputView.messageInput.selectionStart + val text = binding.fragmentMessageInputView.messageInput.text.toString() + chatActivity.chatViewModel.messageDraft.messageCursor = cursor + chatActivity.chatViewModel.messageDraft.messageText = text } }) @@ -615,7 +615,7 @@ class MessageInputFragment : Fragment() { } } } - v?.onTouchEvent(event) ?: true + v?.onTouchEvent(event) != false } } @@ -717,52 +717,54 @@ class MessageInputFragment : Fragment() { } } - private fun replyToMessage(message: IMessage?) { + private fun replyToMessage( + quotedMessageText: String?, + quotedActorDisplayName: String?, + quotedImageUrl: String?, + quotedJsonId: Int + ) { Log.d(TAG, "Reply") - val chatMessage = message as ChatMessage? - chatMessage?.let { - val view = binding.fragmentMessageInputView - view.findViewById(R.id.attachmentButton)?.visibility = - View.GONE - view.findViewById(R.id.cancelReplyButton)?.visibility = - View.VISIBLE + val view = binding.fragmentMessageInputView + view.findViewById(R.id.attachmentButton)?.visibility = + View.GONE + view.findViewById(R.id.cancelReplyButton)?.visibility = + View.VISIBLE - val quotedMessage = view.findViewById(R.id.quotedMessage) + val quotedMessage = view.findViewById(R.id.quotedMessage) - quotedMessage?.maxLines = 2 - quotedMessage?.ellipsize = TextUtils.TruncateAt.END - quotedMessage?.text = it.text - view.findViewById(R.id.quotedMessageAuthor)?.text = - it.actorDisplayName ?: requireContext().getText(R.string.nc_nick_guest) + quotedMessage?.maxLines = 2 + quotedMessage?.ellipsize = TextUtils.TruncateAt.END + quotedMessage?.text = quotedMessageText + view.findViewById(R.id.quotedMessageAuthor)?.text = + quotedActorDisplayName ?: requireContext().getText(R.string.nc_nick_guest) - chatActivity.conversationUser?.let { - val quotedMessageImage = view.findViewById(R.id.quotedMessageImage) - chatMessage.imageUrl?.let { previewImageUrl -> - quotedMessageImage?.visibility = View.VISIBLE + chatActivity.conversationUser?.let { + val quotedMessageImage = view.findViewById(R.id.quotedMessageImage) + quotedImageUrl?.let { previewImageUrl -> + quotedMessageImage?.visibility = View.VISIBLE - val px = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - QUOTED_MESSAGE_IMAGE_MAX_HEIGHT, - resources.displayMetrics - ) + val px = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + QUOTED_MESSAGE_IMAGE_MAX_HEIGHT, + resources.displayMetrics + ) - quotedMessageImage?.maxHeight = px.toInt() - val layoutParams = quotedMessageImage?.layoutParams as FlexboxLayout.LayoutParams - layoutParams.flexGrow = 0f - quotedMessageImage.layoutParams = layoutParams - quotedMessageImage.load(previewImageUrl) { - addHeader("Authorization", chatActivity.credentials!!) - } - } ?: run { - view.findViewById(R.id.quotedMessageImage)?.visibility = View.GONE + quotedMessageImage?.maxHeight = px.toInt() + val layoutParams = quotedMessageImage?.layoutParams as FlexboxLayout.LayoutParams + layoutParams.flexGrow = 0f + quotedMessageImage.layoutParams = layoutParams + quotedMessageImage.load(previewImageUrl) { + addHeader("Authorization", chatActivity.credentials!!) } + } ?: run { + view.findViewById(R.id.quotedMessageImage)?.visibility = View.GONE } - - val quotedChatMessageView = - view.findViewById(R.id.quotedChatMessageView) - quotedChatMessageView?.tag = message?.jsonMessageId - quotedChatMessageView?.visibility = View.VISIBLE } + + val quotedChatMessageView = + view.findViewById(R.id.quotedChatMessageView) + quotedChatMessageView?.tag = quotedJsonId + quotedChatMessageView?.visibility = View.VISIBLE } fun updateOwnTypingStatus(typedText: CharSequence) { @@ -1051,5 +1053,15 @@ class MessageInputFragment : Fragment() { quote.tag = null binding.fragmentMessageInputView.findViewById(R.id.attachmentButton)?.visibility = View.VISIBLE chatActivity.messageInputViewModel.reply(null) + + chatActivity.chatViewModel.messageDraft.quotedMessageText = null + chatActivity.chatViewModel.messageDraft.quotedDisplayName = null + chatActivity.chatViewModel.messageDraft.quotedImageUrl = null + chatActivity.chatViewModel.messageDraft.quotedJsonId = null + } + + private fun isInReplyState(): Boolean { + val jsonId = chatActivity.chatViewModel.messageDraft.quotedJsonId + return jsonId != null } } 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 2b1371d7b..719f873e0 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 @@ -27,6 +27,7 @@ import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.extensions.toIntOrZero import com.nextcloud.talk.jobs.UploadAndShareFilesWorker +import com.nextcloud.talk.models.MessageDraft import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.models.domain.ReactionAddedModel import com.nextcloud.talk.models.domain.ReactionDeletedModel @@ -60,6 +61,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import java.io.File import javax.inject.Inject @@ -89,6 +91,8 @@ class ChatViewModel @Inject constructor( val disposableSet = mutableSetOf() var mediaPlayerDuration = mediaPlayerManager.mediaPlayerDuration val mediaPlayerPosition = mediaPlayerManager.mediaPlayerPosition + var chatRoomToken: String = "" + var messageDraft: MessageDraft = MessageDraft() fun getChatRepository(): ChatMessageRepository = chatRepository @@ -108,6 +112,14 @@ class ChatViewModel @Inject constructor( mediaRecorderManager.handleOnPause() chatRepository.handleOnPause() mediaPlayerManager.handleOnPause() + + runBlocking { + val model = conversationRepository.getLocallyStoredConversation(chatRoomToken) + model?.let { + it.messageDraft = messageDraft + conversationRepository.updateConversation(it) + } + } } override fun onStop(owner: LifecycleOwner) { @@ -889,6 +901,11 @@ class ChatViewModel @Inject constructor( } } + suspend fun updateMessageDraft() { + val model = conversationRepository.getLocallyStoredConversation(chatRoomToken) + messageDraft = model?.messageDraft!! + } + companion object { private val TAG = ChatViewModel::class.simpleName const val JOIN_ROOM_RETRY_COUNT: Long = 3 diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt index be0fb7fe7..e9665f444 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt @@ -307,6 +307,16 @@ class ConversationsListActivity : showNotificationWarning() showShareToScreen = hasActivityActionSendIntent() + // context.getSharedPreferences( + // CHAT_ACTIVITY_LOCAL_NAME, + // MODE_PRIVATE + // ).edit().apply { + // putInt(QUOTED_MESSAGE_ID, -1) + // putString(QUOTED_MESSAGE_NAME, null) + // putString(QUOTED_MESSAGE_TEXT, "") + // putString(QUOTED_MESSAGE_URL, null) + // apply() + // } if (!eventBus.isRegistered(this)) { eventBus.register(this) @@ -2216,6 +2226,7 @@ class ConversationsListActivity : const val UNREAD_BUBBLE_DELAY = 2500 const val BOTTOM_SHEET_DELAY: Long = 2500 private const val KEY_SEARCH_QUERY = "ConversationsListActivity.searchQuery" + private const val CHAT_ACTIVITY_LOCAL_NAME = "com.nextcloud.talk.chat.ChatActivity" const val SEARCH_DEBOUNCE_INTERVAL_MS = 300 const val SEARCH_MIN_CHARS = 1 const val HTTP_UNAUTHORIZED = 401 diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/data/OfflineConversationsRepository.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/data/OfflineConversationsRepository.kt index 92591fe4e..a115ac008 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationlist/data/OfflineConversationsRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationlist/data/OfflineConversationsRepository.kt @@ -36,4 +36,8 @@ interface OfflineConversationsRepository { * to be handled asynchronously. */ fun getRoom(roomToken: String): Job + + suspend fun updateConversation(conversationModel: ConversationModel) + + suspend fun getLocallyStoredConversation(roomToken: String): ConversationModel? } diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt index c4b941962..2e3687ac9 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt @@ -98,12 +98,22 @@ class OfflineFirstConversationsRepository @Inject constructor( runBlocking { _conversationFlow.emit(model) val entityList = listOf(model.asEntity()) - dao.upsertConversations(entityList) + dao.upsertConversations(user.id!!, entityList) } } }) } + override suspend fun updateConversation(conversationModel: ConversationModel) { + val entity = conversationModel.asEntity() + dao.updateConversation(entity) + } + + override suspend fun getLocallyStoredConversation(roomToken: String): ConversationModel? { + val id = user.id!! + return getConversation(id, roomToken) + } + @Suppress("Detekt.TooGenericExceptionCaught") private suspend fun getRoomsFromServer(): List? { var conversationsFromSync: List? = null @@ -126,7 +136,7 @@ class OfflineFirstConversationsRepository @Inject constructor( } deleteLeftConversations(conversationsFromSync) - dao.upsertConversations(conversationsFromSync) + dao.upsertConversations(user.id!!, conversationsFromSync) } catch (e: Exception) { Log.e(TAG, "Something went wrong when fetching conversations", e) } diff --git a/app/src/main/java/com/nextcloud/talk/data/database/dao/ConversationsDao.kt b/app/src/main/java/com/nextcloud/talk/data/database/dao/ConversationsDao.kt index b6b7ae14f..811a4e20d 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/dao/ConversationsDao.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/dao/ConversationsDao.kt @@ -8,11 +8,15 @@ package com.nextcloud.talk.data.database.dao import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy.Companion.REPLACE import androidx.room.Query +import androidx.room.Transaction import androidx.room.Update import androidx.room.Upsert import com.nextcloud.talk.data.database.model.ConversationEntity import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first @Dao interface ConversationsDao { @@ -22,9 +26,27 @@ interface ConversationsDao { @Query("SELECT * FROM Conversations where accountId = :accountId AND token = :token") fun getConversationForUser(accountId: Long, token: String): Flow - @Upsert + @Upsert() fun upsertConversations(conversationEntities: List) + @Insert(onConflict = REPLACE) + suspend fun insertOrUpdate(item: ConversationEntity) + + @Transaction + suspend fun upsertConversations(accountId: Long, serverItems: List) { + serverItems.forEach { serverItem -> + val existingItem = getConversationForUser(accountId, serverItem.token).first() + if (existingItem != null) { + val mergedItem = serverItem + mergedItem.messageDraft = existingItem.messageDraft + insertOrUpdate(mergedItem) + } else { + // Insert new item directly (local-only fields will be default) + insertOrUpdate(serverItem) + } + } + } + /** * Deletes rows in the db matching the specified [conversationIds] */ @@ -36,7 +58,7 @@ interface ConversationsDao { ) fun deleteConversations(conversationIds: List) - @Update + @Update(onConflict = REPLACE) fun updateConversation(conversationEntity: ConversationEntity) @Query( diff --git a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ConversationMapUtils.kt b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ConversationMapUtils.kt index dd28633e3..0953376f7 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ConversationMapUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ConversationMapUtils.kt @@ -1,7 +1,7 @@ /* * Nextcloud Talk - Android Client * - * SPDX-FileCopyrightText: 2024 Your Name + * SPDX-FileCopyrightText: 2024 Julius Linus * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -63,7 +63,8 @@ fun ConversationModel.asEntity() = remoteToken = remoteToken, hasArchived = hasArchived, hasSensitive = hasSensitive, - hasImportant = hasImportant + hasImportant = hasImportant, + messageDraft = messageDraft ) fun ConversationEntity.asModel() = @@ -117,7 +118,8 @@ fun ConversationEntity.asModel() = remoteToken = remoteToken, hasArchived = hasArchived, hasSensitive = hasSensitive, - hasImportant = hasImportant + hasImportant = hasImportant, + messageDraft = messageDraft ) fun Conversation.asEntity(accountId: Long) = diff --git a/app/src/main/java/com/nextcloud/talk/data/database/model/ConversationEntity.kt b/app/src/main/java/com/nextcloud/talk/data/database/model/ConversationEntity.kt index 8cdd4db58..8301b8c17 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/model/ConversationEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/model/ConversationEntity.kt @@ -13,6 +13,7 @@ import androidx.room.ForeignKey import androidx.room.Index import androidx.room.PrimaryKey import com.nextcloud.talk.data.user.model.UserEntity +import com.nextcloud.talk.models.MessageDraft import com.nextcloud.talk.models.json.conversations.ConversationEnums import com.nextcloud.talk.models.json.participants.Participant @@ -96,7 +97,8 @@ data class ConversationEntity( @ColumnInfo(name = "unreadMessages") var unreadMessages: Int = 0, @ColumnInfo(name = "hasArchived") var hasArchived: Boolean = false, @ColumnInfo(name = "hasSensitive") var hasSensitive: Boolean = false, - @ColumnInfo(name = "hasImportant") var hasImportant: Boolean = false + @ColumnInfo(name = "hasImportant") var hasImportant: Boolean = false, + @ColumnInfo(name = "messageDraft") var messageDraft: MessageDraft? = MessageDraft() // missing/not needed: attendeeId // missing/not needed: attendeePin // missing/not needed: attendeePermissions diff --git a/app/src/main/java/com/nextcloud/talk/models/MessageDraft.kt b/app/src/main/java/com/nextcloud/talk/models/MessageDraft.kt new file mode 100644 index 000000000..f8e258619 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/MessageDraft.kt @@ -0,0 +1,57 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Julius Linus + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.models + +import android.os.Parcelable +import androidx.room.TypeConverter +import com.bluelinelabs.logansquare.LoganSquare +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable + +@Parcelize +@JsonObject +@Serializable +data class MessageDraft( + @JsonField(name = ["messageText"]) + var messageText: String = "", + @JsonField(name = ["messageCursor"]) + var messageCursor: Int = 0, + @JsonField(name = ["quotedJsonId"]) + var quotedJsonId: Int? = null, + @JsonField(name = ["quotedDisplayName"]) + var quotedDisplayName: String? = null, + @JsonField(name = ["quotedMessageText"]) + var quotedMessageText: String? = null, + @JsonField(name = ["quoteImageUrl"]) + var quotedImageUrl: String? = null +) : Parcelable { + constructor() : this("", 0, null, null, null, null) +} + +class MessageDraftConverter { + + @TypeConverter + fun fromMessageDraftToString(messageDraft: MessageDraft?): String { + return if (messageDraft == null) { + "" + } else { + LoganSquare.serialize(messageDraft) + } + } + + @TypeConverter + fun fromStringToMessageDraft(value: String): MessageDraft? { + return if (value.isBlank()) { + null + } else { + return LoganSquare.parse(value, MessageDraft::class.java) + } + } +} diff --git a/app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt b/app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt index 59ed023c5..c5ea387f0 100644 --- a/app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt +++ b/app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt @@ -8,6 +8,7 @@ package com.nextcloud.talk.models.domain import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.models.MessageDraft import com.nextcloud.talk.models.json.chat.ChatMessageJson import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.ConversationEnums @@ -65,7 +66,8 @@ class ConversationModel( var hasImportant: Boolean = false, // attributes that don't come from API. This should be changed?! - var password: String? = null + var password: String? = null, + var messageDraft: MessageDraft? = MessageDraft() ) { companion object { diff --git a/app/src/main/java/com/nextcloud/talk/utils/preview/ComposePreviewUtilsDaos.kt b/app/src/main/java/com/nextcloud/talk/utils/preview/ComposePreviewUtilsDaos.kt index 435445774..6283578bf 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preview/ComposePreviewUtilsDaos.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/preview/ComposePreviewUtilsDaos.kt @@ -194,6 +194,10 @@ class DummyConversationDaoImpl : ConversationsDao { override fun upsertConversations(conversationEntities: List) { /* */ } + override suspend fun insertOrUpdate(item: ConversationEntity) { + /**/ + } + override fun deleteConversations(conversationIds: List) { /* */ } override fun updateConversation(conversationEntity: ConversationEntity) { /* */ }