Remove status and progress overlays from TV hero section

Removes eyebrow text, watched status chips, and progress bars from the
focused hero component on the TV home screen. The underlying model and
UI are simplified to display only the title and basic metadata.
This commit is contained in:
2026-04-06 19:38:27 +02:00
parent f0bb37d8ec
commit 0870e8aee4
2 changed files with 9 additions and 92 deletions

View File

@@ -16,12 +16,8 @@ import kotlin.math.roundToInt
internal data class TvFocusedHeroModel(
val id: UUID,
val backdropImageUrl: String,
val eyebrowText: String,
val title: String,
val metadata: List<String>,
val watchedText: String?,
val progressFraction: Float?,
val progressLabel: String?,
) {
val metadataText: String?
get() = metadata.takeIf { it.isNotEmpty() }?.joinToString("")
@@ -30,16 +26,16 @@ internal data class TvFocusedHeroModel(
internal fun FocusableItem.toTvFocusedHeroModel(): TvFocusedHeroModel {
return when (this) {
is ContinueWatchingItem -> when (type) {
BaseItemKind.MOVIE -> movie!!.toTvFocusedHeroModel(sourceLabel = "Continue watching")
BaseItemKind.EPISODE -> episode!!.toTvFocusedHeroModel(sourceLabel = "Continue watching")
BaseItemKind.MOVIE -> movie!!.toTvFocusedHeroModel()
BaseItemKind.EPISODE -> episode!!.toTvFocusedHeroModel()
else -> unsupportedType(type)
}
is NextUpItem -> episode.toTvFocusedHeroModel(sourceLabel = "Next up")
is NextUpItem -> episode.toTvFocusedHeroModel()
is PosterItem -> when (type) {
BaseItemKind.MOVIE -> movie!!.toTvFocusedHeroModel(sourceLabel = "Movie")
BaseItemKind.EPISODE -> episode!!.toTvFocusedHeroModel(sourceLabel = "Episode")
BaseItemKind.SERIES -> series!!.toTvFocusedHeroModel(sourceLabel = "Series")
BaseItemKind.MOVIE -> movie!!.toTvFocusedHeroModel()
BaseItemKind.EPISODE -> episode!!.toTvFocusedHeroModel()
BaseItemKind.SERIES -> series!!.toTvFocusedHeroModel()
else -> unsupportedType(type)
}
}
@@ -50,32 +46,25 @@ internal fun backdropImageUrl(imageUrlPrefix: String?, fallbackImageUrl: String)
return backdropImageUrl.ifBlank { fallbackImageUrl }
}
private fun Movie.toTvFocusedHeroModel(sourceLabel: String): TvFocusedHeroModel {
val progressFraction = progressFraction(progress)
private fun Movie.toTvFocusedHeroModel(): TvFocusedHeroModel {
return TvFocusedHeroModel(
id = id,
backdropImageUrl = backdropImageUrl(
imageUrlPrefix = imageUrlPrefix,
fallbackImageUrl = JellyfinImageHelper.finishImageUrl(imageUrlPrefix, ImageType.PRIMARY)
),
eyebrowText = sourceLabel,
title = title,
metadata = listOf(year, rating, runtime, format).compactMetadata(),
watchedText = "Watched".takeIf { watched },
progressFraction = progressFraction.takeIf { !watched && it != null },
progressLabel = progressLabel(progressFraction).takeIf { !watched && progressFraction != null },
)
}
private fun Episode.toTvFocusedHeroModel(sourceLabel: String): TvFocusedHeroModel {
val progressFraction = progressFraction(progress)
private fun Episode.toTvFocusedHeroModel(): TvFocusedHeroModel {
return TvFocusedHeroModel(
id = id,
backdropImageUrl = backdropImageUrl(
imageUrlPrefix = imageUrlPrefix,
fallbackImageUrl = JellyfinImageHelper.finishImageUrl(imageUrlPrefix, ImageType.PRIMARY)
),
eyebrowText = sourceLabel,
title = title,
metadata = listOf(
"Episode $index",
@@ -84,13 +73,10 @@ private fun Episode.toTvFocusedHeroModel(sourceLabel: String): TvFocusedHeroMode
rating,
format
).compactMetadata(),
watchedText = "Watched".takeIf { watched },
progressFraction = progressFraction.takeIf { !watched && it != null },
progressLabel = progressLabel(progressFraction).takeIf { !watched && progressFraction != null },
)
}
private fun Series.toTvFocusedHeroModel(sourceLabel: String): TvFocusedHeroModel {
private fun Series.toTvFocusedHeroModel(): TvFocusedHeroModel {
val unwatchedText = if (unwatchedEpisodeCount > 0) {
"$unwatchedEpisodeCount unwatched"
} else {
@@ -103,12 +89,8 @@ private fun Series.toTvFocusedHeroModel(sourceLabel: String): TvFocusedHeroModel
imageUrlPrefix = imageUrlPrefix,
fallbackImageUrl = JellyfinImageHelper.finishImageUrl(imageUrlPrefix, ImageType.PRIMARY)
),
eyebrowText = sourceLabel,
title = name,
metadata = listOf(year, seasonLabel(seasonCount), unwatchedText).compactMetadata(),
watchedText = null,
progressFraction = null,
progressLabel = null,
)
}

View File

@@ -3,39 +3,29 @@ package hu.bbara.purefin.tv.home.ui
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
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.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
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.TextAlign
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.common.ui.components.MediaProgressBar
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
internal const val TvHomeHeroTitleTag = "tv-home-hero-title"
internal const val TvHomeHeroStatusTag = "tv-home-hero-status"
internal const val TvHomeHeroProgressLabelTag = "tv-home-hero-progress-label"
private const val TvHomeHeroAnimationMillis = 180
@@ -107,13 +97,6 @@ internal fun TvFocusedItemHero(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.widthIn(max = 720.dp)
) {
Text(
text = hero.eyebrowText.uppercase(),
color = scheme.primary,
fontSize = 10.sp,
letterSpacing = 1.2.sp,
fontWeight = FontWeight.Bold
)
Text(
text = hero.title,
color = scheme.onBackground,
@@ -134,54 +117,6 @@ internal fun TvFocusedItemHero(
overflow = TextOverflow.Ellipsis
)
}
if (hero.watchedText != null || hero.progressFraction != null) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier
.clip(RoundedCornerShape(22.dp))
.background(scheme.surfaceContainerHigh.copy(alpha = 0.92f))
.border(1.dp, scheme.outlineVariant.copy(alpha = 0.6f), RoundedCornerShape(22.dp))
.padding(horizontal = 12.dp, vertical = 10.dp)
) {
hero.watchedText?.let { watchedText ->
TvHomeMetaChip(
text = watchedText,
highlighted = true,
modifier = Modifier.testTag(TvHomeHeroStatusTag)
)
}
if (hero.progressFraction != null && hero.progressLabel != null) {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.width(188.dp)
) {
Text(
text = "Progress",
color = scheme.onSurfaceVariant.copy(alpha = 0.85f),
fontSize = 10.sp,
fontWeight = FontWeight.Medium
)
MediaProgressBar(
progress = hero.progressFraction,
foregroundColor = scheme.primary,
backgroundColor = scheme.surfaceVariant,
contentPadding = PaddingValues(0.dp),
barHeight = 6.dp,
modifier = Modifier
)
}
Text(
text = hero.progressLabel,
color = scheme.onBackground,
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.End,
modifier = Modifier.testTag(TvHomeHeroProgressLabelTag)
)
}
}
}
}
}
}