diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index e05def3805..2f53c666bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -93,7 +93,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, handleDatabaseChangeSet(frozenResults, changeSet) } - private var timelineEventEntities: RealmResults = chunkEntity.sortedTimelineEvents() + private var timelineEventEntities: RealmResults = chunkEntity.sortedTimelineEvents(timelineSettings.rootThreadEventId) private val builtEvents: MutableList = Collections.synchronizedList(ArrayList()) private val builtEventsIndexes: MutableMap = Collections.synchronizedMap(HashMap()) @@ -138,13 +138,18 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, } else if (direction == Timeline.Direction.BACKWARDS && prevChunk != null) { return prevChunk?.loadMore(count, direction, fetchOnServerIfNeeded) ?: LoadMoreResult.FAILURE } - val loadFromStorageCount = loadFromStorage(count, direction) - Timber.v("Has loaded $loadFromStorageCount items from storage in $direction") - val offsetCount = count - loadFromStorageCount + val loadFromStorage = loadFromStorage(count, direction).also{ + logLoadedFromStorage(it,direction) + } + + val offsetCount = count - loadFromStorage.numberOfEvents + return if (direction == Timeline.Direction.FORWARDS && isLastForward.get()) { LoadMoreResult.REACHED_END } else if (direction == Timeline.Direction.BACKWARDS && isLastBackward.get()) { LoadMoreResult.REACHED_END + } else if (timelineSettings.isThreadTimeline() && loadFromStorage.threadReachedEnd) { + LoadMoreResult.REACHED_END } else if (offsetCount == 0) { LoadMoreResult.SUCCESS } else { @@ -188,6 +193,15 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, } } + /** + * Simple log that displays the number and timeline of loaded events + */ + private fun logLoadedFromStorage(loadedFromStorage: LoadedFromStorage, direction: Timeline.Direction) = + Timber.v("[" + + "${if (timelineSettings.isThreadTimeline()) "ThreadTimeLine" else "Timeline"}] Has loaded " + + "${loadedFromStorage.numberOfEvents} items from storage in $direction " + + if (timelineSettings.isThreadTimeline() && loadedFromStorage.threadReachedEnd) "[Reached End]" else "") + fun getBuiltEventIndex(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): Int? { val builtEventIndex = builtEventsIndexes[eventId] if (builtEventIndex != null) { @@ -268,29 +282,31 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, /** * This method tries to read events from the current chunk. + * @return the number of events loaded. If we are in a thread timeline it also returns + * whether or not we reached the end/root message */ - private suspend fun loadFromStorage(count: Int, direction: Timeline.Direction): Int { - val displayIndex = getNextDisplayIndex(direction) ?: return 0 + private suspend fun loadFromStorage(count: Int, direction: Timeline.Direction): LoadedFromStorage { + val displayIndex = getNextDisplayIndex(direction) ?: return LoadedFromStorage() val baseQuery = timelineEventEntities.where() - val timelineEvents = if (timelineSettings.rootThreadEventId != null) { - baseQuery - .beginGroup() - .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, timelineSettings.rootThreadEventId) - .or() - .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, timelineSettings.rootThreadEventId) - .endGroup() - .offsets(direction, count, displayIndex) - .findAll() - .orEmpty() - } else { - baseQuery - .offsets(direction, count, displayIndex) - .findAll() - .orEmpty() - } +// val timelineEvents = if (timelineSettings.rootThreadEventId != null) { +// baseQuery +// .beginGroup() +// .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, timelineSettings.rootThreadEventId) +// .or() +// .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, timelineSettings.rootThreadEventId) +// .endGroup() +// .offsets(direction, count, displayIndex) +// .findAll() +// .orEmpty() +// } else { + val timelineEvents = baseQuery + .offsets(direction, count, displayIndex) + .findAll() + .orEmpty() +// } - if (timelineEvents.isEmpty()) return 0 + if (timelineEvents.isEmpty()) return LoadedFromStorage() fetchRootThreadEventsIfNeeded(timelineEvents) if (direction == Timeline.Direction.FORWARDS) { builtEventsIndexes.entries.forEach { it.setValue(it.value + timelineEvents.size) } @@ -309,9 +325,20 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, builtEvents.add(timelineEvent) } } - return timelineEvents.size + return LoadedFromStorage( + threadReachedEnd = threadReachedEnd(timelineEvents), + numberOfEvents = timelineEvents.size) } + /** + * Returns whether or not the the thread has reached end. It returned false if the current timeline + * is not a thread timeline + */ + private fun threadReachedEnd(timelineEvents: List): Boolean = + timelineSettings.rootThreadEventId?.let { rootThreadId -> + timelineEvents.firstOrNull { it.eventId == rootThreadId }?.let { true } + } ?: false + /** * This function is responsible to fetch and store the root event of a thread event * in order to be able to display the event to the user appropriately @@ -362,7 +389,8 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, val loadMoreResult = try { if (token == null) { if (direction == Timeline.Direction.BACKWARDS || !chunkEntity.hasBeenALastForwardChunk()) return LoadMoreResult.REACHED_END - val lastKnownEventId = chunkEntity.sortedTimelineEvents().firstOrNull()?.eventId ?: return LoadMoreResult.FAILURE + val lastKnownEventId = chunkEntity.sortedTimelineEvents(timelineSettings.rootThreadEventId).firstOrNull()?.eventId + ?: return LoadMoreResult.FAILURE val taskParams = FetchTokenAndPaginateTask.Params(roomId, lastKnownEventId, direction.toPaginationDirection(), count) fetchTokenAndPaginateTask.execute(taskParams).toLoadMoreResult() } else { @@ -473,6 +501,11 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity, onBuiltEvents = this.onBuiltEvents ) } + + private data class LoadedFromStorage( + val threadReachedEnd: Boolean = false, + val numberOfEvents: Int = 0 + ) } private fun RealmQuery.offsets( @@ -493,6 +526,19 @@ private fun Timeline.Direction.toPaginationDirection(): PaginationDirection { return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS } -private fun ChunkEntity.sortedTimelineEvents(): RealmResults { - return timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) +private fun ChunkEntity.sortedTimelineEvents(rootThreadEventId: String?): RealmResults { + return if (rootThreadEventId == null) { + timelineEvents + .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) + } else { + timelineEvents + .where() + .beginGroup() + .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .or() + .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, rootThreadEventId) + .endGroup() + .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) + .findAll() + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index 19d6a1bd28..2641d971b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.database.helper.addIfNecessary import org.matrix.android.sdk.internal.database.helper.addStateEvent import org.matrix.android.sdk.internal.database.helper.addTimelineEvent -import org.matrix.android.sdk.internal.database.helper.merge import org.matrix.android.sdk.internal.database.helper.updateThreadSummaryIfNeeded import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity