Browse Source

Improve a bit timeline pagination

misc/jme/add-logging-to-state-machine
ganfra 2 years ago
parent
commit
c9b4cf3232
  1. 21
      features/messages/src/main/kotlin/io/element/android/features/messages/timeline/TimelinePresenter.kt
  2. 63
      features/messages/src/main/kotlin/io/element/android/features/messages/timeline/TimelineView.kt
  3. 62
      features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemContentView.kt
  4. 4
      features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemEncryptedView.kt
  5. 4
      features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemImageView.kt
  6. 4
      features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemInformativeView.kt
  7. 4
      features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemRedactedView.kt
  8. 4
      features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemTextView.kt
  9. 4
      features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemUnknownView.kt
  10. 9
      libraries/matrix/src/main/kotlin/io/element/android/libraries/matrix/timeline/MatrixTimeline.kt
  11. 118
      libraries/matrix/src/main/kotlin/io/element/android/libraries/matrix/timeline/MatrixTimelineDiffProcessor.kt
  12. 143
      libraries/matrix/src/main/kotlin/io/element/android/libraries/matrix/timeline/RustMatrixTimeline.kt
  13. 14
      libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/timeline/FakeMatrixTimeline.kt

21
features/messages/src/main/kotlin/io/element/android/features/messages/timeline/TimelinePresenter.kt

