Optimize and cleanup library code (mihonapp/mihon#2329)

Co-authored-by: Cuong-Tran <cuongtran.tm@gmail.com>
(cherry picked from commit e62cd0e816402303fdf12513816894624f77e208)
This commit is contained in:
AntsyLich 2025-08-02 09:04:23 +06:00 committed by Cuong-Tran
parent 74a9182b20
commit 1fd86ca74f
19 changed files with 640 additions and 601 deletions

View file

@ -23,13 +23,14 @@ ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ktlint_code_style = intellij_idea
ktlint_function_naming_ignore_when_annotated_with = Composable
ktlint_standard_class-signature = disabled
ktlint_standard_comment-wrapping = disabled
ktlint_standard_discouraged-comment-location = disabled
ktlint_standard_function-expression-body = disabled
ktlint_standard_function-signature = disabled
ktlint_standard_type-argument-comment = disabled
ktlint_standard_type-parameter-comment = disabled
# Avoid ktlint format messy code from SY
ktlint_standard_max-line-length = disabled
ktlint_standard_comment-wrapping = disabled
ktlint_standard_type-argument-comment = disabled
ktlint_standard_value-argument-comment = disabled
ktlint_standard_value-parameter-comment = disabled

View file

@ -11,25 +11,6 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- `Other` - for technical stuff.
## [Unreleased]
### Fixes
- Fix height of description not being calculated correctly if images are present ([@Secozzi](https://github.com/Secozzi)) ([#2382](https://github.com/mihonapp/mihon/pull/2382))
## [v0.19.1] - 2025-08-07
### Changed
- LocalSource now reads ComicInfo.xml file for chapter (if available) to display chapter title, number and scanlator ([@raxod502](https://github.com/radian-software)) ([#2332](https://github.com/mihonapp/mihon/pull/2332))
### Removed
- Predictive back support ([@AntsyLich](https://github.com/AntsyLich)) ([#2362](https://github.com/mihonapp/mihon/pull/2362))
### Fixes
- Fix scrollbar sometimes not showing during scroll or not reaching the bottom with few items ([@anirudhsnayak](https://github.com/anirudhsnayak)) ([#2304](https://github.com/mihonapp/mihon/pull/2304))
- Fix local source EPUB files not loading ([@AntsyLich](https://github.com/AntsyLich)) ([#2369](https://github.com/mihonapp/mihon/pull/2369))
- Fix title text color in light mode on mass migration list ([@AntsyLich](https://github.com/AntsyLich)) ([#2370](https://github.com/mihonapp/mihon/pull/2370))
- Fix 'Default' category showing in library with no user-added categories ([@AntsyLich](https://github.com/AntsyLich)) ([#2371](https://github.com/mihonapp/mihon/pull/2371))
- Fix crash when opening filter sheet with an empty library ([@krysanify](https://github.com/krysanify/)) ([#2355](https://github.com/mihonapp/mihon/pull/2355))
- Fix mark as read/unread not working for selected library items ([@krysanify](https://github.com/krysanify/)) ([#2355](https://github.com/mihonapp/mihon/pull/2355))
## [v0.19.0] - 2025-08-04
### Added
- Add more Kaomoji for empty/error screens ([@ianfhunter](https://github.com/ianfhunter/)) ([#1909](https://github.com/mihonapp/mihon/pull/1909))
- Add user manga notes ([@imkunet](https://github.com/imkunet), [@AntsyLich](https://github.com/AntsyLich)) ([#428](https://github.com/mihonapp/mihon/pull/428))
@ -43,7 +24,6 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Add advanced option to always update manga title from source ([@FlaminSarge](https://github.com/FlaminSarge)) ([#1182](https://github.com/mihonapp/mihon/pull/1182))
- Full predictive back support ([@AntsyLich](https://github.com/AntsyLich)) ([#2085](https://github.com/mihonapp/mihon/pull/2085))
- Add Catppuccin theme (mocha for dark and latte for light, mauve accent) ([@claymorwan](https://github.com/claymorwan/)) ([#2117](https://github.com/mihonapp/mihon/pull/2117))
- Manga mass migration ([@AntsyLich](https://github.com/AntsyLich), [@jobobby04](https://github.com/jobobby04)) ([#2110](https://github.com/mihonapp/mihon/pull/2110), [#2336](https://github.com/mihonapp/mihon/pull/2336), [#2338](https://github.com/mihonapp/mihon/pull/2338), [`f119386`](https://github.com/mihonapp/mihon/commit/f119386))
### Improved
- Significantly improve browsing speed (near instantaneous) ([@AntsyLich](https://github.com/AntsyLich)) ([#1946](https://github.com/mihonapp/mihon/pull/1946))
@ -54,7 +34,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Added autofill support to tracker login dialog ([@AntsyLich](https://github.com/AntsyLich)) ([#2069](https://github.com/mihonapp/mihon/pull/2069))
- Added option to hide missing chapter count ([@User826](https://github.com/User826), [@AntsyLich](https://github.com/AntsyLich)) ([#2108](https://github.com/mihonapp/mihon/pull/2108))
- Use median to determine smart update interval, making it more resilient to long hiatuses ([@Kladki](https://github.com/Kladki)) ([#2251](https://github.com/mihonapp/mihon/pull/2251))
- Optimize library code to potentially better handle big user libraries ([@AntsyLich](https://github.com/AntsyLich)) ([#2329](https://github.com/mihonapp/mihon/pull/2329), [#2341](https://github.com/mihonapp/mihon/pull/2341))
- Optimize library code to potentially better handle big user libraries ([@AntsyLich](https://github.com/AntsyLich)) ([#2329](https://github.com/mihonapp/mihon/pull/2329))
### Changed
- Display all similarly named duplicates in duplicate manga dialogue ([@NarwhalHorns](https://github.com/NarwhalHorns), [@AntsyLich](https://github.com/AntsyLich)) ([#1861](https://github.com/mihonapp/mihon/pull/1861))
@ -399,9 +379,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Branding to Mihon ([@AntsyLich](https://github.com/AntsyLich))
- Minimum supported Android version to 8 ([@AntsyLich](https://github.com/AntsyLich)) ([`dfb3091`](https://github.com/mihonapp/mihon/commit/dfb3091e380dda3e9bfb64bf5c9a685cf3a03d0e))
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.19.1...main
[v0.19.1]: https://github.com/mihonapp/mihon/compare/v0.19.0...v0.19.1
[v0.19.0]: https://github.com/mihonapp/mihon/compare/v0.18.0...v0.19.0
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.18.0...main
[v0.18.0]: https://github.com/mihonapp/mihon/compare/v0.17.1...v0.18.0
[v0.17.1]: https://github.com/mihonapp/mihon/compare/v0.17.0...v0.17.1
[v0.17.0]: https://github.com/mihonapp/mihon/compare/v0.16.5...v0.17.0

View file

@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.util.fastAny
import eu.kanade.tachiyomi.ui.library.LibraryItem
import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.MangaCover
@ -15,7 +14,7 @@ internal fun LibraryComfortableGrid(
items: List<LibraryItem>,
columns: Int,
contentPadding: PaddingValues,
selection: List<LibraryManga>,
selection: Set<Long>,
onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit,
onClickContinueReading: ((LibraryManga) -> Unit)?,
@ -38,7 +37,7 @@ internal fun LibraryComfortableGrid(
) { libraryItem ->
val manga = libraryItem.libraryManga.manga
MangaComfortableGridItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
isSelected = manga.id in selection,
title = manga.title,
coverData = MangaCover(
mangaId = manga.id,

View file

@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.util.fastAny
import eu.kanade.tachiyomi.ui.library.LibraryItem
import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.MangaCover
@ -16,7 +15,7 @@ internal fun LibraryCompactGrid(
showTitle: Boolean,
columns: Int,
contentPadding: PaddingValues,
selection: List<LibraryManga>,
selection: Set<Long>,
onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit,
onClickContinueReading: ((LibraryManga) -> Unit)?,
@ -36,7 +35,7 @@ internal fun LibraryCompactGrid(
) { libraryItem ->
val manga = libraryItem.libraryManga.manga
MangaCompactGridItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
isSelected = manga.id in selection,
title = manga.title.takeIf { showTitle },
coverData = MangaCover(
mangaId = manga.id,

View file

@ -29,22 +29,22 @@ import kotlin.time.Duration.Companion.seconds
fun LibraryContent(
categories: List<Category>,
searchQuery: String?,
selection: List<LibraryManga>,
selection: Set<Long>,
contentPadding: PaddingValues,
currentPage: () -> Int,
hasActiveFilters: Boolean,
showPageTabs: Boolean,
onChangeCurrentPage: (Int) -> Unit,
onMangaClicked: (Long) -> Unit,
onClickManga: (Long) -> Unit,
onContinueReadingClicked: ((LibraryManga) -> Unit)?,
onToggleSelection: (LibraryManga) -> Unit,
onToggleRangeSelection: (LibraryManga) -> Unit,
onRefresh: (Category?) -> Boolean,
onToggleSelection: (Category, LibraryManga) -> Unit,
onToggleRangeSelection: (Category, LibraryManga) -> Unit,
onRefresh: () -> Boolean,
onGlobalSearchClicked: () -> Unit,
getNumberOfMangaForCategory: (Category) -> Int?,
getItemCountForCategory: (Category) -> Int?,
getDisplayMode: (Int) -> PreferenceMutableState<LibraryDisplayMode>,
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
getLibraryForPage: (Int) -> List<LibraryItem>,
getItemsForCategory: (Category) -> List<LibraryItem>,
) {
Column(
modifier = Modifier.padding(
@ -55,13 +55,13 @@ fun LibraryContent(
) {
// SY -->
val coercedCurrentPage = remember(categories) { currentPage().coerceIn(0, categories.lastIndex) }
val pagerState = rememberPagerState(coercedCurrentPage) { categories.size }
// SY <--
val pagerState = rememberPagerState(coercedCurrentPage) { categories.size }
val scope = rememberCoroutineScope()
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
if (showPageTabs && categories.size > 1) {
if (showPageTabs && categories.isNotEmpty()) {
LaunchedEffect(categories) {
if (categories.size <= pagerState.currentPage) {
pagerState.scrollToPage(categories.size - 1)
@ -70,23 +70,20 @@ fun LibraryContent(
LibraryTabs(
categories = categories,
pagerState = pagerState,
getNumberOfMangaForCategory = getNumberOfMangaForCategory,
) { scope.launch { pagerState.animateScrollToPage(it) } }
}
val notSelectionMode = selection.isEmpty()
val onClickManga = { manga: LibraryManga ->
if (notSelectionMode) {
onMangaClicked(manga.manga.id)
} else {
onToggleSelection(manga)
}
getItemCountForCategory = getItemCountForCategory,
onTabItemClick = {
scope.launch {
pagerState.animateScrollToPage(it)
}
},
)
}
PullRefresh(
refreshing = isRefreshing,
enabled = selection.isEmpty(),
onRefresh = {
val started = onRefresh(categories.getOrNull(currentPage()) ?: return@PullRefresh)
val started = onRefresh()
if (!started) return@PullRefresh
scope.launch {
// Fake refresh status but hide it after a second as it's a long running task
@ -95,19 +92,25 @@ fun LibraryContent(
isRefreshing = false
}
},
enabled = notSelectionMode,
) {
LibraryPager(
state = pagerState,
contentPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding()),
hasActiveFilters = hasActiveFilters,
selectedManga = selection,
selection = selection,
searchQuery = searchQuery,
onGlobalSearchClicked = onGlobalSearchClicked,
getCategoryForPage = { page -> categories[page] },
getDisplayMode = getDisplayMode,
getColumnsForOrientation = getColumnsForOrientation,
getLibraryForPage = getLibraryForPage,
onClickManga = onClickManga,
getItemsForCategory = getItemsForCategory,
onClickManga = { category, manga ->
if (selection.isNotEmpty()) {
onToggleSelection(category, manga)
} else {
onClickManga(manga.manga.id)
}
},
onLongClickManga = onToggleRangeSelection,
onClickContinueReading = onContinueReadingClicked,
)

View file

@ -7,7 +7,6 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastAny
import eu.kanade.tachiyomi.ui.library.LibraryItem
import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.MangaCover
@ -18,7 +17,7 @@ import tachiyomi.presentation.core.util.plus
internal fun LibraryList(
items: List<LibraryItem>,
contentPadding: PaddingValues,
selection: List<LibraryManga>,
selection: Set<Long>,
onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit,
onClickContinueReading: ((LibraryManga) -> Unit)?,
@ -45,7 +44,7 @@ internal fun LibraryList(
) { libraryItem ->
val manga = libraryItem.libraryManga.manga
MangaListItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id },
isSelected = manga.id in selection,
title = manga.title,
coverData = MangaCover(
mangaId = manga.id,

View file

@ -20,6 +20,7 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
import eu.kanade.core.preference.PreferenceMutableState
import eu.kanade.tachiyomi.ui.library.LibraryItem
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.library.model.LibraryDisplayMode
import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.i18n.MR
@ -31,14 +32,15 @@ fun LibraryPager(
state: PagerState,
contentPadding: PaddingValues,
hasActiveFilters: Boolean,
selectedManga: List<LibraryManga>,
selection: Set<Long>,
searchQuery: String?,
onGlobalSearchClicked: () -> Unit,
getCategoryForPage: (Int) -> Category,
getDisplayMode: (Int) -> PreferenceMutableState<LibraryDisplayMode>,
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
getLibraryForPage: (Int) -> List<LibraryItem>,
onClickManga: (LibraryManga) -> Unit,
onLongClickManga: (LibraryManga) -> Unit,
getItemsForCategory: (Category) -> List<LibraryItem>,
onClickManga: (Category, LibraryManga) -> Unit,
onLongClickManga: (Category, LibraryManga) -> Unit,
onClickContinueReading: ((LibraryManga) -> Unit)?,
) {
HorizontalPager(
@ -50,9 +52,10 @@ fun LibraryPager(
// To make sure only one offscreen page is being composed
return@HorizontalPager
}
val library = getLibraryForPage(page)
val category = getCategoryForPage(page)
val items = getItemsForCategory(category)
if (library.isEmpty()) {
if (items.isEmpty()) {
LibraryPagerEmptyScreen(
searchQuery = searchQuery,
hasActiveFilters = hasActiveFilters,
@ -72,12 +75,15 @@ fun LibraryPager(
remember { mutableIntStateOf(0) }
}
val onClickManga: (LibraryManga) -> Unit = { onClickManga(category, it) }
val onLongClickManga: (LibraryManga) -> Unit = { onLongClickManga(category, it) }
when (displayMode) {
LibraryDisplayMode.List -> {
LibraryList(
items = library,
items = items,
contentPadding = contentPadding,
selection = selectedManga,
selection = selection,
onClick = onClickManga,
onLongClick = onLongClickManga,
onClickContinueReading = onClickContinueReading,
@ -87,11 +93,11 @@ fun LibraryPager(
}
LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> {
LibraryCompactGrid(
items = library,
items = items,
showTitle = displayMode is LibraryDisplayMode.CompactGrid,
columns = columns,
contentPadding = contentPadding,
selection = selectedManga,
selection = selection,
onClick = onClickManga,
onLongClick = onLongClickManga,
onClickContinueReading = onClickContinueReading,
@ -101,10 +107,10 @@ fun LibraryPager(
}
LibraryDisplayMode.ComfortableGrid -> {
LibraryComfortableGrid(
items = library,
items = items,
columns = columns,
contentPadding = contentPadding,
selection = selectedManga,
selection = selection,
onClick = onClickManga,
onLongClick = onLongClickManga,
onClickContinueReading = onClickContinueReading,
@ -115,10 +121,10 @@ fun LibraryPager(
// KMK -->
LibraryDisplayMode.ComfortableGridPanorama -> {
LibraryComfortableGrid(
items = library,
items = items,
columns = columns,
contentPadding = contentPadding,
selection = selectedManga,
selection = selection,
onClick = onClickManga,
onLongClick = onLongClickManga,
onClickContinueReading = onClickContinueReading,

View file

@ -18,13 +18,11 @@ import tachiyomi.presentation.core.components.material.TabText
internal fun LibraryTabs(
categories: List<Category>,
pagerState: PagerState,
getNumberOfMangaForCategory: (Category) -> Int?,
getItemCountForCategory: (Category) -> Int?,
onTabItemClick: (Int) -> Unit,
) {
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
Column(
modifier = Modifier.zIndex(1f),
) {
Column(modifier = Modifier.zIndex(2f)) {
PrimaryScrollableTabRow(
selectedTabIndex = currentPageIndex,
edgePadding = 0.dp,
@ -39,7 +37,7 @@ internal fun LibraryTabs(
text = {
TabText(
text = category.visualName,
badgeCount = getNumberOfMangaForCategory(category),
badgeCount = getItemCountForCategory(category),
)
},
unselectedContentColor = MaterialTheme.colorScheme.onSurface,

View file

@ -252,28 +252,23 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
// KMK <--
val listToUpdate = if (categoryId != -1L) {
libraryManga.filter { it.category == categoryId }
libraryManga.filter { categoryId in it.categories }
// SY -->
} else if (
group == LibraryGroup.BY_DEFAULT ||
groupLibraryUpdateType == GroupLibraryMode.GLOBAL ||
(groupLibraryUpdateType == GroupLibraryMode.ALL_BUT_UNGROUPED && group == LibraryGroup.UNGROUPED)
) {
val categoriesToUpdate = libraryPreferences.updateCategories().get().map(String::toLong)
val includedManga = if (categoriesToUpdate.isNotEmpty()) {
libraryManga.filter { it.category in categoriesToUpdate }
} else {
libraryManga
}
// SY <--
val includedCategories = libraryPreferences.updateCategories().get().map { it.toLong() }.toSet()
val excludedCategories = libraryPreferences.updateCategoriesExclude().get().map { it.toLong() }.toSet()
val categoriesToExclude = libraryPreferences.updateCategoriesExclude().get().map { it.toLong() }
val excludedMangaIds = if (categoriesToExclude.isNotEmpty()) {
libraryManga.filter { it.category in categoriesToExclude }.map { it.manga.id }
} else {
emptyList()
libraryManga.filter {
val included = includedCategories.isEmpty() || it.categories.intersect(includedCategories).isNotEmpty()
val excluded = it.categories.intersect(excludedCategories).isNotEmpty()
included && !excluded
}
includedManga
.filterNot { it.manga.id in excludedMangaIds }
// SY -->
} else {
when (group) {
LibraryGroup.BY_TRACK_STATUS -> {

View file

@ -18,6 +18,8 @@ data class LibraryItem(
// KMK <--
private val sourceManager: SourceManager = Injekt.get(),
) {
val id: Long = libraryManga.id
// /**
// * Checks if a query matches the manga
// *
@ -26,6 +28,9 @@ data class LibraryItem(
// */
// fun matches(constraint: String): Boolean {
// val sourceName by lazy { sourceManager.getOrStub(libraryManga.manga.source).getNameForMangaInfo() }
// if (constraint.startsWith("id:", true)) {
// return id == constraint.substringAfter("id:").toLongOrNull()
// }
// return libraryManga.manga.title.contains(constraint, true) ||
// (libraryManga.manga.author?.contains(constraint, true) ?: false) ||
// (libraryManga.manga.artist?.contains(constraint, true) ?: false) ||

View file

@ -60,6 +60,7 @@ import exh.recs.batch.RecommendationSearchProgressDialog
import exh.recs.batch.SearchStatus
import exh.source.MERGED_SOURCE_ID
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow
@ -147,18 +148,15 @@ data object LibraryTab : Tab {
defaultCategoryTitle = stringResource(MR.strings.label_default),
page = screenModel.activeCategoryIndex,
)
val tabVisible = state.showCategoryTabs && state.categories.size > 1
LibraryToolbar(
hasActiveFilters = state.hasActiveFilters,
selectedCount = state.selection.size,
title = title,
onClickUnselectAll = screenModel::clearSelection,
onClickSelectAll = { screenModel.selectAll(screenModel.activeCategoryIndex) },
onClickInvertSelection = { screenModel.invertSelection(screenModel.activeCategoryIndex) },
onClickSelectAll = screenModel::selectAll,
onClickInvertSelection = screenModel::invertSelection,
onClickFilter = screenModel::showSettingsDialog,
onClickRefresh = {
onClickRefresh(state.categories[screenModel.activeCategoryIndex.coerceAtMost(state.categories.lastIndex)])
},
onClickRefresh = { onClickRefresh(screenModel.activeCategory) },
onClickGlobalUpdate = { onClickRefresh(null) },
onClickOpenRandomManga = {
scope.launch {
@ -189,7 +187,8 @@ data object LibraryTab : Tab {
Injekt.get<DownloadCache>().invalidateCache()
context.toast(MR.strings.download_cache_invalidated)
},
scrollBehavior = scrollBehavior.takeIf { !tabVisible }, // For scroll overlay when no tab
// For scroll overlay when no tab
scrollBehavior = scrollBehavior.takeIf { !state.showCategoryTabs },
)
},
bottomBar = {
@ -198,15 +197,15 @@ data object LibraryTab : Tab {
onChangeCategoryClicked = screenModel::openChangeCategoryDialog,
onMarkAsReadClicked = { screenModel.markReadSelection(true) },
onMarkAsUnreadClicked = { screenModel.markReadSelection(false) },
onDownloadClicked = screenModel::runDownloadActionSelection
.takeIf { state.selection.fastAll { !it.manga.isLocal() } },
onDownloadClicked = screenModel::performDownloadAction
.takeIf { state.selectedManga.fastAll { !it.isLocal() } },
onDeleteClicked = screenModel::openDeleteMangaDialog,
// SY -->
onClickCleanTitles = screenModel::cleanTitles.takeIf { state.showCleanTitles },
onClickMigrate = {
val selectedMangaIds = state.selection
.filterNot { it.manga.source == MERGED_SOURCE_ID }
.map { it.manga.id }
val selectedMangaIds = state.selectedManga
.filterNot { it.source == MERGED_SOURCE_ID }
.map { it.id }
screenModel.clearSelection()
if (selectedMangaIds.isNotEmpty()) {
PreMigrationScreen.navigateToMigration(
@ -225,18 +224,18 @@ data object LibraryTab : Tab {
// KMK -->
onClickMerge = {
if (state.selection.size == 1) {
val manga = state.selection.first().manga
val manga = state.selectedManga.first()
// Invoke merging for this manga
screenModel.clearSelection()
val smartSearchConfig = SourcesScreen.SmartSearchConfig(manga.title, manga.id)
navigator.push(SourcesScreen(smartSearchConfig))
} else if (state.selection.isNotEmpty()) {
// Invoke multiple merge
val selection = state.selection
val selectedManga = state.selectedManga
screenModel.clearSelection()
scope.launchIO {
val mergingMangas = selection.filterNot { it.manga.source == MERGED_SOURCE_ID }
val mergedMangaId = screenModel.smartSearchMerge(selection)
val mergingMangas = selectedManga.filterNot { it.source == MERGED_SOURCE_ID }
val mergedMangaId = screenModel.smartSearchMerge(selectedManga.toPersistentList())
snackbarHostState.showSnackbar(context.stringResource(SYMR.strings.entry_merged))
if (mergedMangaId != null) {
val result = snackbarHostState.showSnackbar(
@ -246,7 +245,7 @@ data object LibraryTab : Tab {
)
if (result == SnackbarResult.ActionPerformed) {
screenModel.removeMangas(
mangaList = mergingMangas.map { it.manga },
mangas = mergingMangas,
deleteFromLibrary = true,
deleteChapters = false,
)
@ -281,7 +280,9 @@ data object LibraryTab : Tab {
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { contentPadding ->
when {
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
state.isLoading -> {
LoadingScreen(Modifier.padding(contentPadding))
}
state.searchQuery.isNullOrEmpty() && !state.hasActiveFilters && state.isLibraryEmpty -> {
val handler = LocalUriHandler.current
EmptyScreen(
@ -298,7 +299,7 @@ data object LibraryTab : Tab {
}
else -> {
LibraryContent(
categories = state.categories,
categories = state.displayedCategories,
searchQuery = state.searchQuery,
selection = state.selection,
contentPadding = contentPadding,
@ -306,7 +307,7 @@ data object LibraryTab : Tab {
hasActiveFilters = state.hasActiveFilters,
showPageTabs = state.showCategoryTabs || !state.searchQuery.isNullOrEmpty(),
onChangeCurrentPage = { screenModel.activeCategoryIndex = it },
onMangaClicked = { navigator.push(MangaScreen(it)) },
onClickManga = { navigator.push(MangaScreen(it)) },
onContinueReadingClicked = { it: LibraryManga ->
scope.launchIO {
val chapter = screenModel.getNextUnreadChapter(it.manga)
@ -321,18 +322,19 @@ data object LibraryTab : Tab {
Unit
}.takeIf { state.showMangaContinueButton },
onToggleSelection = screenModel::toggleSelection,
onToggleRangeSelection = {
screenModel.toggleRangeSelection(it)
onToggleRangeSelection = { category, manga ->
screenModel.toggleRangeSelection(category, manga)
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
},
onRefresh = onClickRefresh,
onRefresh = { onClickRefresh(screenModel.activeCategory) },
onGlobalSearchClicked = {
navigator.push(GlobalSearchScreen(screenModel.state.value.searchQuery ?: ""))
},
getNumberOfMangaForCategory = { state.getMangaCountForCategory(it) },
getItemCountForCategory = { state.getItemCountForCategory(it) },
getDisplayMode = { screenModel.getDisplayMode() },
getColumnsForOrientation = { screenModel.getColumnsPreferenceForCurrentOrientation(it) },
) { state.getLibraryItemsByPage(it) }
getColumnsForOrientation = { screenModel.getColumnsForOrientation(it) },
getItemsForCategory = { state.getItemsForCategory(it) },
)
}
}
}
@ -340,20 +342,15 @@ data object LibraryTab : Tab {
val onDismissRequest = screenModel::closeDialog
when (val dialog = state.dialog) {
is LibraryScreenModel.Dialog.SettingsSheet -> run {
val category = state.categories.getOrNull(screenModel.activeCategoryIndex)
if (category == null) {
onDismissRequest()
return@run
}
LibrarySettingsDialog(
onDismissRequest = onDismissRequest,
screenModel = settingsScreenModel,
category = category,
category = screenModel.activeCategory,
// SY -->
hasCategories = state.categories.fastAny { !it.isSystemCategory },
hasCategories = state.libraryData.categories.fastAny { !it.isSystemCategory },
// SY <--
// KMK -->
categories = state.libraryCategories,
categories = state.libraryData.categories.filterNot(Category::isSystemCategory),
// KMK <--
)
}

View file

@ -2,11 +2,9 @@ package eu.kanade.tachiyomi.ui.stats
import androidx.compose.ui.util.fastDistinctBy
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastMapNotNull
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.util.fastCountNot
import eu.kanade.core.util.fastFilterNot
import eu.kanade.presentation.more.stats.StatsScreenState
import eu.kanade.presentation.more.stats.data.StatsData
import eu.kanade.tachiyomi.data.download.DownloadManager
@ -108,26 +106,15 @@ class StatsScreenModel(
}
private fun getGlobalUpdateItemCount(libraryManga: List<LibraryManga>): Int {
val includedCategories = preferences.updateCategories().get().map { it.toLong() }
val includedManga = if (includedCategories.isNotEmpty()) {
libraryManga.filter { it.category in includedCategories }
} else {
libraryManga
}
val excludedCategories = preferences.updateCategoriesExclude().get().map { it.toLong() }
val excludedMangaIds = if (excludedCategories.isNotEmpty()) {
libraryManga.fastMapNotNull { manga ->
manga.id.takeIf { manga.category in excludedCategories }
}
} else {
emptyList()
}
val includedCategories = preferences.updateCategories().get().map { it.toLong() }.toSet()
val excludedCategories = preferences.updateCategoriesExclude().get().map { it.toLong() }.toSet()
val updateRestrictions = preferences.autoUpdateMangaRestrictions().get()
return includedManga
.fastFilterNot { it.manga.id in excludedMangaIds }
.fastDistinctBy { it.manga.id }
return libraryManga.filter {
val included = includedCategories.isEmpty() || it.categories.intersect(includedCategories).isNotEmpty()
val excluded = it.categories.intersect(excludedCategories).isNotEmpty()
included && !excluded
}
.fastCountNot {
(MANGA_NON_COMPLETED in updateRestrictions && it.manga.status.toInt() == SManga.COMPLETED) ||
(MANGA_HAS_UNREAD in updateRestrictions && it.unreadCount != 0L) ||

View file

@ -0,0 +1,5 @@
package mihon.core.common.utils
fun <T> Set<T>.mutate(action: (MutableSet<T>) -> Unit): Set<T> {
return toMutableSet().apply(action)
}

View file

@ -104,7 +104,7 @@ object MangaMapper {
// KMK -->
bookmarkedReadCount: Long,
// KMK <--
category: Long,
categories: String,
): LibraryManga = LibraryManga(
manga = mapManga(
id,
@ -136,7 +136,7 @@ object MangaMapper {
isSyncing,
notes,
),
category = category,
categories = categories.split(",").map { it.toLong() },
totalChapters = totalCount,
readCount = readCount.toLong(),
bookmarkCount = bookmarkCount.toLong(),

View file

@ -0,0 +1,98 @@
DROP VIEW IF EXISTS libraryView;
CREATE VIEW libraryView AS
SELECT
M.*,
coalesce(C.total, 0) AS totalCount,
coalesce(C.readCount, 0) AS readCount,
coalesce(C.latestUpload, 0) AS latestUpload,
coalesce(C.fetchedAt, 0) AS chapterFetchedAt,
coalesce(C.lastRead, 0) AS lastRead,
coalesce(C.bookmarkCount, 0) AS bookmarkCount,
-- KMK -->
coalesce(C.bookmarkReadCount, 0) AS bookmarkedReadCount,
-- KMK <--
coalesce(MC.categories, '0') AS categories
FROM mangas M
LEFT JOIN (
SELECT
chapters.manga_id,
count(*) AS total,
sum(read) AS readCount,
coalesce(max(chapters.date_upload), 0) AS latestUpload,
coalesce(max(history.last_read), 0) AS lastRead,
coalesce(max(chapters.date_fetch), 0) AS fetchedAt,
sum(chapters.bookmark) AS bookmarkCount,
-- KMK -->
sum(CASE WHEN chapters.bookmark = 1 AND chapters.read = 1 THEN 1 ELSE 0 END) AS bookmarkReadCount,
-- KMK <--
excluded_scanlators.scanlator AS ex_scanlator
FROM chapters
LEFT JOIN excluded_scanlators
ON chapters.manga_id = excluded_scanlators.manga_id
AND chapters.scanlator = excluded_scanlators.scanlator
LEFT JOIN history
ON chapters._id = history.chapter_id
WHERE ex_scanlator IS NULL
GROUP BY chapters.manga_id
) AS C
ON M._id = C.manga_id
LEFT JOIN (
SELECT manga_id, group_concat(category_id) AS categories
FROM mangas_categories
GROUP BY manga_id
) AS MC
ON MC.manga_id = M._id
WHERE M.source <> 6969
UNION
SELECT
M.*,
coalesce(C.total, 0) AS totalCount,
coalesce(C.readCount, 0) AS readCount,
coalesce(C.latestUpload, 0) AS latestUpload,
coalesce(C.fetchedAt, 0) AS chapterFetchedAt,
coalesce(C.lastRead, 0) AS lastRead,
coalesce(C.bookmarkCount, 0) AS bookmarkCount,
-- KMK -->
coalesce(C.bookmarkReadCount, 0) AS bookmarkedReadCount,
-- KMK <--
coalesce(MC.categories, '0') AS categories
FROM mangas M
LEFT JOIN (
SELECT merged.manga_id,merged.merge_id
FROM merged
GROUP BY merged.merge_id
) AS ME
ON ME.merge_id = M._id
LEFT JOIN(
SELECT
ME.merge_id,
count(*) AS total,
sum(read) AS readCount,
coalesce(max(chapters.date_upload), 0) AS latestUpload,
coalesce(max(history.last_read), 0) AS lastRead,
coalesce(max(chapters.date_fetch), 0) AS fetchedAt,
sum(chapters.bookmark) AS bookmarkCount,
-- KMK -->
sum(CASE WHEN chapters.bookmark = 1 AND chapters.read = 1 THEN 1 ELSE 0 END) AS bookmarkReadCount,
-- KMK <--
excluded_scanlators.scanlator AS ex_scanlator
FROM chapters
LEFT JOIN excluded_scanlators
ON chapters.manga_id = excluded_scanlators.manga_id
AND chapters.scanlator = excluded_scanlators.scanlator
LEFT JOIN history
ON chapters._id = history.chapter_id
LEFT JOIN merged ME
ON ME.manga_id = chapters.manga_id
WHERE ex_scanlator IS NULL
GROUP BY ME.merge_id
) AS C
ON M._id = C.merge_id -- ON ME.merge_id = C.merge_id
LEFT JOIN (
SELECT manga_id, group_concat(category_id) AS categories
FROM mangas_categories
GROUP BY manga_id
) AS MC
ON MC.manga_id = M._id
WHERE M.source = 6969;

View file

@ -11,9 +11,9 @@ SELECT
-- KMK -->
coalesce(C.bookmarkReadCount, 0) AS bookmarkedReadCount,
-- KMK <--
coalesce(MC.category_id, 0) AS category
coalesce(MC.categories, '0') AS categories
FROM mangas M
LEFT JOIN(
LEFT JOIN (
SELECT
chapters.manga_id,
count(*) AS total,
@ -36,7 +36,11 @@ LEFT JOIN(
GROUP BY chapters.manga_id
) AS C
ON M._id = C.manga_id
LEFT JOIN mangas_categories AS MC
LEFT JOIN (
SELECT manga_id, group_concat(category_id) AS categories
FROM mangas_categories
GROUP BY manga_id
) AS MC
ON MC.manga_id = M._id
WHERE M.source <> 6969
UNION
@ -51,7 +55,7 @@ SELECT
-- KMK -->
coalesce(C.bookmarkReadCount, 0) AS bookmarkedReadCount,
-- KMK <--
coalesce(MC.category_id, 0) AS category
coalesce(MC.categories, '0') AS categories
FROM mangas M
LEFT JOIN (
SELECT merged.manga_id,merged.merge_id
@ -84,7 +88,11 @@ LEFT JOIN(
GROUP BY ME.merge_id
) AS C
ON M._id = C.merge_id -- ON ME.merge_id = C.merge_id
LEFT JOIN mangas_categories AS MC
LEFT JOIN (
SELECT manga_id, group_concat(category_id) AS categories
FROM mangas_categories
GROUP BY manga_id
) AS MC
ON MC.manga_id = M._id
WHERE M.source = 6969;

View file

@ -37,23 +37,15 @@ class GetUpcomingManga(
suspend fun updatingMangas(): List<Manga> {
val libraryManga = getLibraryManga.await()
val categoriesToUpdate = libraryPreferences.updateCategories().get().map(String::toLong)
val includedManga = if (categoriesToUpdate.isNotEmpty()) {
libraryManga.filter { it.category in categoriesToUpdate }
} else {
libraryManga
}
val includedCategories = libraryPreferences.updateCategories().get().map { it.toLong() }.toSet()
val excludedCategories = libraryPreferences.updateCategoriesExclude().get().map { it.toLong() }.toSet()
val categoriesToExclude = libraryPreferences.updateCategoriesExclude().get().map { it.toLong() }
val excludedMangaIds = if (categoriesToExclude.isNotEmpty()) {
libraryManga.filter { it.category in categoriesToExclude }.map { it.manga.id }
} else {
emptyList()
val listToUpdate = libraryManga.filter {
val included = includedCategories.isEmpty() || it.categories.intersect(includedCategories).isNotEmpty()
val excluded = it.categories.intersect(excludedCategories).isNotEmpty()
included && !excluded
}
val listToUpdate = includedManga
.filterNot { it.manga.id in excludedMangaIds }
val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get()
val today = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toEpochSecond() * 1000

View file

@ -4,7 +4,7 @@ import tachiyomi.domain.manga.model.Manga
data class LibraryManga(
val manga: Manga,
val category: Long,
val categories: List<Long>,
val totalChapters: Long,
val readCount: Long,
val bookmarkCount: Long,