add chatBlock handling for threads

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2025-07-03 15:26:02 +02:00
parent 7d6cdb9e0d
commit b04a9c49cf
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
19 changed files with 229 additions and 121 deletions

View File

@ -52,7 +52,8 @@ class ChatBlocksDaoTest {
@Test
fun testGetConnectedChatBlocks() =
runTest {
usersDao.saveUser(createUserEntity("account1", "Account 1"))
val user = createUserEntity("account1", "Account 1")
usersDao.saveUser(user)
val account1 = usersDao.getUserWithUserId("account1").blockingGet()
conversationsDao.upsertConversations(
@ -77,6 +78,7 @@ class ChatBlocksDaoTest {
internalConversationId = conversation1.internalId,
accountId = conversation1.accountId,
token = conversation1.token,
threadId = null,
oldestMessageId = 50,
newestMessageId = 60,
hasHistory = true
@ -86,6 +88,7 @@ class ChatBlocksDaoTest {
internalConversationId = conversation1.internalId,
accountId = conversation1.accountId,
token = conversation1.token,
threadId = null,
oldestMessageId = 10,
newestMessageId = 20,
hasHistory = true
@ -95,6 +98,7 @@ class ChatBlocksDaoTest {
internalConversationId = conversation1.internalId,
accountId = conversation1.accountId,
token = conversation1.token,
threadId = null,
oldestMessageId = 45,
newestMessageId = 55,
hasHistory = true
@ -104,6 +108,7 @@ class ChatBlocksDaoTest {
internalConversationId = conversation1.internalId,
accountId = conversation1.accountId,
token = conversation1.token,
threadId = null,
oldestMessageId = 52,
newestMessageId = 58,
hasHistory = true
@ -113,6 +118,7 @@ class ChatBlocksDaoTest {
internalConversationId = conversation1.internalId,
accountId = conversation1.accountId,
token = conversation1.token,
threadId = null,
oldestMessageId = 1,
newestMessageId = 99,
hasHistory = true
@ -122,6 +128,7 @@ class ChatBlocksDaoTest {
internalConversationId = conversation1.internalId,
accountId = conversation1.accountId,
token = conversation1.token,
threadId = null,
oldestMessageId = 59,
newestMessageId = 70,
hasHistory = true
@ -131,6 +138,7 @@ class ChatBlocksDaoTest {
internalConversationId = conversation1.internalId,
accountId = conversation1.accountId,
token = conversation1.token,
threadId = null,
oldestMessageId = 80,
newestMessageId = 90,
hasHistory = true
@ -140,6 +148,7 @@ class ChatBlocksDaoTest {
internalConversationId = conversation2.internalId,
accountId = conversation2.accountId,
token = conversation2.token,
threadId = null,
oldestMessageId = 53,
newestMessageId = 57,
hasHistory = true
@ -156,9 +165,10 @@ class ChatBlocksDaoTest {
chatBlocksDao.upsertChatBlock(chatBlockWithinButOtherConversation)
val results = chatBlocksDao.getConnectedChatBlocks(
conversation1.internalId,
searchedChatBlock.oldestMessageId,
searchedChatBlock.newestMessageId
internalConversationId = conversation1.internalId,
threadId = null,
oldestMessageId = searchedChatBlock.oldestMessageId,
newestMessageId = searchedChatBlock.newestMessageId
)
assertEquals(5, results.first().size)

View File

@ -140,7 +140,11 @@ class ChatMessagesDaoTest {
assertEquals("are", conv1chatMessage3.message)
val chatMessagesConv1Since =
chatMessagesDao.getMessagesForConversationSince(conversation1.internalId, conv1chatMessage3.id)
chatMessagesDao.getMessagesForConversationSince(
conversation1.internalId,
conv1chatMessage3.id,
null
)
assertEquals(3, chatMessagesConv1Since.first().size)
assertEquals("are", chatMessagesConv1Since.first()[0].message)
assertEquals("some", chatMessagesConv1Since.first()[1].message)
@ -150,7 +154,8 @@ class ChatMessagesDaoTest {
chatMessagesDao.getMessagesForConversationBeforeAndEqual(
conversation1.internalId,
conv1chatMessage3.id,
3
3,
null
)
assertEquals(3, chatMessagesConv1To.first().size)
assertEquals("hello", chatMessagesConv1To.first()[2].message)

View File

@ -10,6 +10,7 @@
package com.nextcloud.talk.adapters.messages
import android.content.Context
import android.text.SpannableStringBuilder
import android.util.Log
import android.util.TypedValue
import android.view.View
@ -145,7 +146,10 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
binding.messageAuthor.visibility = View.GONE
}
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
binding.messageText.text = processedMessageText
// binding.messageText.text = processedMessageText
// just for debugging:
binding.messageText.text =
SpannableStringBuilder(processedMessageText).append(" (" + message.jsonMessageId + ")")
} else {
binding.messageText.visibility = View.GONE
binding.checkboxContainer.visibility = View.VISIBLE

View File

@ -10,6 +10,7 @@
package com.nextcloud.talk.adapters.messages
import android.content.Context
import android.text.SpannableStringBuilder
import android.util.Log
import android.util.TypedValue
import android.view.View
@ -159,7 +160,10 @@ class OutcomingTextMessageViewHolder(itemView: View) :
binding.messageTime.layoutParams = layoutParams
viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT)
binding.messageText.text = processedMessageText
// binding.messageText.text = processedMessageText
// just for debugging:
binding.messageText.text =
SpannableStringBuilder(processedMessageText).append(" (" + message.jsonMessageId + ")")
} else {
binding.messageText.visibility = View.GONE
binding.checkboxContainer.visibility = View.VISIBLE

View File

@ -557,7 +557,12 @@ class ChatActivity :
val extras: Bundle? = intent.extras
roomToken = extras?.getString(KEY_ROOM_TOKEN).orEmpty()
threadId = extras?.getLong(KEY_THREAD_ID)
threadId = if (extras?.containsKey(KEY_THREAD_ID) == true) {
extras.getLong(KEY_THREAD_ID)
} else {
null
}
sharedText = extras?.getString(BundleKeys.KEY_SHARED_TEXT).orEmpty()

View File

@ -45,9 +45,11 @@ data class ChatMessage(
var token: String? = null,
var topmostParentId: Long? = null,
var threadId: Long? = null,
var childrenCount: Long? = 0,
var isThread: Boolean = false,
// var childrenCount: Long? = 0,
// guests or users
var actorType: String? = null,

View File

@ -122,16 +122,10 @@ class OfflineFirstChatRepository @Inject constructor(
private var threadId: Long? = null
override fun initData(credentials: String, urlForChatting: String, roomToken: String, threadId: Long?) {
val threadIdAppendedString = if (threadId != null && threadId > 0) {
"@$threadId"
} else {
""
}
internalConversationId = currentUser.id.toString() + "@" + roomToken + threadIdAppendedString
internalConversationId = currentUser.id.toString() + "@" + roomToken
this.credentials = credentials
this.urlForChatting = urlForChatting
this.threadId = threadId // use this threadId in API requests when fetching messages? +
// Introduce ChatBlocks for threads
this.threadId = threadId
}
override fun updateConversation(conversationModel: ConversationModel) {
@ -151,7 +145,8 @@ class OfflineFirstChatRepository @Inject constructor(
Log.d(TAG, "conversationModel.internalId: " + conversationModel.internalId)
Log.d(TAG, "conversationModel.lastReadMessage:" + conversationModel.lastReadMessage)
var newestMessageIdFromDb = chatDao.getNewestMessageId(internalConversationId)
// var newestMessageIdFromDb = chatDao.getNewestMessageId(internalConversationId, threadId)
var newestMessageIdFromDb = chatBlocksDao.getNewestMessageIdFromChatBlocks(internalConversationId, threadId)
Log.d(TAG, "newestMessageIdFromDb: $newestMessageIdFromDb")
val weAlreadyHaveSomeOfflineMessages = newestMessageIdFromDb > 0
@ -197,7 +192,7 @@ class OfflineFirstChatRepository @Inject constructor(
Log.e(TAG, "initial loading of messages failed")
}
newestMessageIdFromDb = chatDao.getNewestMessageId(internalConversationId)
newestMessageIdFromDb = chatBlocksDao.getNewestMessageIdFromChatBlocks(internalConversationId, threadId)
Log.d(TAG, "newestMessageIdFromDb after sync: $newestMessageIdFromDb")
}
@ -211,9 +206,9 @@ class OfflineFirstChatRepository @Inject constructor(
val limit = getCappedMessagesAmountOfChatBlock(newestMessageIdFromDb)
val list = getMessagesBeforeAndEqual(
newestMessageIdFromDb,
internalConversationId,
limit
messageId = newestMessageIdFromDb,
internalConversationId = internalConversationId,
messageLimit = limit
)
if (list.isNotEmpty()) {
handleNewAndTempMessages(
@ -242,7 +237,8 @@ class OfflineFirstChatRepository @Inject constructor(
val amountBetween = chatDao.getCountBetweenMessageIds(
internalConversationId,
messageId,
chatBlock.oldestMessageId
chatBlock.oldestMessageId,
threadId
)
Log.d(TAG, "amount of messages between newestMessageId and oldest message of same ChatBlock:$amountBetween")
@ -292,7 +288,7 @@ class OfflineFirstChatRepository @Inject constructor(
)
withNetworkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
val loadFromServer = hasToLoadPreviousMessagesFromServer(beforeMessageId)
val loadFromServer = hasToLoadPreviousMessagesFromServer(beforeMessageId, DEFAULT_MESSAGES_LIMIT)
if (loadFromServer) {
Log.d(TAG, "Starting online request for loadMoreMessages")
@ -354,7 +350,10 @@ class OfflineFirstChatRepository @Inject constructor(
updateUiForLastCommonRead()
val newestMessage = chatDao.getNewestMessageId(internalConversationId).toInt()
val newestMessage = chatBlocksDao.getNewestMessageIdFromChatBlocks(
internalConversationId,
threadId
).toInt()
// update field map vars for next cycle
fieldMap = getFieldMap(
@ -380,7 +379,7 @@ class OfflineFirstChatRepository @Inject constructor(
}
// remove all temp messages from UI
val oldTempMessages = chatDao.getTempMessagesForConversation(internalConversationId)
val oldTempMessages = chatDao.getTempMessagesForConversation(internalConversationId, threadId)
.first()
.map(ChatMessageEntity::asModel)
oldTempMessages.forEach {
@ -404,7 +403,7 @@ class OfflineFirstChatRepository @Inject constructor(
)
// add the remaining temp messages to UI again
val remainingTempMessages = chatDao.getTempMessagesForConversation(internalConversationId)
val remainingTempMessages = chatDao.getTempMessagesForConversation(internalConversationId, threadId)
.first()
.sortedBy { it.internalId }
.map(ChatMessageEntity::asModel)
@ -417,7 +416,7 @@ class OfflineFirstChatRepository @Inject constructor(
_messageFlow.emit(triple)
}
private suspend fun hasToLoadPreviousMessagesFromServer(beforeMessageId: Long): Boolean {
private suspend fun hasToLoadPreviousMessagesFromServer(beforeMessageId: Long, amountToCheck: Int): Boolean {
val loadFromServer: Boolean
val blockForMessage = getBlockOfMessage(beforeMessageId.toInt())
@ -429,21 +428,19 @@ class OfflineFirstChatRepository @Inject constructor(
Log.d(TAG, "The last chatBlock is reached so we won't request server for older messages")
loadFromServer = false
} else {
// we know that beforeMessageId and blockForMessage.oldestMessageId are in the same block.
// As we want the last DEFAULT_MESSAGES_LIMIT entries before beforeMessageId, we calculate if these
// messages are DEFAULT_MESSAGES_LIMIT entries apart from each other
val amountBetween = chatDao.getCountBetweenMessageIds(
internalConversationId,
beforeMessageId,
blockForMessage.oldestMessageId
blockForMessage.oldestMessageId,
threadId
)
loadFromServer = amountBetween < DEFAULT_MESSAGES_LIMIT
loadFromServer = amountBetween < amountToCheck
Log.d(
TAG,
"Amount between messageId " + beforeMessageId + " and " + blockForMessage.oldestMessageId +
" is: " + amountBetween + " so 'loadFromServer' is " + loadFromServer
" is: " + amountBetween + " and $amountToCheck were needed, so 'loadFromServer' is " +
loadFromServer
)
}
return loadFromServer
@ -479,7 +476,7 @@ class OfflineFirstChatRepository @Inject constructor(
override suspend fun getMessage(messageId: Long, bundle: Bundle): Flow<ChatMessage> {
Log.d(TAG, "Get message with id $messageId")
val loadFromServer = hasToLoadPreviousMessagesFromServer(messageId)
val loadFromServer = hasToLoadPreviousMessagesFromServer(messageId, 1)
if (loadFromServer) {
val fieldMap = getFieldMap(
@ -495,8 +492,10 @@ class OfflineFirstChatRepository @Inject constructor(
Log.d(TAG, "Starting online request for single message (e.g. a reply)")
sync(bundle)
}
return chatDao.getChatMessageForConversation(internalConversationId, messageId)
.map(ChatMessageEntity::asModel)
return chatDao.getChatMessageForConversation(
internalConversationId,
messageId
).map(ChatMessageEntity::asModel)
}
@Suppress("UNCHECKED_CAST", "MagicNumber", "Detekt.TooGenericExceptionCaught")
@ -660,11 +659,12 @@ class OfflineFirstChatRepository @Inject constructor(
internalConversationId = internalConversationId,
accountId = conversationModel.accountId,
token = conversationModel.token,
threadId = threadId,
oldestMessageId = oldestMessageIdForNewChatBlock,
newestMessageId = newestMessageIdForNewChatBlock,
hasHistory = hasHistory
)
chatBlocksDao.upsertChatBlock(newChatBlock)
chatBlocksDao.upsertChatBlock(newChatBlock) // crash when no conversation thread exists!
updateBlocks(newChatBlock)
return chatMessagesFromSyncToProcess
@ -721,7 +721,11 @@ class OfflineFirstChatRepository @Inject constructor(
var blockContainingQueriedMessage: ChatBlockEntity? = null
if (queriedMessageId != null) {
val blocksContainingQueriedMessage =
chatBlocksDao.getChatBlocksContainingMessageId(internalConversationId, queriedMessageId.toLong())
chatBlocksDao.getChatBlocksContainingMessageId(
internalConversationId = internalConversationId,
threadId = threadId,
messageId = queriedMessageId.toLong()
)
val chatBlocks = blocksContainingQueriedMessage.first()
if (chatBlocks.size > 1) {
@ -740,9 +744,10 @@ class OfflineFirstChatRepository @Inject constructor(
private suspend fun updateBlocks(chatBlock: ChatBlockEntity): ChatBlockEntity? {
val connectedChatBlocks =
chatBlocksDao.getConnectedChatBlocks(
internalConversationId,
chatBlock.oldestMessageId,
chatBlock.newestMessageId
internalConversationId = internalConversationId,
threadId = threadId,
oldestMessageId = chatBlock.oldestMessageId,
newestMessageId = chatBlock.newestMessageId
).first()
return if (connectedChatBlocks.size == 1) {
@ -769,7 +774,7 @@ class OfflineFirstChatRepository @Inject constructor(
internalConversationId = internalConversationId,
accountId = conversationModel.accountId,
token = conversationModel.token,
threadId = conversationModel.threadId,
threadId = threadId,
oldestMessageId = oldestIdFromDbChatBlocks,
newestMessageId = newestIdFromDbChatBlocks,
hasHistory = hasHistory
@ -793,7 +798,8 @@ class OfflineFirstChatRepository @Inject constructor(
chatDao.getMessagesForConversationBeforeAndEqual(
internalConversationId,
messageId,
messageLimit
messageLimit,
threadId
).map {
it.map(ChatMessageEntity::asModel)
}.first()
@ -807,7 +813,8 @@ class OfflineFirstChatRepository @Inject constructor(
chatDao.getMessagesForConversationBefore(
internalConversationId,
messageId,
messageLimit
messageLimit,
threadId
).map {
it.map(ChatMessageEntity::asModel)
}.first()
@ -870,7 +877,8 @@ class OfflineFirstChatRepository @Inject constructor(
val sentMessage = chatDao.getTempMessageForConversation(
internalConversationId,
referenceId
referenceId,
threadId
).firstOrNull()
sentMessage?.let {
@ -886,7 +894,8 @@ class OfflineFirstChatRepository @Inject constructor(
val failedMessage = chatDao.getTempMessageForConversation(
internalConversationId,
referenceId
referenceId,
threadId
).firstOrNull()
failedMessage?.let {
it.sendStatus = SendStatus.FAILED
@ -909,7 +918,11 @@ class OfflineFirstChatRepository @Inject constructor(
sendWithoutNotification: Boolean,
referenceId: String
): Flow<Result<ChatMessage?>> {
val messageToResend = chatDao.getTempMessageForConversation(internalConversationId, referenceId).firstOrNull()
val messageToResend = chatDao.getTempMessageForConversation(
internalConversationId,
referenceId,
threadId
).firstOrNull()
return if (messageToResend != null) {
messageToResend.sendStatus = SendStatus.PENDING
chatDao.updateChatMessage(messageToResend)
@ -958,8 +971,7 @@ class OfflineFirstChatRepository @Inject constructor(
try {
val messageToEdit = chatDao.getChatMessageForConversation(
internalConversationId,
message.jsonMessageId
.toLong()
message.jsonMessageId.toLong()
).first()
messageToEdit.message = editedMessageText
chatDao.upsertChatMessage(messageToEdit)
@ -973,7 +985,7 @@ class OfflineFirstChatRepository @Inject constructor(
}
override suspend fun sendUnsentChatMessages(credentials: String, url: String) {
val tempMessages = chatDao.getTempUnsentMessagesForConversation(internalConversationId).first()
val tempMessages = chatDao.getTempUnsentMessagesForConversation(internalConversationId, threadId).first()
tempMessages.sortedBy { it.internalId }.onEach {
sendChatMessage(
credentials,

View File

@ -20,15 +20,17 @@ interface ChatBlocksDao {
@Delete
fun deleteChatBlocks(blocks: List<ChatBlockEntity>)
@Query(
"""
SELECT *
FROM ChatBlocks
WHERE internalConversationId in (:internalConversationId)
ORDER BY newestMessageId ASC
"""
)
fun getChatBlocks(internalConversationId: String): Flow<List<ChatBlockEntity>>
// @Query(
// """
// SELECT *
// FROM ChatBlocks
// WHERE internalConversationId in (:internalConversationId)
// ORDER BY newestMessageId ASC
// """
// )
// fun getChatBlocks(
// internalConversationId: String
// ): Flow<List<ChatBlockEntity>>
// @Query(
// """
@ -50,18 +52,24 @@ interface ChatBlocksDao {
SELECT *
FROM ChatBlocks
WHERE internalConversationId in (:internalConversationId)
AND (:threadId IS NULL OR threadId = :threadId)
AND oldestMessageId <= :messageId
AND newestMessageId >= :messageId
ORDER BY newestMessageId ASC
"""
)
fun getChatBlocksContainingMessageId(internalConversationId: String, messageId: Long): Flow<List<ChatBlockEntity?>>
fun getChatBlocksContainingMessageId(
internalConversationId: String,
threadId: Long?,
messageId: Long
): Flow<List<ChatBlockEntity?>>
@Query(
"""
SELECT *
FROM ChatBlocks
WHERE internalConversationId = :internalConversationId
AND (:threadId IS NULL OR threadId = :threadId)
AND(
(oldestMessageId <= :oldestMessageId AND newestMessageId >= :oldestMessageId)
OR
@ -74,20 +82,23 @@ interface ChatBlocksDao {
)
fun getConnectedChatBlocks(
internalConversationId: String,
threadId: Long?,
oldestMessageId: Long,
newestMessageId: Long
): Flow<List<ChatBlockEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertChatBlock(chatBlock: ChatBlockEntity)
@Query(
"""
DELETE FROM ChatBlocks
WHERE internalConversationId LIKE :pattern
SELECT MAX(newestMessageId) as max_items
FROM ChatBlocks
WHERE internalConversationId = :internalConversationId
AND (:threadId IS NULL OR threadId = :threadId)
"""
)
fun clearChatBlocksForUser(pattern: String)
fun getNewestMessageIdFromChatBlocks(internalConversationId: String, threadId: Long?): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertChatBlock(chatBlock: ChatBlockEntity)
@Query(
"""

View File

@ -18,15 +18,19 @@ import kotlinx.coroutines.flow.Flow
@Dao
@Suppress("Detekt.TooManyFunctions")
interface ChatMessagesDao {
@Query(
"""
SELECT MAX(id) as max_items
FROM ChatMessages
WHERE internalConversationId = :internalConversationId
AND isTemporary = 0
"""
)
fun getNewestMessageId(internalConversationId: String): Long
// @Query(
// """
// SELECT MAX(id) as max_items
// FROM ChatMessages
// WHERE internalConversationId = :internalConversationId
// AND isTemporary = 0
// AND (:threadId IS NULL OR threadId = :threadId)
// """
// )
// fun getNewestMessageId(
// internalConversationId: String,
// threadId: Long?
// ): Long
@Query(
"""
@ -45,10 +49,11 @@ interface ChatMessagesDao {
FROM ChatMessages
WHERE internalConversationId = :internalConversationId
AND isTemporary = 1
AND (:threadId IS NULL OR threadId = :threadId)
ORDER BY timestamp DESC, id DESC
"""
)
fun getTempMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>>
fun getTempMessagesForConversation(internalConversationId: String, threadId: Long?): Flow<List<ChatMessageEntity>>
@Query(
"""
@ -57,10 +62,14 @@ interface ChatMessagesDao {
WHERE internalConversationId = :internalConversationId
AND isTemporary = 1
AND sendStatus != 'SENT_PENDING_ACK'
AND (:threadId IS NULL OR threadId = :threadId)
ORDER BY timestamp DESC, id DESC
"""
)
fun getTempUnsentMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>>
fun getTempUnsentMessagesForConversation(
internalConversationId: String,
threadId: Long?
): Flow<List<ChatMessageEntity>>
@Query(
"""
@ -69,10 +78,15 @@ interface ChatMessagesDao {
WHERE internalConversationId = :internalConversationId
AND referenceId = :referenceId
AND isTemporary = 1
AND (:threadId IS NULL OR threadId = :threadId)
ORDER BY timestamp DESC, id DESC
"""
)
fun getTempMessageForConversation(internalConversationId: String, referenceId: String): Flow<ChatMessageEntity?>
fun getTempMessageForConversation(
internalConversationId: String,
referenceId: String,
threadId: Long?
): Flow<ChatMessageEntity?>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertChatMessages(chatMessages: List<ChatMessageEntity>)
@ -84,7 +98,8 @@ interface ChatMessagesDao {
"""
SELECT *
FROM ChatMessages
WHERE internalConversationId = :internalConversationId AND id = :messageId
WHERE internalConversationId = :internalConversationId
AND id = :messageId
"""
)
fun getChatMessageForConversation(internalConversationId: String, messageId: Long): Flow<ChatMessageEntity>
@ -126,10 +141,15 @@ interface ChatMessagesDao {
FROM ChatMessages
WHERE internalConversationId = :internalConversationId AND id >= :messageId
AND isTemporary = 0
AND (:threadId IS NULL OR threadId = :threadId)
ORDER BY timestamp ASC, id ASC
"""
)
fun getMessagesForConversationSince(internalConversationId: String, messageId: Long): Flow<List<ChatMessageEntity>>
fun getMessagesForConversationSince(
internalConversationId: String,
messageId: Long,
threadId: Long?
): Flow<List<ChatMessageEntity>>
@Query(
"""
@ -138,6 +158,7 @@ interface ChatMessagesDao {
WHERE internalConversationId = :internalConversationId
AND isTemporary = 0
AND id < :messageId
AND (:threadId IS NULL OR threadId = :threadId)
ORDER BY timestamp DESC, id DESC
LIMIT :limit
"""
@ -145,7 +166,8 @@ interface ChatMessagesDao {
fun getMessagesForConversationBefore(
internalConversationId: String,
messageId: Long,
limit: Int
limit: Int,
threadId: Long?
): Flow<List<ChatMessageEntity>>
@Query(
@ -155,6 +177,7 @@ interface ChatMessagesDao {
WHERE internalConversationId = :internalConversationId
AND isTemporary = 0
AND id <= :messageId
AND (:threadId IS NULL OR threadId = :threadId)
ORDER BY timestamp DESC, id DESC
LIMIT :limit
"""
@ -162,7 +185,8 @@ interface ChatMessagesDao {
fun getMessagesForConversationBeforeAndEqual(
internalConversationId: String,
messageId: Long,
limit: Int
limit: Int,
threadId: Long?
): Flow<List<ChatMessageEntity>>
@Query(
@ -171,10 +195,16 @@ interface ChatMessagesDao {
FROM ChatMessages
WHERE internalConversationId = :internalConversationId
AND isTemporary = 0
AND (:threadId IS NULL OR threadId = :threadId)
AND id BETWEEN :newestMessageId AND :oldestMessageId
"""
)
fun getCountBetweenMessageIds(internalConversationId: String, oldestMessageId: Long, newestMessageId: Long): Int
fun getCountBetweenMessageIds(
internalConversationId: String,
oldestMessageId: Long,
newestMessageId: Long,
threadId: Long?
): Int
@Query(
"""

View File

@ -19,8 +19,9 @@ fun ChatMessageJson.asEntity(accountId: Long) =
accountId = accountId,
id = id,
internalConversationId = "$accountId@$token",
topmostParentId = topmostParentId,
childrenCount = childrenCount,
threadId = threadId,
isThread = hasThread,
// childrenCount = childrenCount,
message = message!!,
token = token!!,
actorType = actorType!!,
@ -50,8 +51,9 @@ fun ChatMessageEntity.asModel() =
jsonMessageId = id.toInt(),
message = message,
token = token,
topmostParentId = topmostParentId,
childrenCount = childrenCount,
threadId = threadId,
isThread = isThread,
// childrenCount = childrenCount,
actorType = actorType,
actorId = actorId,
actorDisplayName = actorDisplayName,
@ -82,8 +84,9 @@ fun ChatMessageJson.asModel() =
jsonMessageId = id.toInt(),
message = message,
token = token,
topmostParentId = topmostParentId,
childrenCount = childrenCount,
threadId = threadId,
isThread = hasThread,
// childrenCount = childrenCount,
actorType = actorType,
actorId = actorId,
actorDisplayName = actorDisplayName,

View File

@ -31,7 +31,7 @@ import androidx.room.PrimaryKey
data class ChatBlockEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id: Long = 0,
// accountId@token(@threadId)
// accountId@token
@ColumnInfo(name = "internalConversationId") var internalConversationId: String,
@ColumnInfo(name = "accountId") var accountId: Long? = null,
@ColumnInfo(name = "token") var token: String?,

View File

@ -41,7 +41,8 @@ data class ChatMessageEntity(
@ColumnInfo(name = "id") var id: Long = 0,
// accountId@roomtoken
@ColumnInfo(name = "internalConversationId") var internalConversationId: String,
@ColumnInfo(name = "topmostParentId") var topmostParentId: Long? = null,
@ColumnInfo(name = "threadId") var threadId: Long? = null,
@ColumnInfo(name = "isThread") var isThread: Boolean = false,
@ColumnInfo(name = "actorDisplayName") var actorDisplayName: String,
@ColumnInfo(name = "message") var message: String,

View File

@ -49,8 +49,6 @@ data class ConversationEntity(
// exactly what we want for this case.
@ColumnInfo(name = "token") var token: String,
@ColumnInfo(name = "threadId") var threadId: Long? = null,
@ColumnInfo(name = "displayName") var displayName: String,
// OTHER ATTRIBUTES IN ALPHABETICAL ORDER

View File

@ -350,18 +350,18 @@ object Migrations {
db.execSQL(
"ALTER TABLE ChatMessages " +
"ADD COLUMN topmostParentId INTEGER DEFAULT NULL;"
"ADD COLUMN threadId INTEGER DEFAULT NULL;"
)
db.execSQL(
"ALTER TABLE ChatMessages " +
"ADD COLUMN childrenCount INTEGER DEFAULT 0;"
"ADD COLUMN isThread BOOLEAN DEFAULT 0;"
)
db.execSQL(
"ALTER TABLE Conversations " +
"ADD COLUMN threadId INTEGER DEFAULT NULL;"
)
// db.execSQL(
// "ALTER TABLE ChatMessages " +
// "ADD COLUMN childrenCount INTEGER DEFAULT 0;"
// )
// Foreign key constraints are not active during migration.
// At least db.execSQL("PRAGMA foreign_keys=ON;") etc did not help.

View File

@ -17,7 +17,6 @@ class ConversationModel(
var internalId: String,
var accountId: Long,
var token: String,
var threadId: Long? = null,
var name: String,
var displayName: String,
var description: String,

View File

@ -19,8 +19,13 @@ import kotlinx.parcelize.Parcelize
data class ChatMessageJson(
@JsonField(name = ["id"]) var id: Long = 0,
@JsonField(name = ["token"]) var token: String? = null,
@JsonField(name = ["topmostParentId"]) var topmostParentId: Long? = null,
@JsonField(name = ["childrenCount"]) var childrenCount: Long? = 0,
@JsonField(name = ["threadId"]) var threadId: Long? = null,
// Be aware that variables with "is" at the beginning will lead to the error:
// "@JsonField annotation can only be used on private fields if both getter and setter are present."
// Instead, name it with "has" at the beginning: isThread -> hasThread
@JsonField(name = ["isThread"]) var hasThread: Boolean = false,
// @JsonField(name = ["childrenCount"]) var childrenCount: Long? = 0,
@JsonField(name = ["actorType"]) var actorType: String? = null,
@JsonField(name = ["actorId"]) var actorId: String? = null,
@JsonField(name = ["actorDisplayName"]) var actorDisplayName: String? = null,

View File

@ -28,9 +28,6 @@ data class Conversation(
@JsonField(name = ["token"])
var token: String = "",
@JsonField(name = ["threadId"])
var threadId: Long? = null,
@JsonField(name = ["name"])
var name: String = "",

View File

@ -101,7 +101,10 @@ class ReactionsRepositoryImpl @Inject constructor(
val internalConversationId = "$accountId@$roomToken"
val emoji = model.emoji
val message = dao.getChatMessageForConversation(internalConversationId, id).first()
val message = dao.getChatMessageForConversation(
internalConversationId,
id
).first()
// 2. Check state of entity, create params as needed
if (message.reactions == null) {

View File

@ -23,21 +23,30 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
class DummyChatMessagesDaoImpl : ChatMessagesDao {
override fun getNewestMessageId(internalConversationId: String): Long = 0L
// override fun getNewestMessageId(
// internalConversationId: String,
// threadId: Long?
// ): Long = 0L
override fun getMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>> = flowOf()
override fun getTempMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>> =
flowOf()
override fun getTempMessagesForConversation(
internalConversationId: String,
threadId: Long?
): Flow<List<ChatMessageEntity>> = flowOf()
override fun getTempUnsentMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>> {
override fun getTempUnsentMessagesForConversation(
internalConversationId: String,
threadId: Long?
): Flow<List<ChatMessageEntity>> {
// nothing to return here as long this class is only used for the Search window
return flowOf()
}
override fun getTempMessageForConversation(
internalConversationId: String,
referenceId: String
referenceId: String,
threadId: Long?
): Flow<ChatMessageEntity> = flowOf()
override suspend fun upsertChatMessages(chatMessages: List<ChatMessageEntity>) { /* */ }
@ -59,25 +68,29 @@ class DummyChatMessagesDaoImpl : ChatMessagesDao {
override fun getMessagesForConversationSince(
internalConversationId: String,
messageId: Long
messageId: Long,
threadId: Long?
): Flow<List<ChatMessageEntity>> = flowOf()
override fun getMessagesForConversationBefore(
internalConversationId: String,
messageId: Long,
limit: Int
limit: Int,
threadId: Long?
): Flow<List<ChatMessageEntity>> = flowOf()
override fun getMessagesForConversationBeforeAndEqual(
internalConversationId: String,
messageId: Long,
limit: Int
limit: Int,
threadId: Long?
): Flow<List<ChatMessageEntity>> = flowOf()
override fun getCountBetweenMessageIds(
internalConversationId: String,
oldestMessageId: Long,
newestMessageId: Long
newestMessageId: Long,
threadId: Long?
): Int = 0
override fun clearAllMessagesForUser(pattern: String) { /* */ }
@ -192,22 +205,28 @@ class DummyConversationDaoImpl : ConversationsDao {
class DummyChatBlocksDaoImpl : ChatBlocksDao {
override fun deleteChatBlocks(blocks: List<ChatBlockEntity>) { /* */ }
override fun getChatBlocks(internalConversationId: String): Flow<List<ChatBlockEntity>> = flowOf()
// override fun getChatBlocks(
// internalConversationId: String
// ): Flow<List<ChatBlockEntity>> = flowOf()
override fun getChatBlocksContainingMessageId(
internalConversationId: String,
threadId: Long?,
messageId: Long
): Flow<List<ChatBlockEntity?>> = flowOf()
override fun getConnectedChatBlocks(
internalConversationId: String,
threadId: Long?,
oldestMessageId: Long,
newestMessageId: Long
): Flow<List<ChatBlockEntity>> = flowOf()
override fun getNewestMessageIdFromChatBlocks(internalConversationId: String, threadId: Long?): Long = 0L
override suspend fun upsertChatBlock(chatBlock: ChatBlockEntity) { /* */ }
override fun clearChatBlocksForUser(pattern: String) { /* */ }
// override fun clearChatBlocksForUser(pattern: String) { /* */ }
override fun deleteChatBlocksOlderThan(internalConversationId: String, messageId: Long) { /* */ }
}