mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-12-18 14:42:16 +03:00
WIP temp messages are replaced when same refId received from sever
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
parent
8fbf70ee4e
commit
aff7845e83
10 changed files with 130 additions and 41 deletions
|
@ -2,7 +2,7 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 13,
|
||||
"identityHash": "6986b68476bae871773348987eada812",
|
||||
"identityHash": "ec1e16b220080592a488165e493b4f89",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "User",
|
||||
|
@ -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",
|
||||
|
@ -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": {
|
||||
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -73,8 +73,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
>
|
||||
> = MutableSharedFlow()
|
||||
|
||||
override val updateMessageFlow:
|
||||
Flow<ChatMessage>
|
||||
override val updateMessageFlow: Flow<ChatMessage>
|
||||
get() = _updateMessageFlow
|
||||
|
||||
private val _updateMessageFlow:
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -819,43 +856,35 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createChatMessageEntity(internalConversationId: String, message: String): ChatMessageEntity {
|
||||
// val id = chatMessageCounter++
|
||||
private fun createChatMessageEntity(
|
||||
internalConversationId: String,
|
||||
message: String,
|
||||
referenceId: String
|
||||
): ChatMessageEntity {
|
||||
|
||||
val emoji1 = "\uD83D\uDE00" // 😀
|
||||
val emoji2 = "\uD83D\uDE1C" // 😜
|
||||
val reactions = LinkedHashMap<String, Int>()
|
||||
reactions[emoji1] = 3
|
||||
reactions[emoji2] = 4
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -125,6 +129,8 @@ class ChatViewModel @Inject constructor(
|
|||
_chatMessageViewState.value = ChatMessageErrorState
|
||||
}
|
||||
|
||||
val getRemoveMessageFlow = chatRepository.removeMessageFlow
|
||||
|
||||
val getUpdateMessageFlow = chatRepository.updateMessageFlow
|
||||
|
||||
val getLastCommonReadFlow = chatRepository.lastCommonReadFlow
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -152,7 +152,6 @@ class RepositoryModule {
|
|||
@Provides
|
||||
fun provideInvitationsRepository(ncApi: NcApi): InvitationsRepository = InvitationsRepositoryImpl(ncApi)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideOfflineFirstChatRepository(
|
||||
chatMessagesDao: ChatMessagesDao,
|
||||
|
|
|
@ -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
|
||||
|
@ -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>)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue