Read receipts: branch settings to show/hide them

This commit is contained in:
ganfra 2019-08-14 10:51:09 +02:00
parent 4e8dc72439
commit d3827b8673
8 changed files with 73 additions and 23 deletions

View File

@ -35,5 +35,10 @@ data class TimelineSettings(
/**
* If [filterTypes] is true, the list of types allowed by the list.
*/
val allowedTypes: List<String> = emptyList()
val allowedTypes: List<String> = emptyList(),
/**
* If true, will build read receipts for each event.
*/
val buildReadReceipts: Boolean = true

)

View File

@ -25,12 +25,15 @@ 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)
}

fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true, correctedReadReceipts: List<ReadReceipt>? = null): TimelineEvent {
val readReceipts = if (buildReadReceipts) {
correctedReadReceipts ?: timelineEventEntity.readReceipts
?.let {
readReceiptsSummaryMapper.map(it)
}
} else {
null
}
return TimelineEvent(
root = timelineEventEntity.root?.asDomain()
?: Event("", timelineEventEntity.eventId),

View File

@ -0,0 +1,23 @@
/*
* 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.database.query

internal object FilterContent {

internal const val EDIT_TYPE = "{*\"m.relates_to\"*\"rel_type\":*\"m.replace\"*}"

}

View File

@ -25,7 +25,6 @@ import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.api.util.CancelableBag
import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.ChunkEntity
@ -36,6 +35,7 @@ import im.vector.matrix.android.internal.database.model.EventEntityFields
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
import im.vector.matrix.android.internal.database.query.FilterContent
import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates
import im.vector.matrix.android.internal.database.query.findIncludingEvent
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
@ -65,8 +65,6 @@ import kotlin.collections.HashMap
private const val MIN_FETCHING_COUNT = 30
private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE

internal const val EDIT_FILTER_LIKE = "{*\"m.relates_to\"*\"rel_type\":*\"m.replace\"*}"

internal class DefaultTimeline(
private val roomId: String,
private val initialEventId: String? = null,
@ -154,7 +152,7 @@ internal class DefaultTimeline(
builtEventsIdMap[eventId]?.let { builtIndex ->
//Update an existing event
builtEvents[builtIndex]?.let { te ->
builtEvents[builtIndex] = timelineEventMapper.map(eventEntity, hiddenReadReceipts.correctedReadReceipts(te.root.eventId))
builtEvents[builtIndex] = buildTimelineEvent(eventEntity)
hasChanged = true
}
}
@ -239,7 +237,9 @@ internal class DefaultTimeline(
.findAllAsync()
.also { it.addChangeListener(relationsListener) }

hiddenReadReceipts.start(realm, liveEvents, this)
if (settings.buildReadReceipts) {
hiddenReadReceipts.start(realm, liveEvents, this)
}

isReady.set(true)
}
@ -255,7 +255,9 @@ internal class DefaultTimeline(
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
eventRelations.removeAllChangeListeners()
liveEvents.removeAllChangeListeners()
hiddenReadReceipts.dispose()
if (settings.buildReadReceipts) {
hiddenReadReceipts.dispose()
}
backgroundRealm.getAndSet(null).also {
it.close()
}
@ -482,7 +484,7 @@ internal class DefaultTimeline(
}
offsetResults.forEach { eventEntity ->

val timelineEvent = timelineEventMapper.map(eventEntity)
val timelineEvent = buildTimelineEvent(eventEntity)

if (timelineEvent.isEncrypted()
&& timelineEvent.root.mxDecryptionResult == null) {
@ -500,6 +502,12 @@ internal class DefaultTimeline(
return offsetResults.size
}

private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map(
timelineEventEntity = eventEntity,
buildReadReceipts = settings.buildReadReceipts,
correctedReadReceipts = hiddenReadReceipts.correctedReadReceipts(eventEntity.eventId)
)

/**
* This has to be called on TimelineThread as it access realm live results
*/
@ -584,7 +592,7 @@ internal class DefaultTimeline(
`in`(TimelineEventEntityFields.ROOT.TYPE, settings.allowedTypes.toTypedArray())
}
if (settings.filterEdits) {
not().like(TimelineEventEntityFields.ROOT.CONTENT, EDIT_FILTER_LIKE)
not().like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
}
return this
}

View File

@ -27,7 +27,6 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.internal.database.RealmLiveData
import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.task.TaskExecutor

View File

@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntit
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.FilterContent
import im.vector.matrix.android.internal.database.query.whereInRoom
import io.realm.OrderedRealmCollectionChangeListener
import io.realm.Realm
@ -48,11 +49,13 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu

private val hiddenReadReceiptsListener = OrderedRealmCollectionChangeListener<RealmResults<ReadReceiptsSummaryEntity>> { collection, changeSet ->
var hasChange = false
// Deletion here means we don't have any readReceipts for the given hidden events
changeSet.deletions.forEach {
val eventId = correctedReadReceiptsEventByIndex[it]
val timelineEvent = liveEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, eventId).findFirst()
// We are rebuilding the corresponding event with only his own RR
val readReceipts = readReceiptsSummaryMapper.map(timelineEvent?.readReceipts)
hasChange = hasChange || delegate.rebuildEvent(eventId, readReceipts)
hasChange = delegate.rebuildEvent(eventId, readReceipts) || hasChange
}
correctedReadReceiptsEventByIndex.clear()
correctedReadReceiptsByEvent.clear()
@ -60,10 +63,12 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
val timelineEvent = summary?.timelineEvent?.firstOrNull()
val displayIndex = timelineEvent?.root?.displayIndex
if (displayIndex != null) {
// Then we are looking for the first displayable event after the hidden one
val firstDisplayedEvent = liveEvents.where()
.lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, displayIndex)
.findFirst()

// If we find one, we should
if (firstDisplayedEvent != null) {
correctedReadReceiptsEventByIndex.put(index, firstDisplayedEvent.eventId)
correctedReadReceiptsByEvent.getOrPut(firstDisplayedEvent.eventId, {
@ -79,7 +84,7 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
val sortedReadReceipts = correctedReadReceipts.sortedByDescending {
it.originServerTs
}
hasChange = hasChange || delegate.rebuildEvent(eventId, sortedReadReceipts)
hasChange = delegate.rebuildEvent(eventId, sortedReadReceipts) || hasChange
}
}
if (hasChange) {
@ -91,6 +96,8 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
fun start(realm: Realm, liveEvents: RealmResults<TimelineEventEntity>, delegate: Delegate) {
this.liveEvents = liveEvents
this.delegate = delegate
// We are looking for read receipts set on hidden events.
// We only accept those with a timelineEvent (so coming from pagination/sync).
this.hiddenReadReceipts = ReadReceiptsSummaryEntity.whereInRoom(realm, roomId)
.isNotEmpty(ReadReceiptsSummaryEntityFields.TIMELINE_EVENT)
.isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`)
@ -109,7 +116,7 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu


/**
* We are looking for receipts related to filtered events. So, it's the opposite of [filterEventsWithSettings] method.
* We are looking for receipts related to filtered events. So, it's the opposite of [DefaultTimeline.filterEventsWithSettings] method.
*/
private fun RealmQuery<ReadReceiptsSummaryEntity>.filterReceiptsWithSettings(): RealmQuery<ReadReceiptsSummaryEntity> {
beginGroup()
@ -120,7 +127,7 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
or()
}
if (settings.filterEdits) {
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", EDIT_FILTER_LIKE)
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", FilterContent.EDIT_TYPE)
}
endGroup()
return this

View File

@ -24,4 +24,9 @@ class UserPreferencesProvider @Inject constructor(private val vectorPreferences:
fun shouldShowHiddenEvents(): Boolean {
return vectorPreferences.shouldShowHiddenEvents()
}

fun shouldShowReadReceipts(): Boolean {
return vectorPreferences.showReadReceipts()
}

}

View File

@ -67,7 +67,7 @@ import java.util.concurrent.TimeUnit


class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState,
userPreferencesProvider: UserPreferencesProvider,
private val userPreferencesProvider: UserPreferencesProvider,
private val vectorPreferences: VectorPreferences,
private val session: Session
) : VectorViewModel<RoomDetailViewState>(initialState) {
@ -77,9 +77,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
private val eventId = initialState.eventId
private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>()
private val timelineSettings = if (userPreferencesProvider.shouldShowHiddenEvents()) {
TimelineSettings(30, false, true, TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES)
TimelineSettings(30, false, true, TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES, userPreferencesProvider.shouldShowReadReceipts())
} else {
TimelineSettings(30, true, true, TimelineDisplayableEvents.DISPLAYABLE_TYPES)
TimelineSettings(30, true, true, TimelineDisplayableEvents.DISPLAYABLE_TYPES, userPreferencesProvider.shouldShowReadReceipts())
}

private var timeline = room.createTimeline(eventId, timelineSettings)