mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-26 14:32:28 +00:00
feat: add separate upcoming continue watching row
This commit is contained in:
parent
0ac38dce43
commit
1e4c125ca1
7 changed files with 249 additions and 54 deletions
|
|
@ -589,6 +589,9 @@
|
|||
<string name="settings_continue_watching_sort_mode_default_desc">Sortuj wszystkie elementy według czasu</string>
|
||||
<string name="settings_continue_watching_sort_mode_streaming">Styl streamingowy</string>
|
||||
<string name="settings_continue_watching_sort_mode_streaming_desc">Wydane najpierw, nadchodzące na końcu</string>
|
||||
<string name="settings_continue_watching_sort_mode_split_upcoming">Oddzielny rząd Nadchodzące</string>
|
||||
<string name="settings_continue_watching_sort_mode_split_upcoming_desc">Przenieś niewydane odcinki do oddzielnego rzędu Nadchodzące</string>
|
||||
<string name="upcoming_section_title">Nadchodzące</string>
|
||||
<string name="settings_continue_watching_section_card_style">STYL KARTY</string>
|
||||
<string name="settings_continue_watching_section_on_launch">PRZY URUCHOMIENIU</string>
|
||||
<string name="settings_continue_watching_section_up_next_behavior">ZACHOWANIE NASTĘPNEGO</string>
|
||||
|
|
|
|||
|
|
@ -681,6 +681,9 @@
|
|||
<string name="settings_continue_watching_sort_mode_default_desc">Sort all items by recency</string>
|
||||
<string name="settings_continue_watching_sort_mode_streaming">Streaming Style</string>
|
||||
<string name="settings_continue_watching_sort_mode_streaming_desc">Released items first, upcoming at the end</string>
|
||||
<string name="settings_continue_watching_sort_mode_split_upcoming">Separate Upcoming Row</string>
|
||||
<string name="settings_continue_watching_sort_mode_split_upcoming_desc">Move unaired episodes to a separate Upcoming row</string>
|
||||
<string name="upcoming_section_title">Upcoming</string>
|
||||
<string name="settings_continue_watching_section_card_style">Poster Card Style</string>
|
||||
<string name="settings_continue_watching_section_on_launch">ON LAUNCH</string>
|
||||
<string name="settings_continue_watching_section_up_next_behavior">NEXT UP BEHAVIOR</string>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.nuvio.app.features.home
|
|||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
|
|
@ -13,6 +15,7 @@ import androidx.compose.runtime.snapshotFlow
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.nuvio.app.core.auth.AuthRepository
|
||||
|
|
@ -45,6 +48,7 @@ import com.nuvio.app.features.home.components.HomeHeroSection
|
|||
import com.nuvio.app.features.home.components.HomeSkeletonHero
|
||||
import com.nuvio.app.features.home.components.HomeSkeletonRow
|
||||
import com.nuvio.app.features.home.components.HomeContinueWatchingSectionBottomPadding
|
||||
import com.nuvio.app.features.home.components.ContinueWatchingLayout
|
||||
import com.nuvio.app.features.trakt.TRAKT_CONTINUE_WATCHING_DAYS_CAP_ALL
|
||||
import com.nuvio.app.features.trakt.TraktSettingsRepository
|
||||
import com.nuvio.app.features.trakt.WatchProgressSource
|
||||
|
|
@ -58,11 +62,13 @@ import com.nuvio.app.features.watchprogress.CachedNextUpItem
|
|||
import com.nuvio.app.features.watchprogress.ContinueWatchingEnrichmentCache
|
||||
import com.nuvio.app.features.watchprogress.CurrentDateProvider
|
||||
import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepository
|
||||
import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesUiState
|
||||
import com.nuvio.app.features.watchprogress.ContinueWatchingItem
|
||||
import com.nuvio.app.features.watchprogress.ContinueWatchingSortMode
|
||||
import com.nuvio.app.features.watchprogress.isMalformedNextUpSeedContentId
|
||||
import com.nuvio.app.features.watchprogress.isSeriesTypeForContinueWatching
|
||||
import com.nuvio.app.features.watchprogress.nextUpDismissKey
|
||||
import com.nuvio.app.features.watchprogress.parseReleaseDateToEpochMs
|
||||
import com.nuvio.app.features.watchprogress.resolvedProgressKey
|
||||
import com.nuvio.app.features.watchprogress.shouldTreatAsInProgressForContinueWatching
|
||||
import com.nuvio.app.features.watchprogress.shouldUseAsCompletedSeedForContinueWatching
|
||||
|
|
@ -132,6 +138,7 @@ fun HomeScreen(
|
|||
}.collectAsStateWithLifecycle()
|
||||
val homeListState = rememberLazyListState()
|
||||
val continueWatchingListState = rememberLazyListState()
|
||||
val upcomingListState = rememberLazyListState()
|
||||
val collections by CollectionRepository.collections.collectAsStateWithLifecycle()
|
||||
val continueWatchingPreferences by ContinueWatchingPreferencesRepository.uiState.collectAsStateWithLifecycle()
|
||||
val watchedUiState by WatchedRepository.uiState.collectAsStateWithLifecycle()
|
||||
|
|
@ -286,6 +293,7 @@ fun HomeScreen(
|
|||
val activeProfileId = profileState.activeProfile?.profileIndex ?: 1
|
||||
val cwCacheGeneration by ContinueWatchingEnrichmentCache.generation.collectAsStateWithLifecycle()
|
||||
var hasUserScrolledContinueWatching by remember(activeProfileId) { mutableStateOf(false) }
|
||||
var hasUserScrolledUpcoming by remember(activeProfileId) { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(activeProfileId, continueWatchingListState) {
|
||||
snapshotFlow { continueWatchingListState.isScrollInProgress }.collect { isScrolling ->
|
||||
|
|
@ -293,6 +301,12 @@ fun HomeScreen(
|
|||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(activeProfileId, upcomingListState) {
|
||||
snapshotFlow { upcomingListState.isScrollInProgress }.collect { isScrolling ->
|
||||
if (isScrolling) hasUserScrolledUpcoming = true
|
||||
}
|
||||
}
|
||||
|
||||
var nextUpItemsBySeries by remember(activeProfileId, effectiveWatchProgressSource) {
|
||||
mutableStateOf<Map<String, Pair<Long, ContinueWatchingItem>>>(emptyMap())
|
||||
}
|
||||
|
|
@ -421,7 +435,7 @@ fun HomeScreen(
|
|||
)
|
||||
}
|
||||
|
||||
val continueWatchingItems = remember(
|
||||
val allContinueWatchingItems = remember(
|
||||
visibleContinueWatchingEntries,
|
||||
cachedInProgressItems,
|
||||
effectivNextUpItems,
|
||||
|
|
@ -439,6 +453,17 @@ fun HomeScreen(
|
|||
cloudLibraryUiState = cloudLibraryUiState,
|
||||
)
|
||||
}
|
||||
val (continueWatchingItems, upcomingItems) = remember(
|
||||
allContinueWatchingItems,
|
||||
continueWatchingPreferences.sortMode,
|
||||
) {
|
||||
splitUpcomingItems(
|
||||
items = allContinueWatchingItems,
|
||||
mode = continueWatchingPreferences.sortMode,
|
||||
)
|
||||
}
|
||||
val hasContinueWatchingRows = continueWatchingItems.isNotEmpty() || upcomingItems.isNotEmpty()
|
||||
|
||||
LaunchedEffect(activeProfileId, continueWatchingItems.isNotEmpty(), hasUserScrolledContinueWatching) {
|
||||
if (!hasUserScrolledContinueWatching && continueWatchingItems.isNotEmpty()) {
|
||||
snapshotFlow {
|
||||
|
|
@ -455,6 +480,22 @@ fun HomeScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(activeProfileId, upcomingItems.isNotEmpty(), hasUserScrolledUpcoming) {
|
||||
if (!hasUserScrolledUpcoming && upcomingItems.isNotEmpty()) {
|
||||
snapshotFlow {
|
||||
upcomingListState.firstVisibleItemIndex to
|
||||
upcomingListState.firstVisibleItemScrollOffset
|
||||
}.collect { (index, offset) ->
|
||||
if (
|
||||
!hasUserScrolledUpcoming &&
|
||||
!upcomingListState.isScrollInProgress &&
|
||||
(index != 0 || offset != 0)
|
||||
) {
|
||||
upcomingListState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val enabledAddons = remember(addonsUiState.addons) {
|
||||
addonsUiState.addons.enabledAddons()
|
||||
}
|
||||
|
|
@ -817,7 +858,7 @@ fun HomeScreen(
|
|||
maxWidth.value,
|
||||
continueWatchingPreferences.isVisible,
|
||||
continueWatchingPreferences.style,
|
||||
continueWatchingItems.isNotEmpty(),
|
||||
hasContinueWatchingRows,
|
||||
continueWatchingLayout,
|
||||
posterCardStyle.widthDp,
|
||||
homeSettingsUiState.hideCatalogUnderline,
|
||||
|
|
@ -826,7 +867,7 @@ fun HomeScreen(
|
|||
if (
|
||||
maxWidth.value < 600f &&
|
||||
continueWatchingPreferences.isVisible &&
|
||||
continueWatchingItems.isNotEmpty()
|
||||
hasContinueWatchingRows
|
||||
) {
|
||||
continueWatchingHeroViewportReserveHeight(
|
||||
style = continueWatchingPreferences.style,
|
||||
|
|
@ -882,22 +923,17 @@ fun HomeScreen(
|
|||
|
||||
when {
|
||||
!hasActiveAddons && !hasRenderableCollectionRows -> {
|
||||
if (continueWatchingPreferences.isVisible && continueWatchingItems.isNotEmpty()) {
|
||||
item(key = HOME_CONTINUE_WATCHING_SECTION_KEY) {
|
||||
HomeContinueWatchingSection(
|
||||
items = continueWatchingItems,
|
||||
style = continueWatchingPreferences.style,
|
||||
useEpisodeThumbnails = continueWatchingPreferences.useEpisodeThumbnails,
|
||||
blurNextUp = continueWatchingPreferences.blurNextUp,
|
||||
modifier = Modifier.padding(bottom = HomeContinueWatchingSectionBottomPadding),
|
||||
sectionPadding = homeSectionPadding,
|
||||
layout = continueWatchingLayout,
|
||||
listState = continueWatchingListState,
|
||||
onItemClick = onContinueWatchingClick,
|
||||
onItemLongPress = onContinueWatchingLongPress,
|
||||
)
|
||||
}
|
||||
}
|
||||
homeContinueWatchingSections(
|
||||
preferences = continueWatchingPreferences,
|
||||
continueWatchingItems = continueWatchingItems,
|
||||
upcomingItems = upcomingItems,
|
||||
sectionPadding = homeSectionPadding,
|
||||
layout = continueWatchingLayout,
|
||||
continueWatchingListState = continueWatchingListState,
|
||||
upcomingListState = upcomingListState,
|
||||
onItemClick = onContinueWatchingClick,
|
||||
onItemLongPress = onContinueWatchingLongPress,
|
||||
)
|
||||
item {
|
||||
HomeEmptyStateCard(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
|
|
@ -908,22 +944,17 @@ fun HomeScreen(
|
|||
}
|
||||
|
||||
homeUiState.isLoading && homeUiState.sections.isEmpty() && !hasRenderableCollectionRows -> {
|
||||
if (continueWatchingPreferences.isVisible && continueWatchingItems.isNotEmpty()) {
|
||||
item(key = HOME_CONTINUE_WATCHING_SECTION_KEY) {
|
||||
HomeContinueWatchingSection(
|
||||
items = continueWatchingItems,
|
||||
style = continueWatchingPreferences.style,
|
||||
useEpisodeThumbnails = continueWatchingPreferences.useEpisodeThumbnails,
|
||||
blurNextUp = continueWatchingPreferences.blurNextUp,
|
||||
modifier = Modifier.padding(bottom = HomeContinueWatchingSectionBottomPadding),
|
||||
sectionPadding = homeSectionPadding,
|
||||
layout = continueWatchingLayout,
|
||||
listState = continueWatchingListState,
|
||||
onItemClick = onContinueWatchingClick,
|
||||
onItemLongPress = onContinueWatchingLongPress,
|
||||
)
|
||||
}
|
||||
}
|
||||
homeContinueWatchingSections(
|
||||
preferences = continueWatchingPreferences,
|
||||
continueWatchingItems = continueWatchingItems,
|
||||
upcomingItems = upcomingItems,
|
||||
sectionPadding = homeSectionPadding,
|
||||
layout = continueWatchingLayout,
|
||||
continueWatchingListState = continueWatchingListState,
|
||||
upcomingListState = upcomingListState,
|
||||
onItemClick = onContinueWatchingClick,
|
||||
onItemLongPress = onContinueWatchingLongPress,
|
||||
)
|
||||
items(3) {
|
||||
HomeSkeletonRow(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
|
|
@ -933,7 +964,7 @@ fun HomeScreen(
|
|||
}
|
||||
|
||||
homeUiState.sections.isEmpty() && homeUiState.heroItems.isEmpty() &&
|
||||
(!continueWatchingPreferences.isVisible || continueWatchingItems.isEmpty()) &&
|
||||
(!continueWatchingPreferences.isVisible || !hasContinueWatchingRows) &&
|
||||
!hasRenderableCollectionRows -> {
|
||||
item {
|
||||
if (networkStatusUiState.isOfflineLike) {
|
||||
|
|
@ -957,22 +988,17 @@ fun HomeScreen(
|
|||
}
|
||||
|
||||
else -> {
|
||||
if (continueWatchingPreferences.isVisible && continueWatchingItems.isNotEmpty()) {
|
||||
item(key = HOME_CONTINUE_WATCHING_SECTION_KEY) {
|
||||
HomeContinueWatchingSection(
|
||||
items = continueWatchingItems,
|
||||
style = continueWatchingPreferences.style,
|
||||
useEpisodeThumbnails = continueWatchingPreferences.useEpisodeThumbnails,
|
||||
blurNextUp = continueWatchingPreferences.blurNextUp,
|
||||
modifier = Modifier.padding(bottom = HomeContinueWatchingSectionBottomPadding),
|
||||
sectionPadding = homeSectionPadding,
|
||||
layout = continueWatchingLayout,
|
||||
listState = continueWatchingListState,
|
||||
onItemClick = onContinueWatchingClick,
|
||||
onItemLongPress = onContinueWatchingLongPress,
|
||||
)
|
||||
}
|
||||
}
|
||||
homeContinueWatchingSections(
|
||||
preferences = continueWatchingPreferences,
|
||||
continueWatchingItems = continueWatchingItems,
|
||||
upcomingItems = upcomingItems,
|
||||
sectionPadding = homeSectionPadding,
|
||||
layout = continueWatchingLayout,
|
||||
continueWatchingListState = continueWatchingListState,
|
||||
upcomingListState = upcomingListState,
|
||||
onItemClick = onContinueWatchingClick,
|
||||
onItemLongPress = onContinueWatchingLongPress,
|
||||
)
|
||||
|
||||
keyedEnabledHomeItems.forEach { keyedSettingsItem ->
|
||||
val settingsItem = keyedSettingsItem.value
|
||||
|
|
@ -1018,8 +1044,58 @@ fun HomeScreen(
|
|||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.homeContinueWatchingSections(
|
||||
preferences: ContinueWatchingPreferencesUiState,
|
||||
continueWatchingItems: List<ContinueWatchingItem>,
|
||||
upcomingItems: List<ContinueWatchingItem>,
|
||||
sectionPadding: Dp,
|
||||
layout: ContinueWatchingLayout,
|
||||
continueWatchingListState: LazyListState,
|
||||
upcomingListState: LazyListState,
|
||||
onItemClick: ((ContinueWatchingItem) -> Unit)?,
|
||||
onItemLongPress: ((ContinueWatchingItem) -> Unit)?,
|
||||
) {
|
||||
if (!preferences.isVisible) return
|
||||
|
||||
if (continueWatchingItems.isNotEmpty()) {
|
||||
item(key = HOME_CONTINUE_WATCHING_SECTION_KEY) {
|
||||
HomeContinueWatchingSection(
|
||||
items = continueWatchingItems,
|
||||
style = preferences.style,
|
||||
useEpisodeThumbnails = preferences.useEpisodeThumbnails,
|
||||
blurNextUp = preferences.blurNextUp,
|
||||
modifier = Modifier.padding(bottom = HomeContinueWatchingSectionBottomPadding),
|
||||
sectionPadding = sectionPadding,
|
||||
layout = layout,
|
||||
listState = continueWatchingListState,
|
||||
onItemClick = onItemClick,
|
||||
onItemLongPress = onItemLongPress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (upcomingItems.isNotEmpty()) {
|
||||
item(key = HOME_UPCOMING_SECTION_KEY) {
|
||||
HomeContinueWatchingSection(
|
||||
items = upcomingItems,
|
||||
style = preferences.style,
|
||||
useEpisodeThumbnails = preferences.useEpisodeThumbnails,
|
||||
blurNextUp = preferences.blurNextUp,
|
||||
modifier = Modifier.padding(bottom = HomeContinueWatchingSectionBottomPadding),
|
||||
title = stringResource(Res.string.upcoming_section_title),
|
||||
sectionPadding = sectionPadding,
|
||||
layout = layout,
|
||||
listState = upcomingListState,
|
||||
onItemClick = onItemClick,
|
||||
onItemLongPress = onItemLongPress,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val HOME_CATALOG_PREVIEW_LIMIT = 18
|
||||
private const val HOME_CONTINUE_WATCHING_SECTION_KEY = "home_continue_watching"
|
||||
private const val HOME_UPCOMING_SECTION_KEY = "home_upcoming"
|
||||
internal const val HomeContinueWatchingMaxRecentProgressItems = 300
|
||||
internal const val HomeNextUpInitialResolutionLimit = 32
|
||||
private const val MILLIS_PER_DAY = 24L * 60L * 60L * 1000L
|
||||
|
|
@ -1461,11 +1537,44 @@ internal fun buildHomeContinueWatchingItems(
|
|||
}
|
||||
|
||||
return when (sortMode) {
|
||||
ContinueWatchingSortMode.DEFAULT -> deduplicated.map(HomeContinueWatchingCandidate::item)
|
||||
ContinueWatchingSortMode.DEFAULT,
|
||||
ContinueWatchingSortMode.SPLIT_UPCOMING,
|
||||
-> deduplicated.map(HomeContinueWatchingCandidate::item)
|
||||
ContinueWatchingSortMode.STREAMING_STYLE -> applyStreamingStyleSort(deduplicated, todayIsoDate)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits unaired next-up episodes into a dedicated row when [ContinueWatchingSortMode.SPLIT_UPCOMING]
|
||||
* is active. The main row keeps its recency ordering, while Upcoming is ordered by the nearest
|
||||
* release instant with unknown dates last.
|
||||
*/
|
||||
internal fun splitUpcomingItems(
|
||||
items: List<ContinueWatchingItem>,
|
||||
mode: ContinueWatchingSortMode,
|
||||
nowEpochMs: Long = WatchProgressClock.nowEpochMs(),
|
||||
): Pair<List<ContinueWatchingItem>, List<ContinueWatchingItem>> {
|
||||
if (mode != ContinueWatchingSortMode.SPLIT_UPCOMING) {
|
||||
return items to emptyList()
|
||||
}
|
||||
|
||||
val (upcoming, main) = items.partition { item ->
|
||||
item.isNextUp && parseReleaseDateToEpochMs(item.released)
|
||||
?.let { releaseEpochMs -> releaseEpochMs > nowEpochMs } == true
|
||||
}
|
||||
val sortedUpcoming = upcoming.sortedWith { first, second ->
|
||||
val firstRelease = parseReleaseDateToEpochMs(first.released)
|
||||
val secondRelease = parseReleaseDateToEpochMs(second.released)
|
||||
when {
|
||||
firstRelease == null && secondRelease == null -> 0
|
||||
firstRelease == null -> 1
|
||||
secondRelease == null -> -1
|
||||
else -> firstRelease.compareTo(secondRelease)
|
||||
}
|
||||
}
|
||||
return main to sortedUpcoming
|
||||
}
|
||||
|
||||
private fun applyStreamingStyleSort(
|
||||
candidates: List<HomeContinueWatchingCandidate>,
|
||||
todayIsoDate: String,
|
||||
|
|
|
|||
|
|
@ -235,6 +235,7 @@ internal fun HomeContinueWatchingSection(
|
|||
useEpisodeThumbnails: Boolean = true,
|
||||
blurNextUp: Boolean = false,
|
||||
modifier: Modifier = Modifier,
|
||||
title: String? = null,
|
||||
sectionPadding: Dp? = null,
|
||||
layout: ContinueWatchingLayout? = null,
|
||||
listState: LazyListState = rememberLazyListState(),
|
||||
|
|
@ -250,6 +251,7 @@ internal fun HomeContinueWatchingSection(
|
|||
useEpisodeThumbnails = useEpisodeThumbnails,
|
||||
blurNextUp = blurNextUp,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
title = title,
|
||||
sectionPadding = sectionPadding,
|
||||
layout = layout,
|
||||
listState = listState,
|
||||
|
|
@ -264,6 +266,7 @@ internal fun HomeContinueWatchingSection(
|
|||
useEpisodeThumbnails = useEpisodeThumbnails,
|
||||
blurNextUp = blurNextUp,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
title = title,
|
||||
sectionPadding = homeSectionHorizontalPaddingForWidth(maxWidth.value),
|
||||
layout = rememberContinueWatchingLayout(maxWidth.value),
|
||||
listState = listState,
|
||||
|
|
@ -281,6 +284,7 @@ private fun HomeContinueWatchingSectionContent(
|
|||
useEpisodeThumbnails: Boolean,
|
||||
blurNextUp: Boolean,
|
||||
modifier: Modifier,
|
||||
title: String?,
|
||||
sectionPadding: Dp,
|
||||
layout: ContinueWatchingLayout,
|
||||
listState: LazyListState,
|
||||
|
|
@ -296,7 +300,7 @@ private fun HomeContinueWatchingSectionContent(
|
|||
val displayEntries = disintegration.sync(items)
|
||||
|
||||
NuvioShelfSection(
|
||||
title = stringResource(Res.string.compose_settings_page_continue_watching),
|
||||
title = title ?: stringResource(Res.string.compose_settings_page_continue_watching),
|
||||
entries = displayEntries,
|
||||
modifier = modifier,
|
||||
headerHorizontalPadding = sectionPadding,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode
|
|||
import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_default_desc
|
||||
import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_streaming
|
||||
import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_streaming_desc
|
||||
import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_split_upcoming
|
||||
import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_split_upcoming_desc
|
||||
import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_title
|
||||
import nuvio.composeapp.generated.resources.settings_continue_watching_style_card
|
||||
import nuvio.composeapp.generated.resources.settings_continue_watching_style_card_description
|
||||
|
|
@ -176,6 +178,7 @@ internal fun LazyListScope.continueWatchingSettingsContent(
|
|||
when (sortMode) {
|
||||
ContinueWatchingSortMode.DEFAULT -> Res.string.settings_continue_watching_sort_mode_default
|
||||
ContinueWatchingSortMode.STREAMING_STYLE -> Res.string.settings_continue_watching_sort_mode_streaming
|
||||
ContinueWatchingSortMode.SPLIT_UPCOMING -> Res.string.settings_continue_watching_sort_mode_split_upcoming
|
||||
}
|
||||
)
|
||||
SettingsNavigationRow(
|
||||
|
|
@ -325,6 +328,11 @@ private fun ContinueWatchingSortModeDialog(
|
|||
Res.string.settings_continue_watching_sort_mode_streaming,
|
||||
Res.string.settings_continue_watching_sort_mode_streaming_desc,
|
||||
),
|
||||
Triple(
|
||||
ContinueWatchingSortMode.SPLIT_UPCOMING,
|
||||
Res.string.settings_continue_watching_sort_mode_split_upcoming,
|
||||
Res.string.settings_continue_watching_sort_mode_split_upcoming_desc,
|
||||
),
|
||||
)
|
||||
|
||||
BasicAlertDialog(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ enum class ContinueWatchingSectionStyle {
|
|||
enum class ContinueWatchingSortMode {
|
||||
DEFAULT,
|
||||
STREAMING_STYLE,
|
||||
SPLIT_UPCOMING,
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.nuvio.app.features.debrid.DebridProviders
|
|||
import com.nuvio.app.features.watchprogress.CachedInProgressItem
|
||||
import com.nuvio.app.features.watchprogress.CachedNextUpItem
|
||||
import com.nuvio.app.features.watchprogress.ContinueWatchingItem
|
||||
import com.nuvio.app.features.watchprogress.ContinueWatchingSortMode
|
||||
import com.nuvio.app.features.watchprogress.WatchProgressEntry
|
||||
import com.nuvio.app.features.watchprogress.WatchProgressSourceTraktHistory
|
||||
import com.nuvio.app.features.watchprogress.nextUpDismissKey
|
||||
|
|
@ -162,6 +163,72 @@ class HomeScreenTest {
|
|||
assertEquals("S1E4 • Current", result.single().subtitle)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `split upcoming mode moves only unaired next up episodes and sorts them by release`() {
|
||||
val nowEpochMs = requireNotNull(parseReleaseDateToEpochMs("2026-07-19T12:00:00Z"))
|
||||
val inProgress = progressEntry(
|
||||
videoId = "movie-1",
|
||||
title = "Movie",
|
||||
lastUpdatedEpochMs = 500L,
|
||||
seasonNumber = null,
|
||||
episodeNumber = null,
|
||||
episodeTitle = null,
|
||||
).toContinueWatchingItem()
|
||||
val aired = continueWatchingItem(
|
||||
videoId = "aired:1:2",
|
||||
subtitle = "S1E2 • Aired",
|
||||
).copy(released = "2026-07-19T11:59:59Z")
|
||||
val unknownRelease = continueWatchingItem(
|
||||
videoId = "unknown:1:2",
|
||||
subtitle = "S1E2 • Unknown",
|
||||
)
|
||||
val laterUpcoming = continueWatchingItem(
|
||||
videoId = "later:1:2",
|
||||
subtitle = "S1E2 • Later",
|
||||
).copy(released = "2026-07-21T00:00:00Z")
|
||||
val soonerUpcoming = continueWatchingItem(
|
||||
videoId = "sooner:1:2",
|
||||
subtitle = "S1E2 • Sooner",
|
||||
).copy(released = "2026-07-20T00:00:00Z")
|
||||
|
||||
val (main, upcoming) = splitUpcomingItems(
|
||||
items = listOf(laterUpcoming, inProgress, aired, unknownRelease, soonerUpcoming),
|
||||
mode = ContinueWatchingSortMode.SPLIT_UPCOMING,
|
||||
nowEpochMs = nowEpochMs,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
listOf("movie-1", "aired:1:2", "unknown:1:2"),
|
||||
main.map(ContinueWatchingItem::videoId),
|
||||
)
|
||||
assertEquals(
|
||||
listOf("sooner:1:2", "later:1:2"),
|
||||
upcoming.map(ContinueWatchingItem::videoId),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `non split sort modes keep upcoming episodes in continue watching`() {
|
||||
val futureItem = continueWatchingItem(
|
||||
videoId = "future:1:2",
|
||||
subtitle = "S1E2 • Future",
|
||||
).copy(released = "2099-01-01T00:00:00Z")
|
||||
|
||||
listOf(
|
||||
ContinueWatchingSortMode.DEFAULT,
|
||||
ContinueWatchingSortMode.STREAMING_STYLE,
|
||||
).forEach { mode ->
|
||||
val (main, upcoming) = splitUpcomingItems(
|
||||
items = listOf(futureItem),
|
||||
mode = mode,
|
||||
nowEpochMs = 0L,
|
||||
)
|
||||
|
||||
assertEquals(listOf(futureItem), main)
|
||||
assertTrue(upcoming.isEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build home continue watching items enriches cloud title from library file`() {
|
||||
val file = CloudLibraryFile(id = "8", name = "GOAT.2026.2160p.UHD.mkv")
|
||||
|
|
|
|||
Loading…
Reference in a new issue