Refactor threads to support the new timeline implementation

This commit is contained in:
ariskotsomitopoulos 2022-01-11 12:13:53 +02:00
parent 1b41a72e72
commit 37ec3fdf84
2 changed files with 73 additions and 28 deletions

View File

@ -93,7 +93,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
handleDatabaseChangeSet(frozenResults, changeSet) handleDatabaseChangeSet(frozenResults, changeSet)
} }
private var timelineEventEntities: RealmResults<TimelineEventEntity> = chunkEntity.sortedTimelineEvents() private var timelineEventEntities: RealmResults<TimelineEventEntity> = chunkEntity.sortedTimelineEvents(timelineSettings.rootThreadEventId)
private val builtEvents: MutableList<TimelineEvent> = Collections.synchronizedList(ArrayList()) private val builtEvents: MutableList<TimelineEvent> = Collections.synchronizedList(ArrayList())
private val builtEventsIndexes: MutableMap<String, Int> = Collections.synchronizedMap(HashMap<String, Int>()) private val builtEventsIndexes: MutableMap<String, Int> = Collections.synchronizedMap(HashMap<String, Int>())
@ -138,13 +138,18 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
} else if (direction == Timeline.Direction.BACKWARDS && prevChunk != null) { } else if (direction == Timeline.Direction.BACKWARDS && prevChunk != null) {
return prevChunk?.loadMore(count, direction, fetchOnServerIfNeeded) ?: LoadMoreResult.FAILURE return prevChunk?.loadMore(count, direction, fetchOnServerIfNeeded) ?: LoadMoreResult.FAILURE
} }
val loadFromStorageCount = loadFromStorage(count, direction) val loadFromStorage = loadFromStorage(count, direction).also{
Timber.v("Has loaded $loadFromStorageCount items from storage in $direction") logLoadedFromStorage(it,direction)
val offsetCount = count - loadFromStorageCount }
val offsetCount = count - loadFromStorage.numberOfEvents
return if (direction == Timeline.Direction.FORWARDS && isLastForward.get()) { return if (direction == Timeline.Direction.FORWARDS && isLastForward.get()) {
LoadMoreResult.REACHED_END LoadMoreResult.REACHED_END
} else if (direction == Timeline.Direction.BACKWARDS && isLastBackward.get()) { } else if (direction == Timeline.Direction.BACKWARDS && isLastBackward.get()) {
LoadMoreResult.REACHED_END LoadMoreResult.REACHED_END
} else if (timelineSettings.isThreadTimeline() && loadFromStorage.threadReachedEnd) {
LoadMoreResult.REACHED_END
} else if (offsetCount == 0) { } else if (offsetCount == 0) {
LoadMoreResult.SUCCESS LoadMoreResult.SUCCESS
} else { } 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? { fun getBuiltEventIndex(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): Int? {
val builtEventIndex = builtEventsIndexes[eventId] val builtEventIndex = builtEventsIndexes[eventId]
if (builtEventIndex != null) { if (builtEventIndex != null) {
@ -268,29 +282,31 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
/** /**
* This method tries to read events from the current chunk. * 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 { private suspend fun loadFromStorage(count: Int, direction: Timeline.Direction): LoadedFromStorage {
val displayIndex = getNextDisplayIndex(direction) ?: return 0 val displayIndex = getNextDisplayIndex(direction) ?: return LoadedFromStorage()
val baseQuery = timelineEventEntities.where() val baseQuery = timelineEventEntities.where()
val timelineEvents = if (timelineSettings.rootThreadEventId != null) { // val timelineEvents = if (timelineSettings.rootThreadEventId != null) {
baseQuery // baseQuery
.beginGroup() // .beginGroup()
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, timelineSettings.rootThreadEventId) // .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, timelineSettings.rootThreadEventId)
.or() // .or()
.equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, timelineSettings.rootThreadEventId) // .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, timelineSettings.rootThreadEventId)
.endGroup() // .endGroup()
// .offsets(direction, count, displayIndex)
// .findAll()
// .orEmpty()
// } else {
val timelineEvents = baseQuery
.offsets(direction, count, displayIndex) .offsets(direction, count, displayIndex)
.findAll() .findAll()
.orEmpty() .orEmpty()
} else { // }
baseQuery
.offsets(direction, count, displayIndex)
.findAll()
.orEmpty()
}
if (timelineEvents.isEmpty()) return 0 if (timelineEvents.isEmpty()) return LoadedFromStorage()
fetchRootThreadEventsIfNeeded(timelineEvents) fetchRootThreadEventsIfNeeded(timelineEvents)
if (direction == Timeline.Direction.FORWARDS) { if (direction == Timeline.Direction.FORWARDS) {
builtEventsIndexes.entries.forEach { it.setValue(it.value + timelineEvents.size) } builtEventsIndexes.entries.forEach { it.setValue(it.value + timelineEvents.size) }
@ -309,9 +325,20 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
builtEvents.add(timelineEvent) 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<TimelineEventEntity>): 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 * 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 * 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 { val loadMoreResult = try {
if (token == null) { if (token == null) {
if (direction == Timeline.Direction.BACKWARDS || !chunkEntity.hasBeenALastForwardChunk()) return LoadMoreResult.REACHED_END 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) val taskParams = FetchTokenAndPaginateTask.Params(roomId, lastKnownEventId, direction.toPaginationDirection(), count)
fetchTokenAndPaginateTask.execute(taskParams).toLoadMoreResult() fetchTokenAndPaginateTask.execute(taskParams).toLoadMoreResult()
} else { } else {
@ -473,6 +501,11 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
onBuiltEvents = this.onBuiltEvents onBuiltEvents = this.onBuiltEvents
) )
} }
private data class LoadedFromStorage(
val threadReachedEnd: Boolean = false,
val numberOfEvents: Int = 0
)
} }
private fun RealmQuery<TimelineEventEntity>.offsets( private fun RealmQuery<TimelineEventEntity>.offsets(
@ -493,6 +526,19 @@ private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
} }
private fun ChunkEntity.sortedTimelineEvents(): RealmResults<TimelineEventEntity> { private fun ChunkEntity.sortedTimelineEvents(rootThreadEventId: String?): RealmResults<TimelineEventEntity> {
return timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) 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()
}
} }

View File

@ -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.addIfNecessary
import org.matrix.android.sdk.internal.database.helper.addStateEvent 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.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.helper.updateThreadSummaryIfNeeded
import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.mapper.toEntity
import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity