mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-22 17:41:39 +00:00
feat(tv): make hero responsive to screen height and enrich metadata
This commit is contained in:
@@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import hu.bbara.purefin.core.model.MediaUiModel
|
||||
import hu.bbara.purefin.core.model.MovieUiModel
|
||||
import hu.bbara.purefin.core.model.LibraryUiModel
|
||||
@@ -40,8 +39,7 @@ fun TvHomeScreen(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
TvFocusedItemHero(
|
||||
item = focusedMediaUiModel.value,
|
||||
height = 220.dp
|
||||
item = focusedMediaUiModel.value
|
||||
)
|
||||
TvHomeContent(
|
||||
libraries = libraries,
|
||||
|
||||
@@ -6,9 +6,10 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -20,28 +21,31 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import hu.bbara.purefin.core.model.MediaUiModel
|
||||
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
internal const val TvHomeHeroTitleTag = "tv-home-hero-title"
|
||||
internal const val TvHomeHeroProgressLabelTag = "tv-home-hero-progress-label"
|
||||
|
||||
private const val TvHomeHeroAnimationMillis = 180
|
||||
// Half the screen for the billboard, half for the content rows. Tuned so both
|
||||
// the hero block and the first focused row fit on a 540dp-tall (1080p xhdpi) TV.
|
||||
internal const val TvHomeHeroHeightFraction = 0.5f
|
||||
|
||||
@Composable
|
||||
internal fun TvFocusedItemHero(
|
||||
item: MediaUiModel,
|
||||
height: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
heightFraction: Float = TvHomeHeroHeightFraction,
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(height)
|
||||
.fillMaxHeight(heightFraction)
|
||||
.background(scheme.background)
|
||||
) {
|
||||
Crossfade(
|
||||
@@ -56,20 +60,22 @@ internal fun TvFocusedItemHero(
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
}
|
||||
// Darken the left side so the text block stays readable over any backdrop.
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
Brush.horizontalGradient(
|
||||
colorStops = arrayOf(
|
||||
0.0f to scheme.background,
|
||||
0.28f to scheme.background.copy(alpha = 0.88f),
|
||||
0.62f to scheme.background.copy(alpha = 0.42f),
|
||||
1.0f to scheme.background.copy(alpha = 0.06f)
|
||||
0.0f to scheme.background.copy(alpha = 0.86f),
|
||||
0.34f to scheme.background.copy(alpha = 0.62f),
|
||||
0.66f to scheme.background.copy(alpha = 0.22f),
|
||||
1.0f to scheme.background.copy(alpha = 0.04f)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
// Strong bottom fade so the title and metadata are legible.
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -77,7 +83,8 @@ internal fun TvFocusedItemHero(
|
||||
Brush.verticalGradient(
|
||||
colorStops = arrayOf(
|
||||
0.0f to scheme.background.copy(alpha = 0f),
|
||||
0.56f to scheme.background.copy(alpha = 0.1f),
|
||||
0.40f to scheme.background.copy(alpha = 0.10f),
|
||||
0.72f to scheme.background.copy(alpha = 0.58f),
|
||||
1.0f to scheme.background
|
||||
)
|
||||
)
|
||||
@@ -92,7 +99,7 @@ internal fun TvFocusedItemHero(
|
||||
verticalArrangement = Arrangement.Bottom,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 40.dp, vertical = 18.dp)
|
||||
.padding(horizontal = 40.dp, vertical = 16.dp)
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
@@ -101,23 +108,50 @@ internal fun TvFocusedItemHero(
|
||||
Text(
|
||||
text = hero.primaryText,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 34.sp,
|
||||
fontSize = 40.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 38.sp,
|
||||
maxLines = 1,
|
||||
lineHeight = 44.sp,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.testTag(TvHomeHeroTitleTag)
|
||||
)
|
||||
Text(
|
||||
text = hero.description,
|
||||
color = scheme.onSurfaceVariant,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 18.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
TvHomeHeroMetadataRow(item = hero)
|
||||
if (hero.description.isNotBlank()) {
|
||||
Text(
|
||||
text = hero.description,
|
||||
color = scheme.onSurfaceVariant,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 18.sp,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TvHomeHeroMetadataRow(item: MediaUiModel) {
|
||||
val hasSecondary = item.secondaryText.isNotBlank()
|
||||
val progress = item.progress
|
||||
val hasProgress = progress != null && progress > 0f && progress < 1f
|
||||
if (!hasSecondary && !hasProgress) return
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
if (hasSecondary) {
|
||||
TvHomeMetaChip(text = item.secondaryText)
|
||||
}
|
||||
if (hasProgress) {
|
||||
val percent = ((progress ?: 0f) * 100f).roundToInt()
|
||||
TvHomeMetaChip(
|
||||
text = "Resume \u00B7 $percent%",
|
||||
highlighted = true,
|
||||
modifier = Modifier.testTag(TvHomeHeroProgressLabelTag)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user