MediaViewer: add error case in the UI.

This commit is contained in:
Benoit Marty 2025-01-22 11:15:53 +01:00 committed by Benoit Marty
parent b0bfea9cc4
commit 2267a4b787
5 changed files with 71 additions and 10 deletions

View file

@ -28,6 +28,7 @@ import io.element.android.libraries.mediaviewer.impl.gallery.mediaInfo
import io.element.android.libraries.mediaviewer.impl.gallery.mediaSource import io.element.android.libraries.mediaviewer.impl.gallery.mediaSource
import io.element.android.libraries.mediaviewer.impl.gallery.thumbnailSource import io.element.android.libraries.mediaviewer.impl.gallery.thumbnailSource
import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -68,9 +69,15 @@ class MediaViewerDataSource(
fun dataFlow(): Flow<PersistentList<MediaViewerPageData>> { fun dataFlow(): Flow<PersistentList<MediaViewerPageData>> {
return galleryDataSource.groupedMediaItemsFlow() return galleryDataSource.groupedMediaItemsFlow()
.map { groupedItems -> .map { groupedItems ->
val mediaItems = groupedItems.dataOrNull()?.getItems(galleryMode).orEmpty() if (groupedItems is AsyncData.Failure) {
withContext(dispatcher) { persistentListOf(
buildMediaViewerPageList(mediaItems) MediaViewerPageData.Failure(groupedItems.error),
)
} else {
val mediaItems = groupedItems.dataOrNull()?.getItems(galleryMode).orEmpty()
withContext(dispatcher) {
buildMediaViewerPageList(mediaItems)
}
} }
} }
} }

View file

@ -28,6 +28,10 @@ data class MediaViewerState(
) )
sealed interface MediaViewerPageData { sealed interface MediaViewerPageData {
data class Failure(
val throwable: Throwable,
) : MediaViewerPageData
data class Loading( data class Loading(
val direction: Timeline.PaginationDirection, val direction: Timeline.PaginationDirection,
) : MediaViewerPageData ) : MediaViewerPageData

View file

@ -154,6 +154,11 @@ open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState>
MediaViewerPageData.Loading(Timeline.PaginationDirection.BACKWARDS) MediaViewerPageData.Loading(Timeline.PaginationDirection.BACKWARDS)
), ),
), ),
aMediaViewerState(
listOf(
MediaViewerPageData.Failure(Exception("error"))
),
),
) )
} }

View file

@ -55,6 +55,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo
import io.element.android.libraries.designsystem.components.async.AsyncFailure
import io.element.android.libraries.designsystem.components.async.AsyncLoading import io.element.android.libraries.designsystem.components.async.AsyncLoading
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
@ -123,6 +124,14 @@ fun MediaViewerView(
beyondViewportPageCount = 1, beyondViewportPageCount = 1,
) { page -> ) { page ->
when (val dataForPage = state.listData[page]) { when (val dataForPage = state.listData[page]) {
is MediaViewerPageData.Failure -> {
MediaViewerErrorPage(
throwable = dataForPage.throwable,
onDismiss = {
onBackClick()
},
)
}
is MediaViewerPageData.Loading -> { is MediaViewerPageData.Loading -> {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
state.eventSink(MediaViewerEvents.LoadMore(dataForPage.direction)) state.eventSink(MediaViewerEvents.LoadMore(dataForPage.direction))
@ -200,11 +209,13 @@ fun MediaViewerView(
else -> { else -> {
TopAppBar( TopAppBar(
title = { title = {
Text( if (currentData is MediaViewerPageData.Loading) {
text = stringResource(id = CommonStrings.common_loading_more), Text(
style = ElementTheme.typography.fontBodyMdMedium, text = stringResource(id = CommonStrings.common_loading_more),
color = ElementTheme.colors.textPrimary, style = ElementTheme.typography.fontBodyMdMedium,
) color = ElementTheme.colors.textPrimary,
)
}
}, },
colors = TopAppBarDefaults.topAppBarColors( colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent.copy(0.6f), containerColor = Color.Transparent.copy(0.6f),
@ -386,6 +397,41 @@ private fun MediaViewerLoadingPage(
} }
} }
@Composable
private fun MediaViewerErrorPage(
throwable: Throwable,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
) {
val flickState = rememberFlickToDismissState(dismissThresholdRatio = 0.1f, rotateOnDrag = false)
DismissFlickEffects(
flickState = flickState,
onDismissing = { animationDuration ->
delay(animationDuration / 3)
onDismiss()
},
onDragging = {},
)
FlickToDismiss(
state = flickState,
modifier = modifier.background(backgroundColorFor(flickState))
) {
Box(
modifier = Modifier
.fillMaxSize()
.navigationBarsPadding(),
contentAlignment = Alignment.Center
) {
AsyncFailure(
throwable = throwable,
onRetry = null
)
}
}
}
@Composable @Composable
private fun DismissFlickEffects( private fun DismissFlickEffects(
flickState: FlickToDismissState, flickState: FlickToDismissState,

View file

@ -69,8 +69,7 @@ class MediaViewerDataSourceTest {
galleryDataSource.emitGroupedMediaItems(AsyncData.Loading()) galleryDataSource.emitGroupedMediaItems(AsyncData.Loading())
assertThat(awaitItem().first()).isInstanceOf(MediaViewerPageData.Loading::class.java) assertThat(awaitItem().first()).isInstanceOf(MediaViewerPageData.Loading::class.java)
galleryDataSource.emitGroupedMediaItems(AsyncData.Failure(AN_EXCEPTION)) galleryDataSource.emitGroupedMediaItems(AsyncData.Failure(AN_EXCEPTION))
// TODO Add an error screen in the ui assertThat(awaitItem().first()).isEqualTo(MediaViewerPageData.Failure(AN_EXCEPTION))
assertThat(awaitItem().first()).isInstanceOf(MediaViewerPageData.Loading::class.java)
} }
} }