Skip to content

Commit

Permalink
WIP temp messages are replaced when same refId received from sever
Browse files Browse the repository at this point in the history
Signed-off-by: Marcel Hibbe <[email protected]>
  • Loading branch information
mahibi committed Dec 9, 2024
1 parent adaf581 commit eac090f
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 13,
"identityHash": "6986b68476bae871773348987eada812",
"identityHash": "ec1e16b220080592a488165e493b4f89",
"entities": [
{
"tableName": "User",
Expand Down Expand Up @@ -450,7 +450,7 @@
},
{
"tableName": "ChatMessages",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `systemMessage` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `systemMessage` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `isTemporary` INTEGER NOT NULL, `sendingFailed` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "internalId",
Expand Down Expand Up @@ -601,6 +601,18 @@
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isTemporary",
"columnName": "isTemporary",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "sendingFailed",
"columnName": "sendingFailed",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
Expand Down Expand Up @@ -725,7 +737,7 @@
"views": [],
"setupQueries": [
"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, '6986b68476bae871773348987eada812')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ec1e16b220080592a488165e493b4f89')"
]
}
}
10 changes: 10 additions & 0 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ class ChatActivity :
chatViewModel = ViewModelProvider(this, viewModelFactory)[ChatViewModel::class.java]

messageInputViewModel = ViewModelProvider(this, viewModelFactory)[MessageInputViewModel::class.java]
messageInputViewModel.setData(chatViewModel.getChatRepository())

this.lifecycleScope.launch {
delay(DELAY_TO_SHOW_PROGRESS_BAR)
Expand Down Expand Up @@ -914,6 +915,15 @@ class ChatActivity :
.collect()
}

this.lifecycleScope.launch {
chatViewModel.getRemoveMessageFlow
.onEach {
adapter!!.delete(it)
adapter!!.notifyDataSetChanged()
}
.collect()
}

