feat(app): add shared bounds key functionality for home media navigation

This commit is contained in:
2026-05-02 13:10:34 +02:00
parent 64b1032b74
commit 557679861a
15 changed files with 253 additions and 89 deletions

View File

@@ -41,8 +41,10 @@ import dagger.hilt.android.AndroidEntryPoint
import hu.bbara.purefin.data.SessionBootstrapper import hu.bbara.purefin.data.SessionBootstrapper
import hu.bbara.purefin.jellyfin.JellyfinAuthInterceptor import hu.bbara.purefin.jellyfin.JellyfinAuthInterceptor
import hu.bbara.purefin.data.UserSessionRepository import hu.bbara.purefin.data.UserSessionRepository
import hu.bbara.purefin.navigation.LocalHomeMediaSharedBoundsKey
import hu.bbara.purefin.navigation.LocalNavigationBackStack import hu.bbara.purefin.navigation.LocalNavigationBackStack
import hu.bbara.purefin.navigation.LocalNavigationManager import hu.bbara.purefin.navigation.LocalNavigationManager
import hu.bbara.purefin.navigation.LocalSetHomeMediaSharedBoundsKey
import hu.bbara.purefin.navigation.LocalSharedTransitionScope import hu.bbara.purefin.navigation.LocalSharedTransitionScope
import hu.bbara.purefin.navigation.NavigationCommand import hu.bbara.purefin.navigation.NavigationCommand
import hu.bbara.purefin.navigation.NavigationManager import hu.bbara.purefin.navigation.NavigationManager
@@ -158,6 +160,7 @@ class PurefinActivity : ComponentActivity() {
if (isLoggedIn) { if (isLoggedIn) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val backStack = rememberNavBackStack(Route.Home) as NavBackStack<Route> val backStack = rememberNavBackStack(Route.Home) as NavBackStack<Route>
var homeMediaSharedBoundsKey by remember { mutableStateOf<String?>(null) }
val appEntryProvider = val appEntryProvider =
entryProvider { entryProvider {
entryBuilders.forEach { builder -> builder() } entryBuilders.forEach { builder -> builder() }
@@ -180,7 +183,11 @@ class PurefinActivity : ComponentActivity() {
CompositionLocalProvider( CompositionLocalProvider(
LocalNavigationManager provides navigationManager, LocalNavigationManager provides navigationManager,
LocalNavigationBackStack provides backStack.toList(), LocalNavigationBackStack provides backStack.toList(),
LocalSharedTransitionScope provides this LocalSharedTransitionScope provides this,
LocalHomeMediaSharedBoundsKey provides homeMediaSharedBoundsKey,
LocalSetHomeMediaSharedBoundsKey provides { key ->
homeMediaSharedBoundsKey = key
}
) { ) {
NavDisplay( NavDisplay(
backStack = backStack, backStack = backStack,

View File

@@ -18,4 +18,8 @@ val LocalSharedTransitionScope = compositionLocalOf<SharedTransitionScope?> { nu
val LocalNavSharedAnimatedVisibilityScope = val LocalNavSharedAnimatedVisibilityScope =
compositionLocalOf<AnimatedVisibilityScope?> { null } compositionLocalOf<AnimatedVisibilityScope?> { null }
val LocalHomeMediaSharedBoundsKey = compositionLocalOf<String?> { null }
val LocalSetHomeMediaSharedBoundsKey = staticCompositionLocalOf<(String) -> Unit> { {} }
const val HOME_SEARCH_SHARED_BOUNDS_KEY = "home_search_shared_bounds" const val HOME_SEARCH_SHARED_BOUNDS_KEY = "home_search_shared_bounds"

View File

@@ -27,14 +27,26 @@ fun EntryProviderScope<Route>.appRouteEntryBuilder() {
} }
} }
entry<Route.MovieRoute> { entry<Route.MovieRoute> {
CompositionLocalProvider(
LocalNavSharedAnimatedVisibilityScope provides LocalNavAnimatedContentScope.current
) {
MovieScreen(movie = it.item) MovieScreen(movie = it.item)
} }
}
entry<Route.SeriesRoute> { entry<Route.SeriesRoute> {
CompositionLocalProvider(
LocalNavSharedAnimatedVisibilityScope provides LocalNavAnimatedContentScope.current
) {
SeriesScreen(series = it.item) SeriesScreen(series = it.item)
} }
}
entry<Route.EpisodeRoute> { entry<Route.EpisodeRoute> {
CompositionLocalProvider(
LocalNavSharedAnimatedVisibilityScope provides LocalNavAnimatedContentScope.current
) {
EpisodeScreen(episode = it.item) EpisodeScreen(episode = it.item)
} }
}
entry<Route.LibraryRoute> { entry<Route.LibraryRoute> {
LibraryScreen(library = it.library) LibraryScreen(library = it.library)
} }

View File

@@ -0,0 +1,75 @@
package hu.bbara.purefin.ui.common.media
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import hu.bbara.purefin.navigation.LocalHomeMediaSharedBoundsKey
import hu.bbara.purefin.navigation.LocalNavSharedAnimatedVisibilityScope
import hu.bbara.purefin.navigation.LocalSetHomeMediaSharedBoundsKey
import hu.bbara.purefin.navigation.LocalSharedTransitionScope
import java.util.UUID
fun homeMediaSharedBoundsKey(origin: String, mediaId: UUID): String =
"home_media_${origin}_$mediaId"
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun Modifier.homeMediaSharedBoundsSource(sharedBoundsKey: String): Modifier {
val selectedKey = LocalHomeMediaSharedBoundsKey.current
return homeMediaSharedBounds(sharedBoundsKey.takeIf { it == selectedKey })
}
@Composable
fun Modifier.homeMediaSharedBoundsDestination(): Modifier =
homeMediaSharedBounds(LocalHomeMediaSharedBoundsKey.current)
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun isHomeMediaSharedBoundsTransitionActive(): Boolean {
val sharedBoundsKey = LocalHomeMediaSharedBoundsKey.current
val sharedTransitionScope = LocalSharedTransitionScope.current
return sharedBoundsKey != null && sharedTransitionScope?.isTransitionActive == true
}
@Composable
fun rememberHomeMediaSharedBoundsClick(
sharedBoundsKey: String,
onClick: () -> Unit
): () -> Unit {
val selectSharedBoundsKey = LocalSetHomeMediaSharedBoundsKey.current
return remember(sharedBoundsKey, onClick, selectSharedBoundsKey) {
{
selectSharedBoundsKey(sharedBoundsKey)
onClick()
}
}
}
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
private fun Modifier.homeMediaSharedBounds(sharedBoundsKey: String?): Modifier {
val sharedTransitionScope = LocalSharedTransitionScope.current
val animatedVisibilityScope = LocalNavSharedAnimatedVisibilityScope.current
if (
sharedBoundsKey == null ||
sharedTransitionScope == null ||
animatedVisibilityScope == null
) {
return this
}
return with(sharedTransitionScope) {
this@homeMediaSharedBounds.sharedBounds(
sharedContentState = rememberSharedContentState(key = sharedBoundsKey),
animatedVisibilityScope = animatedVisibilityScope,
enter = fadeIn(),
exit = fadeOut(),
resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds()
)
}
}

View File

@@ -34,6 +34,8 @@ import androidx.hilt.navigation.compose.hiltViewModel
import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen
import hu.bbara.purefin.ui.common.media.MediaHero import hu.bbara.purefin.ui.common.media.MediaHero
import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsDestination
import hu.bbara.purefin.ui.common.media.isHomeMediaSharedBoundsTransitionActive
import hu.bbara.purefin.download.DownloadState import hu.bbara.purefin.download.DownloadState
import hu.bbara.purefin.image.ImageUrlBuilder import hu.bbara.purefin.image.ImageUrlBuilder
import hu.bbara.purefin.navigation.EpisodeDto import hu.bbara.purefin.navigation.EpisodeDto
@@ -117,11 +119,13 @@ private fun EpisodeScreenInternal(
onDownloadClick: () -> Unit, onDownloadClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val isHomeMediaTransitionActive = isHomeMediaSharedBoundsTransitionActive()
Scaffold( Scaffold(
modifier = modifier, modifier = modifier,
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
topBar = { topBar = {
if (!isHomeMediaTransitionActive) {
EpisodeTopBar( EpisodeTopBar(
shortcut = topBarShortcut, shortcut = topBarShortcut,
onBack = onBack, onBack = onBack,
@@ -129,6 +133,7 @@ private fun EpisodeScreenInternal(
modifier = Modifier modifier = Modifier
) )
} }
}
) { innerPadding -> ) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier
@@ -136,6 +141,7 @@ private fun EpisodeScreenInternal(
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
EpisodeHeroSection(episode = episode) EpisodeHeroSection(episode = episode)
if (!isHomeMediaTransitionActive) {
EpisodeDetails( EpisodeDetails(
episode = episode, episode = episode,
downloadState = downloadState, downloadState = downloadState,
@@ -148,6 +154,7 @@ private fun EpisodeScreenInternal(
} }
} }
} }
}
@Composable @Composable
private fun EpisodeHeroSection( private fun EpisodeHeroSection(
@@ -160,6 +167,7 @@ private fun EpisodeHeroSection(
Box( Box(
modifier = modifier modifier = modifier
.homeMediaSharedBoundsDestination()
.fillMaxWidth() .fillMaxWidth()
.height(sectionHeight) .height(sectionHeight)
) { ) {

View File

@@ -26,17 +26,23 @@ import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.ui.common.bar.MediaProgressBar import hu.bbara.purefin.ui.common.bar.MediaProgressBar
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsSource
import hu.bbara.purefin.ui.common.media.rememberHomeMediaSharedBoundsClick
@Composable @Composable
internal fun ContinueWatchingCard( internal fun ContinueWatchingCard(
item: MediaUiModel, item: MediaUiModel,
sharedBoundsKey: String,
onMediaSelected: (MediaUiModel) -> Unit, onMediaSelected: (MediaUiModel) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
val onClick = rememberHomeMediaSharedBoundsClick(sharedBoundsKey) {
onMediaSelected(item)
}
Card( Card(
onClick = { onMediaSelected(item) }, onClick = onClick,
shape = RoundedCornerShape(26.dp), shape = RoundedCornerShape(26.dp),
colors = CardDefaults.cardColors(containerColor = scheme.surfaceContainer), colors = CardDefaults.cardColors(containerColor = scheme.surfaceContainer),
modifier = modifier.width(280.dp) modifier = modifier.width(280.dp)
@@ -44,6 +50,7 @@ internal fun ContinueWatchingCard(
Column(modifier = Modifier.fillMaxWidth()) { Column(modifier = Modifier.fillMaxWidth()) {
Box( Box(
modifier = Modifier modifier = Modifier
.homeMediaSharedBoundsSource(sharedBoundsKey)
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(16f / 9f) .aspectRatio(16f / 9f)
.background(scheme.surfaceContainer) .background(scheme.surfaceContainer)

View File

@@ -7,13 +7,14 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.common.header.SectionHeader import hu.bbara.purefin.ui.common.header.SectionHeader
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsKey
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
@Composable @Composable
@@ -42,9 +43,10 @@ fun ContinueWatchingSection(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
state = listState state = listState
) { ) {
items(items = items, key = { item -> item.id }) { item -> itemsIndexed(items = items, key = { _, item -> item.id }) { index, item ->
ContinueWatchingCard( ContinueWatchingCard(
item = item, item = item,
sharedBoundsKey = homeMediaSharedBoundsKey("continue-$index", item.id),
onMediaSelected = onMediaSelected onMediaSelected = onMediaSelected
) )
} }

View File

@@ -26,18 +26,25 @@ import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.ui.common.bar.MediaProgressBar import hu.bbara.purefin.ui.common.bar.MediaProgressBar
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsSource
import hu.bbara.purefin.ui.common.media.rememberHomeMediaSharedBoundsClick
@Composable @Composable
internal fun SuggestionCard( internal fun SuggestionCard(
item: MediaUiModel, item: MediaUiModel,
sharedBoundsKey: String,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
val description = item.description.trim() val description = item.description.trim()
val onCardClick = rememberHomeMediaSharedBoundsClick(
sharedBoundsKey = sharedBoundsKey,
onClick = onClick
)
ElevatedCard( ElevatedCard(
onClick = onClick, onClick = onCardClick,
colors = CardDefaults.elevatedCardColors(containerColor = scheme.surfaceContainerLow), colors = CardDefaults.elevatedCardColors(containerColor = scheme.surfaceContainerLow),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 1.dp), elevation = CardDefaults.elevatedCardElevation(defaultElevation = 1.dp),
shape = RoundedCornerShape(30.dp), shape = RoundedCornerShape(30.dp),
@@ -45,6 +52,7 @@ internal fun SuggestionCard(
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.homeMediaSharedBoundsSource(sharedBoundsKey)
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(16f / 11f) .aspectRatio(16f / 11f)
) { ) {

View File

@@ -22,6 +22,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsKey
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
@Composable @Composable
@@ -49,9 +50,11 @@ fun SuggestionsSection(
pageSpacing = 16.dp, pageSpacing = 16.dp,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { page -> ) { page ->
val item = items[page]
SuggestionCard( SuggestionCard(
item = items[page], item = item,
onClick = { onItemOpen(items[page]) } sharedBoundsKey = homeMediaSharedBoundsKey("suggestion-$page", item.id),
onClick = { onItemOpen(item) }
) )
} }
if (items.size > 1) { if (items.size > 1) {

View File

@@ -25,6 +25,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.common.badge.WatchStateBadge import hu.bbara.purefin.ui.common.badge.WatchStateBadge
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsSource
import hu.bbara.purefin.ui.common.media.rememberHomeMediaSharedBoundsClick
import hu.bbara.purefin.ui.model.EpisodeUiModel import hu.bbara.purefin.ui.model.EpisodeUiModel
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.ui.model.MovieUiModel import hu.bbara.purefin.ui.model.MovieUiModel
@@ -32,13 +34,17 @@ import hu.bbara.purefin.ui.model.MovieUiModel
@Composable @Composable
internal fun HomeBrowseCard( internal fun HomeBrowseCard(
uiModel: MediaUiModel, uiModel: MediaUiModel,
sharedBoundsKey: String,
onMediaSelected: (MediaUiModel) -> Unit, onMediaSelected: (MediaUiModel) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
val onClick = rememberHomeMediaSharedBoundsClick(sharedBoundsKey) {
onMediaSelected(uiModel)
}
Card( Card(
onClick = { onMediaSelected(uiModel) }, onClick = onClick,
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(containerColor = scheme.surfaceContainer), colors = CardDefaults.cardColors(containerColor = scheme.surfaceContainer),
modifier = modifier.width(188.dp) modifier = modifier.width(188.dp)
@@ -46,6 +52,7 @@ internal fun HomeBrowseCard(
Column(modifier = Modifier.fillMaxWidth()) { Column(modifier = Modifier.fillMaxWidth()) {
Box( Box(
modifier = Modifier modifier = Modifier
.homeMediaSharedBoundsSource(sharedBoundsKey)
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(16f / 10f) .aspectRatio(16f / 10f)
.clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)) .clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp))

View File

@@ -7,13 +7,14 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.common.header.SectionHeader import hu.bbara.purefin.ui.common.header.SectionHeader
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsKey
import hu.bbara.purefin.ui.model.LibraryUiModel import hu.bbara.purefin.ui.model.LibraryUiModel
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
@@ -49,9 +50,13 @@ fun LibraryPosterSection(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
state = listState state = listState
) { ) {
items(items = items, key = { item -> item.id }) { item -> itemsIndexed(items = items, key = { _, item -> item.id }) { index, item ->
HomeBrowseCard( HomeBrowseCard(
uiModel = item, uiModel = item,
sharedBoundsKey = homeMediaSharedBoundsKey(
origin = "library-${library.id}-$index",
mediaId = item.id
),
onMediaSelected = onMediaSelected onMediaSelected = onMediaSelected
) )
} }

View File

@@ -24,17 +24,23 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsSource
import hu.bbara.purefin.ui.common.media.rememberHomeMediaSharedBoundsClick
@Composable @Composable
internal fun NextUpCard( internal fun NextUpCard(
uiModel: MediaUiModel, uiModel: MediaUiModel,
sharedBoundsKey: String,
onMediaSelected: (MediaUiModel) -> Unit, onMediaSelected: (MediaUiModel) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
val onClick = rememberHomeMediaSharedBoundsClick(sharedBoundsKey) {
onMediaSelected(uiModel)
}
Card( Card(
onClick = { onMediaSelected(uiModel) }, onClick = onClick,
shape = RoundedCornerShape(24.dp), shape = RoundedCornerShape(24.dp),
colors = CardDefaults.cardColors(containerColor = scheme.surfaceContainer), colors = CardDefaults.cardColors(containerColor = scheme.surfaceContainer),
modifier = modifier.width(256.dp) modifier = modifier.width(256.dp)
@@ -42,6 +48,7 @@ internal fun NextUpCard(
Column(modifier = Modifier.fillMaxWidth()) { Column(modifier = Modifier.fillMaxWidth()) {
Box( Box(
modifier = Modifier modifier = Modifier
.homeMediaSharedBoundsSource(sharedBoundsKey)
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(16f / 10f) .aspectRatio(16f / 10f)
.background(scheme.surface) .background(scheme.surface)

View File

@@ -7,13 +7,14 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.common.header.SectionHeader import hu.bbara.purefin.ui.common.header.SectionHeader
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsKey
import hu.bbara.purefin.ui.model.MediaUiModel import hu.bbara.purefin.ui.model.MediaUiModel
@Composable @Composable
@@ -42,9 +43,10 @@ fun NextUpSection(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
state = listState state = listState
) { ) {
items(items = items, key = { item -> item.id }) { item -> itemsIndexed(items = items, key = { _, item -> item.id }) { index, item ->
NextUpCard( NextUpCard(
uiModel = item, uiModel = item,
sharedBoundsKey = homeMediaSharedBoundsKey("next-up-$index", item.id),
onMediaSelected = onMediaSelected onMediaSelected = onMediaSelected
) )
} }

View File

@@ -39,6 +39,8 @@ import hu.bbara.purefin.model.Movie
import hu.bbara.purefin.navigation.MovieDto import hu.bbara.purefin.navigation.MovieDto
import hu.bbara.purefin.ui.common.media.MediaHero import hu.bbara.purefin.ui.common.media.MediaHero
import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsDestination
import hu.bbara.purefin.ui.common.media.isHomeMediaSharedBoundsTransitionActive
import hu.bbara.purefin.ui.screen.movie.components.MovieDetails import hu.bbara.purefin.ui.screen.movie.components.MovieDetails
import hu.bbara.purefin.ui.screen.movie.components.MovieTopBar import hu.bbara.purefin.ui.screen.movie.components.MovieTopBar
import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen
@@ -94,15 +96,19 @@ private fun MovieScreenInternal(
onBack: () -> Unit, onBack: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val isHomeMediaTransitionActive = isHomeMediaSharedBoundsTransitionActive()
Scaffold( Scaffold(
modifier = modifier, modifier = modifier,
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
topBar = { topBar = {
if (!isHomeMediaTransitionActive) {
MovieTopBar( MovieTopBar(
onBack = onBack, onBack = onBack,
modifier = Modifier modifier = Modifier
) )
} }
}
) { innerPadding -> ) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier
@@ -110,6 +116,7 @@ private fun MovieScreenInternal(
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
MediaHeroSection(movie = movie) MediaHeroSection(movie = movie)
if (!isHomeMediaTransitionActive) {
MovieDetails( MovieDetails(
movie = movie, movie = movie,
downloadState = downloadState, downloadState = downloadState,
@@ -122,6 +129,7 @@ private fun MovieScreenInternal(
} }
} }
} }
}
@Composable @Composable
fun MediaHeroSection( fun MediaHeroSection(
@@ -134,6 +142,7 @@ fun MediaHeroSection(
Box ( Box (
modifier = modifier modifier = modifier
.homeMediaSharedBoundsDestination()
.height(sectionHeight) .height(sectionHeight)
) { ) {
MediaHero( MediaHero(

View File

@@ -41,6 +41,8 @@ import hu.bbara.purefin.model.Series
import hu.bbara.purefin.navigation.SeriesDto import hu.bbara.purefin.navigation.SeriesDto
import hu.bbara.purefin.ui.common.media.MediaHero import hu.bbara.purefin.ui.common.media.MediaHero
import hu.bbara.purefin.ui.common.media.MediaSynopsis import hu.bbara.purefin.ui.common.media.MediaSynopsis
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsDestination
import hu.bbara.purefin.ui.common.media.isHomeMediaSharedBoundsTransitionActive
import hu.bbara.purefin.ui.screen.series.components.CastRow import hu.bbara.purefin.ui.screen.series.components.CastRow
import hu.bbara.purefin.ui.screen.series.components.EpisodeCarousel import hu.bbara.purefin.ui.screen.series.components.EpisodeCarousel
import hu.bbara.purefin.ui.screen.series.components.SeasonTabs import hu.bbara.purefin.ui.screen.series.components.SeasonTabs
@@ -103,6 +105,7 @@ private fun SeriesScreenInternal(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
val isHomeMediaTransitionActive = isHomeMediaSharedBoundsTransitionActive()
fun getDefaultSeason() : Season { fun getDefaultSeason() : Season {
for (season in series.seasons) { for (season in series.seasons) {
@@ -131,11 +134,13 @@ private fun SeriesScreenInternal(
modifier = modifier, modifier = modifier,
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
topBar = { topBar = {
if (!isHomeMediaTransitionActive) {
SeriesTopBar( SeriesTopBar(
onBack = onBack, onBack = onBack,
modifier = Modifier modifier = Modifier
) )
} }
}
) { innerPadding -> ) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier
@@ -143,6 +148,7 @@ private fun SeriesScreenInternal(
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
SeriesHeroSection(series = series) SeriesHeroSection(series = series)
if (!isHomeMediaTransitionActive) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -190,6 +196,7 @@ private fun SeriesScreenInternal(
} }
} }
} }
}
@Composable @Composable
private fun SeriesHeroSection( private fun SeriesHeroSection(
@@ -202,6 +209,7 @@ private fun SeriesHeroSection(
Box( Box(
modifier = modifier modifier = modifier
.homeMediaSharedBoundsDestination()
.fillMaxWidth() .fillMaxWidth()
.height(sectionHeight) .height(sectionHeight)
) { ) {