mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
refactor(tv): extract hero backdrop and simplify gradient drawing
Split the hero backdrop (image + gradient overlays) into TvHomeHeroBackdrop, rendered behind the home content. Replace nested Box/background gradient approach with drawWithContent for better composability. Clean up unused imports across TvHomeScreen, TvFocusedItemHero, and TvHomeContent.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
package hu.bbara.purefin.ui.screen.home
|
package hu.bbara.purefin.ui.screen.home
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -10,11 +10,12 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import hu.bbara.purefin.core.model.LibraryUiModel
|
||||||
import hu.bbara.purefin.core.model.MediaUiModel
|
import hu.bbara.purefin.core.model.MediaUiModel
|
||||||
import hu.bbara.purefin.core.model.MovieUiModel
|
import hu.bbara.purefin.core.model.MovieUiModel
|
||||||
import hu.bbara.purefin.core.model.LibraryUiModel
|
|
||||||
import hu.bbara.purefin.ui.screen.home.components.TvFocusedItemHero
|
import hu.bbara.purefin.ui.screen.home.components.TvFocusedItemHero
|
||||||
import hu.bbara.purefin.ui.screen.home.components.TvHomeContent
|
import hu.bbara.purefin.ui.screen.home.components.TvHomeContent
|
||||||
|
import hu.bbara.purefin.ui.screen.home.components.TvHomeHeroBackdrop
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -30,11 +31,13 @@ fun TvHomeScreen(
|
|||||||
val focusedMediaUiModel = remember { mutableStateOf<MediaUiModel>(MovieUiModel.createPlaceholder()) }
|
val focusedMediaUiModel = remember { mutableStateOf<MediaUiModel>(MovieUiModel.createPlaceholder()) }
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = modifier
|
modifier = modifier.fillMaxSize(),
|
||||||
.fillMaxSize()
|
color = scheme.background
|
||||||
.background(scheme.background)
|
|
||||||
) {
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
TvHomeHeroBackdrop(
|
||||||
|
backdropImageUrl = focusedMediaUiModel.value.backdropImageUrl
|
||||||
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
@@ -56,4 +59,5 @@ fun TvHomeScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package hu.bbara.purefin.ui.screen.home.components
|
|||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
@@ -16,6 +14,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
@@ -31,40 +30,34 @@ internal const val TvHomeHeroTitleTag = "tv-home-hero-title"
|
|||||||
internal const val TvHomeHeroProgressLabelTag = "tv-home-hero-progress-label"
|
internal const val TvHomeHeroProgressLabelTag = "tv-home-hero-progress-label"
|
||||||
|
|
||||||
private const val TvHomeHeroAnimationMillis = 180
|
private const val TvHomeHeroAnimationMillis = 180
|
||||||
// Half the screen for the billboard, half for the content rows. Tuned so both
|
// The billboard backdrop covers the top portion of the home screen so the hero
|
||||||
// the hero block and the first focused row fit on a 540dp-tall (1080p xhdpi) TV.
|
// text sits at the bottom of the backdrop and the content rows begin below it.
|
||||||
internal const val TvHomeHeroHeightFraction = 0.45f
|
internal const val TvHomeHeroHeightFraction = 0.45f
|
||||||
|
internal const val TvHomeBackdropOffset = 0.20f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The home screen background image plus the darkening gradients that keep the
|
||||||
|
* hero text readable. Drawn behind the home content and sized to the top
|
||||||
|
* [TvHomeHeroHeightFraction] of the screen.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
internal fun TvFocusedItemHero(
|
internal fun TvHomeHeroBackdrop(
|
||||||
item: MediaUiModel,
|
backdropImageUrl: String?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
heightFraction: Float = TvHomeHeroHeightFraction,
|
heightFraction: Float = TvHomeHeroHeightFraction + TvHomeBackdropOffset,
|
||||||
) {
|
) {
|
||||||
val scheme = MaterialTheme.colorScheme
|
val scheme = MaterialTheme.colorScheme
|
||||||
Box(
|
Crossfade(
|
||||||
|
targetState = backdropImageUrl,
|
||||||
|
animationSpec = tween(durationMillis = TvHomeHeroAnimationMillis),
|
||||||
|
label = "tv-home-hero-backdrop",
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.fillMaxHeight(heightFraction)
|
.fillMaxHeight(heightFraction)
|
||||||
.background(scheme.background)
|
.drawWithContent {
|
||||||
) {
|
drawContent()
|
||||||
Crossfade(
|
|
||||||
targetState = item.backdropImageUrl,
|
|
||||||
animationSpec = tween(durationMillis = TvHomeHeroAnimationMillis),
|
|
||||||
label = "tv-home-hero-background"
|
|
||||||
) { imageUrl ->
|
|
||||||
PurefinAsyncImage(
|
|
||||||
model = imageUrl,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// Darken the left side so the text block stays readable over any backdrop.
|
// Darken the left side so the text block stays readable over any backdrop.
|
||||||
Box(
|
drawRect(
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(
|
|
||||||
Brush.horizontalGradient(
|
Brush.horizontalGradient(
|
||||||
colorStops = arrayOf(
|
colorStops = arrayOf(
|
||||||
0.0f to scheme.background.copy(alpha = 0.86f),
|
0.0f to scheme.background.copy(alpha = 0.86f),
|
||||||
@@ -74,12 +67,8 @@ internal fun TvFocusedItemHero(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
// Strong bottom fade so the title and metadata are legible.
|
// Strong bottom fade so the title and metadata are legible.
|
||||||
Box(
|
drawRect(
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(
|
|
||||||
Brush.verticalGradient(
|
Brush.verticalGradient(
|
||||||
colorStops = arrayOf(
|
colorStops = arrayOf(
|
||||||
0.0f to scheme.background.copy(alpha = 0f),
|
0.0f to scheme.background.copy(alpha = 0f),
|
||||||
@@ -89,11 +78,31 @@ internal fun TvFocusedItemHero(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
) { imageUrl ->
|
||||||
|
PurefinAsyncImage(
|
||||||
|
model = imageUrl,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun TvFocusedItemHero(
|
||||||
|
item: MediaUiModel,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
heightFraction: Float = TvHomeHeroHeightFraction,
|
||||||
|
) {
|
||||||
|
val scheme = MaterialTheme.colorScheme
|
||||||
Crossfade(
|
Crossfade(
|
||||||
targetState = item,
|
targetState = item,
|
||||||
animationSpec = tween(durationMillis = TvHomeHeroAnimationMillis),
|
animationSpec = tween(durationMillis = TvHomeHeroAnimationMillis),
|
||||||
label = "tv-home-hero-content"
|
label = "tv-home-hero-content",
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight(heightFraction)
|
||||||
) { hero ->
|
) { hero ->
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.Bottom,
|
verticalArrangement = Arrangement.Bottom,
|
||||||
@@ -129,7 +138,6 @@ internal fun TvFocusedItemHero(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
package hu.bbara.purefin.ui.screen.home.components
|
package hu.bbara.purefin.ui.screen.home.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.gestures.LocalBringIntoViewSpec
|
import androidx.compose.foundation.gestures.LocalBringIntoViewSpec
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
@@ -11,7 +10,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -42,7 +40,6 @@ fun TvHomeContent(
|
|||||||
contentPadding: PaddingValues = PaddingValues(bottom = 32.dp),
|
contentPadding: PaddingValues = PaddingValues(bottom = 32.dp),
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val scheme = MaterialTheme.colorScheme
|
|
||||||
val topOffsetPx = with(LocalDensity.current) { TvHomeFocusedItemTopOffset.toPx() }
|
val topOffsetPx = with(LocalDensity.current) { TvHomeFocusedItemTopOffset.toPx() }
|
||||||
val hasContinueWatching = continueWatching.isNotEmpty()
|
val hasContinueWatching = continueWatching.isNotEmpty()
|
||||||
val hasNextUp = nextUp.isNotEmpty()
|
val hasNextUp = nextUp.isNotEmpty()
|
||||||
@@ -65,7 +62,6 @@ fun TvHomeContent(
|
|||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(scheme.background)
|
|
||||||
.focusRequester(initialFocusRequester),
|
.focusRequester(initialFocusRequester),
|
||||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
contentPadding = contentPadding
|
contentPadding = contentPadding
|
||||||
|
|||||||
Reference in New Issue
Block a user