This commit is contained in:
Marius Butz 2026-05-13 12:47:45 +02:00 committed by GitHub
commit ba99c69007
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 136 additions and 0 deletions

View file

@ -38,6 +38,7 @@ import com.nuvio.app.core.format.formatReleaseDateForDisplay
import com.nuvio.app.features.home.MetaPreview
import kotlinx.coroutines.launch
import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.action_saved
import nuvio.composeapp.generated.resources.episodes_cd_watched
import nuvio.composeapp.generated.resources.hero_add_to_library
import nuvio.composeapp.generated.resources.hero_mark_unwatched
@ -151,6 +152,41 @@ fun NuvioAnimatedWatchedBadge(
}
}
@Composable
fun NuvioBookmarkedBadge(
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.size(22.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.primary),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = Icons.Default.Bookmark,
contentDescription = stringResource(Res.string.action_saved),
tint = MaterialTheme.colorScheme.onPrimary,
modifier = Modifier.size(12.dp),
)
}
}
@Composable
fun NuvioAnimatedBookmarkedBadge(
isVisible: Boolean,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = isVisible,
enter = fadeIn(),
exit = fadeOut(),
modifier = modifier,
) {
NuvioBookmarkedBadge()
}
}
@Composable
private fun PosterSheetHeader(
item: MetaPreview,

View file

@ -107,6 +107,7 @@ fun NuvioPosterCard(
bottomLeftLogoUrl: String? = null,
bottomLeftText: String? = null,
isWatched: Boolean = false,
isSaved: Boolean = false,
onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
) {
@ -185,6 +186,13 @@ fun NuvioPosterCard(
.align(Alignment.TopEnd)
.padding(6.dp),
)
NuvioAnimatedBookmarkedBadge(
isVisible = isSaved,
modifier = Modifier
.align(Alignment.TopStart)
.padding(6.dp),
)
}
if (shouldShowTitleBelow) {
Text(

View file

@ -46,6 +46,8 @@ import com.nuvio.app.core.network.NetworkStatusRepository
import com.nuvio.app.core.ui.NuvioNetworkOfflineCard
import coil3.compose.AsyncImage
import com.nuvio.app.core.format.formatReleaseDateForDisplay
import com.nuvio.app.core.ui.NuvioAnimatedBookmarkedBadge
import com.nuvio.app.core.ui.NuvioAnimatedWatchedBadge
import com.nuvio.app.core.ui.NuvioBackButton
import com.nuvio.app.core.ui.rememberPosterCardStyleUiState
import com.nuvio.app.core.ui.posterCardClickable
@ -55,6 +57,9 @@ 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.library.LibraryRepository
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
@ -81,6 +86,11 @@ fun CatalogScreen(
val gridState = rememberLazyGridState()
var headerHeightPx by remember { mutableIntStateOf(0) }
var observedOfflineState by remember { mutableStateOf(false) }
val libraryUiState by remember {
LibraryRepository.ensureLoaded()
LibraryRepository.uiState
}.collectAsStateWithLifecycle()
val watchedUiState by WatchedRepository.uiState.collectAsStateWithLifecycle()
LaunchedEffect(manifestUrl, type, catalogId, genre, supportsPagination, homeCatalogSettingsUiState.hideUnreleasedContent) {
CatalogRepository.load(
@ -182,10 +192,24 @@ fun CatalogScreen(
key = { item -> item.lazyKey },
) { keyedItem ->
val item = keyedItem.value
val isSaved = remember(
libraryUiState.items,
libraryUiState.sections,
libraryUiState.sourceMode,
item.id,
item.type,
) {
LibraryRepository.isSaved(item.id, item.type)
}
CatalogPosterTile(
item = item,
cornerRadiusDp = posterCardStyle.cornerRadiusDp,
hideLabels = posterCardStyle.hideLabelsEnabled,
isSaved = isSaved,
isWatched = WatchingState.isPosterWatched(
watchedKeys = watchedUiState.watchedKeys,
item = item,
),
onClick = onPosterClick?.let { { it(item) } },
)
}
@ -256,6 +280,8 @@ private fun CatalogPosterTile(
item: MetaPreview,
cornerRadiusDp: Int,
hideLabels: Boolean,
isWatched: Boolean = false,
isSaved: Boolean = false,
onClick: (() -> Unit)? = null,
) {
Column(
@ -277,6 +303,20 @@ private fun CatalogPosterTile(
contentScale = ContentScale.Crop,
)
}
NuvioAnimatedWatchedBadge(
isVisible = isWatched,
modifier = Modifier
.align(Alignment.TopEnd)
.padding(6.dp),
)
NuvioAnimatedBookmarkedBadge(
isVisible = isSaved,
modifier = Modifier
.align(Alignment.TopStart)
.padding(6.dp),
)
}
if (!hideLabels) {
Text(

View file

@ -49,6 +49,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.AsyncImage
import com.nuvio.app.core.ui.NuvioPosterCard
import com.nuvio.app.core.ui.NuvioPosterShape
@ -61,6 +62,9 @@ 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.library.LibraryRepository
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
@ -204,6 +208,11 @@ private fun TabbedGridContent(
onPosterClick: (MetaPreview) -> Unit,
) {
val gridState = rememberLazyGridState()
val libraryUiState by remember {
LibraryRepository.ensureLoaded()
LibraryRepository.uiState
}.collectAsStateWithLifecycle()
val watchedUiState by WatchedRepository.uiState.collectAsStateWithLifecycle()
LaunchedEffect(gridState, uiState.selectedTabIndex, uiState.selectedTabCanLoadMore, uiState.selectedTabIsLoadingMore) {
snapshotFlow { gridState.layoutInfo }
@ -280,11 +289,25 @@ private fun TabbedGridContent(
key = { item -> item.lazyKey },
) { keyedItem ->
val item = keyedItem.value
val isSaved = remember(
libraryUiState.items,
libraryUiState.sections,
libraryUiState.sourceMode,
item.id,
item.type,
) {
LibraryRepository.isSaved(item.id, item.type)
}
NuvioPosterCard(
title = item.name,
imageUrl = item.poster,
shape = NuvioPosterShape.Poster,
detailLine = item.releaseInfo,
isWatched = WatchingState.isPosterWatched(
watchedKeys = watchedUiState.watchedKeys,
item = item,
),
isSaved = isSaved,
onClick = { onPosterClick(item) },
)
}
@ -309,6 +332,7 @@ private fun RowsContent(
onPosterClick: (MetaPreview) -> Unit,
) {
val sections = FolderDetailRepository.getCatalogSectionsForRows()
val watchedUiState by WatchedRepository.uiState.collectAsStateWithLifecycle()
if (uiState.isLoading && sections.isEmpty()) {
LoadingIndicator()
@ -335,6 +359,7 @@ private fun RowsContent(
HomeCatalogRowSection(
section = section,
entries = section.items.take(18),
watchedKeys = watchedUiState.watchedKeys,
onViewAllClick = if (section.canOpenCatalog(18)) {
{ onCatalogClick(section) }
} else {

View file

@ -1,13 +1,17 @@
package com.nuvio.app.features.home.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.nuvio.app.core.format.formatReleaseDateForDisplay
import com.nuvio.app.core.ui.NuvioPosterCard
import com.nuvio.app.core.ui.NuvioPosterShape
import com.nuvio.app.core.ui.rememberPosterCardStyleUiState
import com.nuvio.app.features.home.MetaPreview
import com.nuvio.app.features.home.PosterShape
import com.nuvio.app.features.library.LibraryRepository
@Composable
fun HomePosterCard(
@ -18,6 +22,19 @@ fun HomePosterCard(
onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
) {
val libraryUiState by remember {
LibraryRepository.ensureLoaded()
LibraryRepository.uiState
}.collectAsStateWithLifecycle()
val isSaved = remember(
libraryUiState.items,
libraryUiState.sections,
libraryUiState.sourceMode,
item.id,
item.type,
) {
LibraryRepository.isSaved(item.id, item.type)
}
val posterCardStyle = rememberPosterCardStyleUiState()
val isLandscapeMode = useLandscapeBackdropMode || posterCardStyle.catalogLandscapeModeEnabled
@ -31,6 +48,7 @@ fun HomePosterCard(
bottomLeftLogoUrl = if (isLandscapeMode) item.logo else null,
bottomLeftText = if (isLandscapeMode && item.logo.isNullOrBlank() && !posterCardStyle.hideLabelsEnabled) item.name else null,
isWatched = isWatched,
isSaved = isSaved,
onClick = onClick,
onLongClick = onLongClick,
)

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.launch
import nuvio.composeapp.generated.resources.*
import org.jetbrains.compose.resources.getString
@ -64,6 +66,7 @@ fun LibraryScreen(
LibraryRepository.pullFromServer(ProfileRepository.activeProfileId)
}
}
val watchedUiState by WatchedRepository.uiState.collectAsStateWithLifecycle()
LaunchedEffect(networkStatusUiState.condition, isTraktSource) {
when (networkStatusUiState.condition) {
@ -170,6 +173,7 @@ fun LibraryScreen(
else -> {
librarySections(
watchedKeys = watchedUiState.watchedKeys,
sections = uiState.sections,
onPosterClick = onPosterClick,
onSectionViewAllClick = onSectionViewAllClick,
@ -228,6 +232,7 @@ fun LibraryScreen(
private fun LazyListScope.librarySections(
sections: List<LibrarySection>,
watchedKeys: Set<String>,
onPosterClick: ((LibraryItem) -> Unit)?,
onSectionViewAllClick: ((LibrarySection) -> Unit)?,
onPosterLongClick: (LibraryItem, LibrarySection) -> Unit,
@ -251,6 +256,10 @@ private fun LazyListScope.librarySections(
key = { item -> "${item.type}:${item.id}" },
) { item ->
HomePosterCard(
isWatched = WatchingState.isPosterWatched(
watchedKeys = watchedKeys,
item = item.toMetaPreview(),
),
item = item.toMetaPreview(),
onClick = onPosterClick?.let { { it(item) } },
onLongClick = { onPosterLongClick(item, section) },