diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt index fd26109e0a..a2212b69f3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt @@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.TimelineProvider +import io.element.android.libraries.matrix.api.timeline.getActiveTimeline import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt index ce6bdfc7d3..c4d794b7c4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt @@ -22,17 +22,22 @@ import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.LiveTimelineProvider -import io.element.android.libraries.matrix.api.timeline.TimelineProvider import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.matrix.api.timeline.TimelineProvider +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import java.io.Closeable import java.util.Optional import javax.inject.Inject @@ -49,12 +54,14 @@ class TimelineController @Inject constructor( private val room: MatrixRoom, ) : Closeable, TimelineProvider { + private val coroutineScope = CoroutineScope(SupervisorJob()) + private val liveTimeline = MutableStateFlow(room.liveTimeline) private val detachedTimeline = MutableStateFlow>(Optional.empty()) @OptIn(ExperimentalCoroutinesApi::class) fun timelineItems(): Flow> { - return currentTimelineFlow().flatMapLatest { it.timelineItems } + return currentTimelineFlow.flatMapLatest { it.timelineItems } } fun isLive(): Flow { @@ -62,7 +69,7 @@ class TimelineController @Inject constructor( } suspend fun invokeOnCurrentTimeline(block: suspend (Timeline.() -> Any)) { - currentTimelineFlow().first().run { + currentTimelineFlow.value.run { block(this) } } @@ -89,6 +96,10 @@ class TimelineController @Inject constructor( * This does close the detached timeline if any. */ fun focusOnLive() { + closeDetachedTimeline() + } + + private fun closeDetachedTimeline() { detachedTimeline.getAndUpdate { when { it.isPresent -> { @@ -101,11 +112,12 @@ class TimelineController @Inject constructor( } override fun close() { - focusOnLive() + coroutineScope.cancel() + closeDetachedTimeline() } suspend fun paginate(direction: Timeline.PaginationDirection): Result { - return currentTimelineFlow().first().paginate(direction) + return currentTimelineFlow.value.paginate(direction) .onSuccess { hasReachedEnd -> if (direction == Timeline.PaginationDirection.FORWARDS && hasReachedEnd) { focusOnLive() @@ -113,14 +125,14 @@ class TimelineController @Inject constructor( } } - private fun currentTimelineFlow() = combine(liveTimeline, detachedTimeline) { live, detached -> + private val currentTimelineFlow = combine(liveTimeline, detachedTimeline) { live, detached -> when { detached.isPresent -> detached.get() else -> live } - } + }.stateIn(coroutineScope, SharingStarted.Eagerly, room.liveTimeline) - override suspend fun getActiveTimeline(): Timeline { - return currentTimelineFlow().first() + override fun activeTimelineFlow(): StateFlow { + return currentTimelineFlow } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index 7fb6c1ffdc..726870a5b7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -32,7 +32,9 @@ data class TimelineState( val focusedEventId : EventId?, val focusRequestState: FocusRequestState, val eventSink: (TimelineEvents) -> Unit, -) +){ + val isTimelineEmpty = timelineItems.none { it is TimelineItem.Event } +} sealed interface FocusRequestState { data object None : FocusRequestState diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index f15a0807cd..1e1c04a677 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -63,6 +63,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentProvider import io.element.android.features.messages.impl.typing.TypingNotificationState +import io.element.android.features.messages.impl.typing.TypingNotificationView import io.element.android.features.messages.impl.typing.aTypingNotificationState import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog @@ -125,12 +126,12 @@ fun TimelineView( reverseLayout = useReverseLayout, contentPadding = PaddingValues(vertical = 8.dp), ) { - /* - item(key = UUID.randomUUID()) { - TypingNotificationView(state = typingNotificationState) - } - */ + if(state.isLive) { + item { + TypingNotificationView(state = typingNotificationState) + } + } items( items = state.timelineItems, contentType = { timelineItem -> timelineItem.contentType() }, @@ -165,7 +166,7 @@ fun TimelineView( ) TimelineScrollHelper( - isTimelineEmpty = state.timelineItems.isEmpty(), + isTimelineEmpty = state.isTimelineEmpty, lazyListState = lazyListState, forceJumpToBottomVisibility = forceJumpToBottomVisibility, newEventState = state.newEventState, @@ -185,10 +186,6 @@ private fun FocusRequestStateView( onClearFocusRequestState: () -> Unit, modifier: Modifier = Modifier, ) { - BackHandler(enabled = focusRequestState is FocusRequestState.Fetching) { - onClearFocusRequestState() - } - when (focusRequestState) { is FocusRequestState.Failure -> { ErrorDialog( @@ -198,7 +195,7 @@ private fun FocusRequestStateView( ) } FocusRequestState.Fetching -> { - ProgressDialog(modifier = modifier) + ProgressDialog(modifier = modifier, onDismissRequest = onClearFocusRequestState) } is FocusRequestState.Cached, FocusRequestState.None -> Unit } @@ -254,7 +251,6 @@ private fun BoxScope.TimelineScrollHelper( } LaunchedEffect(canAutoScroll, newEventState) { - Timber.d("TimelineScrollHelper - canAutoScroll: $canAutoScroll, newEventState: $newEventState") val shouldScrollToBottom = isScrollFinished && (canAutoScroll || newEventState == NewEventState.FromMe) if (shouldScrollToBottom) { scrollToBottom() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTests.kt index d1b054faf8..f6c749ea32 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTests.kt @@ -21,15 +21,20 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.timeline.LiveTimelineProvider import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomSummaryDetails +import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import java.lang.IllegalStateException class ForwardMessagesPresenterTests { @get:Rule @@ -37,7 +42,7 @@ class ForwardMessagesPresenterTests { @Test fun `present - initial state`() = runTest { - val presenter = aPresenter() + val presenter = aForwardMessagesPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -50,7 +55,14 @@ class ForwardMessagesPresenterTests { @Test fun `present - forward successful`() = runTest { - val presenter = aPresenter() + val forwardEventLambda = lambdaRecorder { _: EventId, _: List -> + Result.success(Unit) + } + val timeline = FakeTimeline().apply { + this.forwardEventLambda = forwardEventLambda + } + val room = FakeMatrixRoom(liveTimeline = timeline) + val presenter = aForwardMessagesPresenter(fakeMatrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -62,18 +74,23 @@ class ForwardMessagesPresenterTests { val successfulForwardState = awaitItem() assertThat(successfulForwardState.isForwarding).isFalse() assertThat(successfulForwardState.forwardingSucceeded).isNotNull() + assert(forwardEventLambda).isCalledOnce() } } @Test fun `present - select a room and forward failed, then clear`() = runTest { - val room = FakeMatrixRoom() - val presenter = aPresenter(fakeMatrixRoom = room) + val forwardEventLambda = lambdaRecorder { _: EventId, _: List -> + Result.failure(IllegalStateException("error")) + } + val timeline = FakeTimeline().apply { + this.forwardEventLambda = forwardEventLambda + } + val room = FakeMatrixRoom(liveTimeline = timeline) + val presenter = aForwardMessagesPresenter(fakeMatrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - // Test failed forwarding - room.givenForwardEventResult(Result.failure(Throwable("error"))) skipItems(1) val summary = aRoomSummaryDetails() presenter.onRoomSelected(listOf(summary.roomId)) @@ -83,10 +100,11 @@ class ForwardMessagesPresenterTests { // Then clear error failedForwardState.eventSink(ForwardMessagesEvents.ClearError) assertThat(awaitItem().error).isNull() + assert(forwardEventLambda).isCalledOnce() } } - private fun CoroutineScope.aPresenter( + private fun CoroutineScope.aForwardMessagesPresenter( eventId: EventId = AN_EVENT_ID, fakeMatrixRoom: FakeMatrixRoom = FakeMatrixRoom(), coroutineScope: CoroutineScope = this, diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt index 55d2bd8099..e8d2506408 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.TimelineProvider +import io.element.android.libraries.matrix.api.timeline.getActiveTimeline import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import kotlinx.coroutines.flow.first import javax.inject.Inject diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt index b36359aa25..7a3b3f8232 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt @@ -34,22 +34,23 @@ import io.element.android.features.poll.impl.history.model.PollHistoryItemsFacto import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.matrix.api.timeline.TimelineProvider import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject class PollHistoryPresenter @Inject constructor( - private val room: MatrixRoom, private val appCoroutineScope: CoroutineScope, private val sendPollResponseAction: SendPollResponseAction, private val endPollAction: EndPollAction, private val pollHistoryItemFactory: PollHistoryItemsFactory, + private val timelineProvider: TimelineProvider, ) : Presenter { @Composable override fun present(): PollHistoryState { - // TODO use room.rememberPollHistory() when working properly? - val timeline = room.liveTimeline + val timeline by timelineProvider.activeTimelineFlow().collectAsState() val paginationState by timeline.paginationStatus(Timeline.PaginationDirection.BACKWARDS).collectAsState() val pollHistoryItemsFlow = remember { timeline.timelineItems.map { items -> diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt index 9a4af79dd6..07f71f51d3 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt @@ -32,6 +32,7 @@ import io.element.android.features.poll.test.actions.FakeEndPollAction import io.element.android.features.poll.test.actions.FakeSendPollResponseAction import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.timeline.LiveTimelineProvider import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 @@ -174,11 +175,11 @@ class PollHistoryPresenterTest { ), ): PollHistoryPresenter { return PollHistoryPresenter( - room = room, appCoroutineScope = appCoroutineScope, sendPollResponseAction = sendPollResponseAction, endPollAction = endPollAction, pollHistoryItemFactory = pollHistoryItemFactory, + timelineProvider = LiveTimelineProvider(room), ) } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineProvider.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineProvider.kt index 242859fede..5b55b8dc47 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineProvider.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineProvider.kt @@ -19,6 +19,11 @@ package io.element.android.libraries.matrix.api.timeline import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.room.MatrixRoom +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf import javax.inject.Inject /** @@ -26,9 +31,11 @@ import javax.inject.Inject * It could be the current room timeline, or a timeline for a specific event. */ interface TimelineProvider { - suspend fun getActiveTimeline(): Timeline + fun activeTimelineFlow(): StateFlow } +suspend fun TimelineProvider.getActiveTimeline(): Timeline = activeTimelineFlow().first() + /** * Default implementation of [TimelineProvider] that provides the live timeline of a room. */ @@ -36,6 +43,6 @@ interface TimelineProvider { class LiveTimelineProvider @Inject constructor( private val room: MatrixRoom, ) : TimelineProvider { - override suspend fun getActiveTimeline(): Timeline = room.liveTimeline + override fun activeTimelineFlow(): StateFlow = MutableStateFlow(room.liveTimeline) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/HasEncryptionHistoryBanner.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/HasEncryptionHistoryBanner.kt index 882d89bbee..2d6801918e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/HasEncryptionHistoryBanner.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/HasEncryptionHistoryBanner.kt @@ -19,7 +19,7 @@ package io.element.android.libraries.matrix.impl.timeline.postprocessor import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem -fun List.hasEncryptionHistoryBanner(): Boolean { +internal fun List.hasEncryptionHistoryBanner(): Boolean { val firstItem = firstOrNull() return firstItem is MatrixTimelineItem.Virtual && firstItem.virtual is VirtualTimelineItem.EncryptedHistoryBanner diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt index fed2266659..da8392a452 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.impl.timeline.postprocessor +import androidx.annotation.VisibleForTesting import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange import io.element.android.libraries.matrix.api.timeline.item.event.OtherState @@ -43,10 +44,7 @@ class RoomBeginningPostProcessor { private fun processForRoom(items: List): List { if (items.hasEncryptionHistoryBanner()) return items - val roomBeginningItem = MatrixTimelineItem.Virtual( - uniqueId = VirtualTimelineItem.RoomBeginning.toString(), - virtual = VirtualTimelineItem.RoomBeginning - ) + val roomBeginningItem = createRoomBeginningItem() return listOf(roomBeginningItem) + items } @@ -77,4 +75,13 @@ class RoomBeginningPostProcessor { } return newItems } + + @VisibleForTesting + fun createRoomBeginningItem(): MatrixTimelineItem.Virtual { + return MatrixTimelineItem.Virtual( + uniqueId = VirtualTimelineItem.RoomBeginning.toString(), + virtual = VirtualTimelineItem.RoomBeginning + ) + } + } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/DmBeginningTimelineProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt similarity index 86% rename from libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/DmBeginningTimelineProcessorTest.kt rename to libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt index 40a15f7621..2c72481a25 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/DmBeginningTimelineProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt @@ -22,13 +22,14 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MembershipCha import io.element.android.libraries.matrix.api.timeline.item.event.OtherState import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent +import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import org.junit.Test -class DmBeginningTimelineProcessorTest { +class RoomBeginningPostProcessorTest { @Test fun `processor removes room creation event and self-join event from DM timeline`() { val timelineItems = listOf( @@ -36,7 +37,7 @@ class DmBeginningTimelineProcessorTest { MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))), ) val processor = RoomBeginningPostProcessor() - val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true) + val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = false) assertThat(processedItems).isEmpty() } @@ -53,18 +54,30 @@ class DmBeginningTimelineProcessorTest { MatrixTimelineItem.Event("m.room.message", anEventTimelineItem(content = aMessageContent("hi"))), ) val processor = RoomBeginningPostProcessor() - val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true) + val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = false) assertThat(processedItems).isEqualTo(expected) } @Test - fun `processor won't remove items if it's not a DM`() { + fun `processor will add beginning of room item if it's not a DM`() { val timelineItems = listOf( MatrixTimelineItem.Event("m.room.create", anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))), MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))), ) val processor = RoomBeginningPostProcessor() - val processedItems = processor.process(timelineItems, isDm = false, hasMoreToLoadBackwards = true) + val processedItems = processor.process(timelineItems, isDm = false, hasMoreToLoadBackwards = false) + assertThat(processedItems).isEqualTo( + listOf(processor.createRoomBeginningItem()) + timelineItems + ) + } + + @Test + fun `processor will not add beginning of room item if it's not a DM and EncryptedHistoryBanner item is found`() { + val timelineItems = listOf( + MatrixTimelineItem.Virtual("EncryptedHistoryBanner", VirtualTimelineItem.EncryptedHistoryBanner), + ) + val processor = RoomBeginningPostProcessor() + val processedItems = processor.process(timelineItems, isDm = false, hasMoreToLoadBackwards = false) assertThat(processedItems).isEqualTo(timelineItems) } @@ -75,7 +88,7 @@ class DmBeginningTimelineProcessorTest { MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))), ) val processor = RoomBeginningPostProcessor() - val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = false) + val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true) assertThat(processedItems).isEqualTo(timelineItems) } @@ -85,7 +98,7 @@ class DmBeginningTimelineProcessorTest { MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID, MembershipChange.JOINED))), ) val processor = RoomBeginningPostProcessor() - val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = false) + val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true) assertThat(processedItems).isEqualTo(timelineItems) } @@ -96,7 +109,7 @@ class DmBeginningTimelineProcessorTest { MatrixTimelineItem.Event("m.room.member", anEventTimelineItem(content = RoomMembershipContent(A_USER_ID_2, MembershipChange.JOINED))), ) val processor = RoomBeginningPostProcessor() - val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = false) + val processedItems = processor.process(timelineItems, isDm = true, hasMoreToLoadBackwards = true) assertThat(processedItems).isEqualTo(timelineItems) } }