From b1b4bf37e231599dd953d968dbf23d62be1a6a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 8 Oct 2025 13:21:06 +0200 Subject: [PATCH 1/5] Modify `AsyncFailure` so it takes `String?` instead of a `Throwable?` This way, we can customise the error to display (`throwable.message`, `throwable.localizeMessage`, or any other error message we want). --- .../libraries/designsystem/components/async/AsyncFailure.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt index b2050a6d7e8..ac86a2ce473 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt @@ -25,7 +25,7 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun AsyncFailure( - throwable: Throwable, + message: String?, onRetry: (() -> Unit)?, modifier: Modifier = Modifier, ) { @@ -35,7 +35,7 @@ fun AsyncFailure( .padding(vertical = 32.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - Text(text = throwable.message ?: stringResource(id = CommonStrings.error_unknown)) + Text(text = message ?: stringResource(id = CommonStrings.error_unknown)) if (onRetry != null) { Spacer(modifier = Modifier.height(24.dp)) Button( @@ -50,7 +50,7 @@ fun AsyncFailure( @Composable internal fun AsyncFailurePreview() = ElementPreview { AsyncFailure( - throwable = IllegalStateException("An error occurred"), + message = "An error occurred", onRetry = {} ) } From f0b976625551944e94a619435abbc0e551fbe74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 8 Oct 2025 13:22:19 +0200 Subject: [PATCH 2/5] Adapt the error components in `MediaViewerView` and `MediaGalleryView` to this to display a generic error instead of the technical one coming from Rust --- .../mediaviewer/impl/gallery/MediaGalleryView.kt | 8 +++----- .../libraries/mediaviewer/impl/viewer/MediaViewerView.kt | 3 +-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index a48ff93d714..810d074c394 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -230,9 +230,7 @@ private fun MediaGalleryPage( } } is AsyncData.Failure -> { - ErrorContent( - error = groupedMediaItems.error, - ) + ErrorContent() } else -> Unit } @@ -454,9 +452,9 @@ private fun LoadingMoreIndicator( } @Composable -private fun ErrorContent(error: Throwable) { +private fun ErrorContent() { AsyncFailure( - throwable = error, + message = null, onRetry = null, modifier = Modifier.fillMaxSize(), ) 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 110054eb20b..50ca0c75b56 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 @@ -127,7 +127,6 @@ fun MediaViewerView( when (val dataForPage = state.listData[page]) { is MediaViewerPageData.Failure -> { MediaViewerErrorPage( - throwable = dataForPage.throwable, onDismiss = onBackClick, ) } @@ -394,7 +393,6 @@ private fun MediaViewerLoadingPage( @Composable private fun MediaViewerErrorPage( - throwable: Throwable, onDismiss: () -> Unit, modifier: Modifier = Modifier, ) { @@ -411,6 +409,7 @@ private fun MediaViewerErrorPage( AsyncFailure( throwable = throwable, onRetry = null + message = null, ) } } From e891d96fa703cf116d340a74fb40d2b0a90c86bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 8 Oct 2025 13:22:43 +0200 Subject: [PATCH 3/5] Adapt other components using `AsyncFailure` too --- .../android/features/invitepeople/impl/InvitePeopleView.kt | 2 +- .../element/android/features/space/impl/leave/LeaveSpaceView.kt | 2 +- .../android/features/viewfolder/impl/file/ViewFileView.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt index 5363ca43f1c..bc45a3740ef 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt @@ -65,7 +65,7 @@ private fun InvitePeopleViewError( contentAlignment = Alignment.Center, ) { AsyncFailure( - throwable = error, + message = error.localizedMessage, onRetry = null, modifier = Modifier.padding(horizontal = 16.dp), ) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt index 9c8f13405a0..4a1490c5a01 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt @@ -110,7 +110,7 @@ fun LeaveSpaceView( } is AsyncData.Failure -> item { AsyncFailure( - throwable = state.selectableSpaceRooms.error, + message = state.selectableSpaceRooms.error.localizedMessage, onRetry = { state.eventSink(LeaveSpaceEvents.Retry) }, diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt index 4f3b84734fa..0c086cffffc 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt @@ -82,7 +82,7 @@ fun ViewFileView( lines = state.lines.data.toImmutableList(), colorationMode = state.colorationMode, ) - is AsyncData.Failure -> AsyncFailure(throwable = state.lines.error, onRetry = null) + is AsyncData.Failure -> AsyncFailure(message = state.lines.error.localizedMessage, onRetry = null) } } } From 87fc1b175821b9fc7e3540e7b8084936ef4d6d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 8 Oct 2025 13:23:37 +0200 Subject: [PATCH 4/5] Allow retry action in both the gallery and media viewer when the media timeline initialization fails --- .../mediaviewer/impl/datasource/MediaGalleryDataSource.kt | 4 ++++ .../mediaviewer/impl/gallery/MediaGalleryEvents.kt | 2 ++ .../mediaviewer/impl/gallery/MediaGalleryPresenter.kt | 3 +++ .../mediaviewer/impl/gallery/MediaGalleryView.kt | 8 +++++--- .../mediaviewer/impl/viewer/MediaViewerEvents.kt | 1 + .../mediaviewer/impl/viewer/MediaViewerPresenter.kt | 3 +++ .../libraries/mediaviewer/impl/viewer/MediaViewerView.kt | 5 +++-- 7 files changed, 21 insertions(+), 5 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 f40bb08a862..b2610be208f 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 @@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach +import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean interface MediaGalleryDataSource { @@ -76,6 +77,9 @@ class TimelineMediaGalleryDataSource( emit(it) }, { + // We couldn't load the timeline, so we reset the started state to allow retrying + Timber.e(it, "Failed to get timeline for media gallery") + isStarted.set(false) groupedMediaItemsFlow.emit(AsyncData.Failure(it)) }, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt index df7d82c7b29..d75406a1c28 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt @@ -29,4 +29,6 @@ sealed interface MediaGalleryEvents { data object CloseBottomSheet : MediaGalleryEvents data class Delete(val eventId: EventId) : MediaGalleryEvents + + data object ReloadTimeline : MediaGalleryEvents } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index ba3d4d5e1f8..99dc24abf35 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -87,6 +87,9 @@ class MediaGalleryPresenter( is MediaGalleryEvents.ChangeMode -> { mode = event.mode } + is MediaGalleryEvents.ReloadTimeline -> { + mediaGalleryDataSource.start() + } is MediaGalleryEvents.LoadMore -> coroutineScope.launch { mediaGalleryDataSource.loadMore(event.direction) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt index 810d074c394..72a0d720e4c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryView.kt @@ -230,7 +230,9 @@ private fun MediaGalleryPage( } } is AsyncData.Failure -> { - ErrorContent() + ErrorContent( + onRetry = { state.eventSink(MediaGalleryEvents.ReloadTimeline) }, + ) } else -> Unit } @@ -452,10 +454,10 @@ private fun LoadingMoreIndicator( } @Composable -private fun ErrorContent() { +private fun ErrorContent(onRetry: () -> Unit) { AsyncFailure( message = null, - onRetry = null, + onRetry = onRetry, modifier = Modifier.fillMaxSize(), ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt index 708c423d363..d6d0053926d 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt @@ -16,6 +16,7 @@ sealed interface MediaViewerEvents { data class Share(val data: MediaViewerPageData.MediaViewerData) : MediaViewerEvents data class OpenWith(val data: MediaViewerPageData.MediaViewerData) : MediaViewerEvents data class ClearLoadingError(val data: MediaViewerPageData.MediaViewerData) : MediaViewerEvents + data object ReloadTimeline : MediaViewerEvents data class ViewInTimeline(val eventId: EventId) : MediaViewerEvents data class OpenInfo(val data: MediaViewerPageData.MediaViewerData) : MediaViewerEvents data class ConfirmDelete( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt index 6f59bc3ff7f..dccdef32ea5 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt @@ -97,6 +97,9 @@ class MediaViewerPresenter( is MediaViewerEvents.ClearLoadingError -> { dataSource.clearLoadingError(event.data) } + is MediaViewerEvents.ReloadTimeline -> { + dataSource.setup() + } is MediaViewerEvents.SaveOnDisk -> { mediaBottomSheetState = MediaBottomSheetState.Hidden coroutineScope.saveOnDisk(event.data.downloadedMedia.value) 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 50ca0c75b56..285d59eef31 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 @@ -128,6 +128,7 @@ fun MediaViewerView( is MediaViewerPageData.Failure -> { MediaViewerErrorPage( onDismiss = onBackClick, + onRetry = { state.eventSink(MediaViewerEvents.ReloadTimeline) }, ) } is MediaViewerPageData.Loading -> { @@ -394,6 +395,7 @@ private fun MediaViewerLoadingPage( @Composable private fun MediaViewerErrorPage( onDismiss: () -> Unit, + onRetry: () -> Unit, modifier: Modifier = Modifier, ) { MediaViewerFlickToDismiss( @@ -407,9 +409,8 @@ private fun MediaViewerErrorPage( contentAlignment = Alignment.Center ) { AsyncFailure( - throwable = throwable, - onRetry = null message = null, + onRetry = onRetry, ) } } From 6ed35b01aff0b3ac2807284a9378c5cdd5544dde Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 8 Oct 2025 12:05:36 +0000 Subject: [PATCH 5/5] Update screenshots --- ...es.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png | 4 ++-- ...ies.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png | 4 ++-- ....mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png | 4 ++-- ...s.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png | 4 ++-- ...ibraries.mediaviewer.impl.viewer_MediaViewerView_15_en.png | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png index 4cd81a7d433..648be418774 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d73197d295f9181e6f297ecd5da016775a7fd669016528527af5d37f52f1a2e1 -size 14572 +oid sha256:8eb73d17c106db2a650335dc70928603bf810637417a52edb7bd01e9199880b5 +size 17973 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png index f7b9912cd19..c2e33c68b04 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1645075b7bed6fe2fd712996da3860b8db43915f848a53abb8fec2545ecf158 -size 14576 +oid sha256:e538dd4c7235fd79fe9627aa008067126787ed391dbe6f2361372307ad6d708f +size 17983 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png index 7953623173f..dc47ba9bcaf 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:624d1a64dff25fddae712e14ec2f7c9a1cdbc89fbbf9e9938e4eeaee30d588d7 -size 14048 +oid sha256:a2c166860f7d443455adf73ab342693983a2310421b2611a5c748fc8a5357711 +size 17355 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png index 5caea13b501..514cd629a1c 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:176f723c4a2dd25faf869d3a13cd68ab1bf017367b4a74ab100651693c7fac64 -size 14140 +oid sha256:763add4e3c82f9fc81aea7e32da36ccd15951278e9e94f385b2f549579ce4696 +size 17439 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 ef898feace2..8d88179db94 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:58634665793af557117c70203ed96690f6e9291ee1b34b5b803efa707a961080 -size 5085 +oid sha256:50a66633bc960604490a9e9bfcd4f041b319a6f27169ddf489d8e93c7bfc5487 +size 11005