mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-04 09:19:06 +00:00
feat: auto scroll to actively watching season
This commit is contained in:
parent
61a558842f
commit
55d9bbe246
3 changed files with 97 additions and 19 deletions
|
|
@ -501,6 +501,7 @@ fun MetaDetailsScreen(
|
|||
isSaved = isSaved,
|
||||
onPrimaryPlayClick = onPrimaryPlayClick,
|
||||
onSaveClick = toggleSaved,
|
||||
preferredEpisodeSeasonNumber = seriesAction?.seasonNumber,
|
||||
hasProductionSection = hasProductionSection,
|
||||
hasTrailersSection = hasTrailersSection,
|
||||
hasEpisodes = hasEpisodes,
|
||||
|
|
@ -795,6 +796,7 @@ private fun ConfiguredMetaSections(
|
|||
isSaved: Boolean,
|
||||
onPrimaryPlayClick: () -> Unit,
|
||||
onSaveClick: () -> Unit,
|
||||
preferredEpisodeSeasonNumber: Int?,
|
||||
hasProductionSection: Boolean,
|
||||
hasTrailersSection: Boolean,
|
||||
hasEpisodes: Boolean,
|
||||
|
|
@ -887,6 +889,7 @@ private fun ConfiguredMetaSections(
|
|||
DetailSeriesContent(
|
||||
meta = meta,
|
||||
showHeader = showHeader,
|
||||
preferredSeasonNumber = preferredEpisodeSeasonNumber,
|
||||
progressByVideoId = progressByVideoId,
|
||||
watchedKeys = watchedKeys,
|
||||
onEpisodeClick = onEpisodeClick,
|
||||
|
|
|
|||
|
|
@ -27,11 +27,15 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -71,6 +75,7 @@ fun DetailSeriesContent(
|
|||
meta: MetaDetails,
|
||||
modifier: Modifier = Modifier,
|
||||
showHeader: Boolean = true,
|
||||
preferredSeasonNumber: Int? = null,
|
||||
progressByVideoId: Map<String, WatchProgressEntry> = emptyMap(),
|
||||
watchedKeys: Set<String> = emptySet(),
|
||||
onEpisodeClick: ((MetaVideo) -> Unit)? = null,
|
||||
|
|
@ -136,9 +141,13 @@ fun DetailSeriesContent(
|
|||
}
|
||||
|
||||
val seasons = groupedEpisodes.keys.sortedBy(::seasonSortKey)
|
||||
val defaultSeason = seasons.first()
|
||||
var selectedSeason by rememberSaveable(meta.id) { mutableStateOf(defaultSeason) }
|
||||
val currentSeason = selectedSeason.takeIf { it in groupedEpisodes } ?: defaultSeason
|
||||
val defaultSeason = preferredSeasonNumber
|
||||
?.takeIf { it in groupedEpisodes }
|
||||
?: seasons.first()
|
||||
var selectedSeasonOverride by rememberSaveable(meta.id) { mutableStateOf<Int?>(null) }
|
||||
val currentSeason = selectedSeasonOverride
|
||||
?.takeIf { it in groupedEpisodes }
|
||||
?: defaultSeason
|
||||
|
||||
var seasonViewMode by remember {
|
||||
mutableStateOf(SeasonViewModeStorage.load() ?: SeasonViewMode.Posters)
|
||||
|
|
@ -199,13 +208,13 @@ fun DetailSeriesContent(
|
|||
meta = meta,
|
||||
currentSeason = currentSeason,
|
||||
sizing = sizing,
|
||||
onSelect = { selectedSeason = it },
|
||||
onSelect = { selectedSeasonOverride = it },
|
||||
)
|
||||
SeasonViewMode.Text -> SeasonTextChipScrollRow(
|
||||
seasons = seasons,
|
||||
currentSeason = currentSeason,
|
||||
sizing = sizing,
|
||||
onSelect = { selectedSeason = it },
|
||||
onSelect = { selectedSeasonOverride = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -214,7 +223,7 @@ fun DetailSeriesContent(
|
|||
seasons = seasons,
|
||||
currentSeason = currentSeason,
|
||||
sizing = sizing,
|
||||
onSelect = { selectedSeason = it },
|
||||
onSelect = { selectedSeasonOverride = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -325,13 +334,27 @@ private fun SeasonTextChipScrollRow(
|
|||
sizing: SeriesContentSizing,
|
||||
onSelect: (Int) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState()),
|
||||
val seasonListState = rememberLazyListState()
|
||||
var hasPositionedSeasonRow by remember(seasons) { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(seasons, currentSeason) {
|
||||
val currentIndex = seasons.indexOf(currentSeason)
|
||||
if (currentIndex >= 0) {
|
||||
if (hasPositionedSeasonRow) {
|
||||
seasonListState.animateScrollToItem(currentIndex)
|
||||
} else {
|
||||
seasonListState.scrollToItem(currentIndex)
|
||||
hasPositionedSeasonRow = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyRow(
|
||||
state = seasonListState,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(sizing.seasonChipGap),
|
||||
) {
|
||||
seasons.forEach { season ->
|
||||
items(seasons, key = { season -> season }) { season ->
|
||||
val isSelected = season == currentSeason
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -376,13 +399,27 @@ private fun SeasonPosterScrollRow(
|
|||
sizing: SeriesContentSizing,
|
||||
onSelect: (Int) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState()),
|
||||
val seasonListState = rememberLazyListState()
|
||||
var hasPositionedSeasonRow by remember(seasons) { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(seasons, currentSeason) {
|
||||
val currentIndex = seasons.indexOf(currentSeason)
|
||||
if (currentIndex >= 0) {
|
||||
if (hasPositionedSeasonRow) {
|
||||
seasonListState.animateScrollToItem(currentIndex)
|
||||
} else {
|
||||
seasonListState.scrollToItem(currentIndex)
|
||||
hasPositionedSeasonRow = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyRow(
|
||||
state = seasonListState,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(sizing.seasonChipGap),
|
||||
) {
|
||||
seasons.forEach { season ->
|
||||
items(seasons, key = { season -> season }) { season ->
|
||||
SeasonPosterButton(
|
||||
label = season.label(),
|
||||
imageUrl = groupedEpisodes[season]
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
|
@ -37,6 +39,7 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -183,6 +186,40 @@ private fun EpisodesListSubView(
|
|||
(groupedEpisodes[selectedSeason] ?: emptyList())
|
||||
.sortedBy { it.episode ?: 0 }
|
||||
}
|
||||
val seasonListState = rememberLazyListState()
|
||||
val episodeListState = rememberLazyListState()
|
||||
var hasPositionedSeasonRow by remember(availableSeasons) { mutableStateOf(false) }
|
||||
var hasPositionedEpisodeList by remember(selectedSeason) { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(selectedSeason, availableSeasons) {
|
||||
val selectedSeasonIndex = availableSeasons.indexOf(selectedSeason)
|
||||
if (selectedSeasonIndex >= 0) {
|
||||
if (hasPositionedSeasonRow) {
|
||||
seasonListState.animateScrollToItem(selectedSeasonIndex)
|
||||
} else {
|
||||
seasonListState.scrollToItem(selectedSeasonIndex)
|
||||
hasPositionedSeasonRow = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(selectedSeason, seasonEpisodes, currentSeason, currentEpisode) {
|
||||
if (seasonEpisodes.isEmpty()) return@LaunchedEffect
|
||||
val activeEpisodeIndex = if (selectedSeason == currentSeason && currentEpisode != null) {
|
||||
seasonEpisodes.indexOfFirst { episode ->
|
||||
episode.season == currentSeason && episode.episode == currentEpisode
|
||||
}
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
val targetIndex = activeEpisodeIndex.takeIf { it >= 0 } ?: 0
|
||||
if (hasPositionedEpisodeList) {
|
||||
episodeListState.animateScrollToItem(targetIndex)
|
||||
} else {
|
||||
episodeListState.scrollToItem(targetIndex)
|
||||
hasPositionedEpisodeList = true
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
// Header
|
||||
|
|
@ -204,15 +241,15 @@ private fun EpisodesListSubView(
|
|||
|
||||
// Season tabs
|
||||
if (availableSeasons.size > 1) {
|
||||
Row(
|
||||
LazyRow(
|
||||
state = seasonListState,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(horizontal = 20.dp)
|
||||
.padding(bottom = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
availableSeasons.forEach { season ->
|
||||
items(availableSeasons, key = { season -> season }) { season ->
|
||||
val label = if (season == 0) "Specials" else "Season $season"
|
||||
AddonFilterChip(
|
||||
label = label,
|
||||
|
|
@ -242,6 +279,7 @@ private fun EpisodesListSubView(
|
|||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
state = episodeListState,
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(bottom = 16.dp),
|
||||
|
|
|
|||
Loading…
Reference in a new issue