diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index e3798eed..1cb1bd51 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -171,6 +171,7 @@ data class PersonDetailRoute( val personId: Int, val personName: String, val personPhoto: String? = null, + val castAvatarTransitionKey: String? = null, val preferCrew: Boolean = false, ) @@ -766,7 +767,7 @@ private fun MainAppContent( ) } }, - onCastClick = { person -> + onCastClick = { person, avatarTransitionKey -> val tmdbId = person.tmdbId if (tmdbId != null && tmdbId > 0) { navController.navigate( @@ -774,6 +775,7 @@ private fun MainAppContent( personId = tmdbId, personName = person.name, personPhoto = person.photo, + castAvatarTransitionKey = avatarTransitionKey, preferCrew = person.role?.let { it.equals("Director", ignoreCase = true) || it.equals("Writer", ignoreCase = true) || @@ -807,6 +809,7 @@ private fun MainAppContent( personId = route.personId, personName = route.personName, initialProfilePhoto = route.personPhoto, + avatarTransitionKey = route.castAvatarTransitionKey, preferCrew = route.preferCrew, onBack = { navController.popBackStack() }, onOpenMeta = { preview -> diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/CastSharedTransition.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/CastSharedTransition.kt index fdfaccac..ee0019a4 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/CastSharedTransition.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/CastSharedTransition.kt @@ -1,3 +1,11 @@ package com.nuvio.app.features.details -internal fun castAvatarSharedTransitionKey(personId: Int): String = "cast-avatar:$personId" +internal fun castAvatarSharedTransitionKey( + personId: Int, + occurrenceIndex: Int? = null, +): String = + if (occurrenceIndex != null) { + "cast-avatar:$personId:$occurrenceIndex" + } else { + "cast-avatar:$personId" + } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt index 2a235634..2e81224c 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt @@ -107,7 +107,7 @@ fun MetaDetailsScreen( onPlay: ((type: String, videoId: String, parentMetaId: String, parentMetaType: String, title: String, logo: String?, poster: String?, background: String?, seasonNumber: Int?, episodeNumber: Int?, episodeTitle: String?, episodeThumbnail: String?, pauseDescription: String?, resumePositionMs: Long?) -> Unit)? = null, onPlayManually: ((type: String, videoId: String, parentMetaId: String, parentMetaType: String, title: String, logo: String?, poster: String?, background: String?, seasonNumber: Int?, episodeNumber: Int?, episodeTitle: String?, episodeThumbnail: String?, pauseDescription: String?, resumePositionMs: Long?) -> Unit)? = null, onOpenMeta: ((MetaPreview) -> Unit)? = null, - onCastClick: ((MetaPerson) -> Unit)? = null, + onCastClick: ((MetaPerson, String?) -> Unit)? = null, onCompanyClick: ((MetaCompany, String) -> Unit)? = null, sharedTransitionScope: SharedTransitionScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null, @@ -919,7 +919,7 @@ private fun ConfiguredMetaSections( onEpisodeClick: (MetaVideo) -> Unit, onEpisodeLongPress: (MetaVideo) -> Unit, onOpenMeta: ((MetaPreview) -> Unit)?, - onCastClick: ((MetaPerson) -> Unit)?, + onCastClick: ((MetaPerson, String?) -> Unit)?, onCompanyClick: ((MetaCompany, String) -> Unit)?, sharedTransitionScope: SharedTransitionScope?, animatedVisibilityScope: AnimatedVisibilityScope?, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/PersonDetailScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/PersonDetailScreen.kt index 5a6b77e1..627bc666 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/PersonDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/PersonDetailScreen.kt @@ -73,6 +73,7 @@ fun PersonDetailScreen( personId: Int, personName: String, initialProfilePhoto: String? = null, + avatarTransitionKey: String? = null, preferCrew: Boolean = false, onBack: () -> Unit, onOpenMeta: (MetaPreview) -> Unit, @@ -81,6 +82,7 @@ fun PersonDetailScreen( modifier: Modifier = Modifier, ) { var uiState by remember(personId) { mutableStateOf(PersonDetailUiState.Loading) } + val resolvedAvatarTransitionKey = avatarTransitionKey ?: castAvatarSharedTransitionKey(personId) LaunchedEffect(personId) { uiState = PersonDetailUiState.Loading @@ -105,6 +107,7 @@ fun PersonDetailScreen( personId = personId, personName = personName, profilePhoto = initialProfilePhoto, + avatarTransitionKey = resolvedAvatarTransitionKey, sharedTransitionScope = sharedTransitionScope, animatedVisibilityScope = animatedVisibilityScope, ) @@ -119,6 +122,7 @@ fun PersonDetailScreen( person = state.personDetail, onOpenMeta = onOpenMeta, initialProfilePhoto = initialProfilePhoto, + avatarTransitionKey = resolvedAvatarTransitionKey, sharedTransitionScope = sharedTransitionScope, animatedVisibilityScope = animatedVisibilityScope, ) @@ -147,6 +151,7 @@ private fun PersonDetailContent( person: PersonDetail, onOpenMeta: (MetaPreview) -> Unit, initialProfilePhoto: String? = null, + avatarTransitionKey: String, sharedTransitionScope: SharedTransitionScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null, ) { @@ -240,6 +245,7 @@ private fun PersonDetailContent( person = person, collapseProgress = collapseProgress, fallbackProfilePhoto = initialProfilePhoto, + avatarTransitionKey = avatarTransitionKey, sharedTransitionScope = sharedTransitionScope, animatedVisibilityScope = animatedVisibilityScope, ) @@ -292,6 +298,7 @@ private fun HeroSection( person: PersonDetail, collapseProgress: Float = 0f, fallbackProfilePhoto: String? = null, + avatarTransitionKey: String, sharedTransitionScope: SharedTransitionScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null, ) { @@ -299,7 +306,7 @@ 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 avatarCacheKey = avatarTransitionKey val platformContext = LocalPlatformContext.current val avatarRequest = if (!avatarUrl.isNullOrBlank()) { remember(platformContext, avatarUrl, avatarCacheKey) { @@ -317,7 +324,7 @@ private fun HeroSection( with(sharedTransitionScope) { Modifier.sharedElement( sharedContentState = rememberSharedContentState( - key = castAvatarSharedTransitionKey(person.tmdbId), + key = avatarTransitionKey, ), animatedVisibilityScope = animatedVisibilityScope, ) @@ -434,11 +441,12 @@ private fun PersonDetailSkeleton( personId: Int, personName: String, profilePhoto: String? = null, + avatarTransitionKey: String, sharedTransitionScope: SharedTransitionScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null, ) { val accentColor = MaterialTheme.colorScheme.primary - val avatarCacheKey = castAvatarSharedTransitionKey(personId) + val avatarCacheKey = avatarTransitionKey val platformContext = LocalPlatformContext.current val avatarRequest = if (!profilePhoto.isNullOrBlank()) { remember(platformContext, profilePhoto, avatarCacheKey) { @@ -492,7 +500,7 @@ private fun PersonDetailSkeleton( with(sharedTransitionScope) { Modifier.sharedElement( sharedContentState = rememberSharedContentState( - key = castAvatarSharedTransitionKey(personId), + key = avatarTransitionKey, ), animatedVisibilityScope = animatedVisibilityScope, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailCastSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailCastSection.kt index 00e33d68..370a77bb 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailCastSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailCastSection.kt @@ -41,7 +41,7 @@ fun DetailCastSection( cast: List, modifier: Modifier = Modifier, showHeader: Boolean = true, - onCastClick: ((MetaPerson) -> Unit)? = null, + onCastClick: ((MetaPerson, String?) -> Unit)? = null, sharedTransitionScope: SharedTransitionScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null, ) { @@ -61,14 +61,18 @@ fun DetailCastSection( itemsIndexed( items = cast, key = { index, person -> "${person.name}-${person.role.orEmpty()}-${person.photo.orEmpty()}-$index" }, - ) { _, person -> + ) { index, person -> + val sharedTransitionKey = person.tmdbId + ?.takeIf { it > 0 } + ?.let { castAvatarSharedTransitionKey(it, occurrenceIndex = index) } CastItem( person = person, + sharedTransitionKey = sharedTransitionKey, sizing = sizing, sharedTransitionScope = sharedTransitionScope, animatedVisibilityScope = animatedVisibilityScope, onClick = if (onCastClick != null && person.tmdbId != null && person.tmdbId > 0) { - { onCastClick(person) } + { onCastClick(person, sharedTransitionKey) } } else { null }, @@ -84,12 +88,13 @@ fun DetailCastSection( private fun CastItem( person: MetaPerson, modifier: Modifier = Modifier, + sharedTransitionKey: String? = null, sizing: CastSectionSizing, sharedTransitionScope: SharedTransitionScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null, onClick: (() -> Unit)? = null, ) { - val avatarCacheKey = person.tmdbId?.takeIf { it > 0 }?.let(::castAvatarSharedTransitionKey) + val avatarCacheKey = sharedTransitionKey val platformContext = LocalPlatformContext.current val avatarRequest = if (!person.photo.isNullOrBlank() && !avatarCacheKey.isNullOrBlank()) { remember(platformContext, person.photo, avatarCacheKey) { @@ -107,13 +112,12 @@ private fun CastItem( val avatarSharedElementModifier = if ( sharedTransitionScope != null && animatedVisibilityScope != null && - person.tmdbId != null && - person.tmdbId > 0 + !sharedTransitionKey.isNullOrBlank() ) { with(sharedTransitionScope) { Modifier.sharedElement( sharedContentState = rememberSharedContentState( - key = castAvatarSharedTransitionKey(person.tmdbId), + key = sharedTransitionKey, ), animatedVisibilityScope = animatedVisibilityScope, )