From 0f2aa5bfb4bc8fb9f4f4d167aa2bdcab15563251 Mon Sep 17 00:00:00 2001 From: yongsuk44 Date: Wed, 17 Apr 2024 10:42:59 +0900 Subject: [PATCH 1/2] [Jetcaster] Add album image loading --- .../com/example/jetcaster/ui/home/Home.kt | 8 +- .../ui/home/category/PodcastCategory.kt | 32 +++---- .../ui/podcast/PodcastDetailsScreen.kt | 16 +--- .../designsystem/component/PodcastImage.kt | 89 +++++++++++++++++++ 4 files changed, 108 insertions(+), 37 deletions(-) create mode 100644 Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/PodcastImage.kt diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt index 3ed91cdd64..ef038e6009 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt @@ -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 @@ -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 @@ -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 @@ -804,10 +803,9 @@ private fun FollowedPodcastCarouselItem( .align(Alignment.CenterHorizontally) ) { if (podcastImageUrl != null) { - AsyncImage( - model = podcastImageUrl, + PodcastImage( + podcastImageUrl = podcastImageUrl, contentDescription = podcastTitle, - contentScale = ContentScale.Crop, modifier = Modifier .fillMaxSize() .clip(MaterialTheme.shapes.medium), diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt index 29c6e15115..bd9e0eb61c 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt @@ -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 @@ -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)) @@ -174,19 +173,12 @@ 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, + ) ToggleFollowPodcastIconButton( onClick = onToggleFollowClicked, diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt index f8db646b71..6c0eb2518c 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt @@ -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 @@ -203,16 +200,11 @@ 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, ) Column( modifier = Modifier.padding(start = 16.dp) diff --git a/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/PodcastImage.kt b/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/PodcastImage.kt new file mode 100644 index 0000000000..001fad1eab --- /dev/null +++ b/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/PodcastImage.kt @@ -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( + modifier: Modifier = Modifier, + podcastImageUrl: String? = null, + contentDescription: String? = null, + contentScale: ContentScale = ContentScale.Crop, +) { + var imagePainterState by remember { + mutableStateOf(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, + ) + } +} From ffbf9a6d3cd0cb5ca30f2cbfb434decf433926af Mon Sep 17 00:00:00 2001 From: yongsuk44 Date: Thu, 18 Apr 2024 03:32:46 +0900 Subject: [PATCH 2/2] [Jetcaster] Update PodcastImage to require an image URL --- .../com/example/jetcaster/ui/home/Home.kt | 22 +++++++++---------- .../ui/home/category/PodcastCategory.kt | 3 ++- .../ui/podcast/PodcastDetailsScreen.kt | 1 + .../designsystem/component/PodcastImage.kt | 4 ++-- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt index ef038e6009..d4997baa7e 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt @@ -790,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, ) { @@ -802,15 +802,13 @@ private fun FollowedPodcastCarouselItem( .size(FEATURED_PODCAST_IMAGE_SIZE_DP) .align(Alignment.CenterHorizontally) ) { - if (podcastImageUrl != null) { - PodcastImage( - podcastImageUrl = podcastImageUrl, - contentDescription = podcastTitle, - modifier = Modifier - .fillMaxSize() - .clip(MaterialTheme.shapes.medium), - ) - } + PodcastImage( + podcastImageUrl = podcastImageUrl, + contentDescription = podcastTitle, + modifier = Modifier + .fillMaxSize() + .clip(MaterialTheme.shapes.medium), + ) ToggleFollowPodcastIconButton( onClick = onUnfollowedClick, @@ -941,6 +939,8 @@ private fun PreviewPodcastCard() { JetcasterTheme { FollowedPodcastCarouselItem( modifier = Modifier.size(128.dp), + podcastTitle = "", + podcastImageUrl = "", onUnfollowedClick = {} ) } diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt index bd9e0eb61c..2deb2d444b 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt @@ -159,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) {} @@ -178,6 +178,7 @@ private fun TopPodcastRowItem( .fillMaxSize() .clip(MaterialTheme.shapes.medium), podcastImageUrl = podcastImageUrl, + contentDescription = podcastTitle ) ToggleFollowPodcastIconButton( diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt index 6c0eb2518c..8cc1200881 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/podcast/PodcastDetailsScreen.kt @@ -205,6 +205,7 @@ fun PodcastDetailsHeaderItem( .size(148.dp) .clip(MaterialTheme.shapes.large), podcastImageUrl = podcast.imageUrl, + contentDescription = podcast.title ) Column( modifier = Modifier.padding(start = 16.dp) diff --git a/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/PodcastImage.kt b/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/PodcastImage.kt index 001fad1eab..2896f72c01 100644 --- a/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/PodcastImage.kt +++ b/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/PodcastImage.kt @@ -39,9 +39,9 @@ import coil.request.ImageRequest @Composable fun PodcastImage( + podcastImageUrl: String, + contentDescription: String?, modifier: Modifier = Modifier, - podcastImageUrl: String? = null, - contentDescription: String? = null, contentScale: ContentScale = ContentScale.Crop, ) { var imagePainterState by remember {