feat(library): Add view all functionality for library sections and internal library fetching

This commit is contained in:
tapframe 2026-04-01 14:01:18 +05:30
parent 39a5d57f15
commit 054cb7a049
3 changed files with 81 additions and 1 deletions

View file

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

View file

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

View file

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