Use SQLDelight for a Category related queries (#7438)

(cherry picked from commit 2674570792a275abaee7a6ec1ae947d94f69a634)

# Conflicts:
#	app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt
#	app/src/main/java/eu/kanade/domain/category/model/Category.kt
#	app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt
This commit is contained in:
Andreas 2022-07-02 22:12:06 +02:00 committed by Jobobby04
parent 13cb242f51
commit cd2f26a7c9
18 changed files with 188 additions and 109 deletions

View File

@ -19,7 +19,7 @@ if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
shortcutHelper.setFilePath("./shortcuts.xml") shortcutHelper.setFilePath("./shortcuts.xml")
android { android {
namespace = "eu.kanade.tachiyomi.sy" namespace = "eu.kanade.tachiyomi"
compileSdk = AndroidConfig.compileSdk compileSdk = AndroidConfig.compileSdk
ndkVersion = AndroidConfig.ndk ndkVersion = AndroidConfig.ndk

View File

@ -12,13 +12,11 @@ class CategoryRepositoryImpl(
private val handler: DatabaseHandler, private val handler: DatabaseHandler,
) : CategoryRepository { ) : CategoryRepository {
// SY --> override suspend fun getAll(): List<Category> {
override suspend fun awaitAll(): List<Category> {
return handler.awaitList { categoriesQueries.getCategories(categoryMapper) } return handler.awaitList { categoriesQueries.getCategories(categoryMapper) }
} }
// SY <--
override fun getAll(): Flow<List<Category>> { override fun getAllAsFlow(): Flow<List<Category>> {
return handler.subscribeToList { categoriesQueries.getCategories(categoryMapper) } return handler.subscribeToList { categoriesQueries.getCategories(categoryMapper) }
} }

View File

@ -27,6 +27,14 @@ class MangaRepositoryImpl(
return handler.subscribeToOne { mangasQueries.getMangaById(id, mangaMapper) } return handler.subscribeToOne { mangasQueries.getMangaById(id, mangaMapper) }
} }
override suspend fun getMangaByUrlAndSource(url: String, sourceId: Long): Manga? {
return handler.awaitOneOrNull { mangasQueries.getMangaByUrlAndSource(url, sourceId, mangaMapper) }
}
override suspend fun subscribeMangaByUrlAndSource(url: String, sourceId: Long): Flow<Manga?> {
return handler.subscribeToOneOrNull { mangasQueries.getMangaByUrlAndSource(url, sourceId, mangaMapper) }
}
override suspend fun getFavorites(): List<Manga> { override suspend fun getFavorites(): List<Manga> {
return handler.awaitList { mangasQueries.getFavorites(mangaMapper) } return handler.awaitList { mangasQueries.getFavorites(mangaMapper) }
} }

View File

@ -4,6 +4,7 @@ import eu.kanade.data.manga.MangaMergeRepositoryImpl
import eu.kanade.data.manga.MangaMetadataRepositoryImpl import eu.kanade.data.manga.MangaMetadataRepositoryImpl
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
import eu.kanade.domain.manga.interactor.GetFlatMetadataById import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
import eu.kanade.domain.manga.interactor.GetMergedManga import eu.kanade.domain.manga.interactor.GetMergedManga
import eu.kanade.domain.manga.interactor.GetMergedMangaById import eu.kanade.domain.manga.interactor.GetMergedMangaById
import eu.kanade.domain.manga.interactor.GetMergedReferencesById import eu.kanade.domain.manga.interactor.GetMergedReferencesById
@ -30,6 +31,7 @@ class SYDomainModule : InjektModule {
addFactory { SetSourceCategories(get()) } addFactory { SetSourceCategories(get()) }
addFactory { ToggleSources(get()) } addFactory { ToggleSources(get()) }
addFactory { SetMangaFilteredScanlators(get()) } addFactory { SetMangaFilteredScanlators(get()) }
addFactory { GetMangaByUrlAndSource(get()) }
addSingletonFactory<MangaMetadataRepository> { MangaMetadataRepositoryImpl(get()) } addSingletonFactory<MangaMetadataRepository> { MangaMetadataRepositoryImpl(get()) }
addFactory { GetFlatMetadataById(get()) } addFactory { GetFlatMetadataById(get()) }

View File

@ -8,20 +8,18 @@ class GetCategories(
private val categoryRepository: CategoryRepository, private val categoryRepository: CategoryRepository,
) { ) {
// SY -->
suspend fun await(): List<Category> {
return categoryRepository.awaitAll()
}
// SY <--
fun subscribe(): Flow<List<Category>> { fun subscribe(): Flow<List<Category>> {
return categoryRepository.getAll() return categoryRepository.getAllAsFlow()
} }
fun subscribe(mangaId: Long): Flow<List<Category>> { fun subscribe(mangaId: Long): Flow<List<Category>> {
return categoryRepository.getCategoriesByMangaIdAsFlow(mangaId) return categoryRepository.getCategoriesByMangaIdAsFlow(mangaId)
} }
suspend fun await(): List<Category> {
return categoryRepository.getAll()
}
suspend fun await(mangaId: Long): List<Category> { suspend fun await(mangaId: Long): List<Category> {
return categoryRepository.getCategoriesByMangaId(mangaId) return categoryRepository.getCategoriesByMangaId(mangaId)
} }

View File

@ -1,5 +1,7 @@
package eu.kanade.domain.category.model package eu.kanade.domain.category.model
import android.content.Context
import eu.kanade.tachiyomi.R
import java.io.Serializable import java.io.Serializable
import eu.kanade.tachiyomi.data.database.models.Category as DbCategory import eu.kanade.tachiyomi.data.database.models.Category as DbCategory
@ -11,7 +13,20 @@ data class Category(
// SY --> // SY -->
val mangaOrder: List<Long>, val mangaOrder: List<Long>,
// SY <-- // SY <--
) : Serializable ) : Serializable {
companion object {
val default = { context: Context ->
Category(
id = 0,
name = context.getString(R.string.default_category),
order = 0,
flags = 0,
mangaOrder = emptyList()
)
}
}
}
fun Category.toDbCategory(): DbCategory = DbCategory.create(name).also { fun Category.toDbCategory(): DbCategory = DbCategory.create(name).also {
it.id = id.toInt() it.id = id.toInt()

View File

@ -6,11 +6,9 @@ import kotlinx.coroutines.flow.Flow
interface CategoryRepository { interface CategoryRepository {
// SY --> suspend fun getAll(): List<Category>
suspend fun awaitAll(): List<Category>
// SY <--
fun getAll(): Flow<List<Category>> fun getAllAsFlow(): Flow<List<Category>>
suspend fun getCategoriesByMangaId(mangaId: Long): List<Category> suspend fun getCategoriesByMangaId(mangaId: Long): List<Category>

View File

@ -0,0 +1,25 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.Flow
import logcat.LogPriority
class GetMangaByUrlAndSource(
private val mangaRepository: MangaRepository,
) {
suspend fun await(url: String, sourceId: Long): Manga? {
return try {
mangaRepository.getMangaByUrlAndSource(url, sourceId)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
null
}
}
suspend fun subscribe(url: String, sourceId: Long): Flow<Manga?> {
return mangaRepository.subscribeMangaByUrlAndSource(url, sourceId)
}
}

View File

@ -12,6 +12,10 @@ interface MangaRepository {
suspend fun getMangaByIdAsFlow(id: Long): Flow<Manga> suspend fun getMangaByIdAsFlow(id: Long): Flow<Manga>
suspend fun getMangaByUrlAndSource(url: String, sourceId: Long): Manga?
suspend fun subscribeMangaByUrlAndSource(url: String, sourceId: Long): Flow<Manga?>
suspend fun getFavorites(): List<Manga> suspend fun getFavorites(): List<Manga>
fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>> fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>>

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context import android.content.Context
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
@ -34,6 +35,7 @@ import uy.kohesive.injekt.injectLazy
class DownloadManager( class DownloadManager(
private val context: Context, private val context: Context,
private val db: DatabaseHelper = Injekt.get(), private val db: DatabaseHelper = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
) { ) {
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
@ -416,8 +418,9 @@ class DownloadManager(
private fun getChaptersToDelete(chapters: List<Chapter>, manga: Manga): List<Chapter> { private fun getChaptersToDelete(chapters: List<Chapter>, manga: Manga): List<Chapter> {
// Retrieve the categories that are set to exclude from being deleted on read // Retrieve the categories that are set to exclude from being deleted on read
val categoriesToExclude = preferences.removeExcludeCategories().get().map(String::toInt) val categoriesToExclude = preferences.removeExcludeCategories().get().map(String::toInt)
val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking()
.mapNotNull { it.id } val categoriesForManga = runBlocking { getCategories.await(manga.id!!) }
.map { it.id }
.takeUnless { it.isEmpty() } .takeUnless { it.isEmpty() }
?: listOf(0) ?: listOf(0)

View File

@ -7,6 +7,7 @@ import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import eu.kanade.data.chapter.NoChaptersException import eu.kanade.data.chapter.NoChaptersException
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
@ -108,6 +109,7 @@ class LibraryUpdateService(
private val getMangaById: GetMangaById = Injekt.get(), private val getMangaById: GetMangaById = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(), private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(), private val getTracks: GetTracks = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(), private val insertTrack: InsertTrack = Injekt.get(),
@ -427,7 +429,8 @@ class LibraryUpdateService(
val newDbChapters = newChapters.map { it.toDbChapter() } val newDbChapters = newChapters.map { it.toDbChapter() }
if (newChapters.isNotEmpty()) { if (newChapters.isNotEmpty()) {
if (mangaWithNotif.shouldDownloadNewChapters(db, preferences)) { val categoryIds = getCategories.await(domainManga.id).map { it.id }
if (domainManga.shouldDownloadNewChapters(categoryIds, preferences)) {
downloadChapters(mangaWithNotif, newDbChapters) downloadChapters(mangaWithNotif, newDbChapters)
hasDownloads.set(true) hasDownloads.set(true)
} }

View File

@ -1,9 +1,9 @@
package eu.kanade.tachiyomi.source.online.all package eu.kanade.tachiyomi.source.online.all
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.model.toDbChapter import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
@ -44,6 +44,7 @@ import eu.kanade.domain.manga.model.Manga as DomainManga
class MergedSource : HttpSource() { class MergedSource : HttpSource() {
private val db: DatabaseHelper by injectLazy() private val db: DatabaseHelper by injectLazy()
private val getMergedChaptersByMangaId: GetMergedChapterByMangaId by injectLazy() private val getMergedChaptersByMangaId: GetMergedChapterByMangaId by injectLazy()
private val getCategories: GetCategories by injectLazy()
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val downloadManager: DownloadManager by injectLazy() private val downloadManager: DownloadManager by injectLazy()
private val preferences: PreferencesHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy()
@ -169,7 +170,7 @@ class MergedSource : HttpSource() {
throw IllegalArgumentException("Manga references are empty, chapters unavailable, merge is likely corrupted") throw IllegalArgumentException("Manga references are empty, chapters unavailable, merge is likely corrupted")
} }
val ifDownloadNewChapters = downloadChapters && manga.toDbManga().shouldDownloadNewChapters(db, preferences) val ifDownloadNewChapters = downloadChapters && manga.shouldDownloadNewChapters(getCategories.await(manga.id).map { it.id }, preferences)
val semaphore = Semaphore(5) val semaphore = Semaphore(5)
var exception: Exception? = null var exception: Exception? = null
return supervisorScope { return supervisorScope {

View File

@ -461,9 +461,9 @@ open class BrowseSourcePresenter(
* @param manga the manga to get categories from. * @param manga the manga to get categories from.
* @return Array of category ids the manga is in, if none returns default id * @return Array of category ids the manga is in, if none returns default id
*/ */
fun getMangaCategoryIds(manga: Manga): Array<Long?> { suspend fun getMangaCategoryIds(manga: Manga): Array<Long?> {
val categories = db.getCategoriesForManga(manga).executeAsBlocking() val categories = getCategories.await(manga.id!!)
return categories.mapNotNull { it?.id?.toLong() }.toTypedArray() return categories.map { it.id }.toTypedArray()
} }
/** /**

View File

@ -66,6 +66,7 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.recent.history.HistoryController import eu.kanade.tachiyomi.ui.recent.history.HistoryController
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
@ -361,7 +362,7 @@ class MangaController :
router?.popCurrentController() router?.popCurrentController()
router?.replaceTopController( router?.replaceTopController(
MangaController( MangaController(
mergedManga.id!!, mergedManga.id,
true, true,
update = true, update = true,
).withFadeTransaction(), ).withFadeTransaction(),
@ -408,18 +409,22 @@ class MangaController :
} }
private fun onCategoriesClick() { private fun onCategoriesClick() {
val manga = presenter.manga ?: return viewScope.launchIO {
val categories = presenter.getCategories() val manga = presenter.manga ?: return@launchIO
val categories = presenter.getCategories()
val ids = presenter.getMangaCategoryIds(manga) val ids = presenter.getMangaCategoryIds(manga)
val preselected = categories.map { val preselected = categories.map {
if (it.id in ids) { if (it.id in ids) {
QuadStateTextView.State.CHECKED.ordinal QuadStateTextView.State.CHECKED.ordinal
} else { } else {
QuadStateTextView.State.UNCHECKED.ordinal QuadStateTextView.State.UNCHECKED.ordinal
}
}.toTypedArray()
launchUI {
showChangeCategoryDialog(manga.toDbManga(), categories, preselected)
} }
}.toTypedArray() }
showChangeCategoryDialog(manga.toDbManga(), categories, preselected)
} }
private fun showChangeCategoryDialog(manga: Manga, categories: List<Category>, preselected: Array<Int>) { private fun showChangeCategoryDialog(manga: Manga, categories: List<Category>, preselected: Array<Int>) {

View File

@ -5,6 +5,7 @@ import android.os.Bundle
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.SetMangaCategories import eu.kanade.domain.category.interactor.SetMangaCategories
import eu.kanade.domain.category.model.toDbCategory
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
@ -13,12 +14,15 @@ import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetFlatMetadataById import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
import eu.kanade.domain.manga.interactor.GetMangaWithChapters import eu.kanade.domain.manga.interactor.GetMangaWithChapters
import eu.kanade.domain.manga.interactor.GetMergedMangaById import eu.kanade.domain.manga.interactor.GetMergedMangaById
import eu.kanade.domain.manga.interactor.GetMergedReferencesById import eu.kanade.domain.manga.interactor.GetMergedReferencesById
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
import eu.kanade.domain.manga.interactor.SetMangaFilteredScanlators import eu.kanade.domain.manga.interactor.SetMangaFilteredScanlators
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.model.TriStateFilter import eu.kanade.domain.manga.model.TriStateFilter
import eu.kanade.domain.manga.model.isLocal import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
@ -33,9 +37,9 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.toDomainChapter import eu.kanade.tachiyomi.data.database.models.toDomainChapter
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.CustomMangaManager
@ -113,7 +117,6 @@ class MangaPresenter(
val isFromSource: Boolean, val isFromSource: Boolean,
val smartSearched: Boolean, val smartSearched: Boolean,
private val preferences: PreferencesHelper = Injekt.get(), private val preferences: PreferencesHelper = Injekt.get(),
private val db: DatabaseHelper = Injekt.get(),
private val trackManager: TrackManager = Injekt.get(), private val trackManager: TrackManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(),
// SY --> // SY -->
@ -121,6 +124,8 @@ class MangaPresenter(
// SY <-- // SY <--
private val getMangaAndChapters: GetMangaWithChapters = Injekt.get(), private val getMangaAndChapters: GetMangaWithChapters = Injekt.get(),
// SY --> // SY -->
private val getMangaById: GetMangaById = Injekt.get(),
private val getMangaByUrlAndSource: GetMangaByUrlAndSource = Injekt.get(),
private val setMangaFilteredScanlators: SetMangaFilteredScanlators = Injekt.get(), private val setMangaFilteredScanlators: SetMangaFilteredScanlators = Injekt.get(),
private val getMergedChapterByMangaId: GetMergedChapterByMangaId = Injekt.get(), private val getMergedChapterByMangaId: GetMergedChapterByMangaId = Injekt.get(),
private val getMergedMangaById: GetMergedMangaById = Injekt.get(), private val getMergedMangaById: GetMergedMangaById = Injekt.get(),
@ -377,8 +382,12 @@ class MangaPresenter(
val state = successState ?: return val state = successState ?: return
var manga = state.manga var manga = state.manga
if (state.manga.isLocal()) { if (state.manga.isLocal()) {
val newTitle = if (title.isNullOrBlank()) manga.url else title.trim()
val newAuthor = author?.trimOrNull()
val newArtist = artist?.trimOrNull()
val newDesc = description?.trimOrNull()
manga = manga.copy( manga = manga.copy(
ogTitle = if (title.isNullOrBlank()) manga.url else title.trim(), ogTitle = newTitle,
ogAuthor = author?.trimOrNull(), ogAuthor = author?.trimOrNull(),
ogArtist = artist?.trimOrNull(), ogArtist = artist?.trimOrNull(),
ogDescription = description?.trimOrNull(), ogDescription = description?.trimOrNull(),
@ -386,8 +395,19 @@ class MangaPresenter(
ogStatus = status ?: 0, ogStatus = status ?: 0,
) )
(sourceManager.get(LocalSource.ID) as LocalSource).updateMangaInfo(manga.toSManga()) (sourceManager.get(LocalSource.ID) as LocalSource).updateMangaInfo(manga.toSManga())
// TODO launchIO {
db.updateMangaInfo(manga.toDbManga()).executeAsBlocking() updateManga.await(
MangaUpdate(
manga.id,
title = newTitle,
author = newAuthor,
artist = newArtist,
description = newDesc,
genre = tags,
status = status
)
)
}
} else { } else {
val genre = if (!tags.isNullOrEmpty() && tags != state.manga.ogGenre) { val genre = if (!tags.isNullOrEmpty() && tags != state.manga.ogGenre) {
tags tags
@ -413,11 +433,13 @@ class MangaPresenter(
} }
} }
suspend fun smartSearchMerge(context: Context, manga: DomainManga, originalMangaId: Long): Manga { suspend fun smartSearchMerge(context: Context, manga: DomainManga, originalMangaId: Long): DomainManga {
val originalManga = db.getManga(originalMangaId).executeAsBlocking() val db = Injekt.get<DatabaseHelper>()
val originalManga = getMangaById.await(originalMangaId)
?: throw IllegalArgumentException(context.getString(R.string.merge_unknown_manga, originalMangaId)) ?: throw IllegalArgumentException(context.getString(R.string.merge_unknown_manga, originalMangaId))
if (originalManga.source == MERGED_SOURCE_ID) { if (originalManga.source == MERGED_SOURCE_ID) {
val children = db.getMergedMangaReferences(originalMangaId).executeAsBlocking() val children = getMergedReferencesById.await(originalMangaId)
if (children.any { it.mangaSourceId == manga.source && it.mangaUrl == manga.url }) { if (children.any { it.mangaSourceId == manga.source && it.mangaUrl == manga.url }) {
throw IllegalArgumentException(context.getString(R.string.merged_already)) throw IllegalArgumentException(context.getString(R.string.merged_already))
} }
@ -430,7 +452,7 @@ class MangaPresenter(
chapterSortMode = 0, chapterSortMode = 0,
chapterPriority = 0, chapterPriority = 0,
downloadChapters = true, downloadChapters = true,
mergeId = originalManga.id!!, mergeId = originalManga.id,
mergeUrl = originalManga.url, mergeUrl = originalManga.url,
mangaId = manga.id, mangaId = manga.id,
mangaUrl = manga.url, mangaUrl = manga.url,
@ -446,38 +468,40 @@ class MangaPresenter(
chapterSortMode = 0, chapterSortMode = 0,
chapterPriority = -1, chapterPriority = -1,
downloadChapters = false, downloadChapters = false,
mergeId = originalManga.id!!, mergeId = originalManga.id,
mergeUrl = originalManga.url, mergeUrl = originalManga.url,
mangaId = originalManga.id!!, mangaId = originalManga.id,
mangaUrl = originalManga.url, mangaUrl = originalManga.url,
mangaSourceId = MERGED_SOURCE_ID, mangaSourceId = MERGED_SOURCE_ID,
) )
} }
// todo
db.insertMergedMangas(mangaReferences).executeAsBlocking() db.insertMergedMangas(mangaReferences).executeAsBlocking()
return originalManga return originalManga
} else { } else {
val mergedManga = Manga.create(originalManga.url, originalManga.title, MERGED_SOURCE_ID).apply { val mergedManga = Manga.create(originalManga.url, originalManga.title, MERGED_SOURCE_ID).apply {
copyFrom(originalManga) copyFrom(originalManga.toSManga())
favorite = true favorite = true
last_update = originalManga.last_update last_update = originalManga.lastUpdate
viewer_flags = originalManga.viewer_flags viewer_flags = originalManga.viewerFlags.toInt()
chapter_flags = originalManga.chapter_flags chapter_flags = originalManga.chapterFlags.toInt()
sorting = Manga.CHAPTER_SORTING_NUMBER sorting = Manga.CHAPTER_SORTING_NUMBER
date_added = System.currentTimeMillis() date_added = System.currentTimeMillis()
} }
var existingManga = db.getManga(mergedManga.url, mergedManga.source).executeAsBlocking()
var existingManga = getMangaByUrlAndSource.await(mergedManga.url, mergedManga.source)
while (existingManga != null) { while (existingManga != null) {
if (existingManga.favorite) { if (existingManga.favorite) {
throw IllegalArgumentException(context.getString(R.string.merge_duplicate)) throw IllegalArgumentException(context.getString(R.string.merge_duplicate))
} else if (!existingManga.favorite) { } else if (!existingManga.favorite) {
withContext(NonCancellable) { withContext(NonCancellable) {
db.deleteManga(existingManga!!).executeAsBlocking() db.deleteManga(existingManga!!.toDbManga()).executeAsBlocking()
db.deleteMangaForMergedManga(existingManga!!.id!!).executeAsBlocking() db.deleteMangaForMergedManga(existingManga!!.id).executeAsBlocking()
} }
} }
existingManga = db.getManga(mergedManga.url, mergedManga.source).executeAsBlocking() existingManga = getMangaByUrlAndSource.await(mergedManga.url, mergedManga.source)
} }
// Reload chapters immediately // Reload chapters immediately
@ -486,13 +510,12 @@ class MangaPresenter(
val newId = db.insertManga(mergedManga).executeAsBlocking().insertedId() val newId = db.insertManga(mergedManga).executeAsBlocking().insertedId()
if (newId != null) mergedManga.id = newId if (newId != null) mergedManga.id = newId
db.getCategoriesForManga(originalManga) getCategories.await(originalMangaId)
.executeAsBlocking()
.map { MangaCategory.create(mergedManga, it) }
.let { .let {
db.insertMangasCategories(it).executeAsBlocking() setMangaCategories.await(mergedManga.id!!, it.map { it.id })
} }
val originalMangaReference = MergedMangaReference( val originalMangaReference = MergedMangaReference(
id = null, id = null,
isInfoManga = true, isInfoManga = true,
@ -502,7 +525,7 @@ class MangaPresenter(
downloadChapters = true, downloadChapters = true,
mergeId = mergedManga.id!!, mergeId = mergedManga.id!!,
mergeUrl = mergedManga.url, mergeUrl = mergedManga.url,
mangaId = originalManga.id!!, mangaId = originalManga.id,
mangaUrl = originalManga.url, mangaUrl = originalManga.url,
mangaSourceId = originalManga.source, mangaSourceId = originalManga.source,
) )
@ -537,7 +560,7 @@ class MangaPresenter(
db.insertMergedMangas(listOf(originalMangaReference, newMangaReference, mergedMangaReference)).executeAsBlocking() db.insertMergedMangas(listOf(originalMangaReference, newMangaReference, mergedMangaReference)).executeAsBlocking()
return mergedManga return mergedManga.toDomainManga()!!
} }
// Note that if the manga are merged in a different order, this won't trigger, but I don't care lol // Note that if the manga are merged in a different order, this won't trigger, but I don't care lol
@ -545,6 +568,8 @@ class MangaPresenter(
fun updateMergeSettings(mergeReference: MergedMangaReference?, mergedMangaReferences: List<MergedMangaReference>) { fun updateMergeSettings(mergeReference: MergedMangaReference?, mergedMangaReferences: List<MergedMangaReference>) {
launchIO { launchIO {
// todo
val db = Injekt.get<DatabaseHelper>()
mergeReference?.let { mergeReference?.let {
db.updateMergeMangaSettings(it).executeAsBlocking() db.updateMergeMangaSettings(it).executeAsBlocking()
} }
@ -665,8 +690,8 @@ class MangaPresenter(
* *
* @return List of categories, not including the default category * @return List of categories, not including the default category
*/ */
fun getCategories(): List<Category> { suspend fun getCategories(): List<Category> {
return db.getCategories().executeAsBlocking() return getCategories.await().map { it.toDbCategory() }
} }
/** /**
@ -976,9 +1001,12 @@ class MangaPresenter(
} }
private fun downloadNewChapters(chapters: List<Chapter>) { private fun downloadNewChapters(chapters: List<Chapter>) {
val manga = successState?.manga ?: return presenterScope.launchIO {
if (chapters.isEmpty() || !manga.shouldDownloadNewChapters(db, preferences) || manga.isEhBasedManga()) return val manga = successState?.manga ?: return@launchIO
downloadChapters(chapters.map { it.toDomainChapter()!! }) val categories = getCategories.await(manga.id).map { it.id }
if (chapters.isEmpty() || !manga.shouldDownloadNewChapters(categories, preferences) || manga.isEhBasedManga()) return@launchIO
downloadChapters(chapters.map { it.toDomainChapter()!! })
}
} }
/** /**
@ -1110,14 +1138,15 @@ class MangaPresenter(
} }
// SY --> // SY -->
private fun createMdListTrack(): TrackItem { private suspend fun createMdListTrack(): TrackItem {
val state = successState!! val state = successState!!
val mdManga = state.manga.takeIf { it.source in mangaDexSourceIds } val mdManga = state.manga.takeIf { it.source in mangaDexSourceIds }
?: state.mergedData?.manga?.values?.find { it.source in mangaDexSourceIds } ?: state.mergedData?.manga?.values?.find { it.source in mangaDexSourceIds }
?: throw IllegalArgumentException("Could not create initial track") ?: throw IllegalArgumentException("Could not create initial track")
val track = trackManager.mdList.createInitialTracker(state.manga.toDbManga(), mdManga.toDbManga()) val track = trackManager.mdList.createInitialTracker(state.manga.toDbManga(), mdManga.toDbManga())
track.id = db.insertTrack(track).executeAsBlocking().insertedId() .toDomainTrack(false)!!
return TrackItem(track, trackManager.mdList) insertTrack.await(track)
return TrackItem(getTracks.await(mangaId).first { it.syncId == TrackManager.MDLIST }.toDbTrack(), trackManager.mdList)
} }
// SY <-- // SY <--

View File

@ -11,9 +11,9 @@ import androidx.core.text.buildSpannedString
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.model.Category
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.util.preference.bindTo import eu.kanade.tachiyomi.util.preference.bindTo
@ -32,6 +32,7 @@ import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
import eu.kanade.tachiyomi.widget.materialdialogs.setQuadStateMultiChoiceItems import eu.kanade.tachiyomi.widget.materialdialogs.setQuadStateMultiChoiceItems
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -40,13 +41,13 @@ import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
class SettingsDownloadController : SettingsController() { class SettingsDownloadController : SettingsController() {
private val db: DatabaseHelper by injectLazy() private val getCategories: GetCategories by injectLazy()
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = R.string.pref_category_downloads titleRes = R.string.pref_category_downloads
val dbCategories = db.getCategories().executeAsBlocking() val dbCategories = runBlocking { getCategories.await() }
val categories = listOf(Category.createDefault(context)) + dbCategories val categories = listOf(Category.default(context)) + dbCategories
preference { preference {
bindTo(preferences.downloadsDirectory()) bindTo(preferences.downloadsDirectory())
@ -116,7 +117,7 @@ class SettingsDownloadController : SettingsController() {
preferences.removeExcludeCategories().asFlow() preferences.removeExcludeCategories().asFlow()
.onEach { mutable -> .onEach { mutable ->
val selected = mutable val selected = mutable
.mapNotNull { id -> categories.find { it.id == id.toInt() } } .mapNotNull { id -> categories.find { it.id == id.toLong() } }
.sortedBy { it.order } .sortedBy { it.order }
summary = if (selected.isEmpty()) { summary = if (selected.isEmpty()) {
@ -146,7 +147,7 @@ class SettingsDownloadController : SettingsController() {
fun updateSummary() { fun updateSummary() {
val selectedCategories = preferences.downloadNewChapterCategories().get() val selectedCategories = preferences.downloadNewChapterCategories().get()
.mapNotNull { id -> categories.find { it.id == id.toInt() } } .mapNotNull { id -> categories.find { it.id == id.toLong() } }
.sortedBy { it.order } .sortedBy { it.order }
val includedItemsText = if (selectedCategories.isEmpty()) { val includedItemsText = if (selectedCategories.isEmpty()) {
context.getString(R.string.all) context.getString(R.string.all)
@ -155,7 +156,7 @@ class SettingsDownloadController : SettingsController() {
} }
val excludedCategories = preferences.downloadNewChapterCategoriesExclude().get() val excludedCategories = preferences.downloadNewChapterCategoriesExclude().get()
.mapNotNull { id -> categories.find { it.id == id.toInt() } } .mapNotNull { id -> categories.find { it.id == id.toLong() } }
.sortedBy { it.order } .sortedBy { it.order }
val excludedItemsText = if (excludedCategories.isEmpty()) { val excludedItemsText = if (excludedCategories.isEmpty()) {
context.getString(R.string.none) context.getString(R.string.none)
@ -251,11 +252,11 @@ class SettingsDownloadController : SettingsController() {
class DownloadCategoriesDialog : DialogController() { class DownloadCategoriesDialog : DialogController() {
private val preferences: PreferencesHelper = Injekt.get() private val preferences: PreferencesHelper = Injekt.get()
private val db: DatabaseHelper = Injekt.get() private val getCategories: GetCategories = Injekt.get()
override fun onCreateDialog(savedViewState: Bundle?): Dialog { override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val dbCategories = db.getCategories().executeAsBlocking() val dbCategories = runBlocking { getCategories.await() }
val categories = listOf(Category.createDefault(activity!!)) + dbCategories val categories = listOf(Category.default(activity!!)) + dbCategories
val items = categories.map { it.name } val items = categories.map { it.name }
var selected = categories var selected = categories

View File

@ -7,9 +7,9 @@ import androidx.core.content.ContextCompat
import androidx.core.text.buildSpannedString import androidx.core.text.buildSpannedString
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.model.Category
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW
import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING
@ -45,6 +45,7 @@ import eu.kanade.tachiyomi.widget.materialdialogs.setQuadStateMultiChoiceItems
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -52,7 +53,7 @@ import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
class SettingsLibraryController : SettingsController() { class SettingsLibraryController : SettingsController() {
private val db: DatabaseHelper = Injekt.get() private val getCategories: GetCategories by injectLazy()
private val trackManager: TrackManager by injectLazy() private val trackManager: TrackManager by injectLazy()
// SY --> // SY -->
@ -65,8 +66,8 @@ class SettingsLibraryController : SettingsController() {
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = R.string.pref_category_library titleRes = R.string.pref_category_library
val dbCategories = db.getCategories().executeAsBlocking() val dbCategories = runBlocking { getCategories.await() }
val categories = listOf(Category.createDefault(context)) + dbCategories val categories = listOf(Category.default(context)) + dbCategories
preferenceCategory { preferenceCategory {
titleRes = R.string.pref_category_display titleRes = R.string.pref_category_display
@ -136,12 +137,12 @@ class SettingsLibraryController : SettingsController() {
entryValues = arrayOf("-1") + categories.map { it.id.toString() }.toTypedArray() entryValues = arrayOf("-1") + categories.map { it.id.toString() }.toTypedArray()
defaultValue = "-1" defaultValue = "-1"
val selectedCategory = categories.find { it.id == preferences.defaultCategory() } val selectedCategory = categories.find { it.id == preferences.defaultCategory().toLong() }
summary = selectedCategory?.name summary = selectedCategory?.name
?: context.getString(R.string.default_category_summary) ?: context.getString(R.string.default_category_summary)
onChange { newValue -> onChange { newValue ->
summary = categories.find { summary = categories.find {
it.id == (newValue as String).toInt() it.id == (newValue as String).toLong()
}?.name ?: context.getString(R.string.default_category_summary) }?.name ?: context.getString(R.string.default_category_summary)
true true
} }
@ -254,10 +255,10 @@ class SettingsLibraryController : SettingsController() {
fun updateSummary() { fun updateSummary() {
val includedCategories = preferences.libraryUpdateCategories().get() val includedCategories = preferences.libraryUpdateCategories().get()
.mapNotNull { id -> categories.find { it.id == id.toInt() } } .mapNotNull { id -> categories.find { it.id == id.toLong() } }
.sortedBy { it.order } .sortedBy { it.order }
val excludedCategories = preferences.libraryUpdateCategoriesExclude().get() val excludedCategories = preferences.libraryUpdateCategoriesExclude().get()
.mapNotNull { id -> categories.find { it.id == id.toInt() } } .mapNotNull { id -> categories.find { it.id == id.toLong() } }
.sortedBy { it.order } .sortedBy { it.order }
val allExcluded = excludedCategories.size == categories.size val allExcluded = excludedCategories.size == categories.size
@ -399,11 +400,11 @@ class SettingsLibraryController : SettingsController() {
class LibraryGlobalUpdateCategoriesDialog : DialogController() { class LibraryGlobalUpdateCategoriesDialog : DialogController() {
private val preferences: PreferencesHelper = Injekt.get() private val preferences: PreferencesHelper = Injekt.get()
private val db: DatabaseHelper = Injekt.get() private val getCategories: GetCategories = Injekt.get()
override fun onCreateDialog(savedViewState: Bundle?): Dialog { override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val dbCategories = db.getCategories().executeAsBlocking() val dbCategories = runBlocking { getCategories.await() }
val categories = listOf(Category.createDefault(activity!!)) + dbCategories val categories = listOf(Category.default(activity!!)) + dbCategories
val items = categories.map { it.name } val items = categories.map { it.name }
var selected = categories var selected = categories

View File

@ -5,9 +5,7 @@ import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.isLocal import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
@ -56,37 +54,27 @@ fun Manga.removeCovers(coverCache: CoverCache = Injekt.get()): Int {
return coverCache.deleteFromCache(this, true) return coverCache.deleteFromCache(this, true)
} }
fun Manga.shouldDownloadNewChapters(db: DatabaseHelper, prefs: PreferencesHelper): Boolean { fun DomainManga.shouldDownloadNewChapters(categories: List<Long>, prefs: PreferencesHelper): Boolean {
return toDomainManga()?.shouldDownloadNewChapters(db, prefs) ?: false
}
fun DomainManga.shouldDownloadNewChapters(db: DatabaseHelper, prefs: PreferencesHelper): Boolean {
if (!favorite) return false if (!favorite) return false
// Boolean to determine if user wants to automatically download new chapters. // Boolean to determine if user wants to automatically download new chapters.
val downloadNewChapter = prefs.downloadNewChapter().get() val downloadNewChapter = prefs.downloadNewChapter().get()
if (!downloadNewChapter) return false if (!downloadNewChapter) return false
val includedCategories = prefs.downloadNewChapterCategories().get().map { it.toInt() } val includedCategories = prefs.downloadNewChapterCategories().get().map { it.toLong() }
val excludedCategories = prefs.downloadNewChapterCategoriesExclude().get().map { it.toInt() } val excludedCategories = prefs.downloadNewChapterCategoriesExclude().get().map { it.toLong() }
// Default: Download from all categories // Default: Download from all categories
if (includedCategories.isEmpty() && excludedCategories.isEmpty()) return true if (includedCategories.isEmpty() && excludedCategories.isEmpty()) return true
// Get all categories, else default category (0)
val categoriesForManga =
db.getCategoriesForManga(toDbManga()).executeAsBlocking()
.mapNotNull { it.id }
.takeUnless { it.isEmpty() } ?: listOf(0)
// In excluded category // In excluded category
if (categoriesForManga.any { it in excludedCategories }) return false if (categories.any { it in excludedCategories }) return false
// Included category not selected // Included category not selected
if (includedCategories.isEmpty()) return true if (includedCategories.isEmpty()) return true
// In included category // In included category
return categoriesForManga.any { it in includedCategories } return categories.any { it in includedCategories }
} }
suspend fun DomainManga.editCover( suspend fun DomainManga.editCover(