Merge pull request #5187 from nextcloud/feature/3074/messageThreads2

hide thread messages in normal chat / include threadId in fieldMap ...
This commit is contained in:
Marcel Hibbe 2025-07-25 14:48:27 +02:00 committed by GitHub
commit 30de714eb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 115 additions and 21 deletions

View File

@ -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 {
@ -266,8 +334,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) =

View File

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

View File

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

View File

@ -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<ChatMessage>): List<ChatMessage> {
val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap()
val chatMessageMap = chatMessageList.associateBy { it.id }.toMutableMap()
val chatMessageIterator = chatMessageMap.iterator()
while (chatMessageIterator.hasNext()) {
@ -3464,7 +3465,8 @@ class ChatActivity :
if (isInfoMessageAboutDeletion(currentMessage) ||
isReactionsMessage(currentMessage) ||
isPollVotedMessage(currentMessage) ||
isEditMessage(currentMessage)
isEditMessage(currentMessage) ||
isThreadCreatedMessage(currentMessage)
) {
chatMessageIterator.remove()
}
@ -3473,7 +3475,7 @@ class ChatActivity :
}
private fun handleExpandableSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
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()
@ -3496,6 +3498,23 @@ class ChatActivity :
return chatMessageMap.values.toList()
}
private fun handleThreadMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
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<String, ChatMessage>): Boolean =
currentMessage.value.parentMessageId != null &&
currentMessage.value.systemMessageType == ChatMessage
@ -3506,6 +3525,9 @@ class ChatActivity :
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_DELETED ||
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED
private fun isThreadCreatedMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean =
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.THREAD_CREATED
private fun isEditMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean =
currentMessage.value.parentMessageId != null &&
currentMessage.value.systemMessageType == ChatMessage
@ -4202,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()

View File

@ -860,7 +860,7 @@ class MessageInputFragment : Fragment() {
.findViewById<RelativeLayout>(R.id.quotedChatMessageView)?.tag as Int? ?: 0
if (replyMessageId == 0) {
replyMessageId = chatActivity.threadInfo?.thread?.id ?: 0
replyMessageId = chatActivity.conversationThreadInfo?.thread?.id ?: 0
}
sendMessage(

View File

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

View File

@ -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 {

View File

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