From baf28f85cf176ac823a99955a4ef512c4729f1c2 Mon Sep 17 00:00:00 2001 From: hayaksi1 <193020925+hayaksi1@users.noreply.github.com> Date: Sun, 9 Aug 2026 13:52:55 +0300 Subject: [PATCH 1/2] Keep the opened image on screen when the media gallery fails to load Opening an image from the timeline replaces it with a full-screen page reading "error". Two things go wrong. The viewer already holds the item that was tapped, but building the surrounding media timeline can fail and the failure overwrites that cached item, so the picture the user asked for disappears. And the failure page renders the exception message straight from the SDK, which is developer text with nothing actionable in it. Emit the failure only when there is nothing cached, so a viewer opened on a specific event keeps showing that event and stays usable while the rest of the gallery is unavailable. Where there really is nothing to show, the page now uses the same alert icon, title and message as the other error states in this screen, built from strings that already exist. The trade-off is that the timeline error is no longer surfaced in the cache case; it is still logged. Swapping it for a snackbar would need a new surface in the viewer and is left out. --- .../impl/datasource/MediaGalleryDataSource.kt | 4 ++- .../impl/viewer/MediaViewerView.kt | 10 +++---- .../TimelineMediaGalleryDataSourceTest.kt | 30 ++++++++++++++++++- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt index 7bdd4872a3..a123fd2f97 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt @@ -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 -> diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt index 6eca5e9dbc..a0b5efab10 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt @@ -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, ) } @@ -506,7 +504,6 @@ private fun MediaViewerLoadingPage( @Composable private fun MediaViewerErrorPage( - throwable: Throwable, onDismiss: () -> Unit, modifier: Modifier = Modifier, ) { @@ -520,9 +517,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), ) } } diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/TimelineMediaGalleryDataSourceTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/TimelineMediaGalleryDataSourceTest.kt index 81bb23d73c..a18bce58c4 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/TimelineMediaGalleryDataSourceTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/TimelineMediaGalleryDataSourceTest.kt @@ -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>(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(), ) From 813122eebd090f4bc1e818b6cd8a44fb209c63a8 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Sun, 9 Aug 2026 11:48:53 +0000 Subject: [PATCH 2/2] Update screenshots --- ...mediaviewer.impl.viewer_MediaViewerViewLandscape_15_en.png | 4 ++-- ...ibraries.mediaviewer.impl.viewer_MediaViewerView_15_en.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerViewLandscape_15_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerViewLandscape_15_en.png index 4e8d03c6a4..7f50bcff2c 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerViewLandscape_15_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerViewLandscape_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1e56f491ccc4c9ab82dde36a8fca588bdf037fb59b70ea47ffe13a56e22f801 -size 4457 +oid sha256:a476bcf958b54c9cf50636d6037a55750e057db6ed8204d0e807b3535e7c2019 +size 16864 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_15_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_15_en.png index 427ecec595..34740cef8c 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_15_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.viewer_MediaViewerView_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ecbe2b67d8fa3831dce12216545a28fe44d9a22953da309903d93564268f362 -size 5051 +oid sha256:bac3672b83c2e871063a074796266866ea9b5401e60dc51fb67f16d8cb3b3325 +size 17394