Optimize and cleanup library code (#2329)

(cherry picked from commit e62cd0e816402303fdf12513816894624f77e208)

# Conflicts:
#	.editorconfig
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/presentation/library/components/LibraryContent.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt
#	data/src/main/sqldelight/tachiyomi/migrations/6.sqm
#	data/src/main/sqldelight/tachiyomi/view/libraryView.sq
This commit is contained in:
AntsyLich
2025-08-02 08:49:23 +05:45
committed by NGB-Was-Taken
parent 633937b0bc
commit 8317a30d6e
17 changed files with 562 additions and 485 deletions
+4 -2
View File
@@ -23,9 +23,13 @@ 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
# SY
ktlint_standard_filename = disabled
ktlint_standard_argument-list-wrapping = disabled
@@ -33,8 +37,6 @@ ktlint_standard_function-naming = disabled
ktlint_standard_property-naming = disabled
ktlint_standard_multiline-expression-wrapping = disabled
ktlint_standard_string-template-indent = disabled
ktlint_standard_comment-wrapping = disabled
ktlint_standard_max-line-length = disabled
ktlint_standard_type-argument-comment = disabled
ktlint_standard_value-argument-comment = disabled
ktlint_standard_value-parameter-comment = disabled
@@ -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)?,
@@ -35,7 +34,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,
@@ -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,
@@ -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,
)
@@ -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,
@@ -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,
@@ -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,
@@ -130,8 +130,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
private val insertTrack: InsertTrack = Injekt.get()
private val trackerManager: TrackerManager = Injekt.get()
private val mdList = trackerManager.mdList
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get()
private val setReadStatus: SetReadStatus = Injekt.get()
// SY <--
private val notifier = LibraryUpdateNotifier(context)
@@ -156,7 +154,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
setForegroundSafely()
val target = inputData.getString(KEY_TARGET)?.let { Target.valueOf(it) } ?: Target.CHAPTERS
val target = inputData.getString(KEY_TARGET)?.let { Target.valueOf(it) }
?: Target.CHAPTERS
// If this is a chapter update, set the last update time to now
if (target == Target.CHAPTERS) {
@@ -220,28 +219,23 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
// SY <--
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 -> {
@@ -255,6 +249,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
status.int == trackingExtra
}
}
LibraryGroup.BY_SOURCE -> {
val sourceExtra = groupExtra?.nullIfBlank()?.toIntOrNull()
val source = libraryManga.map { it.manga.source }
@@ -264,12 +259,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
if (source != null) libraryManga.filter { it.manga.source == source } else emptyList()
}
LibraryGroup.BY_STATUS -> {
val statusExtra = groupExtra?.toLongOrNull() ?: -1
libraryManga.filter {
it.manga.status == statusExtra
}
}
LibraryGroup.UNGROUPED -> libraryManga
else -> libraryManga
}
@@ -288,8 +285,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
when {
it.manga.updateStrategy == UpdateStrategy.ONLY_FETCH_ONCE && it.totalChapters > 0L -> {
skippedUpdates.add(
it.manga to
context.stringResource(MR.strings.skipped_reason_not_always_update),
it.manga to context.stringResource(MR.strings.skipped_reason_not_always_update),
)
false
}
@@ -311,11 +307,11 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && it.manga.nextUpdate > fetchWindowUpperBound -> {
skippedUpdates.add(
it.manga to
context.stringResource(MR.strings.skipped_reason_not_in_release_period),
it.manga to context.stringResource(MR.strings.skipped_reason_not_in_release_period),
)
false
}
else -> true
}
}
@@ -328,9 +324,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
logcat {
skippedUpdates
.groupBy { it.second }
.map { (reason, entries) ->
"$reason: [${entries.map { it.first.title }.sorted().joinToString()}]"
}
.map { (reason, entries) -> "$reason: [${entries.map { it.first.title }.sorted().joinToString()}]" }
.joinToString()
}
}
@@ -421,13 +415,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
}
} catch (e: Throwable) {
val errorMessage = when (e) {
is NoChaptersException ->
context.stringResource(MR.strings.no_chapters_error)
// failedUpdates will already have the source,
// don't need to copy it into the message
is NoChaptersException -> context.stringResource(
MR.strings.no_chapters_error,
)
// failedUpdates will already have the source, don't need to copy it into the message
is SourceNotInstalledException -> context.stringResource(
MR.strings.loader_not_implemented_error,
)
else -> e.message
}
failedUpdates.add(manga to errorMessage)
@@ -539,7 +534,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
.copyFrom(networkManga)
try {
updateManga.await(updatedManga.toMangaUpdate())
} catch (e: Exception) {
} catch (_: Exception) {
logcat(LogPriority.ERROR) { "Manga doesn't exist anymore" }
}
} catch (e: Throwable) {
@@ -580,7 +575,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
count++
notifier.showProgressNotification(
listOf(Manga.create().copy(ogTitle = networkManga.title)), count, size,
listOf(Manga.create().copy(ogTitle = networkManga.title)),
count,
size,
)
var dbManga = getManga.await(networkManga.url, mangaDex.id)
@@ -697,7 +694,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
}
return file
}
} catch (_: Exception) {}
} catch (_: Exception) {
}
return File("")
}
@@ -722,8 +720,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
private const val ERROR_LOG_HELP_URL = "https://mihon.app/docs/guides/troubleshooting/"
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60
/**
* Key for category to update.
*/
@@ -14,6 +14,8 @@ data class LibraryItem(
val sourceLanguage: String = "",
private val sourceManager: SourceManager = Injekt.get(),
) {
val id: Long = libraryManga.id
/**
* Checks if a query matches the manga
*
@@ -22,6 +24,9 @@ data class LibraryItem(
*/
fun matches(constraint: String): Boolean {
val sourceName by lazy { sourceManager.getOrStub(libraryManga.manga.source).getNameForMangaInfo(null) }
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) ||
File diff suppressed because it is too large Load Diff
@@ -142,18 +142,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 {
@@ -180,7 +177,8 @@ data object LibraryTab : Tab {
// SY <--
searchQuery = state.searchQuery,
onSearchQueryChange = screenModel::search,
scrollBehavior = scrollBehavior.takeIf { !tabVisible }, // For scroll overlay when no tab
// For scroll overlay when no tab
scrollBehavior = scrollBehavior.takeIf { !state.showCategoryTabs },
)
},
bottomBar = {
@@ -189,15 +187,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(
@@ -218,7 +216,10 @@ 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(
@@ -233,9 +234,10 @@ data object LibraryTab : Tab {
),
)
}
else -> {
LibraryContent(
categories = state.categories,
categories = state.displayedCategories,
searchQuery = state.searchQuery,
selection = state.selection,
contentPadding = contentPadding,
@@ -243,7 +245,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)
@@ -258,18 +260,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) },
)
}
}
}
@@ -277,20 +280,16 @@ 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 <--
)
}
is LibraryScreenModel.Dialog.ChangeCategory -> {
ChangeCategoryDialog(
initialSelection = dialog.initialSelection,
@@ -305,6 +304,7 @@ data object LibraryTab : Tab {
},
)
}
is LibraryScreenModel.Dialog.DeleteManga -> {
DeleteLibraryMangaDialog(
containsLocalManga = dialog.manga.any(Manga::isLocal),
@@ -325,6 +325,7 @@ data object LibraryTab : Tab {
},
)
}
LibraryScreenModel.Dialog.SyncFavoritesConfirm -> {
SyncFavoritesConfirmDialog(
onDismissRequest = onDismissRequest,
@@ -334,6 +335,7 @@ data object LibraryTab : Tab {
},
)
}
is LibraryScreenModel.Dialog.RecommendationSearchSheet -> {
RecommendationSearchBottomSheetDialog(
onDismissRequest = onDismissRequest,
@@ -390,14 +392,17 @@ data object LibraryTab : Tab {
screenModel.recommendationSearch.status.value = SearchStatus.Idle
}
is SearchStatus.Finished.WithoutResults -> {
context.toast(SYMR.strings.rec_no_results)
screenModel.recommendationSearch.status.value = SearchStatus.Idle
}
is SearchStatus.Cancelling -> {
screenModel.cancelRecommendationSearch()
screenModel.recommendationSearch.status.value = SearchStatus.Idle
}
else -> {}
}
}
@@ -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) ||
@@ -0,0 +1,5 @@
package mihon.core.common.utils
fun <T> Set<T>.mutate(action: (MutableSet<T>) -> Unit): Set<T> {
return toMutableSet().apply(action)
}
@@ -41,7 +41,7 @@ private val mapper = { cursor: SqlCursor ->
chapterFetchedAt = cursor.getLong(29)!!,
lastRead = cursor.getLong(30)!!,
bookmarkCount = cursor.getDouble(31)!!,
category = cursor.getLong(32)!!,
categories = cursor.getString(32)!!,
)
}
@@ -0,0 +1,39 @@
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,
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
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 excluded_scanlators.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.favorite = 1;
@@ -7,9 +7,9 @@ SELECT
coalesce(C.fetchedAt, 0) AS chapterFetchedAt,
coalesce(C.lastRead, 0) AS lastRead,
coalesce(C.bookmarkCount, 0) AS bookmarkCount,
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,
@@ -28,7 +28,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.favorite = 1;
@@ -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,