mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Read receipts: branch settings to show/hide them
This commit is contained in:
parent
4e8dc72439
commit
d3827b8673
8 changed files with 73 additions and 23 deletions
|
@ -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
|
||||
|
||||
)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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\"*}"
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -24,4 +24,9 @@ class UserPreferencesProvider @Inject constructor(private val vectorPreferences:
|
|||
fun shouldShowHiddenEvents(): Boolean {
|
||||
return vectorPreferences.shouldShowHiddenEvents()
|
||||
}
|
||||
|
||||
fun shouldShowReadReceipts(): Boolean {
|
||||
return vectorPreferences.showReadReceipts()
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue