feat: implement image caching for cast avatars in detail screens

This commit is contained in:
tapframe 2026-04-11 19:05:10 +05:30
parent bc3400fd0f
commit e4e619695e
3 changed files with 53 additions and 4 deletions

3
.gitignore vendored
View file

@ -21,4 +21,5 @@ logs/
Docs
keystore/
scripts/build-distribution.sh
asset
asset
scripts/scrape_android_compose_animation_docs.py

View file

@ -54,6 +54,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage
import coil3.compose.LocalPlatformContext
import coil3.request.ImageRequest
import com.nuvio.app.features.details.components.DetailPosterRailSection
import com.nuvio.app.features.home.MetaPreview
import com.nuvio.app.features.tmdb.TmdbMetadataService
@ -297,6 +299,20 @@ private fun HeroSection(
val heroScale = 1f - (collapseProgress * 0.12f)
val heroAlpha = 1f - (collapseProgress * 0.35f)
val avatarUrl = person.profilePhoto?.takeIf { it.isNotBlank() } ?: fallbackProfilePhoto
val avatarCacheKey = castAvatarSharedTransitionKey(person.tmdbId)
val platformContext = LocalPlatformContext.current
val avatarRequest = if (!avatarUrl.isNullOrBlank()) {
remember(platformContext, avatarUrl, avatarCacheKey) {
ImageRequest.Builder(platformContext)
.data(avatarUrl)
.memoryCacheKey(avatarCacheKey)
.placeholderMemoryCacheKey(avatarCacheKey)
.diskCacheKey(avatarUrl)
.build()
}
} else {
null
}
val avatarSharedElementModifier = if (sharedTransitionScope != null && animatedVisibilityScope != null) {
with(sharedTransitionScope) {
Modifier.sharedElement(
@ -333,7 +349,7 @@ private fun HeroSection(
) {
if (!avatarUrl.isNullOrBlank()) {
AsyncImage(
model = avatarUrl,
model = avatarRequest ?: avatarUrl,
contentDescription = person.name,
modifier = Modifier.matchParentSize(),
contentScale = ContentScale.Crop,
@ -422,6 +438,20 @@ private fun PersonDetailSkeleton(
animatedVisibilityScope: AnimatedVisibilityScope? = null,
) {
val accentColor = MaterialTheme.colorScheme.primary
val avatarCacheKey = castAvatarSharedTransitionKey(personId)
val platformContext = LocalPlatformContext.current
val avatarRequest = if (!profilePhoto.isNullOrBlank()) {
remember(platformContext, profilePhoto, avatarCacheKey) {
ImageRequest.Builder(platformContext)
.data(profilePhoto)
.memoryCacheKey(avatarCacheKey)
.placeholderMemoryCacheKey(avatarCacheKey)
.diskCacheKey(profilePhoto)
.build()
}
} else {
null
}
val accentGradient = remember(accentColor) {
Brush.verticalGradient(
colorStops = arrayOf(
@ -480,7 +510,7 @@ private fun PersonDetailSkeleton(
) {
if (!profilePhoto.isNullOrBlank()) {
AsyncImage(
model = profilePhoto,
model = avatarRequest ?: profilePhoto,
contentDescription = personName,
modifier = Modifier.matchParentSize(),
contentScale = ContentScale.Crop,

View file

@ -19,6 +19,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
@ -29,6 +30,8 @@ import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage
import coil3.compose.LocalPlatformContext
import coil3.request.ImageRequest
import com.nuvio.app.features.details.MetaPerson
import com.nuvio.app.features.details.castAvatarSharedTransitionKey
@ -86,6 +89,21 @@ private fun CastItem(
animatedVisibilityScope: AnimatedVisibilityScope? = null,
onClick: (() -> Unit)? = null,
) {
val avatarCacheKey = person.tmdbId?.takeIf { it > 0 }?.let(::castAvatarSharedTransitionKey)
val platformContext = LocalPlatformContext.current
val avatarRequest = if (!person.photo.isNullOrBlank() && !avatarCacheKey.isNullOrBlank()) {
remember(platformContext, person.photo, avatarCacheKey) {
ImageRequest.Builder(platformContext)
.data(person.photo)
.memoryCacheKey(avatarCacheKey)
.placeholderMemoryCacheKey(avatarCacheKey)
.diskCacheKey(person.photo)
.build()
}
} else {
null
}
val avatarSharedElementModifier = if (
sharedTransitionScope != null &&
animatedVisibilityScope != null &&
@ -124,7 +142,7 @@ private fun CastItem(
) {
if (person.photo != null) {
AsyncImage(
model = person.photo,
model = avatarRequest ?: person.photo,
contentDescription = person.name,
modifier = Modifier.matchParentSize(),
contentScale = ContentScale.Crop,