diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index 3523bbbe..09803098 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -66,6 +66,7 @@ import com.nuvio.app.core.ui.NuvioTheme import com.nuvio.app.features.auth.AuthScreen import com.nuvio.app.features.catalog.CatalogRepository import com.nuvio.app.features.catalog.CatalogScreen +import com.nuvio.app.features.catalog.INTERNAL_LIBRARY_MANIFEST_URL import com.nuvio.app.features.details.MetaDetailsRepository import com.nuvio.app.features.details.MetaDetailsScreen import com.nuvio.app.features.home.HomeCatalogSection @@ -73,6 +74,8 @@ import com.nuvio.app.features.home.HomeScreen import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.library.LibraryItem import com.nuvio.app.features.library.LibraryRepository +import com.nuvio.app.features.library.LibrarySection +import com.nuvio.app.features.library.LibrarySourceMode import com.nuvio.app.features.library.LibraryScreen import com.nuvio.app.features.library.toLibraryItem import com.nuvio.app.features.player.PlayerLaunch @@ -340,6 +343,23 @@ private fun MainAppContent( ) } + val onLibrarySectionViewAllClick: (LibrarySection) -> Unit = { section -> + navController.navigate( + CatalogRoute( + title = section.displayTitle, + subtitle = if (libraryUiState.sourceMode == LibrarySourceMode.TRAKT) { + "Trakt Library" + } else { + "Library" + }, + manifestUrl = INTERNAL_LIBRARY_MANIFEST_URL, + type = section.items.firstOrNull()?.type ?: "movie", + catalogId = section.type, + supportsPagination = false, + ), + ) + } + val onContinueWatchingClick: (ContinueWatchingItem) -> Unit = { item -> val streamContextId = item.pauseDescription ?.takeIf { it.isNotBlank() } @@ -460,6 +480,7 @@ private fun MainAppContent( onLibraryPosterClick = { item -> navController.navigate(DetailRoute(type = item.type, id = item.id)) }, + onLibrarySectionViewAllClick = onLibrarySectionViewAllClick, onContinueWatchingClick = onContinueWatchingClick, onContinueWatchingLongPress = onContinueWatchingLongPress, onSwitchProfile = onSwitchProfile, @@ -758,6 +779,7 @@ private fun AppTabHost( onPosterClick: ((MetaPreview) -> Unit)? = null, onPosterLongClick: ((MetaPreview) -> Unit)? = null, onLibraryPosterClick: ((LibraryItem) -> Unit)? = null, + onLibrarySectionViewAllClick: ((LibrarySection) -> Unit)? = null, onContinueWatchingClick: ((ContinueWatchingItem) -> Unit)? = null, onContinueWatchingLongPress: ((ContinueWatchingItem) -> Unit)? = null, onSwitchProfile: (() -> Unit)? = null, @@ -796,6 +818,7 @@ private fun AppTabHost( LibraryScreen( modifier = Modifier.fillMaxSize(), onPosterClick = onLibraryPosterClick, + onSectionViewAllClick = onLibrarySectionViewAllClick, ) } keepAliveTab( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogRepository.kt index 92982067..a1e17b92 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogRepository.kt @@ -1,5 +1,7 @@ package com.nuvio.app.features.catalog +import com.nuvio.app.features.library.LibraryRepository +import com.nuvio.app.features.library.toMetaPreview import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -9,6 +11,8 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +const val INTERNAL_LIBRARY_MANIFEST_URL = "nuvio://library" + object CatalogRepository { private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private val _uiState = MutableStateFlow(CatalogUiState()) @@ -36,6 +40,10 @@ object CatalogRepository { return } activeRequest = request + if (manifestUrl == INTERNAL_LIBRARY_MANIFEST_URL) { + fetchInternalLibrary(request) + return + } fetchPage(request = request, reset = true) } @@ -52,6 +60,44 @@ object CatalogRepository { _uiState.value = CatalogUiState() } + private fun fetchInternalLibrary(request: CatalogRequest) { + activeJob?.cancel() + _uiState.value = _uiState.value.copy( + isLoading = true, + errorMessage = null, + ) + + activeJob = scope.launch { + runCatching { + LibraryRepository.ensureLoaded() + LibraryRepository.uiState.value.sections + .firstOrNull { it.type == request.catalogId } + ?.items + .orEmpty() + .map { it.toMetaPreview() } + }.fold( + onSuccess = { items -> + if (activeRequest != request) return@fold + _uiState.value = CatalogUiState( + items = items, + isLoading = false, + nextSkip = null, + errorMessage = null, + ) + }, + onFailure = { error -> + if (activeRequest != request) return@fold + _uiState.value = CatalogUiState( + items = emptyList(), + isLoading = false, + nextSkip = null, + errorMessage = error.message ?: "Unable to load catalog items.", + ) + }, + ) + } + } + private fun fetchPage( request: CatalogRequest, reset: Boolean, 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 3735de1d..56724beb 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 @@ -26,6 +26,7 @@ import com.nuvio.app.features.home.components.HomePosterCard fun LibraryScreen( modifier: Modifier = Modifier, onPosterClick: ((LibraryItem) -> Unit)? = null, + onSectionViewAllClick: ((LibrarySection) -> Unit)? = null, ) { val uiState by remember { LibraryRepository.ensureLoaded() @@ -74,6 +75,7 @@ fun LibraryScreen( librarySections( sections = uiState.sections, onPosterClick = onPosterClick, + onSectionViewAllClick = onSectionViewAllClick, onPosterLongClick = { item -> if (!isTraktSource) { pendingRemovalItem = item @@ -101,17 +103,24 @@ fun LibraryScreen( private fun LazyListScope.librarySections( sections: List, onPosterClick: ((LibraryItem) -> Unit)?, + onSectionViewAllClick: ((LibrarySection) -> Unit)?, onPosterLongClick: (LibraryItem) -> Unit, ) { items( items = sections, key = { section -> section.type }, ) { section -> + val previewItems = section.items.take(LIBRARY_SECTION_PREVIEW_LIMIT) NuvioShelfSection( title = section.displayTitle, - entries = section.items, + entries = previewItems, headerHorizontalPadding = 16.dp, rowContentPadding = PaddingValues(horizontal = 16.dp), + onViewAllClick = if (section.items.size > LIBRARY_SECTION_PREVIEW_LIMIT) { + onSectionViewAllClick?.let { { it(section) } } + } else { + null + }, viewAllPillSize = NuvioViewAllPillSize.Compact, key = { item -> "${item.type}:${item.id}" }, ) { item -> @@ -123,3 +132,5 @@ private fun LazyListScope.librarySections( } } } + +private const val LIBRARY_SECTION_PREVIEW_LIMIT = 18