@@ -75,6 +75,7 @@ import com.nuvio.app.features.details.components.DetailProductionSection
7575import com.nuvio.app.features.details.components.DetailSeriesContent
7676import com.nuvio.app.features.details.components.DetailTrailersSection
7777import com.nuvio.app.features.details.components.EpisodeWatchedActionSheet
78+ import com.nuvio.app.features.details.components.SeasonWatchedActionSheet
7879import com.nuvio.app.features.details.components.TrailerPlayerPopup
7980import com.nuvio.app.features.home.MetaPreview
8081import com.nuvio.app.features.library.LibraryRepository
@@ -92,6 +93,7 @@ import com.nuvio.app.features.trailer.TrailerPlaybackResolver
9293import com.nuvio.app.features.trailer.TrailerPlaybackSource
9394import com.nuvio.app.features.watched.WatchedRepository
9495import com.nuvio.app.features.watched.previousReleasedEpisodesBefore
96+ import com.nuvio.app.features.watched.releasedPlayableEpisodes
9597import com.nuvio.app.features.watched.releasedEpisodesForSeason
9698import com.nuvio.app.features.watchprogress.CurrentDateProvider
9799import com.nuvio.app.features.watchprogress.WatchProgressEntry
@@ -151,6 +153,7 @@ fun MetaDetailsScreen(
151153 var autoLoadAttempted by remember(type, id) { mutableStateOf(false ) }
152154 var observedOfflineState by remember(type, id) { mutableStateOf(false ) }
153155 var selectedEpisodeForActions by remember(type, id) { mutableStateOf<MetaVideo ?>(null ) }
156+ var selectedSeasonForActions by remember(type, id) { mutableStateOf<Int ?>(null ) }
154157 val commentsEnabled by remember {
155158 TraktCommentsSettings .ensureLoaded()
156159 TraktCommentsSettings .enabled
@@ -337,7 +340,10 @@ fun MetaDetailsScreen(
337340 LibraryRepository .toggleSaved(meta.toLibraryItem(savedAtEpochMs = 0L ))
338341 }
339342 }
340- val movieProgress = watchProgressUiState.byVideoId[meta.id]
343+ val progressByVideoId = remember(watchProgressUiState.entries) {
344+ watchProgressUiState.byVideoId
345+ }
346+ val movieProgress = progressByVideoId[meta.id]
341347 ?.takeUnless { it.isCompleted }
342348 val cwPrefs by ContinueWatchingPreferencesRepository .uiState.collectAsStateWithLifecycle()
343349 val seriesAction = remember(watchProgressUiState.entries, watchedUiState.items, meta, todayIsoDate, cwPrefs.upNextFromFurthestEpisode) {
@@ -715,11 +721,12 @@ fun MetaDetailsScreen(
715721 },
716722 onCommentClick = { review -> selectedComment = review },
717723 onTrailerClick = resolveTrailer,
718- progressByVideoId = watchProgressUiState.byVideoId ,
724+ progressByVideoId = progressByVideoId ,
719725 watchedKeys = watchedUiState.watchedKeys,
720726 blurUnwatchedEpisodes = metaScreenSettingsUiState.blurUnwatchedEpisodes,
721727 onEpisodeClick = onEpisodePlayClick,
722728 onEpisodeLongPress = { video -> selectedEpisodeForActions = video },
729+ onSeasonLongPress = { season -> selectedSeasonForActions = season },
723730 onOpenMeta = onOpenMeta,
724731 onCastClick = onCastClick,
725732 onCompanyClick = onCompanyClick,
@@ -776,12 +783,12 @@ fun MetaDetailsScreen(
776783 )
777784
778785 selectedEpisodeForActions?.let { selectedEpisode ->
779- val isSelectedEpisodeWatched = remember(meta, selectedEpisode, watchedUiState.watchedKeys) {
780- WatchingState .isEpisodeWatched(
781- watchedKeys = watchedUiState.watchedKeys,
782- metaType = meta.type,
783- metaId = meta.id,
786+ val isSelectedEpisodeWatched = remember(meta, selectedEpisode, watchedUiState.watchedKeys, progressByVideoId) {
787+ isEpisodeWatchedForActions(
788+ meta = meta,
784789 episode = selectedEpisode,
790+ watchedKeys = watchedUiState.watchedKeys,
791+ progressByVideoId = progressByVideoId,
785792 )
786793 }
787794 val previousEpisodes = remember(meta, selectedEpisode, todayIsoDate) {
@@ -796,20 +803,20 @@ fun MetaDetailsScreen(
796803 todayIsoDate = todayIsoDate,
797804 )
798805 }
799- val arePreviousEpisodesWatched = remember(previousEpisodes, watchedUiState.watchedKeys) {
800- WatchingState .areEpisodesWatched(
801- watchedKeys = watchedUiState.watchedKeys,
802- metaType = meta.type,
803- metaId = meta.id,
806+ val arePreviousEpisodesWatched = remember(previousEpisodes, watchedUiState.watchedKeys, progressByVideoId) {
807+ areEpisodesWatchedForActions(
808+ meta = meta,
804809 episodes = previousEpisodes,
810+ watchedKeys = watchedUiState.watchedKeys,
811+ progressByVideoId = progressByVideoId,
805812 )
806813 }
807- val isSeasonWatched = remember(seasonEpisodes, watchedUiState.watchedKeys) {
808- WatchingState .areEpisodesWatched(
809- watchedKeys = watchedUiState.watchedKeys,
810- metaType = meta.type,
811- metaId = meta.id,
814+ val isSeasonWatched = remember(seasonEpisodes, watchedUiState.watchedKeys, progressByVideoId) {
815+ areEpisodesWatchedForActions(
816+ meta = meta,
812817 episodes = seasonEpisodes,
818+ watchedKeys = watchedUiState.watchedKeys,
819+ progressByVideoId = progressByVideoId,
813820 )
814821 }
815822 EpisodeWatchedActionSheet (
@@ -850,6 +857,62 @@ fun MetaDetailsScreen(
850857 )
851858 }
852859
860+ selectedSeasonForActions?.let { selectedSeason ->
861+ val seasonLabel = selectedSeasonLabel(selectedSeason)
862+ val seasonEpisodes = remember(meta, selectedSeason, todayIsoDate) {
863+ meta.releasedEpisodesForSeason(
864+ seasonNumber = selectedSeason,
865+ todayIsoDate = todayIsoDate,
866+ )
867+ }
868+ val previousSeasonEpisodes = remember(meta, selectedSeason, todayIsoDate) {
869+ val normalizedSelectedSeason = selectedSeason.coerceAtLeast(0 )
870+ meta.releasedPlayableEpisodes(todayIsoDate)
871+ .filter { episode ->
872+ val season = episode.season?.coerceAtLeast(0 ) ? : 0
873+ season > 0 && season < normalizedSelectedSeason
874+ }
875+ }
876+ val isSeasonWatched = remember(seasonEpisodes, watchedUiState.watchedKeys, progressByVideoId) {
877+ areEpisodesWatchedForActions(
878+ meta = meta,
879+ episodes = seasonEpisodes,
880+ watchedKeys = watchedUiState.watchedKeys,
881+ progressByVideoId = progressByVideoId,
882+ )
883+ }
884+ val canMarkPreviousSeasons = remember(previousSeasonEpisodes, watchedUiState.watchedKeys, progressByVideoId) {
885+ previousSeasonEpisodes.any { episode ->
886+ ! isEpisodeWatchedForActions(
887+ meta = meta,
888+ episode = episode,
889+ watchedKeys = watchedUiState.watchedKeys,
890+ progressByVideoId = progressByVideoId,
891+ )
892+ }
893+ }
894+ SeasonWatchedActionSheet (
895+ seasonLabel = seasonLabel,
896+ isSeasonWatched = isSeasonWatched,
897+ canMarkPreviousSeasons = canMarkPreviousSeasons,
898+ onDismiss = { selectedSeasonForActions = null },
899+ onToggleSeasonWatched = {
900+ WatchingActions .toggleSeasonWatched(
901+ meta = meta,
902+ episodes = seasonEpisodes,
903+ areCurrentlyWatched = isSeasonWatched,
904+ )
905+ },
906+ onMarkPreviousSeasonsWatched = {
907+ WatchingActions .togglePreviousEpisodesWatched(
908+ meta = meta,
909+ episodes = previousSeasonEpisodes,
910+ areCurrentlyWatched = false ,
911+ )
912+ },
913+ )
914+ }
915+
853916 if (inAppTrailerPlaybackEnabled) {
854917 TrailerPlayerPopup (
855918 visible = selectedTrailer != null ,
@@ -970,6 +1033,49 @@ private fun MetaDetails.isSeriesLikeForEpisodeRatings(): Boolean {
9701033 return hasNumberedEpisodes && normalizedType in setOf (" series" , " show" , " tv" , " tvshow" )
9711034}
9721035
1036+ @Composable
1037+ private fun selectedSeasonLabel (season : Int ): String =
1038+ if (season == 0 ) {
1039+ stringResource(Res .string.episodes_specials)
1040+ } else {
1041+ stringResource(Res .string.episodes_season, season)
1042+ }
1043+
1044+ private fun isEpisodeWatchedForActions (
1045+ meta : MetaDetails ,
1046+ episode : MetaVideo ,
1047+ watchedKeys : Set <String >,
1048+ progressByVideoId : Map <String , WatchProgressEntry >,
1049+ ): Boolean {
1050+ val episodeVideoId = buildPlaybackVideoId(
1051+ parentMetaId = meta.id,
1052+ seasonNumber = episode.season,
1053+ episodeNumber = episode.episode,
1054+ fallbackVideoId = episode.id,
1055+ )
1056+ return progressByVideoId[episodeVideoId]?.isEffectivelyCompleted == true ||
1057+ WatchingState .isEpisodeWatched(
1058+ watchedKeys = watchedKeys,
1059+ metaType = meta.type,
1060+ metaId = meta.id,
1061+ episode = episode,
1062+ )
1063+ }
1064+
1065+ private fun areEpisodesWatchedForActions (
1066+ meta : MetaDetails ,
1067+ episodes : Collection <MetaVideo >,
1068+ watchedKeys : Set <String >,
1069+ progressByVideoId : Map <String , WatchProgressEntry >,
1070+ ): Boolean = episodes.isNotEmpty() && episodes.all { episode ->
1071+ isEpisodeWatchedForActions(
1072+ meta = meta,
1073+ episode = episode,
1074+ watchedKeys = watchedKeys,
1075+ progressByVideoId = progressByVideoId,
1076+ )
1077+ }
1078+
9731079private fun extractImdbId (value : String? ): String? =
9741080 value
9751081 ?.trim()
@@ -1026,6 +1132,7 @@ private fun ConfiguredMetaSections(
10261132 blurUnwatchedEpisodes : Boolean ,
10271133 onEpisodeClick : (MetaVideo ) -> Unit ,
10281134 onEpisodeLongPress : (MetaVideo ) -> Unit ,
1135+ onSeasonLongPress : (Int ) -> Unit ,
10291136 onOpenMeta : ((MetaPreview ) -> Unit )? ,
10301137 onCastClick : ((MetaPerson , String? ) -> Unit )? ,
10311138 onCompanyClick : ((MetaCompany , String ) -> Unit )? ,
@@ -1120,6 +1227,7 @@ private fun ConfiguredMetaSections(
11201227 blurUnwatchedEpisodes = blurUnwatchedEpisodes,
11211228 onEpisodeClick = onEpisodeClick,
11221229 onEpisodeLongPress = onEpisodeLongPress,
1230+ onSeasonLongPress = onSeasonLongPress,
11231231 )
11241232 }
11251233 }
0 commit comments