Merge pull request #2041 from element-hq/feature/fga/fix_timeline_back_pagination_loop

Fix timeline back pagination loop in encrypted room.
This commit is contained in:
Benoit Marty 2023-12-21 15:42:23 +01:00 committed by GitHub
commit 7273abd42b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 84 additions and 123 deletions

View file

@ -32,7 +32,7 @@ class MatrixTimelineItemMapper(
) {
fun map(timelineItem: TimelineItem): MatrixTimelineItem = timelineItem.use {
val uniqueId = timelineItem.uniqueId().toLong()
val uniqueId = timelineItem.uniqueId().toString()
val asEvent = it.asEvent()
if (asEvent != null) {
val eventTimelineItem = eventTimelineItemMapper.map(asEvent)

View file

@ -38,7 +38,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
@ -80,7 +80,6 @@ class RustMatrixTimeline(
lastLoginTimestamp = lastLoginTimestamp,
isRoomEncrypted = matrixRoom.isEncrypted,
isKeyBackupEnabled = isKeyBackupEnabled,
paginationStateFlow = _paginationState,
dispatcher = dispatcher,
)
@ -102,6 +101,11 @@ class RustMatrixTimeline(
override val paginationState: StateFlow<MatrixTimeline.PaginationState> = _paginationState.asStateFlow()
@OptIn(ExperimentalCoroutinesApi::class)
override val timelineItems: Flow<List<MatrixTimelineItem>> = _timelineItems.mapLatest { items ->
encryptedHistoryPostProcessor.process(items)
}
init {
Timber.d("Initialize timeline for room ${matrixRoom.roomId}")
@ -115,9 +119,9 @@ class RustMatrixTimeline(
postDiffs(diffs)
}.launchIn(this)
innerTimeline.backPaginationStatusFlow()
paginationStateFlow()
.onEach {
postPaginationStatus(it)
_paginationState.value = it
}
.launchIn(this)
@ -125,6 +129,44 @@ class RustMatrixTimeline(
}
}
private fun paginationStateFlow(): Flow<MatrixTimeline.PaginationState> {
return combine(
innerTimeline.backPaginationStatusFlow(),
timelineItems,
) { paginationStatus, filteredItems ->
if (filteredItems.hasEncryptionHistoryBanner()) {
return@combine MatrixTimeline.PaginationState(
isBackPaginating = false,
hasMoreToLoadBackwards = false,
beginningOfRoomReached = false,
)
}
when (paginationStatus) {
BackPaginationStatus.IDLE -> {
MatrixTimeline.PaginationState(
isBackPaginating = false,
hasMoreToLoadBackwards = true,
beginningOfRoomReached = false,
)
}
BackPaginationStatus.PAGINATING -> {
MatrixTimeline.PaginationState(
isBackPaginating = true,
hasMoreToLoadBackwards = true,
beginningOfRoomReached = false,
)
}
BackPaginationStatus.TIMELINE_START_REACHED -> {
MatrixTimeline.PaginationState(
isBackPaginating = false,
hasMoreToLoadBackwards = false,
beginningOfRoomReached = true,
)
}
}
}
}
private suspend fun fetchMembers() = withContext(dispatcher) {
initLatch.await()
try {
@ -134,11 +176,6 @@ class RustMatrixTimeline(
}
}
@OptIn(ExperimentalCoroutinesApi::class)
override val timelineItems: Flow<List<MatrixTimelineItem>> = _timelineItems.mapLatest { items ->
encryptedHistoryPostProcessor.process(items)
}
private suspend fun postItems(items: List<TimelineItem>) = coroutineScope {
// Split the initial items in multiple list as there is no pagination in the cached data, so we can post timelineItems asap.
items.chunked(INITIAL_MAX_SIZE).reversed().forEach {
@ -154,39 +191,6 @@ class RustMatrixTimeline(
timelineDiffProcessor.postDiffs(diffs)
}
private fun postPaginationStatus(status: BackPaginationStatus) {
_paginationState.getAndUpdate { currentPaginationState ->
if (hasEncryptionHistoryBanner()) {
return@getAndUpdate currentPaginationState.copy(
isBackPaginating = false,
hasMoreToLoadBackwards = false,
beginningOfRoomReached = false,
)
}
when (status) {
BackPaginationStatus.IDLE -> {
currentPaginationState.copy(
isBackPaginating = false,
hasMoreToLoadBackwards = true
)
}
BackPaginationStatus.PAGINATING -> {
currentPaginationState.copy(
isBackPaginating = true,
hasMoreToLoadBackwards = true
)
}
BackPaginationStatus.TIMELINE_START_REACHED -> {
currentPaginationState.copy(
isBackPaginating = false,
hasMoreToLoadBackwards = false,
beginningOfRoomReached = true,
)
}
}
}
}
override suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> = withContext(dispatcher) {
runCatching {
innerTimeline.fetchDetailsForEvent(eventId.value)
@ -251,8 +255,8 @@ class RustMatrixTimeline(
return _timelineItems.value.firstOrNull { (it as? MatrixTimelineItem.Event)?.eventId == eventId } as? MatrixTimelineItem.Event
}
private fun hasEncryptionHistoryBanner(): Boolean {
val firstItem = _timelineItems.value.firstOrNull()
private fun List<MatrixTimelineItem>.hasEncryptionHistoryBanner(): Boolean {
val firstItem = firstOrNull()
return firstItem is MatrixTimelineItem.Virtual
&& firstItem.virtual is VirtualTimelineItem.EncryptedHistoryBanner
}

View file

@ -16,12 +16,9 @@
package io.element.android.libraries.matrix.impl.timeline.postprocessor
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.Date
@ -31,26 +28,12 @@ class TimelineEncryptedHistoryPostProcessor(
private val lastLoginTimestamp: Date?,
private val isRoomEncrypted: Boolean,
private val isKeyBackupEnabled: Boolean,
private val paginationStateFlow: MutableStateFlow<MatrixTimeline.PaginationState>,
) {
suspend fun process(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> = withContext(dispatcher) {
Timber.d("Process on Thread=${Thread.currentThread()}")
if (!isRoomEncrypted || isKeyBackupEnabled || lastLoginTimestamp == null) return@withContext items
val filteredItems = replaceWithEncryptionHistoryBannerIfNeeded(items)
// Disable back pagination
val wasFiltered = filteredItems !== items
if (wasFiltered) {
paginationStateFlow.getAndUpdate {
it.copy(
isBackPaginating = false,
hasMoreToLoadBackwards = false,
beginningOfRoomReached = false,
)
}
}
filteredItems
replaceWithEncryptionHistoryBannerIfNeeded(items)
}
private fun replaceWithEncryptionHistoryBannerIfNeeded(list: List<MatrixTimelineItem>): List<MatrixTimelineItem> {
@ -62,7 +45,7 @@ class TimelineEncryptedHistoryPostProcessor(
}
return if (lastEncryptedHistoryBannerIndex >= 0) {
val sublist = list.drop(lastEncryptedHistoryBannerIndex + 1).toMutableList()
sublist.add(0, MatrixTimelineItem.Virtual(0L, VirtualTimelineItem.EncryptedHistoryBanner))
sublist.add(0, MatrixTimelineItem.Virtual(VirtualTimelineItem.EncryptedHistoryBanner.toString(), VirtualTimelineItem.EncryptedHistoryBanner))
sublist
} else {
list