From 2ee96f12ff38f30a06695eeb7acadc7d0397ec1d Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Sun, 7 Apr 2024 18:39:05 +0200 Subject: [PATCH 1/2] Fall back to initials avatar when image avatar doesn't work Initially I had it implemented such that it would only fallback on error, but actually it's nice to have also while loading if it takes a while to load the avatar. --- changelog.d/2667.bugfix | 1 + .../designsystem/components/avatar/Avatar.kt | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 changelog.d/2667.bugfix diff --git a/changelog.d/2667.bugfix b/changelog.d/2667.bugfix new file mode 100644 index 0000000000..f72b79503f --- /dev/null +++ b/changelog.d/2667.bugfix @@ -0,0 +1 @@ +Fall back to name-based generated avatars when image avatars don't load. diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt index b1e7bc6be0..5a63eaa7ec 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -32,12 +33,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import coil.compose.AsyncImage +import coil.compose.AsyncImagePainter +import coil.compose.SubcomposeAsyncImage +import coil.compose.SubcomposeAsyncImageContent import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.colors.AvatarColorsProvider import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup -import io.element.android.libraries.designsystem.preview.debugPlaceholderAvatar import io.element.android.libraries.designsystem.text.toSp import io.element.android.libraries.designsystem.theme.components.Text import timber.log.Timber @@ -71,16 +73,23 @@ private fun ImageAvatar( modifier: Modifier = Modifier, contentDescription: String? = null, ) { - AsyncImage( + SubcomposeAsyncImage( model = avatarData, - onError = { - Timber.e(it.result.throwable, "Error loading avatar $it\n${it.result}") - }, contentDescription = contentDescription, contentScale = ContentScale.Crop, - placeholder = debugPlaceholderAvatar(), modifier = modifier - ) + ) { + when (val state = painter.state) { + is AsyncImagePainter.State.Success -> SubcomposeAsyncImageContent() + is AsyncImagePainter.State.Error -> { + SideEffect { + Timber.e(state.result.throwable, "Error loading avatar $state\n${state.result}") + } + InitialsAvatar(avatarData = avatarData) + } + else -> InitialsAvatar(avatarData = avatarData) + } + } } @Composable From 218fc8c89ca79ca53084a09bba3d1fd7f0b5fd84 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Mon, 8 Apr 2024 09:59:02 +0200 Subject: [PATCH 2/2] Add back debugPlaceholderAvatar for inspection mode --- .../designsystem/components/avatar/Avatar.kt | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt index 5a63eaa7ec..173716aee3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt @@ -28,11 +28,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage import coil.compose.AsyncImagePainter import coil.compose.SubcomposeAsyncImage import coil.compose.SubcomposeAsyncImageContent @@ -40,6 +42,7 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.colors.AvatarColorsProvider import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.preview.debugPlaceholderAvatar import io.element.android.libraries.designsystem.text.toSp import io.element.android.libraries.designsystem.theme.components.Text import timber.log.Timber @@ -73,21 +76,32 @@ private fun ImageAvatar( modifier: Modifier = Modifier, contentDescription: String? = null, ) { - SubcomposeAsyncImage( - model = avatarData, - contentDescription = contentDescription, - contentScale = ContentScale.Crop, - modifier = modifier - ) { - when (val state = painter.state) { - is AsyncImagePainter.State.Success -> SubcomposeAsyncImageContent() - is AsyncImagePainter.State.Error -> { - SideEffect { - Timber.e(state.result.throwable, "Error loading avatar $state\n${state.result}") + if (LocalInspectionMode.current) { + // For compose previews, use debugPlaceholderAvatar() + // instead of falling back to initials avatar on load failure + AsyncImage( + model = avatarData, + contentDescription = contentDescription, + placeholder = debugPlaceholderAvatar(), + modifier = modifier + ) + } else { + SubcomposeAsyncImage( + model = avatarData, + contentDescription = contentDescription, + contentScale = ContentScale.Crop, + modifier = modifier + ) { + when (val state = painter.state) { + is AsyncImagePainter.State.Success -> SubcomposeAsyncImageContent() + is AsyncImagePainter.State.Error -> { + SideEffect { + Timber.e(state.result.throwable, "Error loading avatar $state\n${state.result}") + } + InitialsAvatar(avatarData = avatarData) } - InitialsAvatar(avatarData = avatarData) + else -> InitialsAvatar(avatarData = avatarData) } - else -> InitialsAvatar(avatarData = avatarData) } } }