implement lastCommonRead handling

contains one workaround for now, see TODO in updateUiForLastCommonRead method

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2024-08-08 21:33:53 +02:00
parent 5bccdada7c
commit 0390c93ed2
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
9 changed files with 65 additions and 23 deletions

View File

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 10, "version": 10,
"identityHash": "234cdb754d42d9ebf2349763a58a4578", "identityHash": "1b97b7e937102e4087f8534f1204fe94",
"entities": [ "entities": [
{ {
"tableName": "User", "tableName": "User",
@ -138,7 +138,7 @@
}, },
{ {
"tableName": "Conversations", "tableName": "Conversations",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER, `token` TEXT, `name` TEXT, `displayName` TEXT, `description` TEXT, `type` TEXT, `lastPing` INTEGER NOT NULL, `participantType` TEXT, `hasPassword` INTEGER NOT NULL, `sessionId` TEXT, `actorId` TEXT, `actorType` TEXT, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `unreadMention` INTEGER NOT NULL, `lastMessageJson` TEXT, `objectType` TEXT, `notificationLevel` TEXT, `readOnly` TEXT, `lobbyState` TEXT, `lobbyTimer` INTEGER, `lastReadMessage` INTEGER NOT NULL, `hasCall` INTEGER NOT NULL, `callFlag` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `canLeaveConversation` INTEGER, `canDeleteConversation` INTEGER, `unreadMentionDirect` INTEGER, `notificationCalls` INTEGER, `permissions` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `status` TEXT, `statusIcon` TEXT, `statusMessage` TEXT, `statusClearAt` INTEGER, `callRecording` INTEGER NOT NULL, `avatarVersion` TEXT, `isCustomAvatar` INTEGER, `callStartTime` INTEGER, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER, `token` TEXT, `name` TEXT, `displayName` TEXT, `description` TEXT, `type` TEXT, `lastPing` INTEGER NOT NULL, `participantType` TEXT, `hasPassword` INTEGER NOT NULL, `sessionId` TEXT, `actorId` TEXT, `actorType` TEXT, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `unreadMention` INTEGER NOT NULL, `lastMessageJson` TEXT, `objectType` TEXT, `notificationLevel` TEXT, `readOnly` TEXT, `lobbyState` TEXT, `lobbyTimer` INTEGER, `lastReadMessage` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `hasCall` INTEGER NOT NULL, `callFlag` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `canLeaveConversation` INTEGER, `canDeleteConversation` INTEGER, `unreadMentionDirect` INTEGER, `notificationCalls` INTEGER, `permissions` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `status` TEXT, `statusIcon` TEXT, `statusMessage` TEXT, `statusClearAt` INTEGER, `callRecording` INTEGER NOT NULL, `avatarVersion` TEXT, `isCustomAvatar` INTEGER, `callStartTime` INTEGER, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [ "fields": [
{ {
"fieldPath": "internalId", "fieldPath": "internalId",
@ -284,6 +284,12 @@
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
}, },
{
"fieldPath": "lastCommonReadMessage",
"columnName": "lastCommonReadMessage",
"affinity": "INTEGER",
"notNull": true
},
{ {
"fieldPath": "hasCall", "fieldPath": "hasCall",
"columnName": "hasCall", "columnName": "hasCall",
@ -673,7 +679,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '234cdb754d42d9ebf2349763a58a4578')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1b97b7e937102e4087f8534f1204fe94')"
] ]
} }
} }

View File

@ -834,6 +834,14 @@ class ChatActivity :
.collect() .collect()
} }
this.lifecycleScope.launch {
chatViewModel.getLastCommonReadFlow
.onEach {
updateReadStatusOfAllMessages(it)
}
.collect()
}
chatViewModel.reactionDeletedViewState.observe(this) { state -> chatViewModel.reactionDeletedViewState.observe(this) { state ->
when (state) { when (state) {
is ChatViewModel.ReactionDeletedSuccessState -> { is ChatViewModel.ReactionDeletedSuccessState -> {
@ -2526,6 +2534,7 @@ class ChatActivity :
updateReadStatusOfMessage(message, it) updateReadStatusOfMessage(message, it)
} }
} }
adapter!!.notifyDataSetChanged()
} }
} }

View File

