diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index 8932d0734e..06c88db831 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -95,12 +95,6 @@ interface Timeline { */ fun getTimelineEventWithId(eventId: String?): TimelineEvent? - /** - * Returns the first displayable events starting from eventId. - * It does depend on the provided [TimelineSettings]. - */ - fun getFirstDisplayableEventId(eventId: String): String? - interface Listener { /** * Call when the timeline has been updated through pagination or sync. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt index 25c63d6fbc..ceffedb234 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt @@ -24,10 +24,6 @@ data class TimelineSettings( * The initial number of events to retrieve from cache. You might get less events if you don't have loaded enough yet. */ val initialSize: Int, - /** - * Filters for timeline event - */ - val filters: TimelineEventFilters = TimelineEventFilters(), /** * If true, will build read receipts for each event. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt index a2b36ce590..07954650b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt @@ -25,9 +25,9 @@ import javax.inject.Inject internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) { - fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true, correctedReadReceipts: List? = null): TimelineEvent { + fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true): TimelineEvent { val readReceipts = if (buildReadReceipts) { - correctedReadReceipts ?: timelineEventEntity.readReceipts + timelineEventEntity.readReceipts ?.let { readReceiptsSummaryMapper.map(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 61f770b956..1ed142ce23 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -70,14 +69,12 @@ internal class DefaultTimeline( private val paginationTask: PaginationTask, private val timelineEventMapper: TimelineEventMapper, private val settings: TimelineSettings, - private val hiddenReadReceipts: TimelineHiddenReadReceipts, private val timelineInput: TimelineInput, private val eventDecryptor: TimelineEventDecryptor, private val realmSessionProvider: RealmSessionProvider, private val loadRoomMembersTask: LoadRoomMembersTask, private val readReceiptHandler: ReadReceiptHandler ) : Timeline, - TimelineHiddenReadReceipts.Delegate, TimelineInput.Listener, UIEchoManager.Listener { @@ -93,8 +90,7 @@ internal class DefaultTimeline( private val cancelableBag = CancelableBag() private val debouncer = Debouncer(mainHandler) - private lateinit var nonFilteredEvents: RealmResults - private lateinit var filteredEvents: RealmResults + private lateinit var timelineEvents: RealmResults private lateinit var sendingEvents: RealmResults private var prevDisplayIndex: Int? = null @@ -168,16 +164,9 @@ internal class DefaultTimeline( postSnapshot() } - nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() - filteredEvents = nonFilteredEvents.where() - .filterEventsWithSettings(settings) - .findAll() - nonFilteredEvents.addChangeListener(eventsChangeListener) + timelineEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() + timelineEvents.addChangeListener(eventsChangeListener) handleInitialLoad() - if (settings.shouldHandleHiddenReadReceipts()) { - hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this) - } - loadRoomMembersTask .configureWith(LoadRoomMembersTask.Params(roomId)) { this.callback = NoOpMatrixCallback() @@ -205,10 +194,6 @@ internal class DefaultTimeline( } } - private fun TimelineSettings.shouldHandleHiddenReadReceipts(): Boolean { - return buildReadReceipts && (filters.filterEdits || filters.filterTypes) - } - override fun dispose() { if (isStarted.compareAndSet(true, false)) { isReady.set(false) @@ -220,11 +205,8 @@ internal class DefaultTimeline( if (this::sendingEvents.isInitialized) { sendingEvents.removeAllChangeListeners() } - if (this::nonFilteredEvents.isInitialized) { - nonFilteredEvents.removeAllChangeListeners() - } - if (settings.shouldHandleHiddenReadReceipts()) { - hiddenReadReceipts.dispose() + if (this::timelineEvents.isInitialized) { + timelineEvents.removeAllChangeListeners() } clearAllValues() backgroundRealm.getAndSet(null).also { @@ -256,48 +238,6 @@ internal class DefaultTimeline( } } - override fun getFirstDisplayableEventId(eventId: String): String? { - // If the item is built, the id is obviously displayable - val builtIndex = builtEventsIdMap[eventId] - if (builtIndex != null) { - return eventId - } - // Otherwise, we should check if the event is in the db, but is hidden because of filters - return realmSessionProvider.withRealm { localRealm -> - val nonFilteredEvents = buildEventQuery(localRealm) - .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) - .findAll() - - val nonFilteredEvent = nonFilteredEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, eventId) - .findFirst() - - val filteredEvents = nonFilteredEvents.where() - .filterEventsWithSettings(settings) - .findAll() - val isEventInDb = nonFilteredEvent != null - - val isHidden = isEventInDb && filteredEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, eventId) - .findFirst() == null - - if (isHidden) { - val displayIndex = nonFilteredEvent?.displayIndex - if (displayIndex != null) { - // Then we are looking for the first displayable event after the hidden one - val firstDisplayedEvent = filteredEvents.where() - .lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, displayIndex) - .findFirst() - firstDisplayedEvent?.eventId - } else { - null - } - } else { - null - } - } - } - override fun hasMoreToLoad(direction: Timeline.Direction): Boolean { return hasMoreInCache(direction) || !hasReachedEnd(direction) } @@ -319,18 +259,6 @@ internal class DefaultTimeline( listeners.clear() } -// TimelineHiddenReadReceipts.Delegate - - override fun rebuildEvent(eventId: String, readReceipts: List): Boolean { - return rebuildEvent(eventId) { te -> - te.copy(readReceipts = readReceipts) - } - } - - override fun onReadReceiptsUpdated() { - postSnapshot() - } - override fun onNewTimelineEvents(roomId: String, eventIds: List) { if (isLive && this.roomId == roomId) { listeners.forEach { @@ -341,18 +269,13 @@ internal class DefaultTimeline( override fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) { if (roomId != this.roomId || !isLive) return - - val postSnapShot = uiEchoManager.onLocalEchoCreated(timelineEvent) - - if (listOf(timelineEvent).filterEventsWithSettings(settings).isNotEmpty()) { - listeners.forEach { + uiEchoManager.onLocalEchoCreated(timelineEvent) + listeners.forEach { + tryOrNull { it.onNewTimelineEvents(listOf(timelineEvent.eventId)) } } - - if (postSnapShot) { - postSnapshot() - } + postSnapshot() } override fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) { @@ -439,23 +362,21 @@ internal class DefaultTimeline( val builtSendingEvents = mutableListOf() if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) { uiEchoManager.getInMemorySendingEvents() - .filterSendingEventsTo(builtSendingEvents) + .updateWithUiEchoInto(builtSendingEvents) sendingEvents .filter { timelineEvent -> builtSendingEvents.none { it.eventId == timelineEvent.eventId } } .map { timelineEventMapper.map(it) } - .filterSendingEventsTo(builtSendingEvents) + .updateWithUiEchoInto(builtSendingEvents) } return builtSendingEvents } - private fun List.filterSendingEventsTo(target: MutableList) { + private fun List.updateWithUiEchoInto(target: MutableList) { target.addAll( - // Filter out sending event that are not displayable! - filterEventsWithSettings(settings) - // Get most up to date send state (in memory) - .map { uiEchoManager.updateSentStateWithUiEcho(it) } + // Get most up to date send state (in memory) + map { uiEchoManager.updateSentStateWithUiEcho(it) } ) } @@ -465,14 +386,14 @@ internal class DefaultTimeline( private fun getState(direction: Timeline.Direction): TimelineState { return when (direction) { - Timeline.Direction.FORWARDS -> forwardsState.get() + Timeline.Direction.FORWARDS -> forwardsState.get() Timeline.Direction.BACKWARDS -> backwardsState.get() } } private fun updateState(direction: Timeline.Direction, update: (TimelineState) -> TimelineState) { val stateReference = when (direction) { - Timeline.Direction.FORWARDS -> forwardsState + Timeline.Direction.FORWARDS -> forwardsState Timeline.Direction.BACKWARDS -> backwardsState } val currentValue = stateReference.get() @@ -487,9 +408,9 @@ internal class DefaultTimeline( var shouldFetchInitialEvent = false val currentInitialEventId = initialEventId val initialDisplayIndex = if (currentInitialEventId == null) { - nonFilteredEvents.firstOrNull()?.displayIndex + timelineEvents.firstOrNull()?.displayIndex } else { - val initialEvent = nonFilteredEvents.where() + val initialEvent = timelineEvents.where() .equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId) .findFirst() @@ -501,7 +422,7 @@ internal class DefaultTimeline( if (currentInitialEventId != null && shouldFetchInitialEvent) { fetchEvent(currentInitialEventId) } else { - val count = filteredEvents.size.coerceAtMost(settings.initialSize) + val count = timelineEvents.size.coerceAtMost(settings.initialSize) if (initialEventId == null) { paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count) } else { @@ -541,8 +462,7 @@ internal class DefaultTimeline( val eventEntity = results[index] eventEntity?.eventId?.let { eventId -> postSnapshot = rebuildEvent(eventId) { - val builtEvent = buildTimelineEvent(eventEntity) - listOf(builtEvent).filterEventsWithSettings(settings).firstOrNull() + buildTimelineEvent(eventEntity) } || postSnapshot } } @@ -563,9 +483,9 @@ internal class DefaultTimeline( // We are in the case where event exists, but we do not know the token. // Fetch (again) the last event to get a token val lastKnownEventId = if (direction == Timeline.Direction.FORWARDS) { - nonFilteredEvents.firstOrNull()?.eventId + timelineEvents.firstOrNull()?.eventId } else { - nonFilteredEvents.lastOrNull()?.eventId + timelineEvents.lastOrNull()?.eventId } if (lastKnownEventId == null) { updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } @@ -636,7 +556,7 @@ internal class DefaultTimeline( * Return the current Chunk */ private fun getLiveChunk(): ChunkEntity? { - return nonFilteredEvents.firstOrNull()?.chunk?.firstOrNull() + return timelineEvents.firstOrNull()?.chunk?.firstOrNull() } /** @@ -680,14 +600,13 @@ internal class DefaultTimeline( val time = System.currentTimeMillis() - start Timber.v("Built ${offsetResults.size} items from db in $time ms") // For the case where wo reach the lastForward chunk - updateLoadingStates(filteredEvents) + updateLoadingStates(timelineEvents) return offsetResults.size } private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map( timelineEventEntity = eventEntity, - buildReadReceipts = settings.buildReadReceipts, - correctedReadReceipts = hiddenReadReceipts.correctedReadReceipts(eventEntity.eventId) + buildReadReceipts = settings.buildReadReceipts ).let { // eventually enhance with ui echo? (uiEchoManager.decorateEventWithReactionUiEcho(it) ?: it) @@ -699,7 +618,7 @@ internal class DefaultTimeline( private fun getOffsetResults(startDisplayIndex: Int, direction: Timeline.Direction, count: Long): RealmResults { - val offsetQuery = filteredEvents.where() + val offsetQuery = timelineEvents.where() if (direction == Timeline.Direction.BACKWARDS) { offsetQuery .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) @@ -747,7 +666,7 @@ internal class DefaultTimeline( if (isReady.get().not()) { return@post } - updateLoadingStates(filteredEvents) + updateLoadingStates(timelineEvents) val snapshot = createSnapshot() val runnable = Runnable { listeners.forEach { @@ -783,10 +702,10 @@ internal class DefaultTimeline( return object : MatrixCallback { override fun onSuccess(data: TokenChunkEventPersistor.Result) { when (data) { - TokenChunkEventPersistor.Result.SUCCESS -> { + TokenChunkEventPersistor.Result.SUCCESS -> { Timber.v("Success fetching $limit items $direction from pagination request") } - TokenChunkEventPersistor.Result.REACHED_END -> { + TokenChunkEventPersistor.Result.REACHED_END -> { postSnapshot() } TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index c3714a1303..6b50e2e8de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -52,7 +52,6 @@ internal class DefaultTimelineService @AssistedInject constructor( private val paginationTask: PaginationTask, private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, private val timelineEventMapper: TimelineEventMapper, - private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, private val loadRoomMembersTask: LoadRoomMembersTask, private val readReceiptHandler: ReadReceiptHandler ) : TimelineService { @@ -72,7 +71,6 @@ internal class DefaultTimelineService @AssistedInject constructor( paginationTask = paginationTask, timelineEventMapper = timelineEventMapper, settings = settings, - hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings), timelineInput = timelineInput, eventDecryptor = eventDecryptor, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt deleted file mode 100644 index b2c8021f3b..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2021 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.internal.session.room.timeline - -import io.realm.RealmQuery -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.message.MessageContent -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings -import org.matrix.android.sdk.internal.database.model.TimelineEventEntity -import org.matrix.android.sdk.internal.database.query.filterEvents - -internal fun RealmQuery.filterEventsWithSettings(settings: TimelineSettings): RealmQuery { - return filterEvents(settings.filters) -} - -internal fun List.filterEventsWithSettings(settings: TimelineSettings): List { - return filter { event -> - val filterType = !settings.filters.filterTypes - || settings.filters.allowedTypes.any { it.eventType == event.root.type && (it.stateKey == null || it.stateKey == event.root.senderId) } - if (!filterType) return@filter false - - val filterEdits = if (settings.filters.filterEdits && event.root.getClearType() == EventType.MESSAGE) { - val messageContent = event.root.getClearContent().toModel() - messageContent?.relatesTo?.type != RelationType.REPLACE && messageContent?.relatesTo?.type != RelationType.RESPONSE - } else { - true - } - if (!filterEdits) return@filter false - - val filterRedacted = settings.filters.filterRedacted && event.root.isRedacted() - !filterRedacted - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt deleted file mode 100644 index 0ade8ad3b8..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * 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 org.matrix.android.sdk.internal.session.room.timeline - -import android.util.SparseArray -import io.realm.OrderedRealmCollectionChangeListener -import io.realm.Realm -import io.realm.RealmQuery -import io.realm.RealmResults -import org.matrix.android.sdk.api.session.room.model.ReadReceipt -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings -import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper -import org.matrix.android.sdk.internal.database.model.EventEntityFields -import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity -import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntityFields -import org.matrix.android.sdk.internal.database.model.TimelineEventEntity -import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields -import org.matrix.android.sdk.internal.database.query.TimelineEventFilter -import org.matrix.android.sdk.internal.database.query.whereInRoom - -/** - * This class is responsible for handling the read receipts for hidden events (check [TimelineSettings] to see filtering). - * When an hidden event has read receipts, we want to transfer these read receipts on the first older displayed event. - * It has to be used in [DefaultTimeline] and we should call the [start] and [dispose] methods to properly handle realm subscription. - */ -internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, - private val roomId: String, - private val settings: TimelineSettings) { - - interface Delegate { - fun rebuildEvent(eventId: String, readReceipts: List): Boolean - fun onReadReceiptsUpdated() - } - - private val correctedReadReceiptsEventByIndex = SparseArray() - private val correctedReadReceiptsByEvent = HashMap>() - - private lateinit var hiddenReadReceipts: RealmResults - private lateinit var nonFilteredEvents: RealmResults - private lateinit var filteredEvents: RealmResults - private lateinit var delegate: Delegate - - private val hiddenReadReceiptsListener = OrderedRealmCollectionChangeListener> { collection, changeSet -> - if (!collection.isLoaded || !collection.isValid) { - return@OrderedRealmCollectionChangeListener - } - var hasChange = false - // Deletion here means we don't have any readReceipts for the given hidden events - changeSet.deletions.forEach { - val eventId = correctedReadReceiptsEventByIndex.get(it, "") - val timelineEvent = filteredEvents.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 = delegate.rebuildEvent(eventId, readReceipts) || hasChange - } - correctedReadReceiptsEventByIndex.clear() - correctedReadReceiptsByEvent.clear() - for (index in 0 until hiddenReadReceipts.size) { - val summary = hiddenReadReceipts[index] ?: continue - val timelineEvent = summary.timelineEvent?.firstOrNull() ?: continue - val isLoaded = nonFilteredEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, timelineEvent.eventId).findFirst() != null - val displayIndex = timelineEvent.displayIndex - - if (isLoaded) { - // Then we are looking for the first displayable event after the hidden one - val firstDisplayedEvent = filteredEvents.where() - .lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, displayIndex) - .findFirst() - - // If we find one, we should - if (firstDisplayedEvent != null) { - correctedReadReceiptsEventByIndex.put(index, firstDisplayedEvent.eventId) - correctedReadReceiptsByEvent - .getOrPut(firstDisplayedEvent.eventId, { - ArrayList(readReceiptsSummaryMapper.map(firstDisplayedEvent.readReceipts)) - }) - .addAll(readReceiptsSummaryMapper.map(summary)) - } - } - } - if (correctedReadReceiptsByEvent.isNotEmpty()) { - correctedReadReceiptsByEvent.forEach { (eventId, correctedReadReceipts) -> - val sortedReadReceipts = correctedReadReceipts.sortedByDescending { - it.originServerTs - } - hasChange = delegate.rebuildEvent(eventId, sortedReadReceipts) || hasChange - } - } - if (hasChange) { - delegate.onReadReceiptsUpdated() - } - } - - /** - * Start the realm query subscription. Has to be called on an HandlerThread - */ - fun start(realm: Realm, - filteredEvents: RealmResults, - nonFilteredEvents: RealmResults, - delegate: Delegate) { - this.filteredEvents = filteredEvents - this.nonFilteredEvents = nonFilteredEvents - 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.`$`) - .filterReceiptsWithSettings() - .findAllAsync() - .also { it.addChangeListener(hiddenReadReceiptsListener) } - } - - /** - * Dispose the realm query subscription. Has to be called on an HandlerThread - */ - fun dispose() { - if (this::hiddenReadReceipts.isInitialized) { - this.hiddenReadReceipts.removeAllChangeListeners() - } - } - - /** - * Return the current corrected [ReadReceipt] list for an event, or null - */ - fun correctedReadReceipts(eventId: String?): List? { - return correctedReadReceiptsByEvent[eventId] - } - - /** - * We are looking for receipts related to filtered events. So, it's the opposite of [DefaultTimeline.filterEventsWithSettings] method. - */ - private fun RealmQuery.filterReceiptsWithSettings(): RealmQuery { - beginGroup() - var needOr = false - if (settings.filters.filterTypes) { - beginGroup() - // Events: A, B, C, D, (E and S1), F, G, (H and S1), I - // Allowed: A, B, C, (E and S1), G, (H and S2) - // Result: D, F, H, I - settings.filters.allowedTypes.forEachIndexed { index, filter -> - if (filter.stateKey == null) { - notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.TYPE}", filter.eventType) - } else { - beginGroup() - notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.TYPE}", filter.eventType) - or() - notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.STATE_KEY}", filter.stateKey) - endGroup() - } - if (index != settings.filters.allowedTypes.size - 1) { - and() - } - } - endGroup() - needOr = true - } - if (settings.filters.filterUseless) { - if (needOr) or() - equalTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.IS_USELESS}", true) - needOr = true - } - if (settings.filters.filterEdits) { - if (needOr) or() - like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.CONTENT}", TimelineEventFilter.Content.EDIT) - or() - like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.CONTENT}", TimelineEventFilter.Content.RESPONSE) - needOr = true - } - if (settings.filters.filterRedacted) { - if (needOr) or() - like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.UNSIGNED_DATA}", TimelineEventFilter.Unsigned.REDACTED) - } - endGroup() - return this - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt index 67d0d90d77..44326ce1ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt @@ -70,15 +70,13 @@ internal class UIEchoManager( return existingState != sendState } - // return true if should update - fun onLocalEchoCreated(timelineEvent: TimelineEvent): Boolean { - var postSnapshot = false + fun onLocalEchoCreated(timelineEvent: TimelineEvent) { // Manage some ui echos (do it before filter because actual event could be filtered out) when (timelineEvent.root.getClearType()) { EventType.REDACTION -> { } - EventType.REACTION -> { + EventType.REACTION -> { val content = timelineEvent.root.content?.toModel() if (RelationType.ANNOTATION == content?.relatesTo?.type) { val reaction = content.relatesTo.key @@ -91,21 +89,14 @@ internal class UIEchoManager( reaction = reaction ) ) - postSnapshot = listener.rebuildEvent(relatedEventID) { + listener.rebuildEvent(relatedEventID) { decorateEventWithReactionUiEcho(it) - } || postSnapshot + } } } } - - // do not add events that would have been filtered - if (listOf(timelineEvent).filterEventsWithSettings(settings).isNotEmpty()) { - Timber.v("On local echo created: ${timelineEvent.eventId}") - inMemorySendingEvents.add(0, timelineEvent) - postSnapshot = true - } - - return postSnapshot + Timber.v("On local echo created: ${timelineEvent.eventId}") + inMemorySendingEvents.add(0, timelineEvent) } fun decorateEventWithReactionUiEcho(timelineEvent: TimelineEvent): TimelineEvent? { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index f116c2b9af..b2e7004d0f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1205,7 +1205,6 @@ class RoomDetailFragment @Inject constructor( if (summary?.membership == Membership.JOIN) { views.jumpToBottomView.count = summary.notificationCount views.jumpToBottomView.drawBadge = summary.hasUnreadMessages - scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline timelineEventController.update(state) views.inviteView.visibility = View.GONE if (state.tombstoneEvent == null) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 8b8d4fc823..669cf036d1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -1156,16 +1156,15 @@ class RoomDetailViewModel @AssistedInject constructor( private fun handleNavigateToEvent(action: RoomDetailAction.NavigateToEvent) { stopTrackingUnreadMessages() val targetEventId: String = action.eventId - val correctedEventId = timeline.getFirstDisplayableEventId(targetEventId) ?: targetEventId - val indexOfEvent = timeline.getIndexOfEvent(correctedEventId) + val indexOfEvent = timeline.getIndexOfEvent(targetEventId) if (indexOfEvent == null) { // Event is not already in RAM timeline.restartWithEventId(targetEventId) } if (action.highlight) { - setState { copy(highlightedEventId = correctedEventId) } + setState { copy(highlightedEventId = targetEventId) } } - _viewEvents.post(RoomDetailViewEvents.NavigateToEvent(correctedEventId)) + _viewEvents.post(RoomDetailViewEvents.NavigateToEvent(targetEventId)) } private fun handleResendEvent(action: RoomDetailAction.ResendMessage) { @@ -1389,15 +1388,12 @@ class RoomDetailViewModel @AssistedInject constructor( private fun computeUnreadState(events: List, roomSummary: RoomSummary): UnreadState { if (events.isEmpty()) return UnreadState.Unknown val readMarkerIdSnapshot = roomSummary.readMarkerId ?: return UnreadState.Unknown - val firstDisplayableEventId = timeline.getFirstDisplayableEventId(readMarkerIdSnapshot) - val firstDisplayableEventIndex = timeline.getIndexOfEvent(firstDisplayableEventId) - if (firstDisplayableEventId == null || firstDisplayableEventIndex == null) { - return if (timeline.isLive) { - UnreadState.ReadMarkerNotLoaded(readMarkerIdSnapshot) - } else { - UnreadState.Unknown - } - } + val firstDisplayableEventIndex = timeline.getIndexOfEvent(readMarkerIdSnapshot) + ?: return if (timeline.isLive) { + UnreadState.ReadMarkerNotLoaded(readMarkerIdSnapshot) + } else { + UnreadState.Unknown + } for (i in (firstDisplayableEventIndex - 1) downTo 0) { val timelineEvent = events.getOrNull(i) ?: return UnreadState.Unknown val eventId = timelineEvent.root.eventId ?: return UnreadState.Unknown diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnHighlightedEventCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnHighlightedEventCallback.kt index 0eb02f5c75..72f827e469 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnHighlightedEventCallback.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnHighlightedEventCallback.kt @@ -33,8 +33,6 @@ class ScrollOnHighlightedEventCallback(private val recyclerView: RecyclerView, private val scheduledEventId = AtomicReference() - var timeline: Timeline? = null - override fun onInserted(position: Int, count: Int) { scrollIfNeeded() } @@ -45,9 +43,7 @@ class ScrollOnHighlightedEventCallback(private val recyclerView: RecyclerView, private fun scrollIfNeeded() { val eventId = scheduledEventId.get() ?: return - val nonNullTimeline = timeline ?: return - val correctedEventId = nonNullTimeline.getFirstDisplayableEventId(eventId) - val positionToScroll = timelineEventController.searchPositionOfEvent(correctedEventId) + val positionToScroll = timelineEventController.searchPositionOfEvent(eventId) if (positionToScroll != null) { val firstVisibleItem = layoutManager.findFirstCompletelyVisibleItemPosition() val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt index 01c7ad3986..3aee65bf19 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt @@ -17,48 +17,14 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.resources.UserPreferencesProvider -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter -import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import javax.inject.Inject -class TimelineSettingsFactory @Inject constructor( - private val userPreferencesProvider: UserPreferencesProvider, - private val session: Session -) { +class TimelineSettingsFactory @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) { fun create(): TimelineSettings { - return if (userPreferencesProvider.shouldShowHiddenEvents()) { - TimelineSettings( - initialSize = 30, - filters = TimelineEventFilters( - filterEdits = false, - filterRedacted = userPreferencesProvider.shouldShowRedactedMessages().not(), - filterUseless = false, - filterTypes = false), - buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) - } else { - val allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES.createAllowedEventTypeFilters() - TimelineSettings( - initialSize = 30, - filters = TimelineEventFilters( - filterEdits = true, - filterRedacted = userPreferencesProvider.shouldShowRedactedMessages().not(), - filterUseless = true, - filterTypes = true, - allowedTypes = allowedTypes), - buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) - } - } - - private fun List.createAllowedEventTypeFilters(): List { - return map { - EventTypeFilter( - eventType = it, - stateKey = if (it == EventType.STATE_ROOM_MEMBER && !userPreferencesProvider.shouldShowRoomMemberStateEvents()) session.myUserId else null - ) - } + return TimelineSettings( + initialSize = 30, + buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) } }