mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
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.
This commit is contained in:
+3
-1
@@ -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 ->
|
||||
|
||||
+4
-6
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+29
-1
@@ -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(),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user