this.lifecycleScope.launch {
chatViewModel.getUpdateMessageFlow
.onEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ interface ChatMessageRepository : LifecycleAwareManager {
*/
val generalUIFlow: Flow<String>

val removeMessageFlow: Flow<ChatMessage>

fun setData(conversationModel: ConversationModel, credentials: String, urlForChatting: String)

fun loadInitialMessages(withNetworkParams: Bundle): Job
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ class OfflineFirstChatRepository @Inject constructor(
>
> = MutableSharedFlow()

override val updateMessageFlow:
Flow<ChatMessage>
override val updateMessageFlow: Flow<ChatMessage>
get() = _updateMessageFlow

private val _updateMessageFlow:
Expand All @@ -87,8 +86,7 @@ class OfflineFirstChatRepository @Inject constructor(
private val _lastCommonReadFlow:
MutableSharedFlow<Int> = MutableSharedFlow()

override val lastReadMessageFlow:
Flow<Int>
override val lastReadMessageFlow: Flow<Int>
get() = _lastReadMessageFlow

private val _lastReadMessageFlow:
Expand All @@ -99,6 +97,12 @@ class OfflineFirstChatRepository @Inject constructor(

private val _generalUIFlow: MutableSharedFlow<String> = MutableSharedFlow()

override val removeMessageFlow: Flow<ChatMessage>
get() = _removeMessageFlow

private val _removeMessageFlow:
MutableSharedFlow<ChatMessage> = MutableSharedFlow()

private var newXChatLastCommonRead: Int? = null
private var itIsPaused = false
private val scope = CoroutineScope(Dispatchers.IO)
Expand Down Expand Up @@ -174,6 +178,9 @@ class OfflineFirstChatRepository @Inject constructor(
if (newestMessageIdFromDb.toInt() != 0) {
val limit = getCappedMessagesAmountOfChatBlock(newestMessageIdFromDb)

// TODO: somewhere here also handle temp messages. updateUiMessages(chatMessages, showUnreadMessagesMarker)


showMessagesBeforeAndEqual(
internalConversationId,
newestMessageIdFromDb,
Expand Down Expand Up @@ -295,8 +302,7 @@ class OfflineFirstChatRepository @Inject constructor(
val weHaveMessagesFromOurself = chatMessages.any { it.actorId == currentUser.userId }
showUnreadMessagesMarker = showUnreadMessagesMarker && !weHaveMessagesFromOurself

val triple = Triple(true, showUnreadMessagesMarker, chatMessages)
_messageFlow.emit(triple)
updateUiMessages(chatMessages, showUnreadMessagesMarker)
} else {
Log.d(TAG, "resultsFromSync are null or empty")
}
Expand All @@ -319,6 +325,33 @@ class OfflineFirstChatRepository @Inject constructor(
}
}

private suspend fun updateUiMessages(chatMessages : List<ChatMessage>, showUnreadMessagesMarker: Boolean) {
val oldTempMessages = chatDao.getTempMessagesForConversation(internalConversationId)
.first()
.map(ChatMessageEntity::asModel)

oldTempMessages.forEach { _removeMessageFlow.emit(it) }

val tripleChatMessages = Triple(true, showUnreadMessagesMarker, chatMessages)
_messageFlow.emit(tripleChatMessages)


val chatMessagesReferenceIds = chatMessages.mapTo(HashSet(chatMessages.size)) { it.referenceId }
val tempChatMessagesThatCanBeReplaced = oldTempMessages.filter { it.referenceId in chatMessagesReferenceIds }

chatDao.deleteTempChatMessages(
internalConversationId,
tempChatMessagesThatCanBeReplaced.map { it.referenceId!! }
)

val remainingTempMessages = chatDao.getTempMessagesForConversation(internalConversationId)
.first()
.map(ChatMessageEntity::asModel)

val triple = Triple(true, false, remainingTempMessages)
_messageFlow.emit(triple)
}

private suspend fun hasToLoadPreviousMessagesFromServer(beforeMessageId: Long): Boolean {
val loadFromServer: Boolean

Expand Down Expand Up @@ -797,7 +830,11 @@ class OfflineFirstChatRepository @Inject constructor(
): Flow<Result<ChatMessage?>> =
flow {
try {
val tempChatMessageEntity = createChatMessageEntity(internalConversationId, message.toString())
val tempChatMessageEntity = createChatMessageEntity(
internalConversationId,
message.toString(),
referenceId
)
// accessing internalConversationId creates UninitializedPropertyException because ChatViewModel and
// MessageInputViewModel use different instances of ChatRepository for now

Expand All @@ -819,43 +856,35 @@ class OfflineFirstChatRepository @Inject constructor(
}
}

private fun createChatMessageEntity(internalConversationId: String, message: String): ChatMessageEntity {
// val id = chatMessageCounter++

val emoji1 = "\uD83D\uDE00" // 😀
val emoji2 = "\uD83D\uDE1C" // 😜
val reactions = LinkedHashMap<String, Int>()
reactions[emoji1] = 3
reactions[emoji2] = 4
private fun createChatMessageEntity(
internalConversationId: String,
message: String,
referenceId: String
): ChatMessageEntity {

val reactionsSelf = ArrayList<String>()
reactionsSelf.add(emoji1)
val currentTimeMillies = System.currentTimeMillis()

val entity = ChatMessageEntity(
internalId = internalConversationId + "_temp1",
internalId = internalConversationId + "@_temp_" + currentTimeMillies,
internalConversationId = internalConversationId,
id = 111111111,
message = message,
reactions = reactions,
reactionsSelf = reactionsSelf,
id = currentTimeMillies,
message = message + " (temp)",
deleted = false,
token = "",
actorId = "",
actorType = "",
accountId = 1,
token = conversationModel.token,
actorId = currentUser.userId!!,
actorType = "users",
accountId = currentUser.id!!,
messageParameters = null,
messageType = "",
messageType = "comment",
parentMessageId = null,
systemMessageType = ChatMessage.SystemMessageType.DUMMY,
replyable = false,
timestamp = System.currentTimeMillis(),
timestamp = System.currentTimeMillis() / MILLIES,
expirationTimestamp = 0,
actorDisplayName = "test",
lastEditActorType = null,
lastEditTimestamp = 0L,
renderMarkdown = true,
lastEditActorId = "",
lastEditActorDisplayName = ""
actorDisplayName = currentUser.displayName!!,
referenceId = referenceId,
isTemporary = true,
sendingFailed = false
)
return entity
}
Expand All @@ -868,5 +897,6 @@ class OfflineFirstChatRepository @Inject constructor(
private const val HALF_SECOND = 500L
private const val DELAY_TO_ENSURE_MESSAGES_ARE_ADDED: Long = 100
private const val DEFAULT_MESSAGES_LIMIT = 100
private const val MILLIES = 1000
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ class ChatViewModel @Inject constructor(
lateinit var currentLifeCycleFlag: LifeCycleFlag
val disposableSet = mutableSetOf<Disposable>()

fun getChatRepository(): ChatMessageRepository {
return chatRepository
}

override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
currentLifeCycleFlag = LifeCycleFlag.RESUMED
Expand Down Expand Up @@ -125,6 +129,8 @@ class ChatViewModel @Inject constructor(
_chatMessageViewState.value = ChatMessageErrorState
}

val getRemoveMessageFlow = chatRepository.removeMessageFlow

val getUpdateMessageFlow = chatRepository.updateMessageFlow

val getLastCommonReadFlow = chatRepository.lastCommonReadFlow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,27 @@ import java.lang.Thread.sleep
import javax.inject.Inject

class MessageInputViewModel @Inject constructor(
private val chatRepository: ChatMessageRepository,
private val audioRecorderManager: AudioRecorderManager,
private val mediaPlayerManager: MediaPlayerManager,
private val audioFocusRequestManager: AudioFocusRequestManager,
private val appPreferences: AppPreferences
) : ViewModel(),
DefaultLifecycleObserver {

enum class LifeCycleFlag {
PAUSED,
RESUMED,
STOPPED
}

lateinit var chatRepository: ChatMessageRepository
lateinit var currentLifeCycleFlag: LifeCycleFlag
val disposableSet = mutableSetOf<Disposable>()

fun setData(chatMessageRepository: ChatMessageRepository){
chatRepository = chatMessageRepository
}

data class QueuedMessage(
val id: Int,
var message: CharSequence? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ class RepositoryModule {
@Provides
fun provideInvitationsRepository(ncApi: NcApi): InvitationsRepository = InvitationsRepositoryImpl(ncApi)

@Singleton
@Provides
fun provideOfflineFirstChatRepository(
chatMessagesDao: ChatMessagesDao,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface ChatMessagesDao {
SELECT MAX(id) as max_items
FROM ChatMessages
WHERE internalConversationId = :internalConversationId
AND isTemporary = 0
"""
)
fun getNewestMessageId(internalConversationId: String): Long
Expand All @@ -36,6 +37,17 @@ interface ChatMessagesDao {
)
fun getMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>>

@Query(
"""
SELECT *
FROM ChatMessages
WHERE internalConversationId = :internalConversationId
AND isTemporary = 1
ORDER BY timestamp DESC, id DESC
"""
)
fun getTempMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertChatMessages(chatMessages: List<ChatMessageEntity>)

Expand All @@ -59,6 +71,16 @@ interface ChatMessagesDao {
)
fun deleteChatMessages(messageIds: List<Int>)

@Query(
value = """
DELETE FROM ChatMessages
WHERE internalConversationId = :internalConversationId
AND referenceId in (:referenceIds)
AND isTemporary = 1
"""
)
fun deleteTempChatMessages(internalConversationId: String, referenceIds: List<String>)

@Update
fun updateChatMessage(message: ChatMessageEntity)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ data class ChatMessageEntity(
@ColumnInfo(name = "reactionsSelf") var reactionsSelf: ArrayList<String>? = null,
@ColumnInfo(name = "referenceId") var referenceId: String? = null,
@ColumnInfo(name = "systemMessage") var systemMessageType: ChatMessage.SystemMessageType,
@ColumnInfo(name = "timestamp") var timestamp: Long = 0
@ColumnInfo(name = "timestamp") var timestamp: Long = 0,
@ColumnInfo(name = "isTemporary") var isTemporary: Boolean = false,
@ColumnInfo(name = "sendingFailed") var sendingFailed: Boolean = false,
// missing/not needed: silent
)
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,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)
.addMigrations(
Migrations.MIGRATION_6_8,
Migrations.MIGRATION_7_8,
Expand Down

0 comments on commit eac090f

Please sign in to comment.