From 545e92998719fc3460e796a493a3ea0fa184d44c Mon Sep 17 00:00:00 2001 From: Hitman47 Date: Wed, 29 Apr 2026 01:36:08 +0200 Subject: [PATCH 1/3] Add files via upload --- .../kotlin/snd/komelia/ui/library/LibrarySeriesTabState.kt | 5 +++++ .../commonMain/kotlin/snd/komelia/ui/strings/AppStrings.kt | 2 ++ .../commonMain/kotlin/snd/komelia/ui/strings/EnStrings.kt | 1 + 3 files changed, 8 insertions(+) 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..042af192e 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 @@ -29,13 +29,17 @@ import snd.komelia.ui.common.menus.SeriesMenuActions import snd.komelia.ui.series.SeriesFilter import snd.komelia.ui.series.SeriesFilterState 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" + class LibrarySeriesTabState( private val seriesApi: KomgaSeriesApi, referentialApi: KomgaReferentialApi, @@ -224,6 +228,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..9f9b04e10 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,7 @@ data class SeriesFilterStrings( val anyValue: String, val search: String, val sort: String, + val sortRandom: String, val sortTitleAsc: String, val sortTitleDesc: String, val sortDateAddedAsc: String, @@ -487,6 +488,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..aeb64fc5a 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,7 @@ val EnStrings = AppStrings( search = "Search", sort = "Sort by", + sortRandom = "Random", sortTitleAsc = "Title Ascending", sortTitleDesc = "Title Descending", sortDateAddedAsc = "Oldest Added", From 8d52cf125746daa6262c4bb2774dc8f264cac86e Mon Sep 17 00:00:00 2001 From: Hitman47 Date: Wed, 29 Apr 2026 02:01:18 +0200 Subject: [PATCH 2/3] Add files via upload --- .../snd/komelia/ui/library/LibraryScreen.kt | 99 +++++++++---------- .../ui/library/LibrarySeriesTabState.kt | 23 +++++ 2 files changed, 71 insertions(+), 51 deletions(-) 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..cfdb577c4 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 @@ -12,6 +12,7 @@ import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -34,6 +35,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 +62,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 +96,13 @@ 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)) } + } ) } - when (vm.currentTab) { SERIES -> BrowseTab(vm.seriesTabState) COLLECTIONS -> CollectionsTab(vm.collectionsTabState) @@ -129,26 +132,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 +179,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 +210,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 +232,11 @@ fun LibraryToolBar( onBrowseClick: () -> Unit, onCollectionsClick: () -> Unit, onReadListsClick: () -> Unit, + randomSeriesEnabled: Boolean, + onRandomSeriesClick: () -> 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 +256,6 @@ fun LibraryToolBar( contentDescription = null, ) } - LibraryActionsMenu( library = library, actions = libraryActions, @@ -270,49 +264,52 @@ 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 (currentTab == SERIES) item { + TextButton( + onClick = onRandomSeriesClick, + enabled = randomSeriesEnabled, + ) { + Text(strings.seriesFilter.sortRandom) } + } - if (collectionsCount > 0) - item { - FilterChip( - onClick = onCollectionsClick, - selected = currentTab == COLLECTIONS, - label = { Text("Collections") }, - colors = chipColors, - border = null, - ) - } + if (collectionsCount > 0 || readListsCount > 0) item { + FilterChip( + onClick = onBrowseClick, + selected = currentTab == SERIES, + label = { Text("Series") }, + colors = chipColors, + border = null, + ) + } - if (readListsCount > 0) - item { - FilterChip( - onClick = onReadListsClick, - selected = currentTab == READ_LISTS, - label = { Text("Read Lists") }, - 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, + ) + } } } - data class SeriesScreenFilter( val publicationStatus: List? = null, val ageRating: List? = null, @@ -321,4 +318,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 042af192e..cd7702ef8 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 @@ -112,6 +112,12 @@ class LibrarySeriesTabState( fun seriesMenuActions() = SeriesMenuActions(seriesApi, notifications, taskEmitter, screenModelScope) + fun openRandomSeries(onSeriesSelected: (KomgaSeries) -> Unit) { + notifications.runCatchingToNotifications(screenModelScope) { + getRandomSeries(filterState.state.value)?.let(onSeriesSelected) + } + } + fun onPageSizeChange(pageSize: Int) { pageLoadSize.value = pageSize screenModelScope.launch { settingsRepository.putSeriesPageLoadSize(pageSize) } @@ -177,6 +183,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) From ff2b6315015dfd3d9adb39aa446152fa073579b3 Mon Sep 17 00:00:00 2001 From: Hitman47 Date: Wed, 29 Apr 2026 02:01:35 +0200 Subject: [PATCH 3/3] Add files via upload --- .../snd/komelia/ui/library/LibraryScreen.kt | 36 +++++++++++++------ .../ui/library/LibrarySeriesTabState.kt | 9 +++++ .../snd/komelia/ui/strings/AppStrings.kt | 1 + .../snd/komelia/ui/strings/EnStrings.kt | 1 + 4 files changed, 37 insertions(+), 10 deletions(-) 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 cfdb577c4..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 @@ -12,7 +12,6 @@ import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -100,6 +99,9 @@ class LibraryScreen( randomSeriesEnabled = vm.seriesTabState.totalSeriesCount > 0, onRandomSeriesClick = { vm.seriesTabState.openRandomSeries { navigator.push(seriesScreen(it)) } + }, + onRandomUnreadSeriesClick = { + vm.seriesTabState.openRandomUnreadSeries { navigator.push(seriesScreen(it)) } } ) } @@ -234,6 +236,7 @@ fun LibraryToolBar( onReadListsClick: () -> Unit, randomSeriesEnabled: Boolean, onRandomSeriesClick: () -> Unit, + onRandomUnreadSeriesClick: () -> Unit, ) { val chipColors = AppFilterChipDefaults.filterChipColors() val strings = LocalStrings.current @@ -269,15 +272,6 @@ fun LibraryToolBar( Spacer(Modifier.width(5.dp)) } - if (currentTab == SERIES) item { - TextButton( - onClick = onRandomSeriesClick, - enabled = randomSeriesEnabled, - ) { - Text(strings.seriesFilter.sortRandom) - } - } - if (collectionsCount > 0 || readListsCount > 0) item { FilterChip( onClick = onBrowseClick, @@ -307,6 +301,28 @@ fun LibraryToolBar( 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, + ) + } } } 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 cd7702ef8..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,6 +28,8 @@ 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 @@ -39,6 +41,7 @@ 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, @@ -118,6 +121,12 @@ class LibrarySeriesTabState( } } + 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) } 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 9f9b04e10..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 @@ -443,6 +443,7 @@ data class SeriesFilterStrings( val search: String, val sort: String, val sortRandom: String, + val randomUnread: String, val sortTitleAsc: String, val sortTitleDesc: String, val sortDateAddedAsc: String, 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 aeb64fc5a..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 @@ -23,6 +23,7 @@ val EnStrings = AppStrings( sort = "Sort by", sortRandom = "Random", + randomUnread = "Random unread", sortTitleAsc = "Title Ascending", sortTitleDesc = "Title Descending", sortDateAddedAsc = "Oldest Added",