Split MatrixRoom into BaseRoom and JoinedRoom (#4561)

`JoinedRoom` will now contain both a mandatory live timeline reference and all the functionality associated to it.

`BaseRoom` on the other hand will contain only functionality that's shared for both joined and not joined rooms.

`NotJoinedRoom` is a wrapper around `RoomPreviewInfo` data and a possible local `BaseRoom`, if it exists.

The `RustRoomFactory` cache is now gone since the persistent event cache should have the same effect.
This commit is contained in:
Jorge Martin Espinosa 2025-04-23 15:53:40 +02:00 committed by GitHub
parent 91cb84ce8d
commit 619aa6f2de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
193 changed files with 2921 additions and 2567 deletions

View file

@ -10,7 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.datasource
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.RoomScope
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.room.JoinedRoom
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import javax.inject.Inject
@ -24,7 +24,7 @@ interface FocusedTimelineMediaGalleryDataSourceFactory {
@ContributesBinding(RoomScope::class)
class DefaultFocusedTimelineMediaGalleryDataSourceFactory @Inject constructor(
private val room: MatrixRoom,
private val room: JoinedRoom,
private val timelineMediaItemsFactory: TimelineMediaItemsFactory,
private val mediaItemsPostProcessor: MediaItemsPostProcessor,
) : FocusedTimelineMediaGalleryDataSourceFactory {

View file

@ -12,7 +12,7 @@ import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.di.RoomScope
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.room.BaseRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems
@ -40,7 +40,7 @@ interface MediaGalleryDataSource {
@SingleIn(RoomScope::class)
@ContributesBinding(RoomScope::class)
class TimelineMediaGalleryDataSource @Inject constructor(
private val room: MatrixRoom,
private val room: BaseRoom,
private val mediaTimeline: MediaTimeline,
private val timelineMediaItemsFactory: TimelineMediaItemsFactory,
private val mediaItemsPostProcessor: MediaItemsPostProcessor,

View file

@ -13,7 +13,7 @@ import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
@ -37,7 +37,7 @@ interface MediaTimeline {
@SingleIn(RoomScope::class)
@ContributesBinding(RoomScope::class)
class LiveMediaTimeline @Inject constructor(
private val room: MatrixRoom,
private val room: JoinedRoom,
) : MediaTimeline {
private var timeline: Timeline? = null
private val mutex = Mutex()
@ -62,7 +62,7 @@ class LiveMediaTimeline @Inject constructor(
* Optionally, the timeline will only contain the pinned events.
*/
class FocusedMediaTimeline(
private val room: MatrixRoom,
private val room: JoinedRoom,
private val eventId: EventId,
private val onlyPinnedEvents: Boolean,
initialMediaItem: MediaItem.Event,

View file

@ -27,7 +27,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
@ -45,7 +45,7 @@ import kotlinx.coroutines.launch
class MediaGalleryPresenter @AssistedInject constructor(
@Assisted private val navigator: MediaGalleryNavigator,
private val room: MatrixRoom,
private val room: BaseRoom,
private val mediaGalleryDataSource: MediaGalleryDataSource,
private val localMediaFactory: LocalMediaFactory,
private val mediaLoader: MatrixMediaLoader,

View file

@ -30,7 +30,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatch
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
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.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther
import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
@ -53,7 +53,7 @@ class MediaViewerPresenter @AssistedInject constructor(
@Assisted private val inputs: MediaViewerEntryPoint.Params,
@Assisted private val navigator: MediaViewerNavigator,
@Assisted private val dataSource: MediaViewerDataSource,
private val room: MatrixRoom,
private val room: JoinedRoom,
private val localMediaActions: LocalMediaActions,
) : Presenter<MediaViewerState> {
@AssistedFactory

View file

@ -9,7 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.datasource
import com.google.common.truth.Truth.assertThat
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.FakeJoinedRoom
import io.element.android.libraries.mediaviewer.impl.model.aMediaItemImage
import kotlinx.coroutines.test.runTest
import org.junit.Test
@ -18,7 +18,7 @@ class DefaultFocusedTimelineMediaGalleryDataSourceFactoryTest {
@Test
fun `createFor should create a TimelineMediaGalleryDataSource`() = runTest {
val sut = DefaultFocusedTimelineMediaGalleryDataSourceFactory(
room = FakeMatrixRoom(),
room = FakeJoinedRoom(),
timelineMediaItemsFactory = createTimelineMediaItemsFactory(),
mediaItemsPostProcessor = MediaItemsPostProcessor(),
)

View file

@ -10,10 +10,10 @@ package io.element.android.libraries.mediaviewer.impl.datasource
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.JoinedRoom
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.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
@ -79,7 +79,7 @@ class FocusedMediaTimelineTest {
val createTimelineResult = lambdaRecorder<CreateTimelineParams, Result<Timeline>> {
Result.success(FakeTimeline())
}
val room = FakeMatrixRoom(
val room = FakeJoinedRoom(
createTimelineResult = createTimelineResult,
)
val sut = createFocusedMediaTimeline(
@ -96,7 +96,7 @@ class FocusedMediaTimelineTest {
val createTimelineResult = lambdaRecorder<CreateTimelineParams, Result<Timeline>> {
Result.success(FakeTimeline())
}
val room = FakeMatrixRoom(
val room = FakeJoinedRoom(
createTimelineResult = createTimelineResult,
)
val sut = createFocusedMediaTimeline(
@ -110,7 +110,7 @@ class FocusedMediaTimelineTest {
}
private fun createFocusedMediaTimeline(
room: MatrixRoom = FakeMatrixRoom(),
room: JoinedRoom = FakeJoinedRoom(),
eventId: EventId = AN_EVENT_ID,
initialMediaItem: MediaItem.Event = aMediaItemImage(),
onlyPinnedEvent: Boolean = false,

View file

@ -9,9 +9,9 @@ package io.element.android.libraries.mediaviewer.impl.datasource
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems
import io.element.android.tests.testutils.lambda.lambdaRecorder
@ -31,7 +31,7 @@ class LiveMediaTimelineTest {
val createTimelineResult = lambdaRecorder<CreateTimelineParams, Result<Timeline>> {
Result.success(FakeTimeline())
}
val room = FakeMatrixRoom(
val room = FakeJoinedRoom(
createTimelineResult = createTimelineResult,
)
val sut = createLiveMediaTimeline(
@ -47,7 +47,7 @@ class LiveMediaTimelineTest {
}
private fun createLiveMediaTimeline(
room: MatrixRoom = FakeMatrixRoom(),
room: JoinedRoom = FakeJoinedRoom(),
) = LiveMediaTimeline(
room = room,
)

View file

@ -16,7 +16,7 @@ import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.media.ThumbnailInfo
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.JoinedRoom
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.item.event.EventOrTransactionId
@ -28,7 +28,7 @@ import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_UNIQUE_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.aMessageContent
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
@ -56,7 +56,7 @@ class TimelineMediaGalleryDataSourceTest {
fun `test - not started TimelineMediaGalleryDataSource emits no events`() = runTest {
val fakeTimeline = FakeTimeline()
val sut = createTimelineMediaGalleryDataSource(
room = FakeMatrixRoom(
room = FakeJoinedRoom(
createTimelineResult = { Result.success(fakeTimeline) },
roomCoroutineScope = backgroundScope,
)
@ -74,7 +74,7 @@ class TimelineMediaGalleryDataSourceTest {
val fakeTimeline = FakeTimeline()
runTest {
val sut = createTimelineMediaGalleryDataSource(
room = FakeMatrixRoom(
room = FakeJoinedRoom(
createTimelineResult = { Result.success(fakeTimeline) },
roomCoroutineScope = backgroundScope,
)
@ -111,7 +111,7 @@ class TimelineMediaGalleryDataSourceTest {
paginateLambda = paginateLambdaRecorder
}
val sut = createTimelineMediaGalleryDataSource(
room = FakeMatrixRoom(
room = FakeJoinedRoom(
createTimelineResult = { Result.success(fakeTimeline) },
roomCoroutineScope = backgroundScope,
)
@ -134,7 +134,7 @@ class TimelineMediaGalleryDataSourceTest {
redactEventLambda = redactEventLambdaRecorder
}
val sut = createTimelineMediaGalleryDataSource(
room = FakeMatrixRoom(
room = FakeJoinedRoom(
createTimelineResult = { Result.success(fakeTimeline) },
roomCoroutineScope = backgroundScope,
)
@ -153,7 +153,7 @@ class TimelineMediaGalleryDataSourceTest {
@Test
fun `test - failing to load timeline should emit an error`() = runTest {
val sut = createTimelineMediaGalleryDataSource(
room = FakeMatrixRoom(
room = FakeJoinedRoom(
createTimelineResult = { Result.failure(AN_EXCEPTION) },
roomCoroutineScope = backgroundScope,
)
@ -175,7 +175,7 @@ class TimelineMediaGalleryDataSourceTest {
timelineItems = timelineItems,
)
val sut = createTimelineMediaGalleryDataSource(
room = FakeMatrixRoom(
room = FakeJoinedRoom(
createTimelineResult = { Result.success(fakeTimeline) },
roomCoroutineScope = backgroundScope,
)
@ -256,7 +256,7 @@ class TimelineMediaGalleryDataSourceTest {
}
private fun TestScope.createTimelineMediaGalleryDataSource(
room: MatrixRoom = FakeMatrixRoom(
room: JoinedRoom = FakeJoinedRoom(
liveTimeline = FakeTimeline(),
),
): TimelineMediaGalleryDataSource {

View file

@ -12,14 +12,15 @@ import app.cash.turbine.ReceiveTurbine
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
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.room.JoinedRoom
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.A_ROOM_NAME
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.media.FakeMatrixMediaLoader
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediaviewer.impl.datasource.FakeMediaGalleryDataSource
@ -51,8 +52,8 @@ class MediaGalleryPresenterTest {
mediaGalleryDataSource = FakeMediaGalleryDataSource(
startLambda = startLambda,
),
room = FakeMatrixRoom(
initialRoomInfo = aRoomInfo(name = A_ROOM_NAME),
room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(initialRoomInfo = aRoomInfo(name = A_ROOM_NAME)),
createTimelineResult = { Result.success(FakeTimeline()) },
)
)
@ -70,8 +71,8 @@ class MediaGalleryPresenterTest {
@Test
fun `present - change mode`() = runTest {
val presenter = createMediaGalleryPresenter(
room = FakeMatrixRoom(
initialRoomInfo = aRoomInfo(name = A_ROOM_NAME),
room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(initialRoomInfo = aRoomInfo(name = A_ROOM_NAME)),
createTimelineResult = { Result.success(FakeTimeline()) },
)
)
@ -99,11 +100,13 @@ class MediaGalleryPresenterTest {
private suspend fun `present - bottom sheet state - own message`(canDeleteOwn: Boolean) {
val presenter = createMediaGalleryPresenter(
room = FakeMatrixRoom(
sessionId = A_USER_ID,
initialRoomInfo = aRoomInfo(name = A_ROOM_NAME),
room = FakeJoinedRoom(
createTimelineResult = { Result.success(FakeTimeline()) },
canRedactOwnResult = { Result.success(canDeleteOwn) }
baseRoom = FakeBaseRoom(
sessionId = A_USER_ID,
initialRoomInfo = aRoomInfo(name = A_ROOM_NAME),
canRedactOwnResult = { Result.success(canDeleteOwn) }
),
)
)
presenter.test {
@ -142,11 +145,13 @@ class MediaGalleryPresenterTest {
private suspend fun `present - bottom sheet state - other message`(canDeleteOther: Boolean) {
val presenter = createMediaGalleryPresenter(
room = FakeMatrixRoom(
room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
sessionId = A_USER_ID,
initialRoomInfo = aRoomInfo(name = A_ROOM_NAME),
createTimelineResult = { Result.success(FakeTimeline()) },
canRedactOtherResult = { Result.success(canDeleteOther) }
canRedactOtherResult = { Result.success(canDeleteOther) },
),
createTimelineResult = { Result.success(FakeTimeline()) }
)
)
presenter.test {
@ -176,8 +181,8 @@ class MediaGalleryPresenterTest {
@Test
fun `present - delete bottom sheet`() = runTest {
val presenter = createMediaGalleryPresenter(
room = FakeMatrixRoom(
initialRoomInfo = aRoomInfo(name = A_ROOM_NAME),
room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(initialRoomInfo = aRoomInfo(name = A_ROOM_NAME)),
createTimelineResult = { Result.success(FakeTimeline()) },
)
)
@ -244,7 +249,7 @@ class MediaGalleryPresenterTest {
onViewInTimelineClickLambda = onViewInTimelineClickLambda,
)
val presenter = createMediaGalleryPresenter(
room = FakeMatrixRoom(
room = FakeJoinedRoom(
createTimelineResult = { Result.success(FakeTimeline()) },
),
navigator = navigator,
@ -284,7 +289,7 @@ class MediaGalleryPresenterTest {
localMediaActions: FakeLocalMediaActions = FakeLocalMediaActions(),
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
navigator: MediaGalleryNavigator = FakeMediaGalleryNavigator(),
room: MatrixRoom = FakeMatrixRoom(
room: JoinedRoom = FakeJoinedRoom(
liveTimeline = FakeTimeline(),
),
): MediaGalleryPresenter {

View file

@ -15,7 +15,7 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
@ -24,7 +24,8 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader
import io.element.android.libraries.matrix.test.media.aMediaSource
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
import io.element.android.libraries.mediaviewer.api.anApkMediaInfo
@ -77,10 +78,12 @@ class MediaViewerPresenterTest {
@Test
fun `present - initial state null Event`() = runTest {
val presenter = createMediaViewerPresenter(
room = FakeMatrixRoom(
room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) },
)
)
)
presenter.test {
val initialState = awaitFirstItem()
assertThat(initialState.listData).isEmpty()
@ -95,10 +98,12 @@ class MediaViewerPresenterTest {
fun `present - initial state cannot show info`() = runTest {
val presenter = createMediaViewerPresenter(
canShowInfo = false,
room = FakeMatrixRoom(
room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) },
)
)
)
presenter.test {
val initialState = awaitFirstItem()
assertThat(initialState.listData).isEmpty()
@ -113,10 +118,12 @@ class MediaViewerPresenterTest {
fun `present - initial state Event`() = runTest {
val presenter = createMediaViewerPresenter(
eventId = AN_EVENT_ID,
room = FakeMatrixRoom(
room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) },
)
)
)
presenter.test {
val initialState = awaitFirstItem()
assertThat(initialState.listData).isEmpty()
@ -131,11 +138,13 @@ class MediaViewerPresenterTest {
fun `present - initial state Event from other`() = runTest {
val presenter = createMediaViewerPresenter(
eventId = AN_EVENT_ID,
room = FakeMatrixRoom(
room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
sessionId = A_SESSION_ID_2,
canRedactOtherResult = { Result.success(false) },
)
)
)
presenter.test {
val initialState = awaitFirstItem()
assertThat(initialState.listData).isEmpty()
@ -216,9 +225,9 @@ class MediaViewerPresenterTest {
)
val presenter = createMediaViewerPresenter(
mediaGalleryDataSource = mediaGalleryDataSource,
room = FakeMatrixRoom(
room = FakeJoinedRoom(baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) },
)
))
)
val anImage = aMediaItemImage(
mediaSourceUrl = aUrl,
@ -432,9 +441,9 @@ class MediaViewerPresenterTest {
startLambda = { },
)
val presenter = createMediaViewerPresenter(
room = FakeMatrixRoom(
room = FakeJoinedRoom(
liveTimeline = timeline,
canRedactOwnResult = { Result.success(true) },
baseRoom = FakeBaseRoom(canRedactOwnResult = { Result.success(true) }),
),
mediaGalleryDataSource = mediaGalleryDataSource,
mediaViewerNavigator = FakeMediaViewerNavigator(
@ -736,8 +745,8 @@ class MediaViewerPresenterTest {
)
val presenter = createMediaViewerPresenter(
mediaViewerNavigator = navigator,
room = FakeMatrixRoom(
canRedactOwnResult = { Result.success(true) },
room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(canRedactOwnResult = { Result.success(true) }),
)
)
presenter.test {
@ -766,7 +775,7 @@ class MediaViewerPresenterTest {
),
canShowInfo: Boolean = true,
mediaViewerNavigator: MediaViewerNavigator = FakeMediaViewerNavigator(),
room: MatrixRoom = FakeMatrixRoom(
room: JoinedRoom = FakeJoinedRoom(
liveTimeline = FakeTimeline(),
),
): MediaViewerPresenter {