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:
+4
-2
@@ -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
|
||||
+2
-3
@@ -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,7 +7,7 @@ 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 (
|
||||
SELECT
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user