From c18916e6e3a998b5d3bfdd03abc1046d363ffb9a Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Wed, 20 May 2026 00:14:58 +0530 Subject: [PATCH] feat: adding watched markers to remaining places --- .../nuvio/app/core/ui/NuvioPosterActionSheet.kt | 16 ++++++++++++++++ .../nuvio/app/core/ui/NuvioShelfComponents.kt | 7 +------ .../nuvio/app/features/catalog/CatalogScreen.kt | 13 +++++++++++++ .../features/collection/FolderDetailScreen.kt | 16 ++++++++++++++++ .../app/features/details/PersonDetailScreen.kt | 14 +++++++++++--- .../features/details/TmdbEntityBrowseScreen.kt | 10 +++++++++- .../nuvio/app/features/library/LibraryScreen.kt | 15 ++++++++++++++- .../app/features/search/SearchDiscoverContent.kt | 9 ++------- 8 files changed, 82 insertions(+), 18 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt index e226f637..86520e0a 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt @@ -6,6 +6,7 @@ import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -32,6 +33,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import com.nuvio.app.core.format.formatReleaseDateForDisplay @@ -151,6 +153,20 @@ fun NuvioAnimatedWatchedBadge( } } +@Composable +fun BoxScope.NuvioPosterWatchedOverlay( + isWatched: Boolean, + modifier: Modifier = Modifier, + padding: Dp = 6.dp, +) { + NuvioAnimatedWatchedBadge( + isVisible = isWatched, + modifier = modifier + .align(Alignment.TopEnd) + .padding(padding), + ) +} + @Composable private fun PosterSheetHeader( item: MetaPreview, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt index ace10d77..b0cf02fd 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt @@ -179,12 +179,7 @@ fun NuvioPosterCard( } } - NuvioAnimatedWatchedBadge( - isVisible = isWatched, - modifier = Modifier - .align(Alignment.TopEnd) - .padding(6.dp), - ) + NuvioPosterWatchedOverlay(isWatched = isWatched) } if (shouldShowTitleBelow) { Text( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogScreen.kt index c611b161..e068aa4e 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogScreen.kt @@ -47,6 +47,7 @@ import com.nuvio.app.core.ui.NuvioNetworkOfflineCard import coil3.compose.AsyncImage import com.nuvio.app.core.format.formatReleaseDateForDisplay import com.nuvio.app.core.ui.NuvioBackButton +import com.nuvio.app.core.ui.NuvioPosterWatchedOverlay import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.core.ui.posterCardClickable import com.nuvio.app.core.ui.nuvioSafeBottomPadding @@ -55,6 +56,8 @@ import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.home.HomeCatalogSettingsRepository import com.nuvio.app.features.home.PosterShape import com.nuvio.app.features.home.stableKey +import com.nuvio.app.features.watched.WatchedRepository +import com.nuvio.app.features.watching.application.WatchingState import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map @@ -79,6 +82,10 @@ fun CatalogScreen( val homeCatalogSettingsUiState by HomeCatalogSettingsRepository.uiState.collectAsStateWithLifecycle() val posterCardStyle = rememberPosterCardStyleUiState() val networkStatusUiState by NetworkStatusRepository.uiState.collectAsStateWithLifecycle() + val watchedUiState by remember { + WatchedRepository.ensureLoaded() + WatchedRepository.uiState + }.collectAsStateWithLifecycle() val gridState = rememberLazyGridState() var headerHeightPx by remember { mutableIntStateOf(0) } var observedOfflineState by remember { mutableStateOf(false) } @@ -187,6 +194,10 @@ fun CatalogScreen( item = item, cornerRadiusDp = posterCardStyle.cornerRadiusDp, hideLabels = posterCardStyle.hideLabelsEnabled, + isWatched = WatchingState.isPosterWatched( + watchedKeys = watchedUiState.watchedKeys, + item = item, + ), onClick = onPosterClick?.let { { it(item) } }, onLongClick = onPosterLongClick?.let { { it(item) } }, ) @@ -258,6 +269,7 @@ private fun CatalogPosterTile( item: MetaPreview, cornerRadiusDp: Int, hideLabels: Boolean, + isWatched: Boolean, onClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null, ) { @@ -280,6 +292,7 @@ private fun CatalogPosterTile( contentScale = ContentScale.Crop, ) } + NuvioPosterWatchedOverlay(isWatched = isWatched) } if (!hideLabels) { Text( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/FolderDetailScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/FolderDetailScreen.kt index 6101d18a..5881ce2e 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/FolderDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/FolderDetailScreen.kt @@ -61,6 +61,8 @@ import com.nuvio.app.features.home.PosterShape import com.nuvio.app.features.home.canOpenCatalog import com.nuvio.app.features.home.stableKey import com.nuvio.app.features.home.components.HomeCatalogRowSection +import com.nuvio.app.features.watched.WatchedRepository +import com.nuvio.app.features.watching.application.WatchingState import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map @@ -79,6 +81,10 @@ fun FolderDetailScreen( onPosterClick: (MetaPreview) -> Unit, ) { val uiState by FolderDetailRepository.uiState.collectAsState() + val watchedUiState by remember { + WatchedRepository.ensureLoaded() + WatchedRepository.uiState + }.collectAsState() val folder = uiState.folder val coverImageUrl = folder?.coverImageUrl?.takeIf { it.isNotBlank() } val density = LocalDensity.current @@ -160,18 +166,21 @@ fun FolderDetailScreen( when (uiState.viewMode) { FolderViewMode.TABBED_GRID -> TabbedGridContent( uiState = uiState, + watchedKeys = watchedUiState.watchedKeys, modifier = Modifier.weight(1f).then(contentModifier), onTabSelected = { FolderDetailRepository.selectTab(it) }, onPosterClick = onPosterClick, ) FolderViewMode.ROWS -> RowsContent( uiState = uiState, + watchedKeys = watchedUiState.watchedKeys, modifier = Modifier.weight(1f).then(contentModifier), onCatalogClick = onCatalogClick, onPosterClick = onPosterClick, ) FolderViewMode.FOLLOW_LAYOUT -> RowsContent( uiState = uiState, + watchedKeys = watchedUiState.watchedKeys, modifier = Modifier.weight(1f).then(contentModifier), onCatalogClick = onCatalogClick, onPosterClick = onPosterClick, @@ -199,6 +208,7 @@ private fun FolderCoverImage( @Composable private fun TabbedGridContent( uiState: FolderDetailUiState, + watchedKeys: Set, modifier: Modifier = Modifier, onTabSelected: (Int) -> Unit, onPosterClick: (MetaPreview) -> Unit, @@ -285,6 +295,10 @@ private fun TabbedGridContent( imageUrl = item.poster, shape = NuvioPosterShape.Poster, detailLine = item.releaseInfo, + isWatched = WatchingState.isPosterWatched( + watchedKeys = watchedKeys, + item = item, + ), onClick = { onPosterClick(item) }, ) } @@ -304,6 +318,7 @@ private fun TabbedGridContent( @Composable private fun RowsContent( uiState: FolderDetailUiState, + watchedKeys: Set, modifier: Modifier = Modifier, onCatalogClick: (HomeCatalogSection) -> Unit, onPosterClick: (MetaPreview) -> Unit, @@ -340,6 +355,7 @@ private fun RowsContent( } else { null }, + watchedKeys = watchedKeys, onPosterClick = { onPosterClick(it) }, ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/PersonDetailScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/PersonDetailScreen.kt index 769456b6..f209f7f2 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/PersonDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/PersonDetailScreen.kt @@ -53,6 +53,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.lerp import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage import coil3.compose.LocalPlatformContext import coil3.request.ImageRequest @@ -63,6 +64,7 @@ import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.features.details.components.DetailPosterRailSection import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.tmdb.TmdbMetadataService +import com.nuvio.app.features.watched.WatchedRepository import com.nuvio.app.features.watchprogress.CurrentDateProvider import nuvio.composeapp.generated.resources.* import org.jetbrains.compose.resources.getString @@ -89,6 +91,10 @@ fun PersonDetailScreen( modifier: Modifier = Modifier, ) { var uiState by remember(personId) { mutableStateOf(PersonDetailUiState.Loading) } + val watchedUiState by remember { + WatchedRepository.ensureLoaded() + WatchedRepository.uiState + }.collectAsStateWithLifecycle() val resolvedAvatarTransitionKey = avatarTransitionKey ?: castAvatarSharedTransitionKey(personId) LaunchedEffect(personId) { @@ -127,6 +133,7 @@ fun PersonDetailScreen( ) is PersonDetailUiState.Success -> PersonDetailContent( person = state.personDetail, + watchedKeys = watchedUiState.watchedKeys, onOpenMeta = onOpenMeta, initialProfilePhoto = initialProfilePhoto, avatarTransitionKey = resolvedAvatarTransitionKey, @@ -156,6 +163,7 @@ fun PersonDetailScreen( @OptIn(ExperimentalSharedTransitionApi::class) private fun PersonDetailContent( person: PersonDetail, + watchedKeys: Set, onOpenMeta: (MetaPreview) -> Unit, initialProfilePhoto: String? = null, avatarTransitionKey: String, @@ -274,7 +282,7 @@ private fun PersonDetailContent( DetailPosterRailSection( title = stringResource(Res.string.person_popular), items = popularCredits, - watchedKeys = emptySet(), + watchedKeys = watchedKeys, headerHorizontalPadding = 20.dp, onPosterClick = onOpenMeta, ) @@ -285,7 +293,7 @@ private fun PersonDetailContent( DetailPosterRailSection( title = stringResource(Res.string.person_latest), items = latestCredits, - watchedKeys = emptySet(), + watchedKeys = watchedKeys, headerHorizontalPadding = 20.dp, onPosterClick = onOpenMeta, ) @@ -296,7 +304,7 @@ private fun PersonDetailContent( DetailPosterRailSection( title = stringResource(Res.string.person_upcoming), items = upcomingCredits, - watchedKeys = emptySet(), + watchedKeys = watchedKeys, headerHorizontalPadding = 20.dp, onPosterClick = onOpenMeta, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/TmdbEntityBrowseScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/TmdbEntityBrowseScreen.kt index 2c03a8fe..04abb4ca 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/TmdbEntityBrowseScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/TmdbEntityBrowseScreen.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage import nuvio.composeapp.generated.resources.* import org.jetbrains.compose.resources.stringResource @@ -55,6 +56,7 @@ import com.nuvio.app.features.tmdb.TmdbEntityKind import com.nuvio.app.features.tmdb.TmdbEntityMediaType import com.nuvio.app.features.tmdb.TmdbEntityRailType import com.nuvio.app.features.tmdb.TmdbMetadataService +import com.nuvio.app.features.watched.WatchedRepository private sealed interface EntityBrowseUiState { data object Loading : EntityBrowseUiState @@ -75,6 +77,10 @@ fun TmdbEntityBrowseScreen( var uiState by remember(entityKind, entityId) { mutableStateOf(EntityBrowseUiState.Loading) } + val watchedUiState by remember { + WatchedRepository.ensureLoaded() + WatchedRepository.uiState + }.collectAsStateWithLifecycle() val loadFailedMessage = stringResource(Res.string.details_browse_load_failed, entityName) LaunchedEffect(entityKind, entityId) { @@ -106,6 +112,7 @@ fun TmdbEntityBrowseScreen( is EntityBrowseUiState.Success -> EntityBrowseContent( data = state.data, sourceType = sourceType, + watchedKeys = watchedUiState.watchedKeys, onOpenMeta = onOpenMeta, ) } @@ -131,6 +138,7 @@ fun TmdbEntityBrowseScreen( private fun EntityBrowseContent( data: TmdbEntityBrowseData, sourceType: String, + watchedKeys: Set, onOpenMeta: (MetaPreview) -> Unit, ) { val backgroundUrl = remember(data.rails, sourceType) { @@ -208,7 +216,7 @@ private fun EntityBrowseContent( DetailPosterRailSection( title = railTitle, items = rail.items, - watchedKeys = emptySet(), + watchedKeys = watchedKeys, headerHorizontalPadding = 20.dp, onPosterClick = onOpenMeta, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt index dc75e101..863fa3b4 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt @@ -32,6 +32,8 @@ import com.nuvio.app.features.home.components.HomeEmptyStateCard import com.nuvio.app.features.home.components.HomePosterCard import com.nuvio.app.features.home.components.HomeSkeletonRow import com.nuvio.app.features.profiles.ProfileRepository +import com.nuvio.app.features.watched.WatchedRepository +import com.nuvio.app.features.watching.application.WatchingState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.launch @@ -50,6 +52,10 @@ fun LibraryScreen( LibraryRepository.ensureLoaded() LibraryRepository.uiState }.collectAsStateWithLifecycle() + val watchedUiState by remember { + WatchedRepository.ensureLoaded() + WatchedRepository.uiState + }.collectAsStateWithLifecycle() val networkStatusUiState by NetworkStatusRepository.uiState.collectAsStateWithLifecycle() var observedOfflineState by remember { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() @@ -176,6 +182,7 @@ fun LibraryScreen( else -> { librarySections( sections = uiState.sections, + watchedKeys = watchedUiState.watchedKeys, onPosterClick = onPosterClick, onSectionViewAllClick = onSectionViewAllClick, onPosterLongClick = onPosterLongClick, @@ -187,6 +194,7 @@ fun LibraryScreen( private fun LazyListScope.librarySections( sections: List, + watchedKeys: Set, onPosterClick: ((LibraryItem) -> Unit)?, onSectionViewAllClick: ((LibrarySection) -> Unit)?, onPosterLongClick: ((LibraryItem, LibrarySection) -> Unit)?, @@ -209,8 +217,13 @@ private fun LazyListScope.librarySections( viewAllPillSize = NuvioViewAllPillSize.Compact, key = { item -> "${item.type}:${item.id}" }, ) { item -> + val posterItem = item.toMetaPreview() HomePosterCard( - item = item.toMetaPreview(), + item = posterItem, + isWatched = WatchingState.isPosterWatched( + watchedKeys = watchedKeys, + item = posterItem, + ), onClick = onPosterClick?.let { { it(item) } }, onLongClick = onPosterLongClick?.let { { it(item, section) } }, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt index 5648e096..3f1901f3 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt @@ -50,12 +50,12 @@ import coil3.compose.AsyncImage import com.nuvio.app.core.network.NetworkCondition import com.nuvio.app.core.format.formatReleaseDateForDisplay import com.nuvio.app.core.ui.NuvioNetworkOfflineCard -import com.nuvio.app.core.ui.NuvioAnimatedWatchedBadge import com.nuvio.app.core.ui.NuvioBottomSheetActionRow import com.nuvio.app.core.ui.NuvioBottomSheetDivider import com.nuvio.app.core.ui.NuvioModalBottomSheet import com.nuvio.app.core.ui.dismissNuvioBottomSheet import com.nuvio.app.core.ui.nuvioSafeBottomPadding +import com.nuvio.app.core.ui.NuvioPosterWatchedOverlay import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.core.ui.posterCardClickable import com.nuvio.app.features.home.MetaPreview @@ -404,12 +404,7 @@ private fun DiscoverPosterTile( contentScale = ContentScale.Crop, ) } - NuvioAnimatedWatchedBadge( - isVisible = isWatched, - modifier = Modifier - .align(Alignment.TopEnd) - .padding(6.dp), - ) + NuvioPosterWatchedOverlay(isWatched = isWatched) } if (!hideLabels) { Text(