Skip to content

Commit

Permalink
Provide a fallback image when when loading images fail (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
mr3y-the-programmer authored Sep 7, 2024
1 parent e5b3234 commit e2681dd
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.AsyncImage
import com.mr3y.podcaster.LocalStrings
import com.mr3y.podcaster.core.model.Podcast
import com.mr3y.podcaster.core.sampledata.Podcasts
import com.mr3y.podcaster.ui.components.CoilImage
import com.mr3y.podcaster.ui.components.Error
import com.mr3y.podcaster.ui.components.LoadingIndicator
import com.mr3y.podcaster.ui.components.TopBar
Expand Down Expand Up @@ -457,8 +457,8 @@ private fun PodcastsList(
.fillParentMaxWidth()
.clickable(onClick = { onPodcastClick(podcast.id) }),
) {
AsyncImage(
model = podcast.artworkUrl,
CoilImage(
artworkUrl = podcast.artworkUrl,
contentDescription = null,
modifier = Modifier
.size(120.dp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import com.mr3y.podcaster.LocalStrings
import com.mr3y.podcaster.core.model.CurrentlyPlayingEpisode
import com.mr3y.podcaster.core.model.PlayingStatus
import com.mr3y.podcaster.core.sampledata.EpisodeWithDetails
import com.mr3y.podcaster.ui.components.CoilImage
import com.mr3y.podcaster.ui.components.FavoriteButton
import com.mr3y.podcaster.ui.components.MoveToNextButton
import com.mr3y.podcaster.ui.components.MoveToPreviousButton
Expand Down Expand Up @@ -133,8 +133,8 @@ fun ExpandedPlayerView(
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AsyncImage(
model = targetEpisode.artworkUrl,
CoilImage(
artworkUrl = targetEpisode.artworkUrl,
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier = Modifier
Expand Down Expand Up @@ -356,8 +356,8 @@ fun CollapsedPlayerView(
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
) {
AsyncImage(
model = episode.artworkUrl,
CoilImage(
artworkUrl = episode.artworkUrl,
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier = Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ import androidx.core.graphics.drawable.toBitmap
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.asDrawable
import coil3.compose.AsyncImagePainter
import coil3.compose.AsyncImagePainter.State
import coil3.request.allowHardware
import coil3.size.Scale
import com.kmpalette.rememberDominantColorState
Expand Down Expand Up @@ -284,11 +284,8 @@ fun PodcastDetailsScreen(
Header(
artworkUrl = state.podcast.artworkUrl,
sharedTransitionKey = state.podcast.id.toString(),
onState = { state ->
when (state) {
is AsyncImagePainter.State.Success -> bitmap = state.result.image.asDrawable(context.resources).toBitmap().asImageBitmap()
else -> {}
}
onSuccess = {
bitmap = it.result.image.asDrawable(context.resources).toBitmap().asImageBitmap()
},
dominantColor = dominantColorState.color,
subscriptionState = state.subscriptionState,
Expand Down Expand Up @@ -366,7 +363,7 @@ fun PodcastDetailsScreen(
private fun Header(
artworkUrl: String,
sharedTransitionKey: String,
onState: ((AsyncImagePainter.State) -> Unit)?,
onSuccess: ((State.Success) -> Unit)?,
dominantColor: Color,
subscriptionState: SubscriptionState,
isSubscriptionInEditMode: Boolean,
Expand All @@ -393,7 +390,7 @@ private fun Header(
.scale(Scale.FILL)
.allowHardware(false)
},
onState = onState,
onSuccess = onSuccess,
contentScale = ContentScale.FillBounds,
modifier = Modifier
.padding(horizontal = 16.dp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ package com.mr3y.podcaster.ui.components

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.group
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import coil3.compose.AsyncImagePainter.State
import coil3.request.ImageRequest
Expand All @@ -14,9 +22,13 @@ fun CoilImage(
sharedTransitionKey: String,
modifier: Modifier = Modifier,
contentDescription: String? = null,
error: Painter? = rememberVectorPainter(Placeholder),
fallback: Painter? = error,
contentScale: ContentScale = ContentScale.Fit,
config: (ImageRequest.Builder.() -> ImageRequest.Builder) = { this },
onState: ((State) -> Unit)? = null,
onLoading: ((State.Loading) -> Unit)? = null,
onSuccess: ((State.Success) -> Unit)? = null,
onError: ((State.Error) -> Unit)? = null,
) {
val context = LocalContext.current
AsyncImage(
Expand All @@ -28,7 +40,88 @@ fun CoilImage(
.build(),
contentDescription = contentDescription,
modifier = modifier,
error = error,
fallback = fallback,
contentScale = contentScale,
onState = onState,
onLoading = onLoading,
onSuccess = onSuccess,
onError = onError,
)
}

@Composable
fun CoilImage(
artworkUrl: String,
modifier: Modifier = Modifier,
contentDescription: String? = null,
error: Painter? = rememberVectorPainter(Placeholder),
fallback: Painter? = error,
contentScale: ContentScale = ContentScale.Fit,
config: (ImageRequest.Builder.() -> ImageRequest.Builder) = { this },
) {
val context = LocalContext.current
AsyncImage(
model = ImageRequest.Builder(context)
.data(artworkUrl)
.config()
.build(),
contentDescription = contentDescription,
modifier = modifier,
error = error,
fallback = fallback,
contentScale = contentScale,
)
}

val Placeholder: ImageVector
get() {
if (_Placeholder != null) {
return _Placeholder!!
}
_Placeholder = ImageVector.Builder(
name = "Placeholder",
defaultWidth = 180.dp,
defaultHeight = 139.dp,
viewportWidth = 180f,
viewportHeight = 139f
).apply {
group {
path(
fill = SolidColor(Color(0xFFD0D0D0)),
strokeLineWidth = 1f
) {
moveTo(0.001f, 0f)
lineTo(180.12f, 0f)
lineTo(180.12f, 139.794f)
lineTo(0.001f, 139.794f)
close()
}
path(
fill = SolidColor(Color(0xFFFFFFFF)),
strokeLineWidth = 1f
) {
moveTo(104.917f, 66.875f)
lineTo(70.668f, 101.124f)
lineTo(54.7f, 85.156f)
lineTo(12.762f, 127.093f)
lineTo(165.136f, 127.093f)
close()
moveTo(44.627f, 30.143f)
curveTo(41.506f, 30.143f, 38.509f, 31.384f, 36.302f, 33.591f)
curveTo(34.095f, 35.798f, 32.854f, 38.795f, 32.854f, 41.916f)
curveTo(32.854f, 45.037f, 34.095f, 48.034f, 36.302f, 50.241f)
curveTo(38.509f, 52.448f, 41.506f, 53.689f, 44.627f, 53.689f)
curveTo(47.748f, 53.689f, 50.745f, 52.448f, 52.952f, 50.241f)
curveTo(55.159f, 48.034f, 56.4f, 45.037f, 56.4f, 41.916f)
curveTo(56.4f, 38.795f, 55.159f, 35.798f, 52.952f, 33.591f)
curveTo(50.745f, 31.384f, 47.748f, 30.143f, 44.627f, 30.143f)
close()
}
}
}.build()

return _Placeholder!!
}

@Suppress("ObjectPropertyName")
private var _Placeholder: ImageVector? = null

0 comments on commit e2681dd

Please sign in to comment.