feat: adding watched markers to remaining places

This commit is contained in:
tapframe 2026-05-20 00:14:58 +05:30
parent 507d842098
commit c18916e6e3
8 changed files with 82 additions and 18 deletions

View file

@ -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,

View file

@ -179,12 +179,7 @@ fun NuvioPosterCard(
}
}
NuvioAnimatedWatchedBadge(
isVisible = isWatched,
modifier = Modifier
.align(Alignment.TopEnd)
.padding(6.dp),
)
NuvioPosterWatchedOverlay(isWatched = isWatched)
}
if (shouldShowTitleBelow) {
Text(

View file

@ -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(

View file

@ -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<String>,
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<String>,
modifier: Modifier = Modifier,
onCatalogClick: (HomeCatalogSection) -> Unit,
onPosterClick: (MetaPreview) -> Unit,
@ -340,6 +355,7 @@ private fun RowsContent(
} else {
null
},
watchedKeys = watchedKeys,
onPosterClick = { onPosterClick(it) },
)
}

View file

@ -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>(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<String>,
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,
)

View file

@ -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>(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<String>,
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,
)

View file

@ -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<LibrarySection>,
watchedKeys: Set<String>,
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) } },
)

View file

@ -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(