Merge pull request #7362 from hayaksi1/fix/4973-media-gallery-error

Keep the opened image on screen when the media gallery fails to load
This commit is contained in:
Benoit Marty
2026-08-11 14:15:01 +02:00
committed by GitHub
5 changed files with 40 additions and 12 deletions
@@ -83,7 +83,9 @@ class TimelineMediaGalleryDataSource(
},
{
Timber.e(it, "Failed to get media timeline for room ${room.roomId}")
groupedMediaItemsFlow.emit(AsyncData.Failure(it))
if (cache == null) {
groupedMediaItemsFlow.emit(AsyncData.Failure(it))
}
},
)
}.flatMapLatest { timeline ->
@@ -82,7 +82,6 @@ import io.element.android.libraries.audio.api.AudioFocus
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.async.AsyncFailure
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
import io.element.android.libraries.designsystem.components.async.AsyncLoading
import io.element.android.libraries.designsystem.components.button.BackButton
@@ -209,7 +208,6 @@ fun MediaViewerView(
when (val dataForPage = state.listData[page]) {
is MediaViewerPageData.Failure -> {
MediaViewerErrorPage(
throwable = dataForPage.throwable,
onDismiss = onBackClick,
)
}
@@ -507,7 +505,6 @@ private fun MediaViewerLoadingPage(
@Composable
private fun MediaViewerErrorPage(
throwable: Throwable,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
) {
@@ -521,9 +518,10 @@ private fun MediaViewerErrorPage(
.navigationBarsPadding(),
contentAlignment = Alignment.Center
) {
AsyncFailure(
throwable = throwable,
onRetry = null
IconTitleSubtitleMolecule(
iconStyle = BigIcon.Style.AlertSolid,
title = stringResource(CommonStrings.common_something_went_wrong),
subTitle = stringResource(CommonStrings.common_something_went_wrong_message),
)
}
}
@@ -39,6 +39,7 @@ import io.element.android.libraries.matrix.ui.media.contentvalidation.NoopEventC
import io.element.android.libraries.mediaviewer.api.MediaInfo
import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
import io.element.android.libraries.mediaviewer.impl.model.aMediaItemImage
import io.element.android.libraries.mediaviewer.test.util.FileExtensionExtractorWithoutValidation
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder
@@ -171,6 +172,32 @@ class TimelineMediaGalleryDataSourceTest {
}
}
@Test
fun `test - failing to load a focused timeline keeps the item that was opened`() = runTest {
val room = FakeJoinedRoom(
createTimelineResult = { Result.failure(AN_EXCEPTION) },
roomCoroutineScope = backgroundScope,
)
val initialMediaItem = aMediaItemImage()
val sut = createTimelineMediaGalleryDataSource(
room = room,
mediaTimeline = FocusedMediaTimeline(
room = room,
eventId = AN_EVENT_ID,
onlyPinnedEvents = false,
initialMediaItem = initialMediaItem,
),
)
sut.start(backgroundScope)
sut.groupedMediaItemsFlow().test {
val item = awaitItem()
assertThat(item.isSuccess()).isTrue()
assertThat(item.dataOrNull()?.imageAndVideoItems).contains(initialMediaItem)
expectNoEvents()
}
assertThat(sut.getLastData().isFailure()).isFalse()
}
@Test
fun `test - when timeline emits new data, the flow emits the data`() = runTest {
val timelineItems = MutableStateFlow<List<MatrixTimelineItem>>(emptyList())
@@ -269,10 +296,11 @@ internal fun TestScope.createTimelineMediaGalleryDataSource(
room: JoinedRoom = FakeJoinedRoom(
liveTimeline = FakeTimeline(),
),
mediaTimeline: MediaTimeline? = null,
): TimelineMediaGalleryDataSource {
return TimelineMediaGalleryDataSource(
room = room,
mediaTimeline = LiveMediaTimeline(room),
mediaTimeline = mediaTimeline ?: LiveMediaTimeline(room),
timelineMediaItemsFactory = createTimelineMediaItemsFactory(),
mediaItemsPostProcessor = MediaItemsPostProcessor(),
)