refactor(tv): replace custom season tab implementation with TabRow for improved UI consistency

This commit is contained in:
2026-05-01 13:06:01 +02:00
parent 2ff6fe997e
commit 6341510cc2
2 changed files with 39 additions and 50 deletions

View File

@@ -94,7 +94,7 @@ internal fun TvSeriesScreenContent(
series = series, series = series,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(18.dp)) Spacer(modifier = Modifier.height(16.dp))
TvSeasonTabs( TvSeasonTabs(
seasons = series.seasons, seasons = series.seasons,
selectedSeason = selectedSeason, selectedSeason = selectedSeason,
@@ -103,7 +103,7 @@ internal fun TvSeriesScreenContent(
onSelect = { selectedSeason = it }, onSelect = { selectedSeason = it },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(16.dp))
TvEpisodeCarousel( TvEpisodeCarousel(
episodes = selectedSeason.episodes, episodes = selectedSeason.episodes,
onPlayEpisode = { onPlayEpisode(it.id) }, onPlayEpisode = { onPlayEpisode(it.id) },

View File

@@ -6,12 +6,10 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.LocalBringIntoViewSpec import androidx.compose.foundation.gestures.LocalBringIntoViewSpec
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -20,10 +18,10 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.PlayCircle import androidx.compose.material.icons.outlined.PlayCircle
@@ -44,7 +42,6 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
@@ -52,6 +49,9 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow 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 androidx.compose.ui.unit.sp
import androidx.tv.material3.Tab
import androidx.tv.material3.TabDefaults
import androidx.tv.material3.TabRow
import hu.bbara.purefin.image.ArtworkKind import hu.bbara.purefin.image.ArtworkKind
import hu.bbara.purefin.image.ImageUrlBuilder import hu.bbara.purefin.image.ImageUrlBuilder
import hu.bbara.purefin.model.CastMember import hu.bbara.purefin.model.CastMember
@@ -64,6 +64,7 @@ import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow import hu.bbara.purefin.ui.common.media.MediaMetadataFlowRow
import hu.bbara.purefin.ui.screen.home.components.TvHomeRowBringIntoViewSpec import hu.bbara.purefin.ui.screen.home.components.TvHomeRowBringIntoViewSpec
import java.util.UUID import java.util.UUID
import androidx.tv.material3.Text as TvText
internal const val SeriesFirstSeasonTabTag = "series-first-season-tab" internal const val SeriesFirstSeasonTabTag = "series-first-season-tab"
internal const val SeriesNextUpEpisodeCardTag = "series-next-up-episode-card" internal const val SeriesNextUpEpisodeCardTag = "series-next-up-episode-card"
@@ -84,17 +85,21 @@ internal fun TvSeasonTabs(
firstItemTestTag: String? = null, firstItemTestTag: String? = null,
onSelect: (Season) -> Unit onSelect: (Season) -> Unit
) { ) {
Row( val selectedSeasonIndex = seasons.indexOf(selectedSeason).coerceAtLeast(0)
TabRow(
selectedTabIndex = selectedSeasonIndex,
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.horizontalScroll(rememberScrollState()), .wrapContentWidth(Alignment.Start),
horizontalArrangement = Arrangement.spacedBy(20.dp) contentColor = MaterialTheme.colorScheme.onSurface
) { ) {
seasons.forEachIndexed { index, season -> seasons.forEachIndexed { index, season ->
TvSeasonTab( Tab(
name = season.name, selected = index == selectedSeasonIndex,
isSelected = season == selectedSeason, onFocus = { onSelect(season) },
onSelect = { onSelect(season) }, onClick = { onSelect(season) },
colors = TabDefaults.underlinedIndicatorTabColors(),
modifier = Modifier modifier = Modifier
.then( .then(
if (index == 0 && firstItemFocusRequester != null) { if (index == 0 && firstItemFocusRequester != null) {
@@ -103,47 +108,31 @@ internal fun TvSeasonTabs(
Modifier Modifier
} }
) )
) .then(
if (index == 0 && firstItemTestTag != null) {
Modifier.testTag(firstItemTestTag)
} else {
Modifier
}
)
) {
TvText(
text = season.name,
fontSize = 13.sp,
fontWeight = if (index == selectedSeasonIndex) {
FontWeight.Bold
} else {
FontWeight.Medium
},
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
} }
} }
} }
@Composable
private fun TvSeasonTab(
name: String,
isSelected: Boolean,
onSelect: () -> Unit,
modifier: Modifier = Modifier
) {
val scheme = MaterialTheme.colorScheme
val mutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f)
var isFocused by remember { mutableStateOf(false) }
val color = if (isSelected || isFocused) scheme.primary else mutedStrong
val underlineColor = if (isSelected || isFocused) scheme.primary else Color.Transparent
val underlineHeight = if (isFocused) 3.dp else 2.dp
Column(
modifier = modifier
.padding(bottom = 8.dp)
.onFocusChanged { isFocused = it.isFocused }
.clickable { onSelect() }
) {
Text(
text = name,
color = color,
fontSize = 13.sp,
fontWeight = if (isSelected || isFocused) FontWeight.Bold else FontWeight.Medium
)
Spacer(modifier = Modifier.height(8.dp))
Box(
modifier = Modifier
.height(underlineHeight)
.width(52.dp)
.background(underlineColor)
)
}
}
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
internal fun TvEpisodeCarousel( internal fun TvEpisodeCarousel(