Merge pull request #4274 from element-hq/feature/bma/mediaTimelineImprovment
Update Matrix Room API and allow media swipe on pinned event only.
This commit is contained in:
commit
86afffb4bc
31 changed files with 296 additions and 185 deletions
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
|
||||
sealed interface CreateTimelineParams {
|
||||
data class Focused(val focusedEventId: EventId) : CreateTimelineParams
|
||||
data object MediaOnly : CreateTimelineParams
|
||||
data class MediaOnlyFocused(val focusedEventId: EventId) : CreateTimelineParams
|
||||
data object PinnedOnly : CreateTimelineParams
|
||||
}
|
||||
|
|
@ -109,21 +109,12 @@ interface MatrixRoom : Closeable {
|
|||
val liveTimeline: Timeline
|
||||
|
||||
/**
|
||||
* Create a new timeline, focused on the provided Event.
|
||||
* Should not be used directly, see `TimelineController` to manage the various timelines.
|
||||
* Create a new timeline.
|
||||
* @param createTimelineParams contains parameters about how to filter the timeline. Will also configure the date separators.
|
||||
*/
|
||||
suspend fun timelineFocusedOnEvent(eventId: EventId): Result<Timeline>
|
||||
|
||||
/**
|
||||
* Create a new timeline for the pinned events of the room.
|
||||
*/
|
||||
suspend fun pinnedEventsTimeline(): Result<Timeline>
|
||||
|
||||
/**
|
||||
* Create a new timeline for the media events of the room.
|
||||
* @param eventId The event to focus on, if any.
|
||||
*/
|
||||
suspend fun mediaTimeline(eventId: EventId?): Result<Timeline>
|
||||
suspend fun createTimeline(
|
||||
createTimelineParams: CreateTimelineParams,
|
||||
): Result<Timeline>
|
||||
|
||||
fun destroy()
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler
|
|||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
|
||||
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
|
|
@ -214,80 +215,81 @@ class RustMatrixRoom(
|
|||
|
||||
override suspend fun subscribeToSync() = roomSyncSubscriber.subscribe(roomId)
|
||||
|
||||
override suspend fun timelineFocusedOnEvent(eventId: EventId): Result<Timeline> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.timelineWithConfiguration(
|
||||
configuration = TimelineConfiguration(
|
||||
focus = TimelineFocus.Event(
|
||||
eventId = eventId.value,
|
||||
numContextEvents = 50u,
|
||||
),
|
||||
allowedMessageTypes = AllowedMessageTypes.All,
|
||||
internalIdPrefix = "focus_$eventId",
|
||||
dateDividerMode = DateDividerMode.DAILY,
|
||||
)
|
||||
).let { inner ->
|
||||
createTimeline(inner, mode = Timeline.Mode.FOCUSED_ON_EVENT)
|
||||
}
|
||||
}.mapFailure {
|
||||
it.toFocusEventException()
|
||||
}.onFailure {
|
||||
if (it is CancellationException) {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun pinnedEventsTimeline(): Result<Timeline> = withContext(roomDispatcher) {
|
||||
runCatching {
|
||||
innerRoom.timelineWithConfiguration(
|
||||
configuration = TimelineConfiguration(
|
||||
focus = TimelineFocus.PinnedEvents(
|
||||
maxEventsToLoad = 100u,
|
||||
maxConcurrentRequests = 10u,
|
||||
),
|
||||
allowedMessageTypes = AllowedMessageTypes.All,
|
||||
internalIdPrefix = "pinned_events",
|
||||
dateDividerMode = DateDividerMode.DAILY,
|
||||
)
|
||||
).let { inner ->
|
||||
createTimeline(inner, mode = Timeline.Mode.PINNED_EVENTS)
|
||||
}
|
||||
}.onFailure {
|
||||
if (it is CancellationException) {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun mediaTimeline(
|
||||
eventId: EventId?,
|
||||
override suspend fun createTimeline(
|
||||
createTimelineParams: CreateTimelineParams,
|
||||
): Result<Timeline> = withContext(roomDispatcher) {
|
||||
val focus = if (eventId != null) {
|
||||
TimelineFocus.Event(
|
||||
eventId = eventId.value,
|
||||
val focus = when (createTimelineParams) {
|
||||
is CreateTimelineParams.PinnedOnly -> TimelineFocus.PinnedEvents(
|
||||
maxEventsToLoad = 100u,
|
||||
maxConcurrentRequests = 10u,
|
||||
)
|
||||
is CreateTimelineParams.MediaOnly -> TimelineFocus.Live
|
||||
is CreateTimelineParams.Focused -> TimelineFocus.Event(
|
||||
eventId = createTimelineParams.focusedEventId.value,
|
||||
numContextEvents = 50u,
|
||||
)
|
||||
is CreateTimelineParams.MediaOnlyFocused -> TimelineFocus.Event(
|
||||
eventId = createTimelineParams.focusedEventId.value,
|
||||
numContextEvents = 50u,
|
||||
)
|
||||
} else {
|
||||
TimelineFocus.Live
|
||||
}
|
||||
|
||||
val allowedMessageTypes = when (createTimelineParams) {
|
||||
is CreateTimelineParams.MediaOnly,
|
||||
is CreateTimelineParams.MediaOnlyFocused -> AllowedMessageTypes.Only(
|
||||
types = listOf(
|
||||
RoomMessageEventMessageType.FILE,
|
||||
RoomMessageEventMessageType.IMAGE,
|
||||
RoomMessageEventMessageType.VIDEO,
|
||||
RoomMessageEventMessageType.AUDIO,
|
||||
)
|
||||
)
|
||||
is CreateTimelineParams.Focused,
|
||||
CreateTimelineParams.PinnedOnly -> AllowedMessageTypes.All
|
||||
}
|
||||
|
||||
val internalIdPrefix = when (createTimelineParams) {
|
||||
is CreateTimelineParams.PinnedOnly -> "pinned_events"
|
||||
is CreateTimelineParams.Focused -> "focus_${createTimelineParams.focusedEventId}"
|
||||
is CreateTimelineParams.MediaOnly -> "MediaGallery_"
|
||||
is CreateTimelineParams.MediaOnlyFocused -> "MediaGallery_${createTimelineParams.focusedEventId}"
|
||||
}
|
||||
|
||||
// Note that for TimelineFilter.MediaOnlyFocused, the date separator will be filtered out,
|
||||
// but there is no way to exclude data separator at the moment.
|
||||
val dateDividerMode = when (createTimelineParams) {
|
||||
is CreateTimelineParams.MediaOnly,
|
||||
is CreateTimelineParams.MediaOnlyFocused -> DateDividerMode.MONTHLY
|
||||
is CreateTimelineParams.Focused,
|
||||
CreateTimelineParams.PinnedOnly -> DateDividerMode.DAILY
|
||||
}
|
||||
|
||||
runCatching {
|
||||
innerRoom.timelineWithConfiguration(
|
||||
configuration = TimelineConfiguration(
|
||||
focus = focus,
|
||||
allowedMessageTypes = AllowedMessageTypes.Only(
|
||||
types = listOf(
|
||||
RoomMessageEventMessageType.FILE,
|
||||
RoomMessageEventMessageType.IMAGE,
|
||||
RoomMessageEventMessageType.VIDEO,
|
||||
RoomMessageEventMessageType.AUDIO,
|
||||
)
|
||||
),
|
||||
internalIdPrefix = "MediaGallery_",
|
||||
dateDividerMode = DateDividerMode.MONTHLY,
|
||||
allowedMessageTypes = allowedMessageTypes,
|
||||
internalIdPrefix = internalIdPrefix,
|
||||
dateDividerMode = dateDividerMode,
|
||||
)
|
||||
).let { inner ->
|
||||
createTimeline(inner, mode = if (eventId != null) Timeline.Mode.FOCUSED_ON_EVENT else Timeline.Mode.MEDIA)
|
||||
val mode = when (createTimelineParams) {
|
||||
is CreateTimelineParams.Focused -> Timeline.Mode.FOCUSED_ON_EVENT
|
||||
is CreateTimelineParams.MediaOnly -> Timeline.Mode.MEDIA
|
||||
is CreateTimelineParams.MediaOnlyFocused -> Timeline.Mode.FOCUSED_ON_EVENT
|
||||
CreateTimelineParams.PinnedOnly -> Timeline.Mode.PINNED_EVENTS
|
||||
}
|
||||
createTimeline(
|
||||
timeline = inner,
|
||||
mode = mode,
|
||||
)
|
||||
}
|
||||
}.mapFailure {
|
||||
when (createTimelineParams) {
|
||||
is CreateTimelineParams.Focused,
|
||||
is CreateTimelineParams.MediaOnlyFocused -> it.toFocusEventException()
|
||||
CreateTimelineParams.MediaOnly,
|
||||
CreateTimelineParams.PinnedOnly -> it
|
||||
}
|
||||
}.onFailure {
|
||||
if (it is CancellationException) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler
|
|||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
|
||||
import io.element.android.libraries.matrix.api.room.IntentionalMention
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
|
|
@ -138,9 +139,7 @@ class FakeMatrixRoom(
|
|||
private val leaveRoomLambda: () -> Result<Unit> = { lambdaError() },
|
||||
private val updateMembersResult: () -> Unit = { lambdaError() },
|
||||
private val getMembersResult: (Int) -> Result<List<RoomMember>> = { lambdaError() },
|
||||
private val timelineFocusedOnEventResult: (EventId) -> Result<Timeline> = { lambdaError() },
|
||||
private val pinnedEventsTimelineResult: () -> Result<Timeline> = { lambdaError() },
|
||||
private val mediaTimelineResult: (EventId?) -> Result<Timeline> = { lambdaError() },
|
||||
private val createTimelineResult: (CreateTimelineParams) -> Result<Timeline> = { lambdaError() },
|
||||
private val setSendQueueEnabledLambda: (Boolean) -> Unit = { _: Boolean -> },
|
||||
private val saveComposerDraftLambda: (ComposerDraft) -> Result<Unit> = { _: ComposerDraft -> Result.success(Unit) },
|
||||
private val loadComposerDraftLambda: () -> Result<ComposerDraft?> = { Result.success<ComposerDraft?>(null) },
|
||||
|
|
@ -220,16 +219,10 @@ class FakeMatrixRoom(
|
|||
_syncUpdateFlow.tryEmit(_syncUpdateFlow.value + 1)
|
||||
}
|
||||
|
||||
override suspend fun timelineFocusedOnEvent(eventId: EventId): Result<Timeline> = simulateLongTask {
|
||||
timelineFocusedOnEventResult(eventId)
|
||||
}
|
||||
|
||||
override suspend fun pinnedEventsTimeline(): Result<Timeline> = simulateLongTask {
|
||||
pinnedEventsTimelineResult()
|
||||
}
|
||||
|
||||
override suspend fun mediaTimeline(eventId: EventId?): Result<Timeline> = simulateLongTask {
|
||||
mediaTimelineResult(eventId)
|
||||
override suspend fun createTimeline(
|
||||
createTimelineParams: CreateTimelineParams,
|
||||
): Result<Timeline> = simulateLongTask {
|
||||
createTimelineResult(createTimelineParams)
|
||||
}
|
||||
|
||||
override suspend fun subscribeToSync() {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
package io.element.android.libraries.mediaviewer.api
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
|
|
@ -14,6 +15,8 @@ import io.element.android.libraries.architecture.FeatureEntryPoint
|
|||
import io.element.android.libraries.architecture.NodeInputs
|
||||
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.timeline.Timeline
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
interface MediaViewerEntryPoint : FeatureEntryPoint {
|
||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||
|
|
@ -39,9 +42,14 @@ interface MediaViewerEntryPoint : FeatureEntryPoint {
|
|||
val canShowInfo: Boolean,
|
||||
) : NodeInputs
|
||||
|
||||
enum class MediaViewerMode {
|
||||
SingleMedia,
|
||||
TimelineImagesAndVideos,
|
||||
TimelineFilesAndAudios,
|
||||
sealed interface MediaViewerMode : Parcelable {
|
||||
@Parcelize
|
||||
data object SingleMedia : MediaViewerMode
|
||||
|
||||
@Parcelize
|
||||
data class TimelineImagesAndVideos(val timelineMode: Timeline.Mode) : MediaViewerMode
|
||||
|
||||
@Parcelize
|
||||
data class TimelineFilesAndAudios(val timelineMode: Timeline.Mode) : MediaViewerMode
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ interface FocusedTimelineMediaGalleryDataSourceFactory {
|
|||
fun createFor(
|
||||
eventId: EventId,
|
||||
mediaItem: MediaItem.Event,
|
||||
onlyPinnedEvents: Boolean,
|
||||
): MediaGalleryDataSource
|
||||
}
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ class DefaultFocusedTimelineMediaGalleryDataSourceFactory @Inject constructor(
|
|||
override fun createFor(
|
||||
eventId: EventId,
|
||||
mediaItem: MediaItem.Event,
|
||||
onlyPinnedEvents: Boolean,
|
||||
): MediaGalleryDataSource {
|
||||
return TimelineMediaGalleryDataSource(
|
||||
room = room,
|
||||
|
|
@ -37,6 +39,7 @@ class DefaultFocusedTimelineMediaGalleryDataSourceFactory @Inject constructor(
|
|||
room = room,
|
||||
eventId = eventId,
|
||||
initialMediaItem = mediaItem,
|
||||
onlyPinnedEvents = onlyPinnedEvents,
|
||||
),
|
||||
timelineMediaItemsFactory = timelineMediaItemsFactory,
|
||||
mediaItemsPostProcessor = mediaItemsPostProcessor,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ 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.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.timeline.Timeline
|
||||
import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems
|
||||
|
|
@ -44,7 +45,7 @@ class LiveMediaTimeline @Inject constructor(
|
|||
override suspend fun getTimeline(): Result<Timeline> = mutex.withLock {
|
||||
val currentTimeline = timeline
|
||||
if (currentTimeline == null) {
|
||||
room.mediaTimeline(null)
|
||||
room.createTimeline(CreateTimelineParams.MediaOnly)
|
||||
.onSuccess { timeline = it }
|
||||
} else {
|
||||
Result.success(currentTimeline)
|
||||
|
|
@ -58,14 +59,22 @@ class LiveMediaTimeline @Inject constructor(
|
|||
|
||||
/**
|
||||
* A class that will provide a media timeline that is focused on a particular event.
|
||||
* Optionally, the timeline will only contain the pinned events.
|
||||
*/
|
||||
class FocusedMediaTimeline(
|
||||
private val room: MatrixRoom,
|
||||
private val eventId: EventId,
|
||||
private val onlyPinnedEvents: Boolean,
|
||||
initialMediaItem: MediaItem.Event,
|
||||
) : MediaTimeline {
|
||||
override suspend fun getTimeline(): Result<Timeline> {
|
||||
return room.mediaTimeline(eventId)
|
||||
return room.createTimeline(
|
||||
createTimelineParams = if (onlyPinnedEvents) {
|
||||
CreateTimelineParams.PinnedOnly
|
||||
} else {
|
||||
CreateTimelineParams.MediaOnlyFocused(eventId)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override val cache = persistentListOf(
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import io.element.android.libraries.architecture.overlay.operation.show
|
|||
import io.element.android.libraries.di.RoomScope
|
||||
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.timeline.Timeline
|
||||
import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint
|
||||
import io.element.android.libraries.mediaviewer.api.MediaInfo
|
||||
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
|
||||
|
|
@ -96,9 +97,9 @@ class MediaGalleryRootNode @AssistedInject constructor(
|
|||
val mode = when (item) {
|
||||
is MediaItem.Audio,
|
||||
is MediaItem.Voice,
|
||||
is MediaItem.File -> MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios
|
||||
is MediaItem.File -> MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(Timeline.Mode.MEDIA)
|
||||
is MediaItem.Image,
|
||||
is MediaItem.Video -> MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos
|
||||
is MediaItem.Video -> MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(Timeline.Mode.MEDIA)
|
||||
}
|
||||
overlay.show(
|
||||
NavTarget.MediaViewer(
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@ class MediaViewerDataSource(
|
|||
|
||||
private val galleryMode = when (mode) {
|
||||
MediaViewerMode.SingleMedia,
|
||||
MediaViewerMode.TimelineImagesAndVideos -> MediaGalleryMode.Images
|
||||
MediaViewerMode.TimelineFilesAndAudios -> MediaGalleryMode.Files
|
||||
is MediaViewerMode.TimelineImagesAndVideos -> MediaGalleryMode.Images
|
||||
is MediaViewerMode.TimelineFilesAndAudios -> MediaGalleryMode.Files
|
||||
}
|
||||
|
||||
// Map of sourceUrl to local media state
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
|||
import io.element.android.libraries.di.RoomScope
|
||||
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.timeline.Timeline
|
||||
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
|
||||
import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory
|
||||
import io.element.android.libraries.mediaviewer.impl.datasource.FocusedTimelineMediaGalleryDataSourceFactory
|
||||
|
|
@ -69,16 +70,40 @@ class MediaViewerNode @AssistedInject constructor(
|
|||
// Should not happen
|
||||
timelineMediaGalleryDataSource
|
||||
} else {
|
||||
// Does timelineMediaGalleryDataSource knows the eventId?
|
||||
val lastData = timelineMediaGalleryDataSource.getLastData().dataOrNull()
|
||||
val isEventKnown = lastData?.hasEvent(eventId) == true
|
||||
if (isEventKnown) {
|
||||
timelineMediaGalleryDataSource
|
||||
} else {
|
||||
focusedTimelineMediaGalleryDataSourceFactory.createFor(
|
||||
eventId = eventId,
|
||||
mediaItem = inputs.toMediaItem(),
|
||||
)
|
||||
// Can we use a specific timeline?
|
||||
val timelineMode = when (val mode = inputs.mode) {
|
||||
is MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> mode.timelineMode
|
||||
is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> mode.timelineMode
|
||||
else -> null
|
||||
}
|
||||
when (timelineMode) {
|
||||
null -> timelineMediaGalleryDataSource
|
||||
Timeline.Mode.LIVE -> {
|
||||
// Even if the timelineMediaGalleryDataSource does not know the eventId, the SDK will create the timeline faster
|
||||
timelineMediaGalleryDataSource
|
||||
}
|
||||
Timeline.Mode.FOCUSED_ON_EVENT -> {
|
||||
// Does timelineMediaGalleryDataSource knows the eventId?
|
||||
val lastData = timelineMediaGalleryDataSource.getLastData().dataOrNull()
|
||||
val isEventKnown = lastData?.hasEvent(eventId) == true
|
||||
if (isEventKnown) {
|
||||
timelineMediaGalleryDataSource
|
||||
} else {
|
||||
focusedTimelineMediaGalleryDataSourceFactory.createFor(
|
||||
eventId = eventId,
|
||||
mediaItem = inputs.toMediaItem(),
|
||||
onlyPinnedEvents = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
Timeline.Mode.PINNED_EVENTS -> {
|
||||
focusedTimelineMediaGalleryDataSourceFactory.createFor(
|
||||
eventId = eventId,
|
||||
mediaItem = inputs.toMediaItem(),
|
||||
onlyPinnedEvents = true,
|
||||
)
|
||||
}
|
||||
Timeline.Mode.MEDIA -> timelineMediaGalleryDataSource
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,8 +204,8 @@ class MediaViewerPresenter @AssistedInject constructor(
|
|||
private fun showNoMoreItemsSnackbar() {
|
||||
val messageResId = when (inputs.mode) {
|
||||
MediaViewerEntryPoint.MediaViewerMode.SingleMedia,
|
||||
MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> R.string.screen_media_details_no_more_media_to_show
|
||||
MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> R.string.screen_media_details_no_more_files_to_show
|
||||
is MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> R.string.screen_media_details_no_more_media_to_show
|
||||
is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> R.string.screen_media_details_no_more_files_to_show
|
||||
}
|
||||
val message = SnackbarMessage(messageResId)
|
||||
snackbarDispatcher.post(message)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class DefaultFocusedTimelineMediaGalleryDataSourceFactoryTest {
|
|||
val result = sut.createFor(
|
||||
eventId = AN_EVENT_ID,
|
||||
mediaItem = aMediaItemImage(),
|
||||
onlyPinnedEvents = false,
|
||||
)
|
||||
assertThat(result).isInstanceOf(TimelineMediaGalleryDataSource::class.java)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
|
|
@ -75,11 +76,11 @@ class FocusedMediaTimelineTest {
|
|||
|
||||
@Test
|
||||
fun `getTimeline returns the timeline provided by the room`() = runTest {
|
||||
val mediaTimelineResult = lambdaRecorder<EventId?, Result<Timeline>> {
|
||||
val createTimelineResult = lambdaRecorder<CreateTimelineParams, Result<Timeline>> {
|
||||
Result.success(FakeTimeline())
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
mediaTimelineResult = mediaTimelineResult,
|
||||
createTimelineResult = createTimelineResult,
|
||||
)
|
||||
val sut = createFocusedMediaTimeline(
|
||||
room = room,
|
||||
|
|
@ -87,16 +88,36 @@ class FocusedMediaTimelineTest {
|
|||
)
|
||||
val timeline = sut.getTimeline()
|
||||
assertThat(timeline.isSuccess).isTrue()
|
||||
mediaTimelineResult.assertions().isCalledOnce().with(value(AN_EVENT_ID))
|
||||
createTimelineResult.assertions().isCalledOnce().with(value(CreateTimelineParams.MediaOnlyFocused(AN_EVENT_ID)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getTimeline returns the timeline provided by the room for pinned Events`() = runTest {
|
||||
val createTimelineResult = lambdaRecorder<CreateTimelineParams, Result<Timeline>> {
|
||||
Result.success(FakeTimeline())
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
createTimelineResult = createTimelineResult,
|
||||
)
|
||||
val sut = createFocusedMediaTimeline(
|
||||
room = room,
|
||||
eventId = AN_EVENT_ID,
|
||||
onlyPinnedEvent = true,
|
||||
)
|
||||
val timeline = sut.getTimeline()
|
||||
assertThat(timeline.isSuccess).isTrue()
|
||||
createTimelineResult.assertions().isCalledOnce().with(value(CreateTimelineParams.PinnedOnly))
|
||||
}
|
||||
|
||||
private fun createFocusedMediaTimeline(
|
||||
room: MatrixRoom = FakeMatrixRoom(),
|
||||
eventId: EventId = AN_EVENT_ID,
|
||||
initialMediaItem: MediaItem.Event = aMediaItemImage(),
|
||||
onlyPinnedEvent: Boolean = false,
|
||||
) = FocusedMediaTimeline(
|
||||
room = room,
|
||||
eventId = eventId,
|
||||
initialMediaItem = initialMediaItem,
|
||||
onlyPinnedEvents = onlyPinnedEvent,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
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.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
|
|
@ -28,22 +28,22 @@ class LiveMediaTimelineTest {
|
|||
|
||||
@Test
|
||||
fun `getTimeline returns the timeline provided by the room, then from cache`() = runTest {
|
||||
val mediaTimelineResult = lambdaRecorder<EventId?, Result<Timeline>> {
|
||||
val createTimelineResult = lambdaRecorder<CreateTimelineParams, Result<Timeline>> {
|
||||
Result.success(FakeTimeline())
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
mediaTimelineResult = mediaTimelineResult,
|
||||
createTimelineResult = createTimelineResult,
|
||||
)
|
||||
val sut = createLiveMediaTimeline(
|
||||
room = room,
|
||||
)
|
||||
val timeline = sut.getTimeline()
|
||||
assertThat(timeline.isSuccess).isTrue()
|
||||
mediaTimelineResult.assertions().isCalledOnce().with(value(null))
|
||||
createTimelineResult.assertions().isCalledOnce().with(value(CreateTimelineParams.MediaOnly))
|
||||
val timeline2 = sut.getTimeline()
|
||||
assertThat(timeline2.isSuccess).isTrue()
|
||||
// No called another time
|
||||
mediaTimelineResult.assertions().isCalledOnce()
|
||||
createTimelineResult.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
private fun createLiveMediaTimeline(
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
val fakeTimeline = FakeTimeline()
|
||||
val sut = createTimelineMediaGalleryDataSource(
|
||||
room = FakeMatrixRoom(
|
||||
mediaTimelineResult = { Result.success(fakeTimeline) },
|
||||
createTimelineResult = { Result.success(fakeTimeline) },
|
||||
roomCoroutineScope = backgroundScope,
|
||||
)
|
||||
)
|
||||
|
|
@ -75,7 +75,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
runTest {
|
||||
val sut = createTimelineMediaGalleryDataSource(
|
||||
room = FakeMatrixRoom(
|
||||
mediaTimelineResult = { Result.success(fakeTimeline) },
|
||||
createTimelineResult = { Result.success(fakeTimeline) },
|
||||
roomCoroutineScope = backgroundScope,
|
||||
)
|
||||
)
|
||||
|
|
@ -112,7 +112,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
}
|
||||
val sut = createTimelineMediaGalleryDataSource(
|
||||
room = FakeMatrixRoom(
|
||||
mediaTimelineResult = { Result.success(fakeTimeline) },
|
||||
createTimelineResult = { Result.success(fakeTimeline) },
|
||||
roomCoroutineScope = backgroundScope,
|
||||
)
|
||||
)
|
||||
|
|
@ -135,7 +135,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
}
|
||||
val sut = createTimelineMediaGalleryDataSource(
|
||||
room = FakeMatrixRoom(
|
||||
mediaTimelineResult = { Result.success(fakeTimeline) },
|
||||
createTimelineResult = { Result.success(fakeTimeline) },
|
||||
roomCoroutineScope = backgroundScope,
|
||||
)
|
||||
)
|
||||
|
|
@ -154,7 +154,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
fun `test - failing to load timeline should emit an error`() = runTest {
|
||||
val sut = createTimelineMediaGalleryDataSource(
|
||||
room = FakeMatrixRoom(
|
||||
mediaTimelineResult = { Result.failure(AN_EXCEPTION) },
|
||||
createTimelineResult = { Result.failure(AN_EXCEPTION) },
|
||||
roomCoroutineScope = backgroundScope,
|
||||
)
|
||||
)
|
||||
|
|
@ -176,7 +176,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
)
|
||||
val sut = createTimelineMediaGalleryDataSource(
|
||||
room = FakeMatrixRoom(
|
||||
mediaTimelineResult = { Result.success(fakeTimeline) },
|
||||
createTimelineResult = { Result.success(fakeTimeline) },
|
||||
roomCoroutineScope = backgroundScope,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class MediaGalleryPresenterTest {
|
|||
),
|
||||
room = FakeMatrixRoom(
|
||||
displayName = A_ROOM_NAME,
|
||||
mediaTimelineResult = { Result.success(FakeTimeline()) },
|
||||
createTimelineResult = { Result.success(FakeTimeline()) },
|
||||
)
|
||||
)
|
||||
presenter.test {
|
||||
|
|
@ -71,7 +71,7 @@ class MediaGalleryPresenterTest {
|
|||
val presenter = createMediaGalleryPresenter(
|
||||
room = FakeMatrixRoom(
|
||||
displayName = A_ROOM_NAME,
|
||||
mediaTimelineResult = { Result.success(FakeTimeline()) },
|
||||
createTimelineResult = { Result.success(FakeTimeline()) },
|
||||
)
|
||||
)
|
||||
presenter.test {
|
||||
|
|
@ -101,7 +101,7 @@ class MediaGalleryPresenterTest {
|
|||
room = FakeMatrixRoom(
|
||||
sessionId = A_USER_ID,
|
||||
displayName = A_ROOM_NAME,
|
||||
mediaTimelineResult = { Result.success(FakeTimeline()) },
|
||||
createTimelineResult = { Result.success(FakeTimeline()) },
|
||||
canRedactOwnResult = { Result.success(canDeleteOwn) }
|
||||
)
|
||||
)
|
||||
|
|
@ -144,7 +144,7 @@ class MediaGalleryPresenterTest {
|
|||
room = FakeMatrixRoom(
|
||||
sessionId = A_USER_ID,
|
||||
displayName = A_ROOM_NAME,
|
||||
mediaTimelineResult = { Result.success(FakeTimeline()) },
|
||||
createTimelineResult = { Result.success(FakeTimeline()) },
|
||||
canRedactOtherResult = { Result.success(canDeleteOther) }
|
||||
)
|
||||
)
|
||||
|
|
@ -177,7 +177,7 @@ class MediaGalleryPresenterTest {
|
|||
val presenter = createMediaGalleryPresenter(
|
||||
room = FakeMatrixRoom(
|
||||
displayName = A_ROOM_NAME,
|
||||
mediaTimelineResult = { Result.success(FakeTimeline()) },
|
||||
createTimelineResult = { Result.success(FakeTimeline()) },
|
||||
)
|
||||
)
|
||||
presenter.test {
|
||||
|
|
@ -244,7 +244,7 @@ class MediaGalleryPresenterTest {
|
|||
)
|
||||
val presenter = createMediaGalleryPresenter(
|
||||
room = FakeMatrixRoom(
|
||||
mediaTimelineResult = { Result.success(FakeTimeline()) },
|
||||
createTimelineResult = { Result.success(FakeTimeline()) },
|
||||
),
|
||||
navigator = navigator,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class MediaViewerDataSourceTest {
|
|||
fun `test dataFlow with data galleryMode image`() = runTest {
|
||||
val galleryDataSource = FakeMediaGalleryDataSource()
|
||||
val sut = createMediaViewerDataSource(
|
||||
mode = MediaViewerMode.TimelineImagesAndVideos,
|
||||
mode = MediaViewerMode.TimelineImagesAndVideos(timelineMode = Timeline.Mode.MEDIA),
|
||||
galleryDataSource = galleryDataSource,
|
||||
)
|
||||
sut.dataFlow().test {
|
||||
|
|
@ -159,7 +159,7 @@ class MediaViewerDataSourceTest {
|
|||
fun `test dataFlow with data galleryMode files`() = runTest {
|
||||
val galleryDataSource = FakeMediaGalleryDataSource()
|
||||
val sut = createMediaViewerDataSource(
|
||||
mode = MediaViewerMode.TimelineFilesAndAudios,
|
||||
mode = MediaViewerMode.TimelineFilesAndAudios(timelineMode = Timeline.Mode.MEDIA),
|
||||
galleryDataSource = galleryDataSource,
|
||||
)
|
||||
sut.dataFlow().test {
|
||||
|
|
@ -265,7 +265,7 @@ class MediaViewerDataSourceTest {
|
|||
}
|
||||
|
||||
private fun TestScope.createMediaViewerDataSource(
|
||||
mode: MediaViewerMode = MediaViewerMode.TimelineImagesAndVideos,
|
||||
mode: MediaViewerMode = MediaViewerMode.TimelineImagesAndVideos(timelineMode = Timeline.Mode.MEDIA),
|
||||
galleryDataSource: MediaGalleryDataSource = FakeMediaGalleryDataSource(),
|
||||
mediaLoader: MatrixMediaLoader = FakeMatrixMediaLoader(),
|
||||
localMediaFactory: LocalMediaFactory = FakeLocalMediaFactory(mockMediaUrl),
|
||||
|
|
|
|||
|
|
@ -519,7 +519,7 @@ class MediaViewerPresenterTest {
|
|||
@Test
|
||||
fun `present - snackbar displayed when there is no more items forward images and videos`() {
|
||||
`present - snackbar displayed when there is no more items forward`(
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode = Timeline.Mode.MEDIA),
|
||||
expectedSnackbarResId = R.string.screen_media_details_no_more_media_to_show,
|
||||
)
|
||||
}
|
||||
|
|
@ -527,7 +527,7 @@ class MediaViewerPresenterTest {
|
|||
@Test
|
||||
fun `present - snackbar displayed when there is no more items forward files and audio`() {
|
||||
`present - snackbar displayed when there is no more items forward`(
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios,
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(timelineMode = Timeline.Mode.MEDIA),
|
||||
expectedSnackbarResId = R.string.screen_media_details_no_more_files_to_show,
|
||||
)
|
||||
}
|
||||
|
|
@ -547,7 +547,7 @@ class MediaViewerPresenterTest {
|
|||
awaitFirstItem()
|
||||
mediaGalleryDataSource.emitGroupedMediaItems(
|
||||
AsyncData.Success(
|
||||
if (mode == MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||
GroupedMediaItems(
|
||||
imageAndVideoItems = persistentListOf(),
|
||||
fileItems = persistentListOf(aForwardLoadingIndicator, anImage, aBackwardLoadingIndicator),
|
||||
|
|
@ -568,7 +568,7 @@ class MediaViewerPresenterTest {
|
|||
// data source claims that there is no more items to load forward
|
||||
mediaGalleryDataSource.emitGroupedMediaItems(
|
||||
AsyncData.Success(
|
||||
if (mode == MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||
GroupedMediaItems(
|
||||
imageAndVideoItems = persistentListOf(),
|
||||
fileItems = persistentListOf(anImage, aBackwardLoadingIndicator),
|
||||
|
|
@ -590,7 +590,7 @@ class MediaViewerPresenterTest {
|
|||
@Test
|
||||
fun `present - snackbar displayed when there is no more items backward images and videos`() {
|
||||
`present - snackbar displayed when there is no more items backward`(
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode = Timeline.Mode.MEDIA),
|
||||
expectedSnackbarResId = R.string.screen_media_details_no_more_media_to_show,
|
||||
)
|
||||
}
|
||||
|
|
@ -598,7 +598,7 @@ class MediaViewerPresenterTest {
|
|||
@Test
|
||||
fun `present - snackbar displayed when there is no more items backward files and audio`() {
|
||||
`present - snackbar displayed when there is no more items backward`(
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios,
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(timelineMode = Timeline.Mode.MEDIA),
|
||||
expectedSnackbarResId = R.string.screen_media_details_no_more_files_to_show,
|
||||
)
|
||||
}
|
||||
|
|
@ -618,7 +618,7 @@ class MediaViewerPresenterTest {
|
|||
awaitFirstItem()
|
||||
mediaGalleryDataSource.emitGroupedMediaItems(
|
||||
AsyncData.Success(
|
||||
if (mode == MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||
GroupedMediaItems(
|
||||
imageAndVideoItems = persistentListOf(),
|
||||
fileItems = persistentListOf(aForwardLoadingIndicator, anImage, aBackwardLoadingIndicator),
|
||||
|
|
@ -640,7 +640,7 @@ class MediaViewerPresenterTest {
|
|||
// data source claims that there is no more items to load backward
|
||||
mediaGalleryDataSource.emitGroupedMediaItems(
|
||||
AsyncData.Success(
|
||||
if (mode == MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||
GroupedMediaItems(
|
||||
imageAndVideoItems = persistentListOf(),
|
||||
fileItems = persistentListOf(aForwardLoadingIndicator, anImage),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue