mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-24 03:36:51 +00:00
feat(series): improve season and episode loading handling
This commit is contained in:
@@ -13,11 +13,13 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.withFrameNanos
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.tv.material3.MaterialTheme
|
||||||
|
import androidx.tv.material3.Text as TvText
|
||||||
import hu.bbara.purefin.core.feature.content.series.SeriesViewModel
|
import hu.bbara.purefin.core.feature.content.series.SeriesViewModel
|
||||||
import hu.bbara.purefin.model.Episode
|
import hu.bbara.purefin.model.Episode
|
||||||
import hu.bbara.purefin.model.Season
|
import hu.bbara.purefin.model.Season
|
||||||
@@ -49,7 +51,7 @@ fun TvSeriesScreen(
|
|||||||
val series = viewModel.series.collectAsStateWithLifecycle()
|
val series = viewModel.series.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val seriesData = series.value
|
val seriesData = series.value
|
||||||
if (seriesData != null && seriesData.seasons.isNotEmpty()) {
|
if (seriesData != null) {
|
||||||
TvSeriesScreenContent(
|
TvSeriesScreenContent(
|
||||||
series = seriesData,
|
series = seriesData,
|
||||||
onPlayEpisode = viewModel::onPlayEpisode,
|
onPlayEpisode = viewModel::onPlayEpisode,
|
||||||
@@ -74,28 +76,27 @@ internal fun TvSeriesScreenContent(
|
|||||||
) {
|
) {
|
||||||
val defaultSeason = series.defaultSeason(focusedSeasonId)
|
val defaultSeason = series.defaultSeason(focusedSeasonId)
|
||||||
var selectedSeasonId by remember(series.id, focusedSeasonId) {
|
var selectedSeasonId by remember(series.id, focusedSeasonId) {
|
||||||
mutableStateOf(defaultSeason.id)
|
mutableStateOf(defaultSeason?.id)
|
||||||
}
|
}
|
||||||
val selectedSeason = series.seasons.firstOrNull { it.id == selectedSeasonId } ?: defaultSeason
|
val selectedSeason = series.seasons.firstOrNull { it.id == selectedSeasonId } ?: defaultSeason
|
||||||
val initialFocusSeasonId = remember(series.id, focusedSeasonId) { defaultSeason.id }
|
val initialFocusSeasonId = defaultSeason?.id
|
||||||
val initialFocusSeason = series.seasons.firstOrNull { it.id == initialFocusSeasonId } ?: defaultSeason
|
val initialFocusSeason = initialFocusSeasonId?.let { seasonId ->
|
||||||
val initialFocusedEpisodeId = initialFocusSeason.focusTargetEpisodeId(focusedEpisodeId)
|
series.seasons.firstOrNull { it.id == seasonId }
|
||||||
|
} ?: defaultSeason
|
||||||
|
val initialFocusedEpisodeId = initialFocusSeason?.focusTargetEpisodeId(focusedEpisodeId)
|
||||||
val seasonTabFocusRequester = remember { FocusRequester() }
|
val seasonTabFocusRequester = remember { FocusRequester() }
|
||||||
var requestedInitialEpisodeFocus by remember(series.id, focusedSeasonId, focusedEpisodeId) {
|
var requestedInitialEpisodeFocus by remember(series.id, focusedSeasonId, focusedEpisodeId) {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
var requestedInitialSeasonFocus by remember(series.id, focusedSeasonId, focusedEpisodeId) {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
val waitingForInitialEpisodes = initialFocusedEpisodeId == null &&
|
val waitingForInitialEpisodes = initialFocusedEpisodeId == null &&
|
||||||
initialFocusSeason.episodes.isEmpty() &&
|
initialFocusSeason?.episodes?.isEmpty() == true &&
|
||||||
initialFocusSeason.episodeCount > 0
|
initialFocusSeason.episodeCount > 0
|
||||||
|
|
||||||
LaunchedEffect(series.id, selectedSeason.id) {
|
LaunchedEffect(series.id, selectedSeason?.id) {
|
||||||
onLoadSeasonEpisodes(series.id, selectedSeason.id)
|
selectedSeason?.let { onLoadSeasonEpisodes(series.id, it.id) }
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(series.id, initialFocusedEpisodeId, waitingForInitialEpisodes) {
|
|
||||||
if (initialFocusedEpisodeId != null || waitingForInitialEpisodes) return@LaunchedEffect
|
|
||||||
withFrameNanos { }
|
|
||||||
seasonTabFocusRequester.requestFocus()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TvMediaDetailScaffold(
|
TvMediaDetailScaffold(
|
||||||
@@ -117,35 +118,48 @@ internal fun TvSeriesScreenContent(
|
|||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
TvSeasonTabs(
|
if (selectedSeason != null) {
|
||||||
seasons = series.seasons,
|
TvSeasonTabs(
|
||||||
selectedSeason = selectedSeason,
|
seasons = series.seasons,
|
||||||
selectedItemFocusRequester = seasonTabFocusRequester,
|
selectedSeason = selectedSeason,
|
||||||
firstItemTestTag = SeriesFirstSeasonTabTag,
|
selectedItemFocusRequester = seasonTabFocusRequester,
|
||||||
onSelect = { selectedSeasonId = it.id },
|
firstItemTestTag = SeriesFirstSeasonTabTag,
|
||||||
modifier = Modifier.fillMaxWidth()
|
requestSelectedItemFocus = initialFocusedEpisodeId == null &&
|
||||||
)
|
!waitingForInitialEpisodes &&
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
!requestedInitialSeasonFocus,
|
||||||
TvEpisodeCarousel(
|
onSelectedItemFocusRequested = { requestedInitialSeasonFocus = true },
|
||||||
episodes = selectedSeason.episodes,
|
onSelect = { selectedSeasonId = it.id },
|
||||||
onPlayEpisode = { onPlayEpisode(it.id) },
|
modifier = Modifier.fillMaxWidth()
|
||||||
focusedEpisodeId = initialFocusedEpisodeId,
|
)
|
||||||
requestFocus = selectedSeason.id == initialFocusSeasonId && !requestedInitialEpisodeFocus,
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
onFocusRequested = { requestedInitialEpisodeFocus = true },
|
TvEpisodeCarousel(
|
||||||
upFocusRequester = seasonTabFocusRequester,
|
episodes = selectedSeason.episodes,
|
||||||
modifier = Modifier.fillMaxWidth()
|
onPlayEpisode = { onPlayEpisode(it.id) },
|
||||||
)
|
focusedEpisodeId = initialFocusedEpisodeId,
|
||||||
|
requestFocus = selectedSeason.id == initialFocusSeasonId && !requestedInitialEpisodeFocus,
|
||||||
|
onFocusRequested = { requestedInitialEpisodeFocus = true },
|
||||||
|
upFocusRequester = seasonTabFocusRequester,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
TvText(
|
||||||
|
text = "Loading seasons...",
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Series.defaultSeason(focusedSeasonId: UUID?): Season {
|
private fun Series.defaultSeason(focusedSeasonId: UUID?): Season? {
|
||||||
if (focusedSeasonId != null) {
|
if (focusedSeasonId != null) {
|
||||||
seasons.firstOrNull { it.id == focusedSeasonId }?.let { return it }
|
seasons.firstOrNull { it.id == focusedSeasonId }?.let { return it }
|
||||||
}
|
}
|
||||||
|
|
||||||
return seasons.firstOrNull { it.unwatchedEpisodeCount > 0 } ?: seasons.first()
|
return seasons.firstOrNull { it.unwatchedEpisodeCount > 0 } ?: seasons.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Season.nextUpEpisode(): Episode? {
|
private fun Season.nextUpEpisode(): Episode? {
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ 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.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -45,6 +45,7 @@ import androidx.compose.ui.focus.focusProperties
|
|||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.focusRestorer
|
import androidx.compose.ui.focus.focusRestorer
|
||||||
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,9 +53,6 @@ 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.core.image.ArtworkKind
|
import hu.bbara.purefin.core.image.ArtworkKind
|
||||||
import hu.bbara.purefin.core.image.ImageUrlBuilder
|
import hu.bbara.purefin.core.image.ImageUrlBuilder
|
||||||
import hu.bbara.purefin.model.CastMember
|
import hu.bbara.purefin.model.CastMember
|
||||||
@@ -80,18 +78,32 @@ internal fun TvSeriesMetaChips(series: Series) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
internal fun TvSeasonTabs(
|
internal fun TvSeasonTabs(
|
||||||
seasons: List<Season>,
|
seasons: List<Season>,
|
||||||
selectedSeason: Season?,
|
selectedSeason: Season?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
selectedItemFocusRequester: FocusRequester? = null,
|
selectedItemFocusRequester: FocusRequester? = null,
|
||||||
firstItemTestTag: String? = null,
|
firstItemTestTag: String? = null,
|
||||||
|
requestSelectedItemFocus: Boolean = false,
|
||||||
|
onSelectedItemFocusRequested: () -> Unit = {},
|
||||||
onSelect: (Season) -> Unit
|
onSelect: (Season) -> Unit
|
||||||
) {
|
) {
|
||||||
val selectedSeasonIndex = seasons.indexOf(selectedSeason).coerceAtLeast(0)
|
val selectedSeasonIndex = seasons.indexOf(selectedSeason).coerceAtLeast(0)
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
TabRow(
|
LaunchedEffect(selectedSeasonIndex, requestSelectedItemFocus, seasons.size) {
|
||||||
selectedTabIndex = selectedSeasonIndex,
|
if (seasons.isEmpty()) return@LaunchedEffect
|
||||||
|
listState.scrollToItem(selectedSeasonIndex)
|
||||||
|
if (requestSelectedItemFocus && selectedItemFocusRequester != null) {
|
||||||
|
withFrameNanos { }
|
||||||
|
selectedItemFocusRequester.requestFocus()
|
||||||
|
onSelectedItemFocusRequested()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyRow(
|
||||||
|
state = listState,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.then(
|
.then(
|
||||||
if (selectedItemFocusRequester != null) {
|
if (selectedItemFocusRequester != null) {
|
||||||
@@ -101,16 +113,16 @@ internal fun TvSeasonTabs(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.focusGroup()
|
.focusGroup()
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
.wrapContentWidth(Alignment.Start),
|
horizontalArrangement = Arrangement.spacedBy(0.dp),
|
||||||
contentColor = MaterialTheme.colorScheme.onSurface
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
seasons.forEachIndexed { index, season ->
|
itemsIndexed(seasons, key = { _, season -> season.id }) { index, season ->
|
||||||
Tab(
|
TvSeasonTab(
|
||||||
selected = index == selectedSeasonIndex,
|
name = season.name,
|
||||||
onFocus = { onSelect(season) },
|
isSelected = index == selectedSeasonIndex,
|
||||||
|
onFocused = { onSelect(season) },
|
||||||
onClick = { onSelect(season) },
|
onClick = { onSelect(season) },
|
||||||
colors = TabDefaults.underlinedIndicatorTabColors(),
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.then(
|
.then(
|
||||||
if (index == selectedSeasonIndex && selectedItemFocusRequester != null) {
|
if (index == selectedSeasonIndex && selectedItemFocusRequester != null) {
|
||||||
@@ -126,24 +138,48 @@ internal fun TvSeasonTabs(
|
|||||||
Modifier
|
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,
|
||||||
|
onFocused: () -> Unit,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
val color = if (isSelected) scheme.primary else scheme.onSurface
|
||||||
|
val indicatorColor = if (isSelected) scheme.primary else Color.Transparent
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.onFocusChanged { state ->
|
||||||
|
if (state.isFocused) onFocused()
|
||||||
|
}
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
) {
|
||||||
|
TvText(
|
||||||
|
text = name,
|
||||||
|
color = color,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Medium,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(2.dp)
|
||||||
|
.background(indicatorColor)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun TvEpisodeCarousel(
|
internal fun TvEpisodeCarousel(
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ fun SeriesScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val seriesData = seriesState.value
|
val seriesData = seriesState.value
|
||||||
if (seriesData != null && seriesData.seasons.isNotEmpty()) {
|
if (seriesData != null) {
|
||||||
LaunchedEffect(seriesData) {
|
LaunchedEffect(seriesData) {
|
||||||
viewModel.observeSeriesDownloadState(seriesData)
|
viewModel.observeSeriesDownloadState(seriesData)
|
||||||
}
|
}
|
||||||
@@ -170,22 +170,22 @@ private fun SeriesScreenInternal(
|
|||||||
) {
|
) {
|
||||||
val scheme = MaterialTheme.colorScheme
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
|
||||||
fun getDefaultSeason(): Season {
|
fun getDefaultSeason(): Season? {
|
||||||
return series.seasons.firstOrNull { it.unwatchedEpisodeCount > 0 } ?: series.seasons.first()
|
return series.seasons.firstOrNull { it.unwatchedEpisodeCount > 0 } ?: series.seasons.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectedSeasonId by remember(series.id) { mutableStateOf(getDefaultSeason().id) }
|
var selectedSeasonId by remember(series.id) { mutableStateOf(getDefaultSeason()?.id) }
|
||||||
val selectedSeason =
|
val selectedSeason =
|
||||||
series.seasons.firstOrNull { it.id == selectedSeasonId } ?: getDefaultSeason()
|
series.seasons.firstOrNull { it.id == selectedSeasonId } ?: getDefaultSeason()
|
||||||
val nextUpEpisode = selectedSeason.episodes.firstOrNull { !it.watched }
|
val nextUpEpisode = selectedSeason?.episodes?.firstOrNull { !it.watched }
|
||||||
?: selectedSeason.episodes.firstOrNull()
|
?: selectedSeason?.episodes?.firstOrNull()
|
||||||
|
|
||||||
LaunchedEffect(series.id, selectedSeason.id) {
|
LaunchedEffect(series.id, selectedSeason?.id) {
|
||||||
onLoadSeasonEpisodes(series.id, selectedSeason.id)
|
selectedSeason?.let { onLoadSeasonEpisodes(series.id, it.id) }
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(selectedSeason.id, selectedSeason.episodes) {
|
LaunchedEffect(selectedSeason?.id, selectedSeason?.episodes) {
|
||||||
onObserveSeasonDownloadState(selectedSeason.episodes)
|
selectedSeason?.let { onObserveSeasonDownloadState(it.episodes) }
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaDetailScaffold(
|
MediaDetailScaffold(
|
||||||
@@ -198,17 +198,19 @@ private fun SeriesScreenInternal(
|
|||||||
SeriesHeroContent(series = series)
|
SeriesHeroContent(series = series)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
SeriesActionButtons(
|
if (selectedSeason != null) {
|
||||||
nextUpEpisode = nextUpEpisode,
|
SeriesActionButtons(
|
||||||
selectedSeason = selectedSeason,
|
nextUpEpisode = nextUpEpisode,
|
||||||
seriesDownloadState = seriesDownloadState,
|
selectedSeason = selectedSeason,
|
||||||
seasonDownloadState = seasonDownloadState,
|
seriesDownloadState = seriesDownloadState,
|
||||||
isSmartDownloadEnabled = isSmartDownloadEnabled,
|
seasonDownloadState = seasonDownloadState,
|
||||||
offline = offline,
|
isSmartDownloadEnabled = isSmartDownloadEnabled,
|
||||||
onDownloadOptionSelected = { option ->
|
offline = offline,
|
||||||
onDownloadOptionSelected(option, selectedSeason)
|
onDownloadOptionSelected = { option ->
|
||||||
}
|
onDownloadOptionSelected(option, selectedSeason)
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
MediaSynopsis(
|
MediaSynopsis(
|
||||||
synopsis = series.synopsis,
|
synopsis = series.synopsis,
|
||||||
bodyColor = scheme.onSurface,
|
bodyColor = scheme.onSurface,
|
||||||
@@ -216,14 +218,22 @@ private fun SeriesScreenInternal(
|
|||||||
bodyLineHeight = null,
|
bodyLineHeight = null,
|
||||||
titleSpacing = 8.dp
|
titleSpacing = 8.dp
|
||||||
)
|
)
|
||||||
SeasonTabs(
|
if (selectedSeason != null) {
|
||||||
seasons = series.seasons,
|
SeasonTabs(
|
||||||
selectedSeason = selectedSeason,
|
seasons = series.seasons,
|
||||||
onSelect = { selectedSeasonId = it.id }
|
selectedSeason = selectedSeason,
|
||||||
)
|
onSelect = { selectedSeasonId = it.id }
|
||||||
EpisodeCarousel(
|
)
|
||||||
episodes = selectedSeason.episodes,
|
EpisodeCarousel(
|
||||||
)
|
episodes = selectedSeason.episodes,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = "Loading seasons...",
|
||||||
|
color = scheme.onSurfaceVariant,
|
||||||
|
fontSize = 13.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
if (series.cast.isNotEmpty()) {
|
if (series.cast.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = "Cast",
|
text = "Cast",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.content.Intent
|
|||||||
import androidx.compose.foundation.background
|
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.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
|
||||||
@@ -20,8 +19,8 @@ import androidx.compose.foundation.layout.sizeIn
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
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.itemsIndexed
|
||||||
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.Add
|
import androidx.compose.material.icons.outlined.Add
|
||||||
@@ -333,14 +332,23 @@ internal fun SeasonTabs(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onSelect: (Season) -> Unit
|
onSelect: (Season) -> Unit
|
||||||
) {
|
) {
|
||||||
Row(
|
val selectedSeasonIndex = seasons.indexOf(selectedSeason).coerceAtLeast(0)
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
LaunchedEffect(selectedSeasonIndex, seasons.size) {
|
||||||
|
if (seasons.isNotEmpty()) {
|
||||||
|
listState.scrollToItem(selectedSeasonIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyRow(
|
||||||
|
state = listState,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
.horizontalScroll(rememberScrollState()),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(20.dp),
|
horizontalArrangement = Arrangement.spacedBy(20.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
seasons.forEachIndexed { index, season ->
|
itemsIndexed(seasons, key = { _, season -> season.id }) { index, season ->
|
||||||
SeasonTab(
|
SeasonTab(
|
||||||
name = season.name,
|
name = season.name,
|
||||||
isSelected = season == selectedSeason,
|
isSelected = season == selectedSeason,
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ class JellyfinApiClient @Inject constructor(
|
|||||||
userId = getUserId(),
|
userId = getUserId(),
|
||||||
seriesId = seriesId,
|
seriesId = seriesId,
|
||||||
fields = defaultItemFields,
|
fields = defaultItemFields,
|
||||||
enableUserData = true,
|
enableUserData = false,
|
||||||
)
|
)
|
||||||
result.content.items
|
result.content.items
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user