Unverified Commit 9567523e authored by Lucio Maciel's avatar Lucio Maciel Committed by GitHub

Merge pull request #1615 from luckcoolla/fix/chat_history_sync

[WIP][FIX] Chat history sync
parents b01c1258 0cb06f84
...@@ -158,23 +158,36 @@ class ChatRoomAdapter( ...@@ -158,23 +158,36 @@ class ChatRoomAdapter(
} }
fun prependData(dataSet: List<BaseUiModel<*>>) { fun prependData(dataSet: List<BaseUiModel<*>>) {
val item = dataSet.indexOfFirst { newItem -> //---At first we will update all already saved elements with received updated ones
this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType } > -1 val filteredDataSet = dataSet.filter { newItem ->
} val matchedIndex = this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType }
if (item == -1) { if (matchedIndex > -1) {
this.dataSet.addAll(0, dataSet) this.dataSet[matchedIndex] = newItem
notifyItemRangeInserted(0, dataSet.size) notifyItemChanged(matchedIndex)
} else { }
dataSet.forEach { item -> return@filter (matchedIndex < 0)
val index = this.dataSet.indexOfFirst { }
item.messageId == it.messageId && item.viewType == it.viewType val minAdditionDate = filteredDataSet.minBy { it.message.timestamp } ?: return
} //---In the most cases we will just add new elements to the top of messages heap
if (index > -1) { if (minAdditionDate.message.timestamp > this.dataSet[0].message.timestamp) {
this.dataSet[index] = item this.dataSet.addAll(0, filteredDataSet)
notifyItemChanged(index) notifyItemRangeInserted(0, filteredDataSet.size)
} return
} }
} //---Else branch: merging messages---
//---We are inserting new received elements into set. Sort them by time+type and show
if (filteredDataSet.isEmpty()) return
this.dataSet.addAll(0, filteredDataSet)
val tmp = this.dataSet.sortedWith(Comparator { t, t2 ->
val timeComparison = t.message.timestamp.compareTo(t2.message.timestamp)
if (timeComparison == 0) {
return@Comparator t.viewType.compareTo(t2.viewType)
}
timeComparison
}).reversed()
this.dataSet.clear()
this.dataSet.addAll(tmp)
notifyDataSetChanged()
} }
fun updateItem(message: BaseUiModel<*>) { fun updateItem(message: BaseUiModel<*>) {
......
...@@ -184,7 +184,8 @@ class ChatRoomPresenter @Inject constructor( ...@@ -184,7 +184,8 @@ class ChatRoomPresenter @Inject constructor(
isBroadcast = chatIsBroadcast, isRoom = true isBroadcast = chatIsBroadcast, isRoom = true
) )
) )
if (oldMessages.isNotEmpty()) { val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId)
if (oldMessages.isNotEmpty() && lastSyncDate != null) {
view.showMessages(oldMessages, clearDataSet) view.showMessages(oldMessages, clearDataSet)
loadMissingMessages() loadMissingMessages()
} else { } else {
...@@ -226,6 +227,19 @@ class ChatRoomPresenter @Inject constructor( ...@@ -226,6 +227,19 @@ class ChatRoomPresenter @Inject constructor(
client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result
} }
messagesRepository.saveAll(messages) messagesRepository.saveAll(messages)
//we are saving last sync date of latest synced chat room message
if (offset == 0L) {
//if success - saving last synced time
if (messages.isEmpty()) {
//chat history is empty - just saving current date
messagesRepository.saveLastSyncDate(chatRoomId, System.currentTimeMillis())
} else {
//assume that BE returns ordered messages, the first message is the latest one
messagesRepository.saveLastSyncDate(chatRoomId, messages.first().timestamp)
}
}
view.showMessages( view.showMessages(
mapper.map( mapper.map(
messages, messages,
...@@ -275,7 +289,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -275,7 +289,7 @@ class ChatRoomPresenter @Inject constructor(
timestamp = Instant.now().toEpochMilli(), timestamp = Instant.now().toEpochMilli(),
sender = SimpleUser(null, username, username), sender = SimpleUser(null, username, username),
attachments = null, attachments = null,
avatar = currentServer.avatarUrl(username!!), avatar = currentServer.avatarUrl(username ?: ""),
channels = null, channels = null,
editedAt = null, editedAt = null,
editedBy = null, editedBy = null,
...@@ -483,11 +497,13 @@ class ChatRoomPresenter @Inject constructor( ...@@ -483,11 +497,13 @@ class ChatRoomPresenter @Inject constructor(
private fun loadMissingMessages() { private fun loadMissingMessages() {
launch(parent = strategy.jobs) { launch(parent = strategy.jobs) {
if (chatRoomId != null && chatRoomType != null) { chatRoomId?.let { chatRoomId ->
val roomType = roomTypeOf(chatRoomType!!) val roomType = roomTypeOf(chatRoomType)
messagesRepository.getByRoomId(chatRoomId!!) val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId)
.sortedByDescending { it.timestamp }.firstOrNull()?.let { lastMessage -> // lastSyncDate or 0. LastSyncDate could be in case when we sent some messages offline(and saved them locally),
val instant = Instant.ofEpochMilli(lastMessage.timestamp).toString() // but never has obtained chatMessages(or history) from remote. In this case we should sync all chat history from beginning
val instant = Instant.ofEpochMilli(lastSyncDate ?: 0).toString()
//
try { try {
val messages = val messages =
retryIO(description = "history($chatRoomId, $roomType, $instant)") { retryIO(description = "history($chatRoomId, $roomType, $instant)") {
...@@ -506,6 +522,9 @@ class ChatRoomPresenter @Inject constructor( ...@@ -506,6 +522,9 @@ class ChatRoomPresenter @Inject constructor(
isRoom = true isRoom = true
)) ))
messagesRepository.saveAll(messages.result) messagesRepository.saveAll(messages.result)
//if success - saving last synced time
//assume that BE returns ordered messages, the first message is the latest one
messagesRepository.saveLastSyncDate(chatRoomId, messages.result.first().timestamp)
launchUI(strategy) { launchUI(strategy) {
view.showNewMessage(models, true) view.showNewMessage(models, true)
...@@ -524,7 +543,6 @@ class ChatRoomPresenter @Inject constructor( ...@@ -524,7 +543,6 @@ class ChatRoomPresenter @Inject constructor(
} }
} }
} }
}
/** /**
* Delete the message with the given id. * Delete the message with the given id.
......
...@@ -72,4 +72,22 @@ interface MessagesRepository { ...@@ -72,4 +72,22 @@ interface MessagesRepository {
suspend fun getAllUnsent(): List<Message> suspend fun getAllUnsent(): List<Message>
suspend fun getUnsentByRoomId(roomId: String): List<Message> suspend fun getUnsentByRoomId(roomId: String): List<Message>
/**
* Save time of the latest room messages sync.
* Call this fun only when the latest messages list being received via /history or /messages network calls
*
* @param rid The id of the room the messages are.
* @param timeMillis time of room messages sync or the latest room message timestamp(which came with /history request)
*/
suspend fun saveLastSyncDate(rid: String, timeMillis: Long)
/**
* Get time when the room chat history has been loaded last time.
*
* @param rid The id of the room the messages are.
*
* @return Last Sync time or Null.
*/
suspend fun getLastSyncDate(rid: String): Long?
} }
\ No newline at end of file
...@@ -7,8 +7,16 @@ import kotlinx.coroutines.experimental.withContext ...@@ -7,8 +7,16 @@ import kotlinx.coroutines.experimental.withContext
class MemoryMessagesRepository : MessagesRepository { class MemoryMessagesRepository : MessagesRepository {
private var lastSyncDates: HashMap<String, Long> = HashMap()
private val messages: HashMap<String, Message> = HashMap() private val messages: HashMap<String, Message> = HashMap()
override suspend fun saveLastSyncDate(rid: String, timeMillis: Long) {
lastSyncDates[rid] = timeMillis
}
override suspend fun getLastSyncDate(rid: String) = lastSyncDates[rid]
override suspend fun getById(id: String): Message? = withContext(CommonPool) { override suspend fun getById(id: String): Message? = withContext(CommonPool) {
return@withContext messages[id] return@withContext messages[id]
} }
......
...@@ -13,6 +13,27 @@ class SharedPreferencesMessagesRepository( ...@@ -13,6 +13,27 @@ class SharedPreferencesMessagesRepository(
private val moshi: Moshi, private val moshi: Moshi,
private val currentServerInteractor: GetCurrentServerInteractor private val currentServerInteractor: GetCurrentServerInteractor
) : MessagesRepository { ) : MessagesRepository {
private val KEY_LAST_SYNC_DATE = "KEY_LAST_SYNC_DATE"
override suspend fun saveLastSyncDate(rid: String, timeMillis: Long) {
withContext(CommonPool) {
currentServerInteractor.get()?.let {
prefs.edit().putLong(getSyncDateKey(it, rid), timeMillis).apply()
}
}
}
override suspend fun getLastSyncDate(rid: String): Long? = withContext(CommonPool) {
currentServerInteractor.get()?.also { server ->
if (!prefs.contains(getSyncDateKey(server, rid)))
return@withContext null
val time = prefs.getLong(getSyncDateKey(server, rid), -1)
return@withContext if (time == -1L) null else time
}
return@withContext null
}
private fun getSyncDateKey(server: String, rid: String) = "${KEY_LAST_SYNC_DATE}_$server $rid"
override suspend fun getById(id: String): Message? = withContext(CommonPool) { override suspend fun getById(id: String): Message? = withContext(CommonPool) {
currentServerInteractor.get()?.also { server -> currentServerInteractor.get()?.also { server ->
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment