Skip to content

Commit

Permalink
[Jetcaster] Add Podcast image loading (#1347)
Browse files Browse the repository at this point in the history
- Added the PotcastImage component
- Added a ProgressIndicator when loading images


[image_load.webm](https://github.com/android/compose-samples/assets/38935359/7e1cfdc8-8219-4957-8e3f-a7072ac4a47d)
  • Loading branch information
arriolac authored Apr 17, 2024
2 parents 03ad355 + ffbf9a6 commit 6f2e501
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 47 deletions.
26 changes: 12 additions & 14 deletions Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Devices
Expand All @@ -101,7 +100,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.core.layout.WindowSizeClass
import androidx.window.core.layout.WindowWidthSizeClass
import coil.compose.AsyncImage
import com.example.jetcaster.R
import com.example.jetcaster.core.model.CategoryInfo
import com.example.jetcaster.core.model.EpisodeInfo
Expand All @@ -110,6 +108,7 @@ import com.example.jetcaster.core.model.LibraryInfo
import com.example.jetcaster.core.model.PlayerEpisode
import com.example.jetcaster.core.model.PodcastCategoryFilterResult
import com.example.jetcaster.core.model.PodcastInfo
import com.example.jetcaster.designsystem.component.PodcastImage
import com.example.jetcaster.ui.home.discover.discoverItems
import com.example.jetcaster.ui.home.library.libraryItems
import com.example.jetcaster.ui.podcast.PodcastDetailsScreen
Expand Down Expand Up @@ -791,9 +790,9 @@ private fun FollowedPodcasts(

@Composable
private fun FollowedPodcastCarouselItem(
podcastTitle: String,
podcastImageUrl: String,
modifier: Modifier = Modifier,
podcastImageUrl: String? = null,
podcastTitle: String? = null,
lastEpisodeDateText: String? = null,
onUnfollowedClick: () -> Unit,
) {
Expand All @@ -803,16 +802,13 @@ private fun FollowedPodcastCarouselItem(
.size(FEATURED_PODCAST_IMAGE_SIZE_DP)
.align(Alignment.CenterHorizontally)
) {
if (podcastImageUrl != null) {
AsyncImage(
model = podcastImageUrl,
contentDescription = podcastTitle,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.clip(MaterialTheme.shapes.medium),
)
}
PodcastImage(
podcastImageUrl = podcastImageUrl,
contentDescription = podcastTitle,
modifier = Modifier
.fillMaxSize()
.clip(MaterialTheme.shapes.medium),
)

ToggleFollowPodcastIconButton(
onClick = onUnfollowedClick,
Expand Down Expand Up @@ -943,6 +939,8 @@ private fun PreviewPodcastCard() {
JetcasterTheme {
FollowedPodcastCarouselItem(
modifier = Modifier.size(128.dp),
podcastTitle = "",
podcastImageUrl = "",
onUnfollowedClick = {}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,15 @@ import androidx.compose.runtime.Composable
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.LocalContext
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.example.jetcaster.core.model.EpisodeInfo
import com.example.jetcaster.core.model.PlayerEpisode
import com.example.jetcaster.core.model.PodcastCategoryFilterResult
import com.example.jetcaster.core.model.PodcastInfo
import com.example.jetcaster.designsystem.component.PodcastImage
import com.example.jetcaster.designsystem.theme.Keyline1
import com.example.jetcaster.ui.home.PreviewEpisodes
import com.example.jetcaster.ui.home.PreviewPodcasts
Expand Down Expand Up @@ -147,9 +144,11 @@ private fun CategoryPodcastRow(
podcastImageUrl = podcast.imageUrl,
isFollowed = podcast.isSubscribed ?: false,
onToggleFollowClicked = { onTogglePodcastFollowed(podcast) },
modifier = Modifier.width(128.dp).clickable {
navigateToPodcastDetails(podcast)
}
modifier = Modifier
.width(128.dp)
.clickable {
navigateToPodcastDetails(podcast)
}
)

if (index < lastIndex) Spacer(Modifier.width(24.dp))
Expand All @@ -160,10 +159,10 @@ private fun CategoryPodcastRow(
@Composable
private fun TopPodcastRowItem(
podcastTitle: String,
podcastImageUrl: String,
isFollowed: Boolean,
modifier: Modifier = Modifier,
onToggleFollowClicked: () -> Unit,
podcastImageUrl: String? = null,
) {
Column(
modifier.semantics(mergeDescendants = true) {}
Expand All @@ -174,19 +173,13 @@ private fun TopPodcastRowItem(
.aspectRatio(1f)
.align(Alignment.CenterHorizontally)
) {
if (podcastImageUrl != null) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(podcastImageUrl)
.crossfade(true)
.build(),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.clip(MaterialTheme.shapes.medium),
)
}
PodcastImage(
modifier = Modifier
.fillMaxSize()
.clip(MaterialTheme.shapes.medium),
podcastImageUrl = podcastImageUrl,
contentDescription = podcastTitle
)

ToggleFollowPodcastIconButton(
onClick = onToggleFollowClicked,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,17 @@ import androidx.compose.runtime.setValue
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.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.example.jetcaster.R
import com.example.jetcaster.core.model.EpisodeInfo
import com.example.jetcaster.core.model.PlayerEpisode
import com.example.jetcaster.core.model.PodcastInfo
import com.example.jetcaster.designsystem.component.PodcastImage
import com.example.jetcaster.designsystem.theme.Keyline1
import com.example.jetcaster.ui.home.PreviewEpisodes
import com.example.jetcaster.ui.home.PreviewPodcasts
Expand Down Expand Up @@ -203,16 +200,12 @@ fun PodcastDetailsHeaderItem(
verticalAlignment = Alignment.Bottom,
modifier = Modifier.fillMaxWidth()
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(podcast.imageUrl)
.crossfade(true)
.build(),
contentDescription = null,
contentScale = ContentScale.Crop,
PodcastImage(
modifier = Modifier
.size(148.dp)
.clip(MaterialTheme.shapes.large)
.clip(MaterialTheme.shapes.large),
podcastImageUrl = podcast.imageUrl,
contentDescription = podcast.title
)
Column(
modifier = Modifier.padding(start = 16.dp)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.jetcaster.designsystem.component

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImagePainter
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest

@Composable
fun PodcastImage(
podcastImageUrl: String,
contentDescription: String?,
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Crop,
) {
var imagePainterState by remember {
mutableStateOf<AsyncImagePainter.State>(AsyncImagePainter.State.Empty)
}

val imageLoader = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data(podcastImageUrl)
.crossfade(true)
.build(),
contentScale = contentScale,
onState = { state -> imagePainterState = state }
)

Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
when (imagePainterState) {
is AsyncImagePainter.State.Loading -> {
CircularProgressIndicator(
modifier = Modifier
.size(48.dp)
.align(Alignment.Center)
)
}

else -> {
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
)
}
}

Image(
painter = imageLoader,
contentDescription = contentDescription,
contentScale = contentScale,
modifier = modifier,
)
}
}

0 comments on commit 6f2e501

Please sign in to comment.