@ -28,11 +28,13 @@ import io.element.android.features.messages.timeline.factories.TimelineItemsFact @@ -28,11 +28,13 @@ import io.element.android.features.messages.timeline.factories.TimelineItemsFact
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.core.EventId
import io.element.android.libraries.matrix.room.MatrixRoom
import io.element.android.libraries.matrix.timeline.MatrixTimeline
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
private const val backPaginationEventLimit = 20
@ -55,9 +57,13 @@ class TimelinePresenter @Inject constructor( @@ -55,9 +57,13 @@ class TimelinePresenter @Inject constructor(
.flow()
.collectAsState(emptyList())
val paginationState = timeline
.paginationState()
.collectAsState()
fun handleEvents(event: TimelineEvents) {
when (event) {
TimelineEvents.LoadMore -> localCoroutineScope.loadMore()
TimelineEvents.LoadMore -> localCoroutineScope.loadMore(paginationState.value)
is TimelineEvents.SetHighlightedEvent -> highlightedEventId.value = event.eventId
}
}
@ -66,6 +72,11 @@ class TimelinePresenter @Inject constructor( @@ -66,6 +72,11 @@ class TimelinePresenter @Inject constructor(
timeline
.timelineItems()
.onEach(timelineItemsFactory::replaceWith)
.onEach { timelineItems ->
if (timelineItems.isEmpty()) {
loadMore(paginationState.value)
}
}
.launchIn(this)
}
@ -83,7 +94,11 @@ class TimelinePresenter @Inject constructor( @@ -83,7 +94,11 @@ class TimelinePresenter @Inject constructor(
)
}
private fun CoroutineScope.loadMore() = launch {
timeline.paginateBackwards(backPaginationEventLimit, backPaginationPageSize)
private fun CoroutineScope.loadMore(paginationState: MatrixTimeline.PaginationState) = launch {
if (paginationState.canBackPaginate && !paginationState.isBackPaginating) {
timeline.paginateBackwards(backPaginationEventLimit, backPaginationPageSize)
} else {
Timber.v("Can't back paginate as paginationState = $paginationState")
}
}
}

63
features/messages/src/main/kotlin/io/element/android/features/messages/timeline/TimelineView.kt

@ -34,7 +34,7 @@ import androidx.compose.foundation.layout.widthIn @@ -34,7 +34,7 @@ import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
@ -54,26 +54,17 @@ import androidx.compose.ui.tooling.preview.Preview @@ -54,26 +54,17 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import io.element.android.features.messages.timeline.model.bubble.BubbleState
import io.element.android.features.messages.timeline.components.MessageEventBubble
import io.element.android.features.messages.timeline.components.TimelineItemEncryptedView
import io.element.android.features.messages.timeline.components.TimelineItemImageView
import io.element.android.features.messages.timeline.components.TimelineItemReactionsView
import io.element.android.features.messages.timeline.components.TimelineItemRedactedView
import io.element.android.features.messages.timeline.components.TimelineItemTextView
import io.element.android.features.messages.timeline.components.TimelineItemUnknownView
import io.element.android.features.messages.timeline.components.event.TimelineItemEventContentView
import io.element.android.features.messages.timeline.components.virtual.TimelineItemDaySeparatorView
import io.element.android.features.messages.timeline.components.virtual.TimelineLoadingMoreIndicator
import io.element.android.features.messages.timeline.model.TimelineItem
import io.element.android.features.messages.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.timeline.model.bubble.BubbleState
import io.element.android.features.messages.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.timeline.model.event.TimelineItemImageContent
import io.element.android.features.messages.timeline.model.event.TimelineItemRedactedContent
import io.element.android.features.messages.timeline.model.event.TimelineItemTextBasedContent
import io.element.android.features.messages.timeline.model.event.TimelineItemUnknownContent
import io.element.android.features.messages.timeline.model.event.TimelineItemEventContentProvider
import io.element.android.features.messages.timeline.model.virtual.TimelineItemDaySeparatorModel
import io.element.android.features.messages.timeline.model.virtual.TimelineItemLoadingModel
import io.element.android.features.messages.timeline.model.event.TimelineItemEventContentProvider
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
@ -92,6 +83,11 @@ fun TimelineView( @@ -92,6 +83,11 @@ fun TimelineView(
onMessageClicked: (TimelineItem.Event) -> Unit = {},
onMessageLongClicked: (TimelineItem.Event) -> Unit = {},
) {
fun onReachedLoadMore() {
state.eventSink(TimelineEvents.LoadMore)
}
val lazyListState = rememberLazyListState()
Box(modifier = modifier) {
LazyColumn(
@ -101,24 +97,23 @@ fun TimelineView( @@ -101,24 +97,23 @@ fun TimelineView(
verticalArrangement = Arrangement.Bottom,
reverseLayout = true
) {
items(
itemsIndexed(
items = state.timelineItems,
contentType = { timelineItem -> timelineItem.contentType() },
key = { timelineItem -> timelineItem.key() },
) { timelineItem ->
contentType = { _, timelineItem -> timelineItem.contentType() },
key = { _, timelineItem -> timelineItem.key() },
) { index, timelineItem ->
TimelineItemRow(
timelineItem = timelineItem,
isHighlighted = timelineItem.key() == state.highlightedEventId?.value,
onClick = onMessageClicked,
onLongClick = onMessageLongClicked
)
if (index == state.timelineItems.lastIndex) {
onReachedLoadMore()
}
}
}
fun onReachedLoadMore() {
state.eventSink(TimelineEvents.LoadMore)
}
TimelineScrollHelper(
lazyListState = lazyListState,
timelineItems = state.timelineItems,
@ -231,31 +226,7 @@ fun TimelineItemEventRow( @@ -231,31 +226,7 @@ fun TimelineItemEventRow(
.widthIn(max = 320.dp)
) {
val contentModifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)
when (event.content) {
is TimelineItemEncryptedContent -> TimelineItemEncryptedView(
content = event.content,
modifier = contentModifier
)
is TimelineItemRedactedContent -> TimelineItemRedactedView(
content = event.content,
modifier = contentModifier
)
is TimelineItemTextBasedContent -> TimelineItemTextView(
content = event.content,
interactionSource = interactionSource,
modifier = contentModifier,
onTextClicked = onClick,
onTextLongClicked = onLongClick
)
is TimelineItemUnknownContent -> TimelineItemUnknownView(
content = event.content,
modifier = contentModifier
)
is TimelineItemImageContent -> TimelineItemImageView(
content = event.content,
modifier = contentModifier
)
}
TimelineItemEventContentView(event.content, interactionSource, onClick, onLongClick, contentModifier)
}
TimelineItemReactionsView(
reactionsState = event.reactionsState,

62
features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemContentView.kt

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.messages.timeline.components.event
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import io.element.android.features.messages.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.timeline.model.event.TimelineItemImageContent
import io.element.android.features.messages.timeline.model.event.TimelineItemRedactedContent
import io.element.android.features.messages.timeline.model.event.TimelineItemTextBasedContent
import io.element.android.features.messages.timeline.model.event.TimelineItemUnknownContent
@Composable
fun TimelineItemEventContentView(
content: TimelineItemEventContent,
interactionSource: MutableInteractionSource,
onClick: () -> Unit,
onLongClick: () -> Unit,
modifier: Modifier = Modifier
) {
when (content) {
is TimelineItemEncryptedContent -> TimelineItemEncryptedView(
content = content,
modifier = modifier
)
is TimelineItemRedactedContent -> TimelineItemRedactedView(
content = content,
modifier = modifier
)
is TimelineItemTextBasedContent -> TimelineItemTextView(
content = content,
interactionSource = interactionSource,
modifier = modifier,
onTextClicked = onClick,
onTextLongClicked = onLongClick
)
is TimelineItemUnknownContent -> TimelineItemUnknownView(
content = content,
modifier = modifier
)
is TimelineItemImageContent -> TimelineItemImageView(
content = content,
modifier = modifier
)
}
}

4
features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/TimelineItemEncryptedView.kt → features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemEncryptedView.kt

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 New Vector Ltd
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.messages.timeline.components
package io.element.android.features.messages.timeline.components.event
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Warning

4
features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/TimelineItemImageView.kt → features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemImageView.kt

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 New Vector Ltd
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.messages.timeline.components
package io.element.android.features.messages.timeline.components.event
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio

4
features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/TimelineItemInformativeView.kt → features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemInformativeView.kt

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 New Vector Ltd
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.messages.timeline.components
package io.element.android.features.messages.timeline.components.event
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer

4
features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/TimelineItemRedactedView.kt → features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemRedactedView.kt

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 New Vector Ltd
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.messages.timeline.components
package io.element.android.features.messages.timeline.components.event
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete

4
features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/TimelineItemTextView.kt → features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemTextView.kt

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 New Vector Ltd
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.messages.timeline.components
package io.element.android.features.messages.timeline.components.event
import android.text.SpannableString
import android.text.style.URLSpan

4
features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/TimelineItemUnknownView.kt → features/messages/src/main/kotlin/io/element/android/features/messages/timeline/components/event/TimelineItemUnknownView.kt

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 New Vector Ltd
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.features.messages.timeline.components
package io.element.android.features.messages.timeline.components.event
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info

9
libraries/matrix/src/main/kotlin/io/element/android/libraries/matrix/timeline/MatrixTimeline.kt

@ -18,10 +18,17 @@ package io.element.android.libraries.matrix.timeline @@ -18,10 +18,17 @@ package io.element.android.libraries.matrix.timeline
import io.element.android.libraries.matrix.core.EventId
import kotlinx.coroutines.flow.Flow
import org.matrix.rustcomponents.sdk.TimelineListener
import kotlinx.coroutines.flow.StateFlow
interface MatrixTimeline {
data class PaginationState(
val isBackPaginating: Boolean,
val canBackPaginate: Boolean
)
fun paginationState(): StateFlow<PaginationState>
fun timelineItems(): Flow<List<MatrixTimelineItem>>
suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit>
fun initialize()

118
libraries/matrix/src/main/kotlin/io/element/android/libraries/matrix/timeline/MatrixTimelineDiffProcessor.kt

@ -0,0 +1,118 @@ @@ -0,0 +1,118 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.timeline
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.TimelineChange
import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineListener
import org.matrix.rustcomponents.sdk.VirtualTimelineItem
internal class MatrixTimelineDiffProcessor(
private val paginationState: MutableStateFlow<MatrixTimeline.PaginationState>,
private val timelineItems: MutableStateFlow<List<MatrixTimelineItem>>,
private val coroutineScope: CoroutineScope,
private val diffDispatcher: CoroutineDispatcher,
) : TimelineListener {
override fun onUpdate(update: TimelineDiff) {
coroutineScope.launch {
updateTimelineItems {
applyDiff(update)
}
when (val firstItem = timelineItems.value.firstOrNull()) {
is MatrixTimelineItem.Virtual -> updateBackPaginationState(firstItem.virtual)
else -> updateBackPaginationState(null)
}
}
}
private fun updateBackPaginationState(virtualItem: VirtualTimelineItem?) {
val currentPaginationState = paginationState.value
val newPaginationState = when (virtualItem) {
VirtualTimelineItem.LoadingIndicator -> currentPaginationState.copy(
isBackPaginating = true,
canBackPaginate = true
)
VirtualTimelineItem.TimelineStart -> currentPaginationState.copy(
isBackPaginating = false,
canBackPaginate = false
)
else -> currentPaginationState.copy(
isBackPaginating = false,
canBackPaginate = true
)
}
paginationState.value = newPaginationState
}
private suspend fun updateTimelineItems(block: MutableList<MatrixTimelineItem>.() -> Unit) =
withContext(diffDispatcher) {
val mutableTimelineItems = timelineItems.value.toMutableList()
block(mutableTimelineItems)
timelineItems.value = mutableTimelineItems
}
private fun MutableList<MatrixTimelineItem>.applyDiff(diff: TimelineDiff) {
when (diff.change()) {
TimelineChange.APPEND -> {
val items = diff.append()?.map { it.asMatrixTimelineItem() } ?: return
addAll(items)
}
TimelineChange.PUSH_BACK -> {
val item = diff.pushBack()?.asMatrixTimelineItem() ?: return
add(item)
}
TimelineChange.PUSH_FRONT -> {
val item = diff.pushFront()?.asMatrixTimelineItem() ?: return
add(0, item)
}
TimelineChange.SET -> {
val updateAtData = diff.set() ?: return
val item = updateAtData.item.asMatrixTimelineItem()
set(updateAtData.index.toInt(), item)
}
TimelineChange.INSERT -> {
val insertAtData = diff.insert() ?: return
val item = insertAtData.item.asMatrixTimelineItem()
add(insertAtData.index.toInt(), item)
}
TimelineChange.REMOVE -> {
val removeAtData = diff.remove() ?: return
removeAt(removeAtData.toInt())
}
TimelineChange.RESET -> {
clear()
val items = diff.reset()?.map { it.asMatrixTimelineItem() } ?: return
addAll(items)
}
TimelineChange.POP_FRONT -> {
removeFirstOrNull()
}
TimelineChange.POP_BACK -> {
removeLastOrNull()
}
TimelineChange.CLEAR -> {
clear()
}
}
}
}

143
libraries/matrix/src/main/kotlin/io/element/android/libraries/matrix/timeline/RustMatrixTimeline.kt

@ -18,135 +18,63 @@ package io.element.android.libraries.matrix.timeline @@ -18,135 +18,63 @@ package io.element.android.libraries.matrix.timeline
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.core.EventId
import io.element.android.libraries.matrix.room.RustMatrixRoom
import io.element.android.libraries.matrix.room.MatrixRoom
import io.element.android.libraries.matrix.util.TaskHandleBag
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.PaginationOptions
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
import org.matrix.rustcomponents.sdk.TimelineChange
import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineItem
import org.matrix.rustcomponents.sdk.TimelineListener
import timber.log.Timber
class RustMatrixTimeline(
private val matrixRoom: RustMatrixRoom,
private val matrixRoom: MatrixRoom,
private val innerRoom: Room,
private val slidingSyncRoom: SlidingSyncRoom,
private val coroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers,
) : MatrixTimeline {
private val innerTimelineListener = object : TimelineListener {
override fun onUpdate(update: TimelineDiff) {
coroutineScope.launch {
updateTimelineItems {
applyDiff(update)
}
}
}
}
private val timelineItems: MutableStateFlow<List<MatrixTimelineItem>> =
MutableStateFlow(emptyList())
private val paginationState = MutableStateFlow(
MatrixTimeline.PaginationState(canBackPaginate = true, isBackPaginating = false)
)
private val innerTimelineListener = MatrixTimelineDiffProcessor(
paginationState = paginationState,
timelineItems = timelineItems,
coroutineScope = coroutineScope,
diffDispatcher = coroutineDispatchers.diffUpdateDispatcher
)
private val listenerTokens = TaskHandleBag()
override fun paginationState(): StateFlow<MatrixTimeline.PaginationState> {
return paginationState
}
@OptIn(FlowPreview::class)
override fun timelineItems(): Flow<List<MatrixTimelineItem>> {
return timelineItems.sample(50)
}
private fun MutableList<MatrixTimelineItem>.applyDiff(diff: TimelineDiff) {
when (diff.change()) {
TimelineChange.APPEND -> {
val items = diff.append()?.map { it.asMatrixTimelineItem() } ?: return
addAll(items)
}
TimelineChange.PUSH_BACK -> {
val item = diff.pushBack()?.asMatrixTimelineItem() ?: return
add(item)
}
TimelineChange.PUSH_FRONT -> {
val item = diff.pushFront()?.asMatrixTimelineItem() ?: return
add(0, item)
}
TimelineChange.SET -> {
val updateAtData = diff.set() ?: return
val item = updateAtData.item.asMatrixTimelineItem()
set(updateAtData.index.toInt(), item)
}
TimelineChange.INSERT -> {
val insertAtData = diff.insert() ?: return
val item = insertAtData.item.asMatrixTimelineItem()
add(insertAtData.index.toInt(), item)
}
TimelineChange.REMOVE -> {
val removeAtData = diff.remove() ?: return
removeAt(removeAtData.toInt())
}
TimelineChange.RESET -> {
clear()
val items = diff.reset()?.map { it.asMatrixTimelineItem() } ?: return
addAll(items)
}
TimelineChange.POP_FRONT -> {
removeFirstOrNull()
}
TimelineChange.POP_BACK -> {
removeLastOrNull()
}
TimelineChange.CLEAR -> {
clear()
}
}
}
override suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit> = withContext(coroutineDispatchers.io) {
runCatching {
Timber.v("Start back paginating for room ${slidingSyncRoom.roomId()} ")
val paginationOptions = PaginationOptions.UntilNumItems(
eventLimit = requestSize.toUShort(),
items = untilNumberOfItems.toUShort()
)
innerRoom.paginateBackwards(paginationOptions)
}.onFailure {
Timber.e(it, "Fail to paginate for room ${slidingSyncRoom.roomId()}")
}.onSuccess {
Timber.v("Success back paginating for room ${slidingSyncRoom.roomId()}")
}
}
private suspend fun updateTimelineItems(block: MutableList<MatrixTimelineItem>.() -> Unit) =
withContext(coroutineDispatchers.diffUpdateDispatcher) {
val mutableTimelineItems = timelineItems.value.toMutableList()
block(mutableTimelineItems)
timelineItems.value = mutableTimelineItems
}
private suspend fun addListener(timelineListener: TimelineListener): Result<List<TimelineItem>> = withContext(coroutineDispatchers.computation) {
runCatching {
val result = slidingSyncRoom.subscribeAndAddTimelineListener(timelineListener, null)
listenerTokens += result.taskHandle
result.items
}
}
override fun initialize() {
Timber.v("Init timeline for room ${slidingSyncRoom.roomId()}")
Timber.v("Init timeline for room ${matrixRoom.roomId}")
coroutineScope.launch {
matrixRoom.fetchMembers()
.onFailure {
Timber.e(it, "Fail to fetch members for room ${slidingSyncRoom.roomId()}")
Timber.e(it, "Fail to fetch members for room ${matrixRoom.roomId}")
}.onSuccess {
Timber.v("Success fetching members for room ${slidingSyncRoom.roomId()}")
Timber.v("Success fetching members for room ${matrixRoom.roomId}")
}
}
coroutineScope.launch {
@ -154,16 +82,18 @@ class RustMatrixTimeline( @@ -154,16 +82,18 @@ class RustMatrixTimeline(
result
.onSuccess { timelineItems ->
val matrixTimelineItems = timelineItems.map { it.asMatrixTimelineItem() }
updateTimelineItems { addAll(matrixTimelineItems) }
withContext(coroutineDispatchers.diffUpdateDispatcher) {
this@RustMatrixTimeline.timelineItems.value = matrixTimelineItems
}
}
.onFailure {
Timber.e("Failed adding timeline listener on room with identifier: ${slidingSyncRoom.roomId()})")
Timber.e("Failed adding timeline listener on room with identifier: ${matrixRoom.roomId})")
}
}
}
override fun dispose() {
Timber.v("Dispose timeline for room ${slidingSyncRoom.roomId()}")
Timber.v("Dispose timeline for room ${matrixRoom.roomId}")
listenerTokens.dispose()
}
@ -181,4 +111,27 @@ class RustMatrixTimeline( @@ -181,4 +111,27 @@ class RustMatrixTimeline(
override suspend fun replyMessage(inReplyToEventId: EventId, message: String): Result<Unit> {
return matrixRoom.replyMessage(inReplyToEventId, message)
}
override suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit> = withContext(coroutineDispatchers.io) {
runCatching {
Timber.v("Start back paginating for room ${matrixRoom.roomId} ")
val paginationOptions = PaginationOptions.UntilNumItems(
eventLimit = requestSize.toUShort(),
items = untilNumberOfItems.toUShort()
)
innerRoom.paginateBackwards(paginationOptions)
}.onFailure {
Timber.e(it, "Fail to paginate for room ${matrixRoom.roomId}")
}.onSuccess {
Timber.v("Success back paginating for room ${matrixRoom.roomId}")
}
}
private suspend fun addListener(timelineListener: TimelineListener): Result<List<TimelineItem>> = withContext(coroutineDispatchers.io) {
runCatching {
val result = slidingSyncRoom.subscribeAndAddTimelineListener(timelineListener, null)
listenerTokens += result.taskHandle
result.items
}
}
}

14
libraries/matrixtest/src/main/kotlin/io/element/android/libraries/matrixtest/timeline/FakeMatrixTimeline.kt

@ -21,11 +21,19 @@ import io.element.android.libraries.matrix.timeline.MatrixTimeline @@ -21,11 +21,19 @@ import io.element.android.libraries.matrix.timeline.MatrixTimeline
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.emptyFlow
import org.matrix.rustcomponents.sdk.TimelineListener
class FakeMatrixTimeline : MatrixTimeline {
override var callback: MatrixTimeline.Callback? = null
private val paginationState = MutableStateFlow(
MatrixTimeline.PaginationState(canBackPaginate = true, isBackPaginating = false)
)
override fun paginationState(): StateFlow<MatrixTimeline.PaginationState> {
return paginationState
}
override fun timelineItems(): Flow<List<MatrixTimelineItem>> {
return emptyFlow()
@ -36,8 +44,6 @@ class FakeMatrixTimeline : MatrixTimeline { @@ -36,8 +44,6 @@ class FakeMatrixTimeline : MatrixTimeline {
return Result.success(Unit)
}
override fun addListener(timelineListener: TimelineListener) = Unit
override fun initialize() = Unit
override fun dispose() = Unit

Loading…
Cancel
Save