Timeline : makes sure pagination state is computed correctly and only in one place.
This commit is contained in:
@@ -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,
|
||||
)
|
||||
|
||||
@@ -115,9 +114,9 @@ class RustMatrixTimeline(
|
||||
postDiffs(diffs)
|
||||
}.launchIn(this)
|
||||
|
||||
innerTimeline.backPaginationStatusFlow()
|
||||
paginationStateFlow()
|
||||
.onEach {
|
||||
postPaginationStatus(it)
|
||||
_paginationState.value = it
|
||||
}
|
||||
.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) {
|
||||
initLatch.await()
|
||||
try {
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -17,11 +17,9 @@
|
||||
package io.element.android.libraries.matrix.impl.timeline.postprocessor
|
||||
|
||||
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.item.virtual.VirtualTimelineItem
|
||||
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -97,15 +95,8 @@ class TimelineEncryptedHistoryPostProcessorTest {
|
||||
}
|
||||
|
||||
@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 {
|
||||
val paginationStateFlow = MutableStateFlow(
|
||||
MatrixTimeline.PaginationState(
|
||||
hasMoreToLoadBackwards = true,
|
||||
isBackPaginating = false,
|
||||
beginningOfRoomReached = false,
|
||||
)
|
||||
)
|
||||
val processor = createPostProcessor(paginationStateFlow = paginationStateFlow)
|
||||
fun `given a list with several with lower or equal timestamps than lastLoginTimestamp, then they're replaced`() = runTest {
|
||||
val processor = createPostProcessor()
|
||||
val items = listOf(
|
||||
MatrixTimelineItem.Event("0L", anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time - 1)),
|
||||
MatrixTimelineItem.Event("0L", anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time)),
|
||||
@@ -117,32 +108,16 @@ class TimelineEncryptedHistoryPostProcessorTest {
|
||||
MatrixTimelineItem.Event("0L", anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1))
|
||||
)
|
||||
)
|
||||
assertThat(paginationStateFlow.value).isEqualTo(
|
||||
MatrixTimeline.PaginationState(
|
||||
hasMoreToLoadBackwards = false,
|
||||
isBackPaginating = false,
|
||||
beginningOfRoomReached = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun TestScope.createPostProcessor(
|
||||
lastLoginTimestamp: Date? = defaultLastLoginTimestamp,
|
||||
isRoomEncrypted: Boolean = true,
|
||||
isKeyBackupEnabled: Boolean = false,
|
||||
paginationStateFlow: MutableStateFlow<MatrixTimeline.PaginationState> =
|
||||
MutableStateFlow(
|
||||
MatrixTimeline.PaginationState(
|
||||
hasMoreToLoadBackwards = true,
|
||||
isBackPaginating = false,
|
||||
beginningOfRoomReached = false,
|
||||
)
|
||||
)
|
||||
) = TimelineEncryptedHistoryPostProcessor(
|
||||
lastLoginTimestamp = lastLoginTimestamp,
|
||||
isRoomEncrypted = isRoomEncrypted,
|
||||
isKeyBackupEnabled = isKeyBackupEnabled,
|
||||
paginationStateFlow = paginationStateFlow,
|
||||
dispatcher = StandardTestDispatcher(testScheduler)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user