From 8bfbf834aee8fc7017d18a56ada6d591c17f8c07 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 21 Jul 2025 16:59:50 +0200 Subject: [PATCH 1/5] include threadId in fieldMap + add test for getChatBlocksContainingMessageId Signed-off-by: Marcel Hibbe --- .../data/database/dao/ChatBlocksDaoTest.kt | 72 ++++++++++++++++++- .../network/OfflineFirstChatRepository.kt | 3 + .../talk/data/source/local/TalkDatabase.kt | 2 +- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/talk/data/database/dao/ChatBlocksDaoTest.kt b/app/src/androidTest/java/com/nextcloud/talk/data/database/dao/ChatBlocksDaoTest.kt index 4f2f39971..3f5594478 100644 --- a/app/src/androidTest/java/com/nextcloud/talk/data/database/dao/ChatBlocksDaoTest.kt +++ b/app/src/androidTest/java/com/nextcloud/talk/data/database/dao/ChatBlocksDaoTest.kt @@ -25,6 +25,7 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import java.lang.Boolean @RunWith(AndroidJUnit4::class) class ChatBlocksDaoTest { @@ -49,6 +50,73 @@ class ChatBlocksDaoTest { @After fun closeDb() = db.close() + @Test + fun testGetChatBlocksContainingMessageId() = + runTest { + val user = createUserEntity("account1", "Account 1") + usersDao.saveUser(user) + val account1 = usersDao.getUserWithUserId("account1").blockingGet() + + conversationsDao.upsertConversations( + listOf( + createConversationEntity( + accountId = account1.id, + "abc", + roomName = "Conversation One" + ), + createConversationEntity( + accountId = account1.id, + "def", + roomName = "Conversation Two" + ) + ) + ) + + val conversation1 = conversationsDao.getConversationsForUser(account1.id).first()[0] + + val chatBlock1 = ChatBlockEntity( + internalConversationId = conversation1.internalId, + accountId = conversation1.accountId, + token = conversation1.token, + threadId = 123, + oldestMessageId = 50, + newestMessageId = 60, + hasHistory = true + ) + + val chatBlock2 = ChatBlockEntity( + internalConversationId = conversation1.internalId, + accountId = conversation1.accountId, + token = conversation1.token, + threadId = 123, + oldestMessageId = 10, + newestMessageId = 20, + hasHistory = true + ) + + val chatBlock3 = ChatBlockEntity( + internalConversationId = conversation1.internalId, + accountId = conversation1.accountId, + token = conversation1.token, + threadId = null, + oldestMessageId = 50, + newestMessageId = 60, + hasHistory = true + ) + + chatBlocksDao.upsertChatBlock(chatBlock1) + chatBlocksDao.upsertChatBlock(chatBlock2) + chatBlocksDao.upsertChatBlock(chatBlock3) + + val chatBlocksOfThread = chatBlocksDao.getChatBlocksContainingMessageId( + internalConversationId = conversation1.internalId, + threadId = 123, + messageId = 55 + ) + + assertEquals(1, chatBlocksOfThread.first().size) + } + @Test fun testGetConnectedChatBlocks() = runTest { @@ -264,8 +332,8 @@ class ChatBlocksDaoTest { serverVersion = null, clientCertificate = null, externalSignalingServer = null, - current = java.lang.Boolean.FALSE, - scheduledForDeletion = java.lang.Boolean.FALSE + current = Boolean.FALSE, + scheduledForDeletion = Boolean.FALSE ) private fun createConversationEntity(accountId: Long, token: String, roomName: String) = 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 4b35f0fc6..796b74fb2 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 @@ -465,8 +465,11 @@ class OfflineFirstChatRepository @Inject constructor( fieldMap["lastCommonReadId"] = it } + threadId?.let { fieldMap["threadId"] = it.toInt() } + fieldMap["timeout"] = timeout fieldMap["limit"] = limit + fieldMap["lookIntoFuture"] = if (lookIntoFuture) 1 else 0 fieldMap["setReadMarker"] = if (setReadMarker) 1 else 0 diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt index 18179bc8e..d0cb5b3ee 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt @@ -104,7 +104,7 @@ abstract class TalkDatabase : RoomDatabase() { return Room .databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName) // comment out openHelperFactory to view the database entries in Android Studio for debugging - .openHelperFactory(factory) + // .openHelperFactory(factory) .fallbackToDestructiveMigrationFrom(true, 18) .addMigrations( Migrations.MIGRATION_6_8, From a26d1c05bcd8ab56292f61ef8576cc0666819331 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 25 Jul 2025 11:20:39 +0200 Subject: [PATCH 2/5] hide thread created system messages Signed-off-by: Marcel Hibbe --- app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 bf155103e..63fe70bcf 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -3464,7 +3464,8 @@ class ChatActivity : if (isInfoMessageAboutDeletion(currentMessage) || isReactionsMessage(currentMessage) || isPollVotedMessage(currentMessage) || - isEditMessage(currentMessage) + isEditMessage(currentMessage) || + isThreadCreatedMessage(currentMessage) ) { chatMessageIterator.remove() } @@ -3506,6 +3507,9 @@ class ChatActivity : currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_DELETED || currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED + private fun isThreadCreatedMessage(currentMessage: MutableMap.MutableEntry): Boolean = + currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.THREAD_CREATED + private fun isEditMessage(currentMessage: MutableMap.MutableEntry): Boolean = currentMessage.value.parentMessageId != null && currentMessage.value.systemMessageType == ChatMessage From 172d2291cc5d7c489783b1ee3e8294073b310d8d Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 25 Jul 2025 12:05:16 +0200 Subject: [PATCH 3/5] hide thread messages in normal chat Signed-off-by: Marcel Hibbe --- .../messages/IncomingTextMessageViewHolder.kt | 2 +- .../OutcomingTextMessageViewHolder.kt | 2 +- .../com/nextcloud/talk/chat/ChatActivity.kt | 40 ++++++++++++++----- .../talk/chat/MessageInputFragment.kt | 2 +- .../talk/ui/dialog/MessageActionsDialog.kt | 2 +- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index fad7b0e4a..2fe46f8cf 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -168,7 +168,7 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : binding.messageQuote.quotedChatMessageView.visibility = if (!message.isDeleted && message.parentMessageId != null && - message.parentMessageId != chatActivity.threadId + message.parentMessageId != chatActivity.conversationThreadId ) { processParentMessage(message) View.VISIBLE diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 412dcfbbf..e58e69c8a 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -183,7 +183,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : binding.messageQuote.quotedChatMessageView.visibility = if (!message.isDeleted && message.parentMessageId != null && - message.parentMessageId != chatActivity.threadId + message.parentMessageId != chatActivity.conversationThreadId ) { processParentMessage(message) View.VISIBLE 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 63fe70bcf..905bc9b37 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -353,8 +353,8 @@ class ChatActivity : var sessionIdAfterRoomJoined: String? = null lateinit var roomToken: String - var threadId: Long? = null - var threadInfo: ThreadInfo? = null + var conversationThreadId: Long? = null + var conversationThreadInfo: ThreadInfo? = null var conversationUser: User? = null lateinit var spreedCapabilities: SpreedCapability var chatApiVersion: Int = 1 @@ -506,10 +506,10 @@ class ChatActivity : credentials!!, urlForChatting, roomToken, - threadId + conversationThreadId ) - threadId?.let { + conversationThreadId?.let { val threadUrl = ApiUtils.getUrlForThread( version = 1, baseUrl = conversationUser!!.baseUrl, @@ -571,7 +571,7 @@ class ChatActivity : roomToken = extras?.getString(KEY_ROOM_TOKEN).orEmpty() - threadId = if (extras?.containsKey(KEY_THREAD_ID) == true) { + conversationThreadId = if (extras?.containsKey(KEY_THREAD_ID) == true) { extras.getLong(KEY_THREAD_ID) } else { null @@ -966,6 +966,7 @@ class ChatActivity : var chatMessageList = triple.third chatMessageList = handleSystemMessages(chatMessageList) + chatMessageList = handleThreadMessages(chatMessageList) if (chatMessageList.isEmpty()) { return@onEach } @@ -1296,7 +1297,7 @@ class ChatActivity : } is ChatViewModel.ThreadRetrieveUiState.Success -> { - threadInfo = uiState.thread + conversationThreadInfo = uiState.thread } } } @@ -2668,7 +2669,7 @@ class ChatActivity : title.text = if (isChatThread()) { - threadInfo?.first?.message + conversationThreadInfo?.first?.message } else if (currentConversation?.displayName != null) { try { EmojiCompat.get().process(currentConversation?.displayName as CharSequence).toString() @@ -2683,7 +2684,7 @@ class ChatActivity : if (isChatThread()) { val repliesAmountTitle = String.format( resources.getString(R.string.thread_replies_amount), - threadInfo?.thread?.numReplies + conversationThreadInfo?.thread?.numReplies ) statusMessageViewContents(repliesAmountTitle) } else if (currentConversation?.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) { @@ -3455,7 +3456,7 @@ class ChatActivity : } private fun handleSystemMessages(chatMessageList: List): List { - val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap() + val chatMessageMap = chatMessageList.associateBy { it.id }.toMutableMap() val chatMessageIterator = chatMessageMap.iterator() while (chatMessageIterator.hasNext()) { @@ -3474,7 +3475,7 @@ class ChatActivity : } private fun handleExpandableSystemMessages(chatMessageList: List): List { - val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap() + val chatMessageMap = chatMessageList.associateBy { it.id }.toMutableMap() val chatMessageIterator = chatMessageMap.iterator() while (chatMessageIterator.hasNext()) { val currentMessage = chatMessageIterator.next() @@ -3497,6 +3498,23 @@ class ChatActivity : return chatMessageMap.values.toList() } + private fun handleThreadMessages(chatMessageList: List): List { + val chatMessageMap = chatMessageList.associateBy { it.id }.toMutableMap() + + if (conversationThreadId == null) { + val chatMessageIterator = chatMessageMap.iterator() + while (chatMessageIterator.hasNext()) { + val currentMessage = chatMessageIterator.next() + + if (currentMessage.value.isThread) { + chatMessageIterator.remove() + } + } + } + + return chatMessageMap.values.toList() + } + private fun isInfoMessageAboutDeletion(currentMessage: MutableMap.MutableEntry): Boolean = currentMessage.value.parentMessageId != null && currentMessage.value.systemMessageType == ChatMessage @@ -4206,7 +4224,7 @@ class ChatActivity : } } - private fun isChatThread(): Boolean = threadId != null && threadId!! > 0 + private fun isChatThread(): Boolean = conversationThreadId != null && conversationThreadId!! > 0 fun openThread(messageId: Long) { val bundle = Bundle() 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..5fb11a468 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -862,7 +862,7 @@ class MessageInputFragment : Fragment() { .findViewById(R.id.quotedChatMessageView)?.tag as Int? ?: 0 if (replyMessageId == 0) { - replyMessageId = chatActivity.threadInfo?.thread?.id ?: 0 + replyMessageId = chatActivity.conversationThreadInfo?.thread?.id ?: 0 } sendMessage( diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt index f43da9e82..b0af7a78b 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt @@ -152,7 +152,7 @@ class MessageActionsDialog( isOnline ) initMenuStartThread(!message.isThread) - initMenuOpenThread(message.isThread && chatActivity.threadId == null) + initMenuOpenThread(message.isThread && chatActivity.conversationThreadId == null) initMenuEditMessage(isMessageEditable) initMenuDeleteMessage(showMessageDeletionButton && isOnline) initMenuForwardMessage( From a7ca099cbc2a9f7b356a7d9a4891d1ef8d9724b7 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 25 Jul 2025 12:15:38 +0200 Subject: [PATCH 4/5] update threads overview in onResume Signed-off-by: Marcel Hibbe --- .../talk/threadsoverview/ThreadsOverviewActivity.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/threadsoverview/ThreadsOverviewActivity.kt b/app/src/main/java/com/nextcloud/talk/threadsoverview/ThreadsOverviewActivity.kt index 962466a8a..d2d504012 100644 --- a/app/src/main/java/com/nextcloud/talk/threadsoverview/ThreadsOverviewActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/threadsoverview/ThreadsOverviewActivity.kt @@ -69,6 +69,8 @@ class ThreadsOverviewActivity : BaseActivity() { lateinit var threadsOverviewViewModel: ThreadsOverviewViewModel + var roomToken: String = "" + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) @@ -80,9 +82,7 @@ class ThreadsOverviewActivity : BaseActivity() { val colorScheme = viewThemeUtils.getColorScheme(this) val extras: Bundle? = intent.extras - val roomToken = extras?.getString(KEY_ROOM_TOKEN).orEmpty() - - threadsOverviewViewModel.init(roomToken) + roomToken = extras?.getString(KEY_ROOM_TOKEN).orEmpty() setContent { val backgroundColor = colorResource(id = R.color.bg_default) @@ -136,6 +136,7 @@ class ThreadsOverviewActivity : BaseActivity() { override fun onResume() { super.onResume() supportActionBar?.show() + threadsOverviewViewModel.init(roomToken) } companion object { From 7fda6883c44830b890d34da8be509a25518f13d4 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 25 Jul 2025 13:00:51 +0200 Subject: [PATCH 5/5] uncomment openHelperFactory Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/data/source/local/TalkDatabase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt index d0cb5b3ee..18179bc8e 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt @@ -104,7 +104,7 @@ abstract class TalkDatabase : RoomDatabase() { return Room .databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName) // comment out openHelperFactory to view the database entries in Android Studio for debugging - // .openHelperFactory(factory) + .openHelperFactory(factory) .fallbackToDestructiveMigrationFrom(true, 18) .addMigrations( Migrations.MIGRATION_6_8,