@ -30,6 +30,8 @@ interface ChatMessageRepository : LifecycleAwareManager {
val updateMessageFlow: Flow<ChatMessage> val updateMessageFlow: Flow<ChatMessage>
val lastCommonReadFlow: Flow<Int>
fun setData( fun setData(
conversationModel: ConversationModel, conversationModel: ConversationModel,
credentials: String, credentials: String,

View File

@ -30,6 +30,7 @@ import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@ -72,6 +73,13 @@ class OfflineFirstChatRepository @Inject constructor(
private val _updateMessageFlow: private val _updateMessageFlow:
MutableSharedFlow<ChatMessage> = MutableSharedFlow() MutableSharedFlow<ChatMessage> = MutableSharedFlow()
override val lastCommonReadFlow:
Flow<Int>
get() = _lastCommonReadFlow
private val _lastCommonReadFlow:
MutableSharedFlow<Int> = MutableSharedFlow()
private var newXChatLastCommonRead: Int? = null private var newXChatLastCommonRead: Int? = null
private var itIsPaused = false private var itIsPaused = false
private val scope = CoroutineScope(Dispatchers.IO) private val scope = CoroutineScope(Dispatchers.IO)
@ -96,6 +104,8 @@ class OfflineFirstChatRepository @Inject constructor(
scope.launch { scope.launch {
Log.d(TAG, "---- loadInitialMessages ------------") Log.d(TAG, "---- loadInitialMessages ------------")
newXChatLastCommonRead = conversationModel.lastCommonReadMessage
val fieldMap = getFieldMap( val fieldMap = getFieldMap(
lookIntoFuture = false, lookIntoFuture = false,
includeLastKnown = true, includeLastKnown = true,
@ -113,10 +123,24 @@ class OfflineFirstChatRepository @Inject constructor(
internalConversationId, internalConversationId,
chatDao.getNewestMessageId(internalConversationId) chatDao.getNewestMessageId(internalConversationId)
) )
updateUiForLastCommonRead()
initMessagePolling() initMessagePolling()
} }
private fun updateUiForLastCommonRead(){
scope.launch {
// TODO improve...
// delay is a dirty workaround to make sure messages are added to adapter on initial load before setting
// their read status.
// This workaround causes that the checkmarks seem to switch whenever sending a message
delay(200)
newXChatLastCommonRead?.let {
_lastCommonReadFlow.emit(it)
}
}
}
override fun loadMoreMessages( override fun loadMoreMessages(
beforeMessageId: Long, beforeMessageId: Long,
roomToken: String, roomToken: String,
@ -141,6 +165,7 @@ class OfflineFirstChatRepository @Inject constructor(
} }
showLast100MessagesBefore(internalConversationId, beforeMessageId) showLast100MessagesBefore(internalConversationId, beforeMessageId)
updateUiForLastCommonRead()
} }
override fun initMessagePolling(): Job = override fun initMessagePolling(): Job =
@ -174,6 +199,8 @@ class OfflineFirstChatRepository @Inject constructor(
_messageFlow.emit(pair) _messageFlow.emit(pair)
} }
updateUiForLastCommonRead()
// Process read status if not null // Process read status if not null
// val lastKnown = datastore.getLastKnownId(internalConversationId, 0) // val lastKnown = datastore.getLastKnownId(internalConversationId, 0)
// list = list.map { chatMessage -> // list = list.map { chatMessage ->
@ -245,9 +272,9 @@ class OfflineFirstChatRepository @Inject constructor(
fieldMap["lastKnownMessageId"] = lastKnown fieldMap["lastKnownMessageId"] = lastKnown
} }
// newXChatLastCommonRead?.let { newXChatLastCommonRead?.let {
// fieldMap["lastCommonReadId"] = if (it > 0) it else lastKnown fieldMap["lastCommonReadId"] = it
// } }
fieldMap["timeout"] = if (lookIntoFuture) 30 else 0 fieldMap["timeout"] = if (lookIntoFuture) 30 else 0
fieldMap["limit"] = 100 fieldMap["limit"] = 100
@ -291,26 +318,13 @@ class OfflineFirstChatRepository @Inject constructor(
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
// .timeout(3, TimeUnit.SECONDS) // .timeout(3, TimeUnit.SECONDS)
.map { .map { it ->
when (it.code()) { when (it.code()) {
HTTP_CODE_OK -> { HTTP_CODE_OK -> {
Log.d(TAG, "getMessagesFromServer HTTP_CODE_OK") Log.d(TAG, "getMessagesFromServer HTTP_CODE_OK")
// newXChatLastCommonRead = it.headers()["X-Chat-Last-Common-Read"]?.let { newXChatLastCommonRead = it.headers()["X-Chat-Last-Common-Read"]?.let {
// Integer.parseInt(it) Integer.parseInt(it)
// } }
//
// val xChatLastGivenHeader: String? = it.headers()["X-Chat-Last-Given"]
// val lastKnownId = if (it.headers().size > 0 &&
// xChatLastGivenHeader?.isNotEmpty() == true
// ) {
// xChatLastGivenHeader.toInt()
// } else {
//
// }
//
// // if (lastKnownId > 0) {
// datastore.saveLastKnownId(internalConversationId, lastKnownId)
// // }
return@map Pair( return@map Pair(
HTTP_CODE_OK, HTTP_CODE_OK,

View File

@ -120,6 +120,8 @@ class ChatViewModel @Inject constructor(
val getUpdateMessageFlow = chatRepository.updateMessageFlow val getUpdateMessageFlow = chatRepository.updateMessageFlow
val getLastCommonReadFlow = chatRepository.lastCommonReadFlow
val getConversationFlow = conversationRepository.conversationFlow val getConversationFlow = conversationRepository.conversationFlow
.onEach { .onEach {
_getRoomViewState.value = GetRoomSuccessState _getRoomViewState.value = GetRoomSuccessState

View File

@ -38,6 +38,7 @@ fun ConversationModel.asEntity() =
lobbyState = lobbyState, lobbyState = lobbyState,
lobbyTimer = lobbyTimer, lobbyTimer = lobbyTimer,
lastReadMessage = lastReadMessage, lastReadMessage = lastReadMessage,
lastCommonReadMessage = lastCommonReadMessage,
hasCall = hasCall, hasCall = hasCall,
callFlag = callFlag, callFlag = callFlag,
canStartCall = canStartCall, canStartCall = canStartCall,
@ -86,6 +87,7 @@ fun ConversationEntity.asModel() =
lobbyState = lobbyState, lobbyState = lobbyState,
lobbyTimer = lobbyTimer, lobbyTimer = lobbyTimer,
lastReadMessage = lastReadMessage, lastReadMessage = lastReadMessage,
lastCommonReadMessage = lastCommonReadMessage,
hasCall = hasCall, hasCall = hasCall,
callFlag = callFlag, callFlag = callFlag,
canStartCall = canStartCall, canStartCall = canStartCall,
@ -134,6 +136,7 @@ fun Conversation.asEntity(accountId: Long) =
lobbyState = lobbyState, lobbyState = lobbyState,
lobbyTimer = lobbyTimer, lobbyTimer = lobbyTimer,
lastReadMessage = lastReadMessage, lastReadMessage = lastReadMessage,
lastCommonReadMessage = lastCommonReadMessage,
hasCall = hasCall, hasCall = hasCall,
callFlag = callFlag, callFlag = callFlag,
canStartCall = canStartCall, canStartCall = canStartCall,

View File

@ -68,6 +68,7 @@ data class ConversationEntity(
@ColumnInfo(name = "lobbyState") var lobbyState: ConversationEnums.LobbyState? = null, @ColumnInfo(name = "lobbyState") var lobbyState: ConversationEnums.LobbyState? = null,
@ColumnInfo(name = "lobbyTimer") var lobbyTimer: Long? = null, @ColumnInfo(name = "lobbyTimer") var lobbyTimer: Long? = null,
@ColumnInfo(name = "lastReadMessage") var lastReadMessage: Int = 0, @ColumnInfo(name = "lastReadMessage") var lastReadMessage: Int = 0,
@ColumnInfo(name = "lastCommonReadMessage") var lastCommonReadMessage: Int = 0,
@ColumnInfo(name = "hasCall") var hasCall: Boolean = false, @ColumnInfo(name = "hasCall") var hasCall: Boolean = false,
@ColumnInfo(name = "callFlag") var callFlag: Int = 0, @ColumnInfo(name = "callFlag") var callFlag: Int = 0,
@ColumnInfo(name = "canStartCall") var canStartCall: Boolean = false, @ColumnInfo(name = "canStartCall") var canStartCall: Boolean = false,

View File

@ -41,6 +41,7 @@ class ConversationModel(
var lobbyState: ConversationEnums.LobbyState? = null, var lobbyState: ConversationEnums.LobbyState? = null,
var lobbyTimer: Long? = null, var lobbyTimer: Long? = null,
var lastReadMessage: Int = 0, var lastReadMessage: Int = 0,
var lastCommonReadMessage: Int = 0,
var hasCall: Boolean = false, var hasCall: Boolean = false,
var callFlag: Int = 0, var callFlag: Int = 0,
var canStartCall: Boolean = false, var canStartCall: Boolean = false,
@ -101,6 +102,7 @@ class ConversationModel(
lobbyState = conversation.lobbyState?.let { ConversationEnums.LobbyState.valueOf(it.name) }, lobbyState = conversation.lobbyState?.let { ConversationEnums.LobbyState.valueOf(it.name) },
lobbyTimer = conversation.lobbyTimer, lobbyTimer = conversation.lobbyTimer,
lastReadMessage = conversation.lastReadMessage, lastReadMessage = conversation.lastReadMessage,
lastCommonReadMessage = conversation.lastCommonReadMessage,
hasCall = conversation.hasCall, hasCall = conversation.hasCall,
callFlag = conversation.callFlag, callFlag = conversation.callFlag,
canStartCall = conversation.canStartCall, canStartCall = conversation.canStartCall,

View File

@ -90,6 +90,9 @@ data class Conversation(
@JsonField(name = ["lastReadMessage"]) @JsonField(name = ["lastReadMessage"])
var lastReadMessage: Int = 0, var lastReadMessage: Int = 0,
@JsonField(name = ["lastCommonReadMessage"])
var lastCommonReadMessage: Int = 0,
@JsonField(name = ["hasCall"]) @JsonField(name = ["hasCall"])
var hasCall: Boolean = false, var hasCall: Boolean = false,