Read receipts: fix not appearing RR
This commit is contained in:
parent
21deb2551d
commit
06dcf75a32
|
@ -17,7 +17,6 @@
|
|||
package im.vector.matrix.android.internal.database.mapper
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.UserEntity
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
|
@ -40,9 +39,6 @@ internal class ReadReceiptsSummaryMapper @Inject constructor(@SessionDatabase pr
|
|||
?: return@mapNotNull null
|
||||
ReadReceipt(user.asDomain(), it.originServerTs.toLong())
|
||||
}
|
||||
.sortedByDescending {
|
||||
it.originServerTs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,9 +26,11 @@ import javax.inject.Inject
|
|||
internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) {
|
||||
|
||||
fun map(timelineEventEntity: TimelineEventEntity, correctedReadReceipts: List<ReadReceipt>? = null): TimelineEvent {
|
||||
val readReceipts = correctedReadReceipts ?: timelineEventEntity.readReceipts?.let {
|
||||
readReceiptsSummaryMapper.map(it)
|
||||
}
|
||||
val readReceipts = correctedReadReceipts ?: timelineEventEntity.readReceipts
|
||||
?.let {
|
||||
readReceiptsSummaryMapper.map(it)
|
||||
}
|
||||
|
||||
return TimelineEvent(
|
||||
root = timelineEventEntity.root?.asDomain()
|
||||
?: Event("", timelineEventEntity.eventId),
|
||||
|
@ -38,7 +40,9 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS
|
|||
senderName = timelineEventEntity.senderName,
|
||||
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
||||
senderAvatar = timelineEventEntity.senderAvatar,
|
||||
readReceipts = readReceipts ?: emptyList()
|
||||
readReceipts = readReceipts?.sortedByDescending {
|
||||
it.originServerTs
|
||||
} ?: emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,8 @@ internal class DefaultReadService @Inject constructor(private val roomId: String
|
|||
return Transformations.map(liveEntity) { realmResults ->
|
||||
realmResults.firstOrNull()?.let {
|
||||
readReceiptsSummaryMapper.map(it)
|
||||
}?.sortedByDescending {
|
||||
it.originServerTs
|
||||
} ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.matrix.android.internal.session.room.timeline
|
||||
|
||||
import android.util.SparseArray
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
|
@ -34,8 +33,6 @@ import im.vector.matrix.android.internal.database.model.ChunkEntityFields
|
|||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||
|
@ -68,7 +65,7 @@ import kotlin.collections.HashMap
|
|||
private const val MIN_FETCHING_COUNT = 30
|
||||
private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE
|
||||
|
||||
private const val EDIT_FILTER_LIKE = "{*\"m.relates_to\"*\"rel_type\":*\"m.replace\"*}"
|
||||
internal const val EDIT_FILTER_LIKE = "{*\"m.relates_to\"*\"rel_type\":*\"m.replace\"*}"
|
||||
|
||||
internal class DefaultTimeline(
|
||||
private val roomId: String,
|
||||
|
@ -79,9 +76,9 @@ internal class DefaultTimeline(
|
|||
private val paginationTask: PaginationTask,
|
||||
private val cryptoService: CryptoService,
|
||||
private val timelineEventMapper: TimelineEventMapper,
|
||||
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
|
||||
private val settings: TimelineSettings
|
||||
) : Timeline {
|
||||
private val settings: TimelineSettings,
|
||||
private val hiddenReadReceipts: TimelineHiddenReadReceipts
|
||||
) : Timeline, TimelineHiddenReadReceipts.Delegate {
|
||||
|
||||
private companion object {
|
||||
val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
|
||||
|
@ -104,9 +101,6 @@ internal class DefaultTimeline(
|
|||
|
||||
private lateinit var liveEvents: RealmResults<TimelineEventEntity>
|
||||
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
|
||||
private var hiddenReadReceipts: RealmResults<ReadReceiptsSummaryEntity>? = null
|
||||
private val correctedReadReceiptsEventByIndex = SparseArray<String>()
|
||||
private val correctedReadReceiptsByEvent = HashMap<String, MutableList<ReadReceipt>>()
|
||||
|
||||
private var roomEntity: RoomEntity? = null
|
||||
|
||||
|
@ -118,10 +112,8 @@ internal class DefaultTimeline(
|
|||
private val backwardsPaginationState = AtomicReference(PaginationState())
|
||||
private val forwardsPaginationState = AtomicReference(PaginationState())
|
||||
|
||||
|
||||
private val timelineID = UUID.randomUUID().toString()
|
||||
|
||||
|
||||
private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService)
|
||||
|
||||
private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<TimelineEventEntity>> { results, changeSet ->
|
||||
|
@ -162,7 +154,7 @@ internal class DefaultTimeline(
|
|||
builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||
//Update an existing event
|
||||
builtEvents[builtIndex]?.let { te ->
|
||||
builtEvents[builtIndex] = timelineEventMapper.map(eventEntity, correctedReadReceiptsByEvent[te.root.eventId])
|
||||
builtEvents[builtIndex] = timelineEventMapper.map(eventEntity, hiddenReadReceipts.correctedReadReceipts(te.root.eventId))
|
||||
hasChanged = true
|
||||
}
|
||||
}
|
||||
|
@ -192,56 +184,8 @@ internal class DefaultTimeline(
|
|||
postSnapshot()
|
||||
}
|
||||
|
||||
private val hiddenReadReceiptsListener = OrderedRealmCollectionChangeListener<RealmResults<ReadReceiptsSummaryEntity>> { collection, changeSet ->
|
||||
var hasChange = false
|
||||
changeSet.deletions.forEach {
|
||||
val eventId = correctedReadReceiptsEventByIndex[it]
|
||||
val timelineEvent = liveEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, eventId).findFirst()
|
||||
builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||
builtEvents[builtIndex]?.let { te ->
|
||||
builtEvents[builtIndex] = te.copy(readReceipts = readReceiptsSummaryMapper.map(timelineEvent?.readReceipts))
|
||||
hasChange = true
|
||||
}
|
||||
}
|
||||
}
|
||||
correctedReadReceiptsEventByIndex.clear()
|
||||
correctedReadReceiptsByEvent.clear()
|
||||
val loadedReadReceipts = collection.where().greaterThan("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.DISPLAY_INDEX}", prevDisplayIndex).findAll()
|
||||
loadedReadReceipts.forEachIndexed { index, summary ->
|
||||
val timelineEvent = summary?.timelineEvent?.firstOrNull()
|
||||
val displayIndex = timelineEvent?.root?.displayIndex
|
||||
if (displayIndex != null) {
|
||||
val firstDisplayedEvent = liveEvents.where()
|
||||
.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||
.lessThan(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, displayIndex)
|
||||
.findFirst()
|
||||
|
||||
if (firstDisplayedEvent != null) {
|
||||
correctedReadReceiptsEventByIndex.put(index, firstDisplayedEvent.eventId)
|
||||
correctedReadReceiptsByEvent.getOrPut(firstDisplayedEvent.eventId, {
|
||||
readReceiptsSummaryMapper.map(firstDisplayedEvent.readReceipts).toMutableList()
|
||||
}).addAll(
|
||||
readReceiptsSummaryMapper.map(summary)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (correctedReadReceiptsByEvent.isNotEmpty()) {
|
||||
correctedReadReceiptsByEvent.forEach { (eventId, correctedReadReceipts) ->
|
||||
builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||
builtEvents[builtIndex]?.let { te ->
|
||||
builtEvents[builtIndex] = te.copy(readReceipts = correctedReadReceipts)
|
||||
hasChange = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasChange) {
|
||||
postSnapshot()
|
||||
}
|
||||
}
|
||||
|
||||
// Public methods ******************************************************************************
|
||||
// Public methods ******************************************************************************
|
||||
|
||||
override fun paginate(direction: Timeline.Direction, count: Int) {
|
||||
BACKGROUND_HANDLER.post {
|
||||
|
@ -295,12 +239,7 @@ internal class DefaultTimeline(
|
|||
.findAllAsync()
|
||||
.also { it.addChangeListener(relationsListener) }
|
||||
|
||||
hiddenReadReceipts = ReadReceiptsSummaryEntity.whereInRoom(realm, roomId)
|
||||
.isNotEmpty(ReadReceiptsSummaryEntityFields.TIMELINE_EVENT)
|
||||
.isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`)
|
||||
.filterReceiptsWithSettings()
|
||||
.findAllAsync()
|
||||
.also { it.addChangeListener(hiddenReadReceiptsListener) }
|
||||
hiddenReadReceipts.start(realm, liveEvents, this)
|
||||
|
||||
isReady.set(true)
|
||||
}
|
||||
|
@ -315,8 +254,8 @@ internal class DefaultTimeline(
|
|||
cancelableBag.cancel()
|
||||
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
|
||||
eventRelations.removeAllChangeListeners()
|
||||
hiddenReadReceipts?.removeAllChangeListeners()
|
||||
liveEvents.removeAllChangeListeners()
|
||||
hiddenReadReceipts.dispose()
|
||||
backgroundRealm.getAndSet(null).also {
|
||||
it.close()
|
||||
}
|
||||
|
@ -328,6 +267,22 @@ internal class DefaultTimeline(
|
|||
return hasMoreInCache(direction) || !hasReachedEnd(direction)
|
||||
}
|
||||
|
||||
// TimelineHiddenReadReceipts.Delegate
|
||||
|
||||
override fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean {
|
||||
return builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||
//Update the relation of existing event
|
||||
builtEvents[builtIndex]?.let { te ->
|
||||
builtEvents[builtIndex] = te.copy(readReceipts = readReceipts)
|
||||
true
|
||||
}
|
||||
} ?: false
|
||||
}
|
||||
|
||||
override fun onReadReceiptsUpdated() {
|
||||
postSnapshot()
|
||||
}
|
||||
|
||||
// Private methods *****************************************************************************
|
||||
|
||||
private fun hasMoreInCache(direction: Timeline.Direction): Boolean {
|
||||
|
@ -608,7 +563,7 @@ internal class DefaultTimeline(
|
|||
debouncer.debounce("post_snapshot", runnable, 50)
|
||||
}
|
||||
|
||||
// Extension methods ***************************************************************************
|
||||
// Extension methods ***************************************************************************
|
||||
|
||||
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
|
||||
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
|
||||
|
@ -634,23 +589,6 @@ internal class DefaultTimeline(
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* We are looking for receipts related to filtered events. So, it's the opposite of [filterEventsWithSettings] method.
|
||||
*/
|
||||
private fun RealmQuery<ReadReceiptsSummaryEntity>.filterReceiptsWithSettings(): RealmQuery<ReadReceiptsSummaryEntity> {
|
||||
beginGroup()
|
||||
if (settings.filterTypes) {
|
||||
not().`in`("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", settings.allowedTypes.toTypedArray())
|
||||
}
|
||||
if (settings.filterTypes && settings.filterEdits) {
|
||||
or()
|
||||
}
|
||||
if (settings.filterEdits) {
|
||||
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", EDIT_FILTER_LIKE)
|
||||
}
|
||||
endGroup()
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
private data class PaginationState(
|
||||
|
|
|
@ -53,8 +53,8 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St
|
|||
paginationTask,
|
||||
cryptoService,
|
||||
timelineEventMapper,
|
||||
readReceiptsSummaryMapper,
|
||||
settings
|
||||
settings,
|
||||
TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.room.timeline
|
||||
|
||||
import android.util.SparseArray
|
||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.whereInRoom
|
||||
import io.realm.OrderedRealmCollectionChangeListener
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.RealmResults
|
||||
|
||||
internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
|
||||
private val roomId: String,
|
||||
private val settings: TimelineSettings) {
|
||||
|
||||
interface Delegate {
|
||||
fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean
|
||||
fun onReadReceiptsUpdated()
|
||||
}
|
||||
|
||||
private val correctedReadReceiptsEventByIndex = SparseArray<String>()
|
||||
private val correctedReadReceiptsByEvent = HashMap<String, MutableList<ReadReceipt>>()
|
||||
|
||||
private lateinit var hiddenReadReceipts: RealmResults<ReadReceiptsSummaryEntity>
|
||||
private lateinit var liveEvents: RealmResults<TimelineEventEntity>
|
||||
private lateinit var delegate: Delegate
|
||||
|
||||
private val hiddenReadReceiptsListener = OrderedRealmCollectionChangeListener<RealmResults<ReadReceiptsSummaryEntity>> { collection, changeSet ->
|
||||
var hasChange = false
|
||||
changeSet.deletions.forEach {
|
||||
val eventId = correctedReadReceiptsEventByIndex[it]
|
||||
val timelineEvent = liveEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, eventId).findFirst()
|
||||
val readReceipts = readReceiptsSummaryMapper.map(timelineEvent?.readReceipts)
|
||||
hasChange = hasChange || delegate.rebuildEvent(eventId, readReceipts)
|
||||
}
|
||||
correctedReadReceiptsEventByIndex.clear()
|
||||
correctedReadReceiptsByEvent.clear()
|
||||
hiddenReadReceipts.forEachIndexed { index, summary ->
|
||||
val timelineEvent = summary?.timelineEvent?.firstOrNull()
|
||||
val displayIndex = timelineEvent?.root?.displayIndex
|
||||
if (displayIndex != null) {
|
||||
val firstDisplayedEvent = liveEvents.where()
|
||||
.lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, displayIndex)
|
||||
.findFirst()
|
||||
|
||||
if (firstDisplayedEvent != null) {
|
||||
correctedReadReceiptsEventByIndex.put(index, firstDisplayedEvent.eventId)
|
||||
correctedReadReceiptsByEvent.getOrPut(firstDisplayedEvent.eventId, {
|
||||
readReceiptsSummaryMapper.map(firstDisplayedEvent.readReceipts).toMutableList()
|
||||
}).addAll(
|
||||
readReceiptsSummaryMapper.map(summary)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (correctedReadReceiptsByEvent.isNotEmpty()) {
|
||||
correctedReadReceiptsByEvent.forEach { (eventId, correctedReadReceipts) ->
|
||||
val sortedReadReceipts = correctedReadReceipts.sortedByDescending {
|
||||
it.originServerTs
|
||||
}
|
||||
hasChange = hasChange || delegate.rebuildEvent(eventId, sortedReadReceipts)
|
||||
}
|
||||
}
|
||||
if (hasChange) {
|
||||
delegate.onReadReceiptsUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun start(realm: Realm, liveEvents: RealmResults<TimelineEventEntity>, delegate: Delegate) {
|
||||
this.liveEvents = liveEvents
|
||||
this.delegate = delegate
|
||||
this.hiddenReadReceipts = ReadReceiptsSummaryEntity.whereInRoom(realm, roomId)
|
||||
.isNotEmpty(ReadReceiptsSummaryEntityFields.TIMELINE_EVENT)
|
||||
.isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`)
|
||||
.filterReceiptsWithSettings()
|
||||
.findAllAsync()
|
||||
.also { it.addChangeListener(hiddenReadReceiptsListener) }
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
this.hiddenReadReceipts?.removeAllChangeListeners()
|
||||
}
|
||||
|
||||
fun correctedReadReceipts(eventId: String?): List<ReadReceipt>? {
|
||||
return correctedReadReceiptsByEvent[eventId]
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We are looking for receipts related to filtered events. So, it's the opposite of [filterEventsWithSettings] method.
|
||||
*/
|
||||
private fun RealmQuery<ReadReceiptsSummaryEntity>.filterReceiptsWithSettings(): RealmQuery<ReadReceiptsSummaryEntity> {
|
||||
beginGroup()
|
||||
if (settings.filterTypes) {
|
||||
not().`in`("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", settings.allowedTypes.toTypedArray())
|
||||
}
|
||||
if (settings.filterTypes && settings.filterEdits) {
|
||||
or()
|
||||
}
|
||||
if (settings.filterEdits) {
|
||||
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", EDIT_FILTER_LIKE)
|
||||
}
|
||||
endGroup()
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -78,7 +78,9 @@ internal class ReadReceiptHandler @Inject constructor() {
|
|||
for ((eventId, receiptDict) in content) {
|
||||
val userIdsDict = receiptDict[READ_KEY] ?: continue
|
||||
val readReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
|
||||
?: realm.createObject(ReadReceiptsSummaryEntity::class.java, eventId)
|
||||
?: realm.createObject(ReadReceiptsSummaryEntity::class.java, eventId).apply {
|
||||
this.roomId = roomId
|
||||
}
|
||||
|
||||
for ((userId, paramsDict) in userIdsDict) {
|
||||
val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0
|
||||
|
|
Loading…
Reference in New Issue