diff --git a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/library/LibraryScreen.kt b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/library/LibraryScreen.kt index e48224528..e145b95b0 100644 --- a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/library/LibraryScreen.kt +++ b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/library/LibraryScreen.kt @@ -34,6 +34,7 @@ import snd.komelia.ui.LoadState.Uninitialized import snd.komelia.ui.LocalKomgaState import snd.komelia.ui.LocalOfflineMode import snd.komelia.ui.LocalReloadEvents +import snd.komelia.ui.LocalStrings import snd.komelia.ui.LocalViewModelFactory import snd.komelia.ui.ReloadableScreen import snd.komelia.ui.collection.CollectionScreen @@ -60,10 +61,8 @@ import kotlin.jvm.Transient class LibraryScreen( val libraryId: KomgaLibraryId? = null, - @Transient - private val seriesFilter: SeriesScreenFilter? = null + @Transient private val seriesFilter: SeriesScreenFilter? = null ) : ReloadableScreen { - override val key: ScreenKey = "${libraryId}_${seriesFilter.hashCode()}" @Composable @@ -96,10 +95,16 @@ class LibraryScreen( readListsCount = vm.readListsCount, onBrowseClick = vm::toBrowseTab, onCollectionsClick = vm::toCollectionsTab, - onReadListsClick = vm::toReadListsTab + onReadListsClick = vm::toReadListsTab, + randomSeriesEnabled = vm.seriesTabState.totalSeriesCount > 0, + onRandomSeriesClick = { + vm.seriesTabState.openRandomSeries { navigator.push(seriesScreen(it)) } + }, + onRandomUnreadSeriesClick = { + vm.seriesTabState.openRandomUnreadSeries { navigator.push(seriesScreen(it)) } + } ) } - when (vm.currentTab) { SERIES -> BrowseTab(vm.seriesTabState) COLLECTIONS -> CollectionsTab(vm.collectionsTabState) @@ -129,26 +134,23 @@ class LibraryScreen( else -> { val loading = state is Loading || state is Uninitialized + SeriesListContent( series = seriesTabState.series, seriesActions = seriesTabState.seriesMenuActions(), seriesTotalCount = seriesTabState.totalSeriesCount, onSeriesClick = { navigator.push(seriesScreen(it)) }, - editMode = seriesTabState.isInEditMode.collectAsState().value, onEditModeChange = seriesTabState::onEditModeChange, selectedSeries = seriesTabState.selectedSeries, onSeriesSelect = seriesTabState::onSeriesSelect, - isLoading = loading, filterState = seriesTabState.filterState, - currentPage = seriesTabState.currentSeriesPage, totalPages = seriesTabState.totalSeriesPages, pageSize = seriesTabState.pageLoadSize.collectAsState().value, onPageSizeChange = seriesTabState::onPageSizeChange, onPageChange = seriesTabState::onPageChange, - minSize = seriesTabState.cardWidth.collectAsState().value, ) } @@ -179,19 +181,15 @@ class LibraryScreen( onCollectionClick = { navigator push CollectionScreen(it) }, onCollectionDelete = collectionsTabState::onCollectionDelete, isLoading = loading, - totalPages = collectionsTabState.totalPages, currentPage = collectionsTabState.currentPage, pageSize = collectionsTabState.pageSize, onPageChange = collectionsTabState::onPageChange, onPageSizeChange = collectionsTabState::onPageSizeChange, - minSize = collectionsTabState.cardWidth.collectAsState().value ) - } } - } @Composable @@ -214,19 +212,16 @@ class LibraryScreen( onReadListClick = { navigator push ReadListScreen(it) }, onReadListDelete = readListTabState::onReadListDelete, isLoading = loading, - totalPages = readListTabState.totalPages, currentPage = readListTabState.currentPage, pageSize = readListTabState.pageSize, onPageChange = readListTabState::onPageChange, onPageSizeChange = readListTabState::onPageSizeChange, - minSize = readListTabState.cardWidth.collectAsState().value ) } } } - } @Composable @@ -239,9 +234,12 @@ fun LibraryToolBar( onBrowseClick: () -> Unit, onCollectionsClick: () -> Unit, onReadListsClick: () -> Unit, + randomSeriesEnabled: Boolean, + onRandomSeriesClick: () -> Unit, + onRandomUnreadSeriesClick: () -> Unit, ) { - val chipColors = AppFilterChipDefaults.filterChipColors() + val strings = LocalStrings.current var showOptionsMenu by remember { mutableStateOf(false) } val isAdmin = LocalKomgaState.current.authenticatedUser.collectAsState().value?.roleAdmin() ?: true val isOffline = LocalOfflineMode.current.collectAsState().value @@ -261,7 +259,6 @@ fun LibraryToolBar( contentDescription = null, ) } - LibraryActionsMenu( library = library, actions = libraryActions, @@ -270,49 +267,65 @@ fun LibraryToolBar( ) } } - Text(library?.let { library.name } ?: "All Libraries") + Text(library?.let { library.name } ?: "All Libraries") Spacer(Modifier.width(5.dp)) } + if (collectionsCount > 0 || readListsCount > 0) item { + FilterChip( + onClick = onBrowseClick, + selected = currentTab == SERIES, + label = { Text("Series") }, + colors = chipColors, + border = null, + ) + } - if (collectionsCount > 0 || readListsCount > 0) - item { - FilterChip( - onClick = onBrowseClick, - selected = currentTab == SERIES, - label = { Text("Series") }, - colors = chipColors, - border = null, - ) - } + if (collectionsCount > 0) item { + FilterChip( + onClick = onCollectionsClick, + selected = currentTab == COLLECTIONS, + label = { Text("Collections") }, + colors = chipColors, + border = null, + ) + } - if (collectionsCount > 0) - item { - FilterChip( - onClick = onCollectionsClick, - selected = currentTab == COLLECTIONS, - label = { Text("Collections") }, - colors = chipColors, - border = null, - ) - } + if (readListsCount > 0) item { + FilterChip( + onClick = onReadListsClick, + selected = currentTab == READ_LISTS, + label = { Text("Read Lists") }, + colors = chipColors, + border = null, + ) + } - if (readListsCount > 0) - item { - FilterChip( - onClick = onReadListsClick, - selected = currentTab == READ_LISTS, - label = { Text("Read Lists") }, - colors = chipColors, - border = null, - ) - } + item { + FilterChip( + onClick = onRandomSeriesClick, + enabled = randomSeriesEnabled, + selected = false, + label = { Text(strings.seriesFilter.sortRandom) }, + colors = chipColors, + border = null, + ) + } + item { + FilterChip( + onClick = onRandomUnreadSeriesClick, + enabled = randomSeriesEnabled, + selected = false, + label = { Text(strings.seriesFilter.randomUnread) }, + colors = chipColors, + border = null, + ) + } } } - data class SeriesScreenFilter( val publicationStatus: List? = null, val ageRating: List? = null, @@ -321,4 +334,4 @@ data class SeriesScreenFilter( val genres: List? = null, val tags: List? = null, val authors: List? = null, -) \ No newline at end of file +) diff --git a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/library/LibrarySeriesTabState.kt b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/library/LibrarySeriesTabState.kt index c514c6126..452863976 100644 --- a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/library/LibrarySeriesTabState.kt +++ b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/library/LibrarySeriesTabState.kt @@ -28,14 +28,21 @@ import snd.komelia.ui.LoadState import snd.komelia.ui.common.menus.SeriesMenuActions import snd.komelia.ui.series.SeriesFilter import snd.komelia.ui.series.SeriesFilterState +import snd.komga.client.book.KomgaReadStatus.IN_PROGRESS +import snd.komga.client.book.KomgaReadStatus.UNREAD import snd.komga.client.common.KomgaPageRequest +import snd.komga.client.common.KomgaSort.Direction.ASC import snd.komga.client.common.KomgaSort.KomgaSeriesSort +import snd.komga.client.common.KomgaSort.Order import snd.komga.client.common.Page import snd.komga.client.library.KomgaLibrary import snd.komga.client.search.allOfSeries import snd.komga.client.series.KomgaSeries import snd.komga.client.sse.KomgaEvent +private const val SERIES_RANDOM_SORT = "random" +private val SERIES_UNREAD_STATUSES = listOf(UNREAD, IN_PROGRESS) + class LibrarySeriesTabState( private val seriesApi: KomgaSeriesApi, referentialApi: KomgaReferentialApi, @@ -108,6 +115,18 @@ class LibrarySeriesTabState( fun seriesMenuActions() = SeriesMenuActions(seriesApi, notifications, taskEmitter, screenModelScope) + fun openRandomSeries(onSeriesSelected: (KomgaSeries) -> Unit) { + notifications.runCatchingToNotifications(screenModelScope) { + getRandomSeries(filterState.state.value)?.let(onSeriesSelected) + } + } + + fun openRandomUnreadSeries(onSeriesSelected: (KomgaSeries) -> Unit) { + notifications.runCatchingToNotifications(screenModelScope) { + getRandomSeries(filterState.state.value.copy(readStatus = SERIES_UNREAD_STATUSES))?.let(onSeriesSelected) + } + } + fun onPageSizeChange(pageSize: Int) { pageLoadSize.value = pageSize screenModelScope.launch { settingsRepository.putSeriesPageLoadSize(pageSize) } @@ -173,6 +192,23 @@ class LibrarySeriesTabState( ) } + private suspend fun getRandomSeries(filter: SeriesFilter): KomgaSeries? { + val condition = allOfSeries { + library.value?.let { library { isEqualTo(it.id) } } + filter.addConditionTo(this) + } + + return seriesApi.getSeriesList( + conditionBuilder = condition, + fulltextSearch = filter.searchTerm.ifBlank { null }, + pageRequest = KomgaPageRequest( + size = 1, + pageIndex = 0, + sort = SeriesSort.RANDOM.komgaSort + ) + ).content.firstOrNull() + } + private fun delayLoadState(): Deferred { return screenModelScope.async { delay(200) @@ -224,6 +260,7 @@ class LibrarySeriesTabState( TITLE_DESC(KomgaSeriesSort.byTitleDesc()), DATE_ADDED_DESC(KomgaSeriesSort.byCreatedDateDesc()), DATE_ADDED_ASC(KomgaSeriesSort.byCreatedDateAsc()), + RANDOM(KomgaSeriesSort(listOf(Order(SERIES_RANDOM_SORT, ASC)))), // FOLDER_NAME_ASC(KomgaSeriesSort.byFolderNameAsc()), // FOLDER_NAME_DESC(KomgaSeriesSort.byFolderNameDesc()), // BOOKS_COUNT_ASC(KomgaSeriesSort.byBooksCountAsc()), diff --git a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/strings/AppStrings.kt b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/strings/AppStrings.kt index 14a26a384..93a482b7a 100644 --- a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/strings/AppStrings.kt +++ b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/strings/AppStrings.kt @@ -442,6 +442,8 @@ data class SeriesFilterStrings( val anyValue: String, val search: String, val sort: String, + val sortRandom: String, + val randomUnread: String, val sortTitleAsc: String, val sortTitleDesc: String, val sortDateAddedAsc: String, @@ -487,6 +489,7 @@ data class SeriesFilterStrings( LibrarySeriesTabState.SeriesSort.RELEASE_DATE_DESC -> sortReleaseDateDesc LibrarySeriesTabState.SeriesSort.UPDATED_DESC -> sortUpdatedDesc LibrarySeriesTabState.SeriesSort.UPDATED_ASC -> sortUpdatedAsc + LibrarySeriesTabState.SeriesSort.RANDOM -> sortRandom // FOLDER_NAME_ASC -> sortFolderNameAsc // FOLDER_NAME_DESC -> sortFolderNameDesc // BOOKS_COUNT_ASC -> sortBooksCountAsc diff --git a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/strings/EnStrings.kt b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/strings/EnStrings.kt index 3f0725001..41d522587 100644 --- a/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/strings/EnStrings.kt +++ b/komelia-ui/src/commonMain/kotlin/snd/komelia/ui/strings/EnStrings.kt @@ -22,6 +22,8 @@ val EnStrings = AppStrings( search = "Search", sort = "Sort by", + sortRandom = "Random", + randomUnread = "Random unread", sortTitleAsc = "Title Ascending", sortTitleDesc = "Title Descending", sortDateAddedAsc = "Oldest Added",