mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
fix(tv): align detail screen sizing
This commit is contained in:
@@ -32,8 +32,8 @@ import hu.bbara.purefin.image.ImageUrlBuilder
|
||||
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
||||
|
||||
internal val MediaDetailHorizontalPadding = 48.dp
|
||||
private val MediaDetailHeaderTopPadding = 24.dp
|
||||
private val MediaDetailHeaderBottomPadding = 24.dp
|
||||
private val MediaDetailHeaderTopPadding = 6.dp
|
||||
private val MediaDetailHeaderBottomPadding = 4.dp
|
||||
private const val MediaDetailBodyImageWidthFraction = 0.66f
|
||||
|
||||
internal val TvMediaDetailBringIntoViewSpec: BringIntoViewSpec =
|
||||
@@ -77,7 +77,7 @@ internal fun TvMediaDetailScaffold(
|
||||
internal fun TvMediaDetailBodyBox(
|
||||
backgroundImageUrl: String,
|
||||
modifier: Modifier = Modifier,
|
||||
heightFraction: Float = 0.48f,
|
||||
heightFraction: Float = 0.24f,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
@@ -173,12 +173,11 @@ internal fun MediaDetailOverviewSection(
|
||||
title = "Overview",
|
||||
titleColor = scheme.onBackground,
|
||||
bodyColor = scheme.onSurfaceVariant.copy(alpha = 0.85f),
|
||||
titleFontSize = 22.sp,
|
||||
bodyFontSize = 16.sp,
|
||||
bodyLineHeight = 24.sp,
|
||||
titleSpacing = 14.dp,
|
||||
collapsedLines = 5,
|
||||
collapseInitially = false
|
||||
titleFontSize = 15.sp,
|
||||
bodyFontSize = 11.sp,
|
||||
bodyLineHeight = 14.sp,
|
||||
titleSpacing = 2.dp,
|
||||
collapsedLines = 1
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -99,15 +99,15 @@ internal fun TvEpisodeScreenContent(
|
||||
playFocusRequester = playFocusRequester,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
}
|
||||
Column(modifier = it.fillMaxWidth()) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
MediaDetailOverviewSection(
|
||||
synopsis = episode.synopsis,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
Column(modifier = it.fillMaxWidth()) {
|
||||
MediaDetailPlaybackSection(
|
||||
@@ -115,7 +115,7 @@ internal fun TvEpisodeScreenContent(
|
||||
subtitles = "ENG",
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
Column(modifier = it.fillMaxWidth()) {
|
||||
MediaDetailSectionTitle(text = "Cast")
|
||||
@@ -125,4 +125,3 @@ internal fun TvEpisodeScreenContent(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,47 +39,47 @@ internal fun TvEpisodeHeroSection(
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.widthIn(max = 760.dp)
|
||||
.widthIn(max = 560.dp)
|
||||
) {
|
||||
if (!seriesTitle.isNullOrBlank()) {
|
||||
Text(
|
||||
text = seriesTitle,
|
||||
color = scheme.primary,
|
||||
fontSize = 18.sp,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
}
|
||||
Text(
|
||||
text = episode.title,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 42.sp,
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 48.sp,
|
||||
maxLines = 2,
|
||||
lineHeight = 28.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = "Episode ${episode.index}",
|
||||
color = mutedStrong,
|
||||
fontSize = 18.sp,
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
MediaMetadataFlowRow(
|
||||
items = listOf(episode.releaseDate, episode.rating, episode.runtime, episode.format),
|
||||
highlightedItem = episode.format
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
MediaResumeButton(
|
||||
text = mediaPlayButtonText(episode.progress, episode.watched),
|
||||
progress = mediaPlaybackProgress(episode.progress),
|
||||
onClick = onPlay,
|
||||
modifier = Modifier
|
||||
.sizeIn(minWidth = 216.dp, maxWidth = 240.dp)
|
||||
.sizeIn(minWidth = 160.dp, maxWidth = 192.dp)
|
||||
.focusRequester(playFocusRequester)
|
||||
.testTag(EpisodePlayButtonTag),
|
||||
focusedScale = 1.08f,
|
||||
@@ -88,7 +88,10 @@ internal fun TvEpisodeHeroSection(
|
||||
focusBorderColor = scheme.onBackground,
|
||||
overlayBorderWidth = 2.dp,
|
||||
overlayBorderColor = scheme.primary.copy(alpha = 0.95f),
|
||||
focusable = true
|
||||
focusable = true,
|
||||
height = 40.dp,
|
||||
textSize = 13.sp,
|
||||
iconSize = 20.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ internal fun TvMovieScreenContent(
|
||||
) {
|
||||
TvMediaDetailBodyBox(
|
||||
backgroundImageUrl = tvMediaDetailBackgroundImageUrl(movie.imageUrlPrefix),
|
||||
modifier = Modifier
|
||||
modifier = it
|
||||
) {
|
||||
TvMovieHeroSection(
|
||||
movie = movie,
|
||||
@@ -75,27 +75,26 @@ internal fun TvMovieScreenContent(
|
||||
playFocusRequester = playFocusRequester,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
}
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Column(modifier = it.fillMaxWidth()) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
MediaDetailOverviewSection(
|
||||
synopsis = movie.synopsis,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = it.fillMaxWidth()) {
|
||||
MediaDetailPlaybackSection(
|
||||
audioTrack = "ENG",
|
||||
subtitles = "ENG",
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
if (movie.cast.isNotEmpty()) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = it.fillMaxWidth()) {
|
||||
MediaDetailSectionTitle(text = "Cast")
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
// MediaCastRow(cast = movie.cast)
|
||||
@@ -103,3 +102,4 @@ internal fun TvMovieScreenContent(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,29 +37,29 @@ internal fun TvMovieHeroSection(
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.widthIn(max = 760.dp)
|
||||
.widthIn(max = 560.dp)
|
||||
) {
|
||||
Text(
|
||||
text = movie.title,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 42.sp,
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 48.sp,
|
||||
maxLines = 2,
|
||||
lineHeight = 28.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
MediaMetadataFlowRow(
|
||||
items = listOf(movie.year, movie.rating, movie.runtime, movie.format),
|
||||
highlightedItem = movie.format
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
MediaResumeButton(
|
||||
text = mediaPlayButtonText(movie.progress, movie.watched),
|
||||
progress = mediaPlaybackProgress(movie.progress),
|
||||
onClick = onPlay,
|
||||
modifier = Modifier
|
||||
.sizeIn(minWidth = 216.dp, maxWidth = 240.dp)
|
||||
.sizeIn(minWidth = 160.dp, maxWidth = 192.dp)
|
||||
.focusRequester(playFocusRequester)
|
||||
.testTag(MoviePlayButtonTag),
|
||||
focusedScale = 1.08f,
|
||||
@@ -68,7 +68,10 @@ internal fun TvMovieHeroSection(
|
||||
focusBorderColor = scheme.onBackground,
|
||||
overlayBorderWidth = 2.dp,
|
||||
overlayBorderColor = scheme.primary.copy(alpha = 0.95f),
|
||||
focusable = true
|
||||
focusable = true,
|
||||
height = 40.dp,
|
||||
textSize = 13.sp,
|
||||
iconSize = 20.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,15 +93,15 @@ internal fun TvSeriesScreenContent(
|
||||
firstContentFocusRequester = firstContentFocusRequester,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
}
|
||||
Column(modifier = it.fillMaxWidth()) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
MediaDetailOverviewSection(
|
||||
synopsis = series.synopsis,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
TvSeasonTabs(
|
||||
seasons = series.seasons,
|
||||
@@ -111,7 +111,7 @@ internal fun TvSeriesScreenContent(
|
||||
onSelect = { selectedSeason = it },
|
||||
modifier = it
|
||||
)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
TvEpisodeCarousel(
|
||||
episodes = selectedSeason.episodes,
|
||||
modifier = it
|
||||
@@ -128,7 +128,6 @@ internal fun TvSeriesScreenContent(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun Series.defaultSeason(): Season {
|
||||
for (season in seasons) {
|
||||
if (season.episodes.any { !it.watched }) {
|
||||
|
||||
@@ -191,54 +191,53 @@ internal fun TvSeriesHeroSection(
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.widthIn(max = 760.dp)
|
||||
.widthIn(max = 560.dp)
|
||||
) {
|
||||
Text(
|
||||
text = series.name,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 42.sp,
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 48.sp,
|
||||
maxLines = 2,
|
||||
lineHeight = 28.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
TvSeriesMetaChips(series = series)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
if (nextUpEpisode != null) {
|
||||
Text(
|
||||
text = nextUpEpisode.heroStatusText(),
|
||||
color = scheme.primary,
|
||||
fontSize = 18.sp,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = nextUpEpisode.title,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 22.sp,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 2,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = "Episode ${nextUpEpisode.index} • ${nextUpEpisode.runtime}",
|
||||
color = mutedStrong,
|
||||
fontSize = 14.sp,
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
MediaResumeButton(
|
||||
text = mediaPlayButtonText(nextUpEpisode.progress, nextUpEpisode.watched),
|
||||
progress = mediaPlaybackProgress(nextUpEpisode.progress),
|
||||
onClick = { onPlayEpisode(nextUpEpisode) },
|
||||
modifier = Modifier
|
||||
.sizeIn(minWidth = 216.dp, maxWidth = 240.dp)
|
||||
.sizeIn(minWidth = 160.dp, maxWidth = 192.dp)
|
||||
.focusRequester(playFocusRequester)
|
||||
.focusProperties { down = firstContentFocusRequester }
|
||||
.testTag(SeriesPlayButtonTag),
|
||||
@@ -248,13 +247,16 @@ internal fun TvSeriesHeroSection(
|
||||
focusBorderColor = scheme.onBackground,
|
||||
overlayBorderWidth = 2.dp,
|
||||
overlayBorderColor = scheme.primary.copy(alpha = 0.95f),
|
||||
focusable = true
|
||||
focusable = true,
|
||||
height = 40.dp,
|
||||
textSize = 13.sp,
|
||||
iconSize = 20.dp
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "Choose a season below to start watching.",
|
||||
color = mutedStrong,
|
||||
fontSize = 16.sp,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -35,6 +36,7 @@ import androidx.compose.ui.graphics.drawscope.clipRect
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@@ -50,7 +52,10 @@ fun MediaResumeButton(
|
||||
focusBorderColor: Color = Color.Transparent,
|
||||
overlayBorderWidth: Dp = 0.dp,
|
||||
overlayBorderColor: Color = Color.Transparent,
|
||||
focusable: Boolean = false
|
||||
focusable: Boolean = false,
|
||||
height: Dp = 52.dp,
|
||||
textSize: TextUnit = 16.sp,
|
||||
iconSize: Dp = 24.dp
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val primaryColor = scheme.primary
|
||||
@@ -73,7 +78,7 @@ fun MediaResumeButton(
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.graphicsLayer { scaleX = scale; scaleY = scale }
|
||||
.height(52.dp)
|
||||
.height(height)
|
||||
.background(haloColor, shape)
|
||||
.border(
|
||||
width = if (isFocused) focusBorderWidth else 0.dp,
|
||||
@@ -91,7 +96,12 @@ fun MediaResumeButton(
|
||||
.background(onPrimaryColor),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ResumeButtonContent(text = text, color = primaryColor)
|
||||
ResumeButtonContent(
|
||||
text = text,
|
||||
color = primaryColor,
|
||||
textSize = textSize,
|
||||
iconSize = iconSize
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
@@ -111,7 +121,12 @@ fun MediaResumeButton(
|
||||
.background(primaryColor),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ResumeButtonContent(text = text, color = onPrimaryColor)
|
||||
ResumeButtonContent(
|
||||
text = text,
|
||||
color = onPrimaryColor,
|
||||
textSize = textSize,
|
||||
iconSize = iconSize
|
||||
)
|
||||
}
|
||||
|
||||
if (isFocused && overlayBorderWidth > 0.dp) {
|
||||
@@ -125,13 +140,23 @@ fun MediaResumeButton(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ResumeButtonContent(text: String, color: Color) {
|
||||
private fun ResumeButtonContent(
|
||||
text: String,
|
||||
color: Color,
|
||||
textSize: TextUnit,
|
||||
iconSize: Dp
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(text, color = color, fontWeight = FontWeight.Bold, fontSize = 16.sp)
|
||||
Text(text, color = color, fontWeight = FontWeight.Bold, fontSize = textSize)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Icon(Icons.Filled.PlayArrow, null, tint = color)
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PlayArrow,
|
||||
contentDescription = null,
|
||||
tint = color,
|
||||
modifier = Modifier.size(iconSize)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user