Timeline : makes sure pagination state is computed correctly and only in one place.

This commit is contained in:
ganfra 2023-12-15 15:02:46 +01:00
parent 7fc0ac1ced
commit d837aa060c
3 changed files with 46 additions and 84 deletions

View file

@ -38,7 +38,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -80,7 +80,6 @@ class RustMatrixTimeline(
lastLoginTimestamp = lastLoginTimestamp, lastLoginTimestamp = lastLoginTimestamp,
isRoomEncrypted = matrixRoom.isEncrypted, isRoomEncrypted = matrixRoom.isEncrypted,
isKeyBackupEnabled = isKeyBackupEnabled, isKeyBackupEnabled = isKeyBackupEnabled,
paginationStateFlow = _paginationState,
dispatcher = dispatcher, dispatcher = dispatcher,
) )
@ -115,9 +114,9 @@ class RustMatrixTimeline(
postDiffs(diffs) postDiffs(diffs)
}.launchIn(this) }.launchIn(this)
innerTimeline.backPaginationStatusFlow() paginationStateFlow()
.onEach { .onEach {
postPaginationStatus(it) _paginationState.value = it
} }
.launchIn(this) .launchIn(this)
@ -125,6 +124,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) { private suspend fun fetchMembers() = withContext(dispatcher) {
initLatch.await() initLatch.await()
try { try {
@ -154,39 +191,6 @@ class RustMatrixTimeline(
timelineDiffProcessor.postDiffs(diffs) 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) { override suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> = withContext(dispatcher) {
runCatching { runCatching {
innerTimeline.fetchDetailsForEvent(eventId.value) innerTimeline.fetchDetailsForEvent(eventId.value)
@ -251,8 +255,8 @@ class RustMatrixTimeline(
return _timelineItems.value.firstOrNull { (it as? MatrixTimelineItem.Event)?.eventId == eventId } as? MatrixTimelineItem.Event return _timelineItems.value.firstOrNull { (it as? MatrixTimelineItem.Event)?.eventId == eventId } as? MatrixTimelineItem.Event
} }
private fun hasEncryptionHistoryBanner(): Boolean { private fun List<MatrixTimelineItem>.hasEncryptionHistoryBanner(): Boolean {
val firstItem = _timelineItems.value.firstOrNull() val firstItem = firstOrNull()
return firstItem is MatrixTimelineItem.Virtual return firstItem is MatrixTimelineItem.Virtual
&& firstItem.virtual is VirtualTimelineItem.EncryptedHistoryBanner && firstItem.virtual is VirtualTimelineItem.EncryptedHistoryBanner
} }

View file

@ -16,12 +16,9 @@
package io.element.android.libraries.matrix.impl.timeline.postprocessor 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.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.util.Date import java.util.Date
@ -31,26 +28,12 @@ class TimelineEncryptedHistoryPostProcessor(
private val lastLoginTimestamp: Date?, private val lastLoginTimestamp: Date?,
private val isRoomEncrypted: Boolean, private val isRoomEncrypted: Boolean,
private val isKeyBackupEnabled: Boolean, private val isKeyBackupEnabled: Boolean,
private val paginationStateFlow: MutableStateFlow<MatrixTimeline.PaginationState>,
) { ) {
suspend fun process(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> = withContext(dispatcher) { suspend fun process(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> = withContext(dispatcher) {
Timber.d("Process on Thread=${Thread.currentThread()}") Timber.d("Process on Thread=${Thread.currentThread()}")
if (!isRoomEncrypted || isKeyBackupEnabled || lastLoginTimestamp == null) return@withContext items if (!isRoomEncrypted || isKeyBackupEnabled || lastLoginTimestamp == null) return@withContext items
replaceWithEncryptionHistoryBannerIfNeeded(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
} }
private fun replaceWithEncryptionHistoryBannerIfNeeded(list: List<MatrixTimelineItem>): List<MatrixTimelineItem> { private fun replaceWithEncryptionHistoryBannerIfNeeded(list: List<MatrixTimelineItem>): List<MatrixTimelineItem> {

View file

@ -17,11 +17,9 @@
package io.element.android.libraries.matrix.impl.timeline.postprocessor package io.element.android.libraries.matrix.impl.timeline.postprocessor
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
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.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@ -97,15 +95,8 @@ class TimelineEncryptedHistoryPostProcessorTest {
} }
@Test @Test
fun `given a list with several with lower or equal timestamps than lastLoginTimestamp, they're replaced and the user can't back paginate`() = runTest { fun `given a list with several with lower or equal timestamps than lastLoginTimestamp, then they're replaced`() = runTest {
val paginationStateFlow = MutableStateFlow( val processor = createPostProcessor()
MatrixTimeline.PaginationState(
hasMoreToLoadBackwards = true,
isBackPaginating = false,
beginningOfRoomReached = false,
)
)
val processor = createPostProcessor(paginationStateFlow = paginationStateFlow)
val items = listOf( val items = listOf(
MatrixTimelineItem.Event("0L", anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time - 1)), MatrixTimelineItem.Event("0L", anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time - 1)),
MatrixTimelineItem.Event("0L", anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time)), MatrixTimelineItem.Event("0L", anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time)),
@ -117,32 +108,16 @@ class TimelineEncryptedHistoryPostProcessorTest {
MatrixTimelineItem.Event("0L", anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1)) MatrixTimelineItem.Event("0L", anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1))
) )
) )
assertThat(paginationStateFlow.value).isEqualTo(
MatrixTimeline.PaginationState(
hasMoreToLoadBackwards = false,
isBackPaginating = false,
beginningOfRoomReached = false,
)
)
} }
private fun TestScope.createPostProcessor( private fun TestScope.createPostProcessor(
lastLoginTimestamp: Date? = defaultLastLoginTimestamp, lastLoginTimestamp: Date? = defaultLastLoginTimestamp,
isRoomEncrypted: Boolean = true, isRoomEncrypted: Boolean = true,
isKeyBackupEnabled: Boolean = false, isKeyBackupEnabled: Boolean = false,
paginationStateFlow: MutableStateFlow<MatrixTimeline.PaginationState> =
MutableStateFlow(
MatrixTimeline.PaginationState(
hasMoreToLoadBackwards = true,
isBackPaginating = false,
beginningOfRoomReached = false,
)
)
) = TimelineEncryptedHistoryPostProcessor( ) = TimelineEncryptedHistoryPostProcessor(
lastLoginTimestamp = lastLoginTimestamp, lastLoginTimestamp = lastLoginTimestamp,
isRoomEncrypted = isRoomEncrypted, isRoomEncrypted = isRoomEncrypted,
isKeyBackupEnabled = isKeyBackupEnabled, isKeyBackupEnabled = isKeyBackupEnabled,
paginationStateFlow = paginationStateFlow,
dispatcher = StandardTestDispatcher(testScheduler) dispatcher = StandardTestDispatcher(testScheduler)
) )
} }