fix: using unique key for transition

This commit is contained in:
tapframe 2026-04-11 20:00:41 +05:30
parent 81af639fe9
commit fa7e3aabfc
5 changed files with 38 additions and 15 deletions

View file

@ -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 ->

View file

@ -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"
}

View file

@ -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?,

View file

@ -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>(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,
)

View file

@ -41,7 +41,7 @@ fun DetailCastSection(
cast: List<MetaPerson>,
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,
)