diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index b929105a1c..2fb9325bfd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -300,7 +300,7 @@ fun buildLogWritersToAdd( ) = buildList { if (!BuildConfig.DEBUG) add(CrashlyticsLogWriter()) - if (logPath != null) add(RollingUniFileLogWriter(logPath = logPath, isVerbose = isVerbose)) + if (logPath != null && !BuildConfig.DEBUG) add(RollingUniFileLogWriter(logPath = logPath, isVerbose = isVerbose)) } private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt index 00521bafe0..fc2f87bacf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt @@ -57,8 +57,10 @@ data class BackupManga( @ProtoNumber(805) var customGenre: List? = null, ) { fun getMangaImpl(): MangaImpl { - return MangaImpl().apply { - url = this@BackupManga.url + return MangaImpl( + source = this.source, + url = this.url, + ).apply { title = this@BackupManga.title artist = this@BackupManga.artist author = this@BackupManga.author @@ -67,7 +69,6 @@ data class BackupManga( status = this@BackupManga.status thumbnail_url = this@BackupManga.thumbnailUrl favorite = this@BackupManga.favorite - source = this@BackupManga.source date_added = this@BackupManga.dateAdded viewer_flags = ( this@BackupManga.viewer_flags diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt index a0022e5729..3c4763dbfc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt @@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.data.database.models import android.content.Context import dev.icerock.moko.resources.StringResource import eu.kanade.tachiyomi.ui.library.LibrarySort +import java.io.Serializable import yokai.i18n.MR import yokai.util.lang.getString -import java.io.Serializable interface Category : Serializable { @@ -56,7 +56,21 @@ interface Category : Serializable { fun mangaOrderToString(): String = if (mangaSort != null) mangaSort.toString() else mangaOrder.joinToString("/") + // For dynamic categories + fun dynamicHeaderKey(): String { + if (!isDynamic) throw IllegalStateException("This category is not a dynamic category") + + return when { + sourceId != null -> "${name}$sourceSplitter${sourceId}" + langId != null -> "${langId}$langSplitter${name}" + else -> name + } + } + companion object { + const val sourceSplitter = "◘•◘" + const val langSplitter = "⨼⨦⨠" + var lastCategoriesAddedTo = emptySet() fun create(name: String): Category = CategoryImpl().apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt index 6e685036ca..de3e30df4e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt @@ -32,10 +32,14 @@ class CategoryImpl : Category { val category = other as Category + if (isDynamic && category.isDynamic) return dynamicHeaderKey() == category.dynamicHeaderKey() + return name == category.name } override fun hashCode(): Int { + if (isDynamic) return dynamicHeaderKey().hashCode() + return name.hashCode() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt index e017568209..7631836fcf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt @@ -1,10 +1,10 @@ package eu.kanade.tachiyomi.data.database.models -import eu.kanade.tachiyomi.ui.library.LibraryItem +import eu.kanade.tachiyomi.domain.manga.models.Manga import kotlin.math.roundToInt -import yokai.data.updateStrategyAdapter data class LibraryManga( + val manga: Manga, var unread: Int = 0, var read: Int = 0, var category: Int = 0, @@ -13,41 +13,11 @@ data class LibraryManga( var latestUpdate: Long = 0, var lastRead: Long = 0, var lastFetch: Long = 0, -) : MangaImpl() { - - var realMangaCount = 0 - get() = if (isBlank()) field else throw IllegalStateException("realMangaCount is only accessible by placeholders") - set(value) { - if (!isBlank()) throw IllegalStateException("realMangaCount can only be set by placeholders") - field = value - } - +) { val hasRead get() = read > 0 - @Transient - var items: List? = null - get() = if (isHidden()) field else throw IllegalStateException("items only accessible by placeholders") - set(value) { - if (!isHidden()) throw IllegalStateException("items can only be set by placeholders") - field = value - } - companion object { - fun createBlank(categoryId: Int): LibraryManga = LibraryManga().apply { - title = "" - id = Long.MIN_VALUE - category = categoryId - } - - fun createHide(categoryId: Int, title: String, hiddenItems: List): LibraryManga = - createBlank(categoryId).apply { - this.title = title - this.status = -1 - this.read = hiddenItems.size - this.items = hiddenItems - } - fun mapper( // manga id: Long, @@ -78,34 +48,37 @@ data class LibraryManga( latestUpdate: Long, lastRead: Long, lastFetch: Long, - ): LibraryManga = createBlank(categoryId.toInt()).apply { - this.id = id - this.source = source - this.url = url - this.artist = artist - this.author = author - this.description = description - this.genre = genre - this.title = title - this.status = status.toInt() - this.thumbnail_url = thumbnailUrl - this.favorite = favorite - this.last_update = lastUpdate ?: 0L - this.initialized = initialized - this.viewer_flags = viewerFlags.toInt() - this.hide_title = hideTitle - this.chapter_flags = chapterFlags.toInt() - this.date_added = dateAdded ?: 0L - this.filtered_scanlators = filteredScanlators - this.update_strategy = updateStrategy.let(updateStrategyAdapter::decode) - this.cover_last_modified = coverLastModified - this.read = readCount.roundToInt() - this.unread = maxOf((total - readCount).roundToInt(), 0) - this.totalChapters = total.toInt() - this.bookmarkCount = bookmarkCount.roundToInt() - this.latestUpdate = latestUpdate - this.lastRead = lastRead - this.lastFetch = lastFetch - } + ): LibraryManga = LibraryManga( + manga = Manga.mapper( + id = id, + source = source, + url = url, + artist = artist, + author = author, + description = description, + genre = genre, + title = title, + status = status, + thumbnailUrl = thumbnailUrl, + favorite = favorite, + lastUpdate = lastUpdate, + initialized = initialized, + viewerFlags = viewerFlags, + hideTitle = hideTitle, + chapterFlags = chapterFlags, + dateAdded = dateAdded, + filteredScanlators = filteredScanlators, + updateStrategy = updateStrategy, + coverLastModified = coverLastModified, + ), + read = readCount.roundToInt(), + unread = maxOf((total - readCount).roundToInt(), 0), + totalChapters = total.toInt(), + bookmarkCount = bookmarkCount.roundToInt(), + category = categoryId.toInt(), + latestUpdate = latestUpdate, + lastRead = lastRead, + lastFetch = lastFetch, + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt index fb560da49c..42a0c038eb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt @@ -182,15 +182,13 @@ var Manga.vibrantCoverColor: Int? id?.let { MangaCoverMetadata.setVibrantColor(it, value) } } -fun Manga.Companion.create(source: Long) = MangaImpl().apply { - this.source = source -} - -fun Manga.Companion.create(pathUrl: String, title: String, source: Long = 0) = MangaImpl().apply { - url = pathUrl - this.title = title - this.source = source -} +fun Manga.Companion.create(url: String, title: String, source: Long = 0) = + MangaImpl( + source = source, + url = url, + ).apply { + this.title = title + } fun Manga.Companion.mapper( id: Long, @@ -213,14 +211,12 @@ fun Manga.Companion.mapper( filteredScanlators: String?, updateStrategy: Long, coverLastModified: Long, -) = create(source).apply { +) = create(url, title, source).apply { this.id = id - this.url = url this.artist = artist this.author = author this.description = description this.genre = genre - this.title = title this.status = status.toInt() this.thumbnail_url = thumbnailUrl this.favorite = favorite diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt index afe855bff3..ff0677e9c6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt @@ -12,7 +12,11 @@ import eu.kanade.tachiyomi.domain.manga.models.Manga data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History, var extraChapters: List = emptyList()) { companion object { - fun createBlank() = MangaChapterHistory(MangaImpl(), ChapterImpl(), HistoryImpl()) + fun createBlank() = MangaChapterHistory( + MangaImpl(null, -1, ""), + ChapterImpl(), + HistoryImpl(), + ) fun mapper( // manga diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt index df0342a5d0..6046ddab15 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt @@ -8,13 +8,11 @@ import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.UpdateStrategy import uy.kohesive.injekt.injectLazy -open class MangaImpl : Manga { - - override var id: Long? = null - - override var source: Long = -1 - - override lateinit var url: String +open class MangaImpl( + override var id: Long? = null, + override var source: Long = -1, + override var url: String = "", +) : Manga { private val customMangaManager: CustomMangaManager by injectLazy() @@ -107,7 +105,7 @@ open class MangaImpl : Manga { } override fun hashCode(): Int { - return if (::url.isInitialized) { + return if (url.isNotBlank()) { url.hashCode() } else { (id ?: 0L).hashCode() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt index cdcf00262a..8f686664a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt @@ -4,6 +4,7 @@ import android.content.Context import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.data.database.models.MangaImpl import eu.kanade.tachiyomi.domain.manga.models.Manga +import java.nio.charset.StandardCharsets import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest @@ -26,7 +27,6 @@ import yokai.domain.library.custom.interactor.GetCustomManga import yokai.domain.library.custom.interactor.RelinkCustomManga import yokai.domain.library.custom.model.CustomMangaInfo import yokai.domain.library.custom.model.CustomMangaInfo.Companion.getMangaInfo -import java.nio.charset.StandardCharsets class CustomMangaManager(val context: Context) { private val scope = CoroutineScope(Dispatchers.IO) @@ -176,8 +176,7 @@ class CustomMangaManager(val context: Context) { val status: Int? = null, ) { - fun toManga() = MangaImpl().apply { - id = this@MangaJson.id + fun toManga() = MangaImpl(id = this.id).apply { title = this@MangaJson.title ?: "" author = this@MangaJson.author artist = this@MangaJson.artist @@ -272,9 +271,6 @@ class CustomMangaManager(val context: Context) { } } - private fun mangaFromComicInfoObject(id: Long, comicInfo: ComicInfo) = MangaImpl().apply { - this.id = id - this.copyFromComicInfo(comicInfo) - this.title = comicInfo.series?.value ?: "" - } + private fun mangaFromComicInfoObject(id: Long, comicInfo: ComicInfo) = + MangaImpl(id = id).apply { this.copyFromComicInfo(comicInfo) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt index b2e8eb44c1..105d61c6a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt @@ -173,16 +173,17 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val mangaList = ( if (savedMangasList != null) { - val mangas = getLibraryManga.await().filter { - it.id in savedMangasList - }.distinctBy { it.id } + val mangas = + getLibraryManga.await() + .filter { it.manga.id in savedMangasList } + .distinctBy { it.manga.id } val categoryId = inputData.getInt(KEY_CATEGORY, -1) if (categoryId > -1) categoryIds.add(categoryId) mangas } else { getMangaToUpdate() } - ).sortedBy { it.title } + ).sortedBy { it.manga.title } return withIOContext { try { @@ -227,7 +228,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet private suspend fun updateChaptersJob(mangaToAdd: List) { // Initialize the variables holding the progress of the updates. mangaToUpdate.addAll(mangaToAdd) - mangaToUpdateMap.putAll(mangaToAdd.groupBy { it.source }) + mangaToUpdateMap.putAll(mangaToAdd.groupBy { it.manga.source }) checkIfMassiveUpdate() coroutineScope { val list = mangaToUpdateMap.keys.map { source -> @@ -257,42 +258,42 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet private suspend fun updateDetails(mangaToUpdate: List) = coroutineScope { // Initialize the variables holding the progress of the updates. val count = AtomicInteger(0) - val asyncList = mangaToUpdate.groupBy { it.source }.values.map { list -> + val asyncList = mangaToUpdate.groupBy { it.manga.source }.values.map { list -> async { requestSemaphore.withPermit { list.forEach { manga -> ensureActive() - val source = sourceManager.get(manga.source) as? HttpSource ?: return@async + val source = sourceManager.get(manga.manga.source) as? HttpSource ?: return@async notifier.showProgressNotification( - manga, + manga.manga, count.andIncrement, mangaToUpdate.size, ) ensureActive() val networkManga = try { - source.getMangaDetails(manga.copy()) + source.getMangaDetails(manga.manga.copy()) } catch (e: java.lang.Exception) { Logger.e(e) null } if (networkManga != null) { - manga.prepareCoverUpdate(coverCache, networkManga, false) - val thumbnailUrl = manga.thumbnail_url - manga.copyFrom(networkManga) - manga.initialized = true + manga.manga.prepareCoverUpdate(coverCache, networkManga, false) + val thumbnailUrl = manga.manga.thumbnail_url + manga.manga.copyFrom(networkManga) + manga.manga.initialized = true val request: ImageRequest = - if (thumbnailUrl != manga.thumbnail_url) { + if (thumbnailUrl != manga.manga.thumbnail_url) { // load new covers in background - ImageRequest.Builder(context).data(manga.cover()) + ImageRequest.Builder(context).data(manga.manga.cover()) .memoryCachePolicy(CachePolicy.DISABLED).build() } else { - ImageRequest.Builder(context).data(manga.cover()) + ImageRequest.Builder(context).data(manga.manga.cover()) .memoryCachePolicy(CachePolicy.DISABLED) .diskCachePolicy(CachePolicy.WRITE_ONLY) .build() } context.imageLoader.execute(request) - updateManga.await(manga.toMangaUpdate()) + updateManga.await(manga.manga.toMangaUpdate()) } } } @@ -313,9 +314,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val loggedServices = trackManager.services.filter { it.isLogged } mangaToUpdate.forEach { manga -> - notifier.showProgressNotification(manga, count++, mangaToUpdate.size) + notifier.showProgressNotification(manga.manga, count++, mangaToUpdate.size) - val tracks = getTrack.awaitAllByMangaId(manga.id!!) + val tracks = getTrack.awaitAllByMangaId(manga.manga.id!!) tracks.forEach { track -> val service = trackManager.getService(track.sync_id) @@ -324,7 +325,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val newTrack = service.refresh(track) insertTrack.await(newTrack) - syncChaptersWithTrackServiceTwoWay(getChapter.awaitAll(manga.id!!, false), track, service) + syncChaptersWithTrackServiceTwoWay(getChapter.awaitAll(manga.manga.id!!, false), track, service) } catch (e: Exception) { Logger.e(e) } @@ -376,7 +377,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet private fun checkIfMassiveUpdate() { val largestSourceSize = mangaToUpdate - .groupBy { it.source } + .groupBy { it.manga.source } .filterKeys { sourceManager.get(it) !is UnmeteredSource } .maxOfOrNull { it.value.size } ?: 0 if (largestSourceSize > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) { @@ -391,7 +392,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val httpSource = sourceManager.get(source) as? HttpSource ?: return false while (count < mangaToUpdateMap[source]!!.size) { val manga = mangaToUpdateMap[source]!![count] - val shouldDownload = manga.shouldDownloadNewChapters(preferences) + val shouldDownload = manga.manga.shouldDownloadNewChapters(preferences) if (updateMangaChapters(manga, this.count.andIncrement, httpSource, shouldDownload)) { hasDownloads = true } @@ -410,15 +411,15 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet try { var hasDownloads = false ensureActive() - notifier.showProgressNotification(manga, progress, mangaToUpdate.size) - val fetchedChapters = source.getChapterList(manga.copy()) + notifier.showProgressNotification(manga.manga, progress, mangaToUpdate.size) + val fetchedChapters = source.getChapterList(manga.manga.copy()) if (fetchedChapters.isNotEmpty()) { - val newChapters = syncChaptersWithSource(fetchedChapters, manga, source) + val newChapters = syncChaptersWithSource(fetchedChapters, manga.manga, source) if (newChapters.first.isNotEmpty()) { if (shouldDownload) { downloadChapters( - manga, + manga.manga, newChapters.first.sortedBy { it.chapter_number }, ) hasDownloads = true @@ -428,24 +429,24 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet } if (deleteRemoved && newChapters.second.isNotEmpty()) { val removedChapters = newChapters.second.filter { - downloadManager.isChapterDownloaded(it, manga) && + downloadManager.isChapterDownloaded(it, manga.manga) && newChapters.first.none { newChapter -> newChapter.chapter_number == it.chapter_number && it.scanlator.isNullOrBlank() } } if (removedChapters.isNotEmpty()) { - downloadManager.deleteChapters(removedChapters, manga, source) + downloadManager.deleteChapters(removedChapters, manga.manga, source) } } if (newChapters.first.size + newChapters.second.size > 0) { - sendUpdate(manga.id) + sendUpdate(manga.manga.id) } } return@coroutineScope hasDownloads } catch (e: Exception) { if (e !is CancellationException) { - failedUpdates[manga] = e.message - Logger.e { "Failed updating: ${manga.title}: $e" } + failedUpdates[manga.manga] = e.message + Logger.e { "Failed updating: ${manga.manga.title}: $e" } } return@coroutineScope false } @@ -461,17 +462,17 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val restrictions = preferences.libraryUpdateMangaRestriction().get() return mangaToAdd.filter { manga -> when { - MANGA_NON_COMPLETED in restrictions && manga.status == SManga.COMPLETED -> { - skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_completed) + MANGA_NON_COMPLETED in restrictions && manga.manga.status == SManga.COMPLETED -> { + skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_completed) } MANGA_HAS_UNREAD in restrictions && manga.unread != 0 -> { - skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_not_caught_up) + skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_not_caught_up) } MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasRead -> { - skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_not_started) + skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_not_started) } - manga.update_strategy != UpdateStrategy.ALWAYS_UPDATE -> { - skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_not_always_update) + manga.manga.update_strategy != UpdateStrategy.ALWAYS_UPDATE -> { + skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_not_always_update) } else -> { return@filter true @@ -503,10 +504,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet preferences.libraryUpdateCategories().get().map(String::toInt) if (categoriesToUpdate.isNotEmpty()) { categoryIds.addAll(categoriesToUpdate) - libraryManga.filter { it.category in categoriesToUpdate }.distinctBy { it.id } + libraryManga.filter { it.category in categoriesToUpdate }.distinctBy { it.manga.id } } else { categoryIds.addAll(getCategories.await().mapNotNull { it.id } + 0) - libraryManga.distinctBy { it.id } + libraryManga.distinctBy { it.manga.id } } } @@ -564,13 +565,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet } private fun addMangaToQueue(categoryId: Int, manga: List) { - val mangas = filterMangaToUpdate(manga).sortedBy { it.title } + val mangas = filterMangaToUpdate(manga).sortedBy { it.manga.title } categoryIds.add(categoryId) addManga(mangas) } private fun addCategory(categoryId: Int) { - val mangas = filterMangaToUpdate(runBlocking { getMangaToUpdate(categoryId) }).sortedBy { it.title } + val mangas = filterMangaToUpdate(runBlocking { getMangaToUpdate(categoryId) }).sortedBy { it.manga.title } categoryIds.add(categoryId) addManga(mangas) } @@ -579,7 +580,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val distinctManga = mangaToAdd.filter { it !in mangaToUpdate } mangaToUpdate.addAll(distinctManga) checkIfMassiveUpdate() - distinctManga.groupBy { it.source }.forEach { + distinctManga.groupBy { it.manga.source }.forEach { // if added queue items is a new source not in the async list or an async list has // finished running if (mangaToUpdateMap[it.key].isNullOrEmpty()) { @@ -727,9 +728,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet if (mangaToUse != null) { builder.putLongArray( KEY_MANGAS, - mangaToUse.firstOrNull()?.id?.let { longArrayOf(it) } ?: longArrayOf(), + mangaToUse.firstOrNull()?.manga?.id?.let { longArrayOf(it) } ?: longArrayOf(), ) - extraManga = mangaToUse.subList(1, mangaToUse.size).mapNotNull { it.id } + extraManga = mangaToUse.subList(1, mangaToUse.size).mapNotNull { it.manga.id } } } val inputData = builder.build() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index fd878892e7..323361883f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -185,14 +185,14 @@ class LibraryUpdateNotifier(private val context: Context) { val manga = it.key val chapters = it.value val chapterNames = chapters.map { chapter -> - chapter.preferredChapterName(context, manga, preferences) + chapter.preferredChapterName(context, manga.manga, preferences) } notifications.add( Pair( context.notification(Notifications.CHANNEL_NEW_CHAPTERS) { setSmallIcon(R.drawable.ic_yokai) try { - val request = ImageRequest.Builder(context).data(manga.cover()) + val request = ImageRequest.Builder(context).data(manga.manga.cover()) .networkCachePolicy(CachePolicy.DISABLED) .diskCachePolicy(CachePolicy.ENABLED) .transformations(CircleCropTransformation()) @@ -205,7 +205,7 @@ class LibraryUpdateNotifier(private val context: Context) { } catch (_: Exception) { } setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) - setContentTitle(manga.title) + setContentTitle(manga.manga.title) color = ContextCompat.getColor(context, R.color.secondaryTachiyomi) val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) { "${chapterNames.take(MAX_CHAPTERS - 1).joinToString(", ")}, " + @@ -224,7 +224,7 @@ class LibraryUpdateNotifier(private val context: Context) { setContentIntent( NotificationReceiver.openChapterPendingActivity( context, - manga, + manga.manga, chapters.first(), ), ) @@ -233,7 +233,7 @@ class LibraryUpdateNotifier(private val context: Context) { context.getString(MR.strings.mark_as_read), NotificationReceiver.markAsReadPendingBroadcast( context, - manga, + manga.manga, chapters, Notifications.ID_NEW_CHAPTERS, ), @@ -243,13 +243,13 @@ class LibraryUpdateNotifier(private val context: Context) { context.getString(MR.strings.view_chapters), NotificationReceiver.openChapterPendingActivity( context, - manga, + manga.manga, Notifications.ID_NEW_CHAPTERS, ), ) setAutoCancel(true) }, - manga.id.hashCode(), + manga.manga.id.hashCode(), ), ) } @@ -281,13 +281,13 @@ class LibraryUpdateNotifier(private val context: Context) { NotificationCompat.BigTextStyle() .bigText( updates.keys.joinToString("\n") { - it.title.chop(45) + it.manga.title.chop(45) }, ), ) } } else if (!preferences.hideNotificationContent().get()) { - setContentText(updates.keys.first().title.chop(45)) + setContentText(updates.keys.first().manga.title.chop(45)) } priority = NotificationCompat.PRIORITY_HIGH setGroup(Notifications.GROUP_NEW_CHAPTERS) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index 271d9d1755..db4390ece6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -13,15 +13,15 @@ import eu.kanade.tachiyomi.util.lang.removeArticles import eu.kanade.tachiyomi.util.system.isLTR import eu.kanade.tachiyomi.util.system.timeSpanFromNow import eu.kanade.tachiyomi.util.system.withDefContext +import java.util.* import kotlinx.coroutines.runBlocking import uy.kohesive.injekt.injectLazy -import yokai.domain.ui.UiPreferences -import yokai.i18n.MR -import yokai.util.lang.getString -import java.util.* import yokai.domain.category.interactor.GetCategories import yokai.domain.chapter.interactor.GetChapter import yokai.domain.history.interactor.GetHistory +import yokai.domain.ui.UiPreferences +import yokai.i18n.MR +import yokai.util.lang.getString /** * Adapter storing a list of manga in a certain category. @@ -117,8 +117,8 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : */ fun indexOf(manga: Manga): Int { return currentItems.indexOfFirst { - if (it is LibraryItem) { - it.manga.id == manga.id + if (it is LibraryMangaItem) { + it.manga.manga.id == manga.id } else { false } @@ -142,7 +142,7 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : */ fun allIndexOf(manga: Manga): List { return currentItems.mapIndexedNotNull { index, it -> - if (it is LibraryItem && it.manga.id == manga.id) { + if (it is LibraryMangaItem && it.manga.manga.id == manga.id) { index } else { null @@ -164,7 +164,7 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : } else { val filteredManga = withDefContext { mangas.filter { it.filter(s) } } if (filteredManga.isEmpty() && controller?.presenter?.showAllCategories == false) { - val catId = mangas.firstOrNull()?.let { it.header?.catId ?: it.manga.category } + val catId = (mangas.firstOrNull() as? LibraryMangaItem)?.let { it.header?.catId ?: it.manga.category } val blankItem = catId?.let { controller.presenter.blankItem(it) } updateDataSet(blankItem ?: emptyList()) } else { @@ -202,18 +202,19 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : vibrateOnCategoryChange(item.category.name) item.category.name } - is LibraryItem -> { - val text = if (item.manga.isBlank()) { - return item.header?.category?.name.orEmpty() - } else { + is LibraryPlaceholderItem -> { + item.header?.category?.name.orEmpty() + } + is LibraryMangaItem -> { + val text = when (getSort(position)) { LibrarySort.DragAndDrop -> { - if (item.header.category.isDynamic && item.manga.id != null) { + if (item.header.category.isDynamic && item.manga.manga.id != null) { // FIXME: Don't do blocking - val category = runBlocking { getCategories.awaitByMangaId(item.manga.id!!) }.firstOrNull()?.name + val category = runBlocking { getCategories.awaitByMangaId(item.manga.manga.id!!) }.firstOrNull()?.name category ?: context.getString(MR.strings.default_value) } else { - val title = item.manga.title + val title = item.manga.manga.title if (preferences.removeArticles().get()) { title.removeArticles().chop(15) } else { @@ -222,14 +223,14 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : } } LibrarySort.DateFetched -> { - val id = item.manga.id ?: return "" + val id = item.manga.manga.id ?: return "" // FIXME: Don't do blocking val history = runBlocking { getChapter.awaitAll(id, false) } val last = history.maxOfOrNull { it.date_fetch } context.timeSpanFromNow(MR.strings.fetched_, last ?: 0) } LibrarySort.LastRead -> { - val id = item.manga.id ?: return "" + val id = item.manga.manga.id ?: return "" // FIXME: Don't do blocking val history = runBlocking { getHistory.awaitAllByMangaId(id) } val last = history.maxOfOrNull { it.last_read } @@ -256,21 +257,20 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : } } LibrarySort.LatestChapter -> { - context.timeSpanFromNow(MR.strings.updated_, item.manga.last_update) + context.timeSpanFromNow(MR.strings.updated_, item.manga.manga.last_update) } LibrarySort.DateAdded -> { - context.timeSpanFromNow(MR.strings.added_, item.manga.date_added) + context.timeSpanFromNow(MR.strings.added_, item.manga.manga.date_added) } LibrarySort.Title -> { val title = if (preferences.removeArticles().get()) { - item.manga.title.removeArticles() + item.manga.manga.title.removeArticles() } else { - item.manga.title + item.manga.manga.title } getFirstLetter(title) } } - } if (!isSingleCategory) { vibrateOnCategoryChange(item.header?.category?.name.orEmpty()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 393fe3244d..9f49ec22f5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -451,7 +451,7 @@ open class LibraryController( private fun setActiveCategory() { val currentCategory = presenter.categories.indexOfFirst { - if (presenter.showAllCategories) it.order == activeCategory else presenter.currentCategory == it.id + if (presenter.showAllCategories) it.order == activeCategory else presenter.currentCategoryId == it.id } if (currentCategory > -1) { binding.categoryRecycler.setCategories(currentCategory) @@ -521,14 +521,13 @@ open class LibraryController( } private fun openRandomManga(global: Boolean) { - val items = if (global) { - presenter.allLibraryItems - } else { - adapter.currentItems - }.filter { (it is LibraryItem && !it.manga.isBlank() && !it.manga.isHidden() && (!it.manga.initialized || it.manga.unread > 0)) } + val items = + if (global) { presenter.currentLibraryItems } else { adapter.currentItems } + .filterIsInstance() + .filter { !it.manga.manga.initialized || it.manga.unread > 0 } if (items.isNotEmpty()) { - val item = items.random() as LibraryItem - openManga(item.manga) + val item = items.random() as LibraryMangaItem + openManga(item.manga.manga) } } @@ -662,7 +661,7 @@ open class LibraryController( createActionModeIfNeeded() } - if (presenter.libraryItems.isNotEmpty() && !isSubClass) { + if (presenter.libraryItemsToDisplay.isNotEmpty() && !isSubClass) { presenter.restoreLibrary() if (justStarted) { val activityBinding = activityBinding ?: return @@ -706,7 +705,7 @@ open class LibraryController( if (!LibraryUpdateJob.isRunning(context)) { when { !presenter.showAllCategories && presenter.groupType == BY_DEFAULT -> { - presenter.findCurrentCategory()?.let { + presenter.currentCategory?.let { updateLibrary(it) } } @@ -904,7 +903,7 @@ open class LibraryController( } } else { val newOffset = - presenter.categories.indexOfFirst { presenter.currentCategory == it.id } + + presenter.categories.indexOfFirst { presenter.currentCategoryId == it.id } + (if (next) 1 else -1) if (if (!next) { newOffset > -1 @@ -1013,7 +1012,7 @@ open class LibraryController( override fun getSpanSize(position: Int): Int { if (libraryLayout == LibraryItem.LAYOUT_LIST) return managerSpanCount val item = this@LibraryController.mAdapter?.getItem(position) - return if (item is LibraryHeaderItem || item is SearchGlobalItem || (item is LibraryItem && item.manga.isBlank())) { + return if (item is LibraryHeaderItem || item is SearchGlobalItem || item is LibraryPlaceholderItem) { managerSpanCount } else { 1 @@ -1459,7 +1458,7 @@ open class LibraryController( adapter.removeAllScrollableHeaders() } adapter.setFilter(query) - if (presenter.allLibraryItems.isEmpty()) return true + if (presenter.currentLibraryItems.isEmpty()) return true viewScope.launchUI { adapter.performFilterAsync() } @@ -1478,7 +1477,6 @@ open class LibraryController( } private fun setSelection(manga: Manga, selected: Boolean) { - if (manga.isBlank()) return val currentMode = adapter.mode if (selected) { if (selectedMangas.add(manga)) { @@ -1528,7 +1526,7 @@ open class LibraryController( toggleSelection(position) return } - val manga = (adapter.getItem(position) as? LibraryItem)?.manga ?: return + val manga = (adapter.getItem(position) as? LibraryMangaItem)?.manga?.manga ?: return val activity = activity ?: return val chapter = presenter.getFirstUnread(manga) ?: return activity.apply { @@ -1544,9 +1542,8 @@ open class LibraryController( } private fun toggleSelection(position: Int) { - val item = adapter.getItem(position) as? LibraryItem ?: return - if (item.manga.isBlank()) return - setSelection(item.manga, !adapter.isSelected(position)) + val item = adapter.getItem(position) as? LibraryMangaItem ?: return + setSelection(item.manga.manga, !adapter.isSelected(position)) invalidateActionMode() } @@ -1562,14 +1559,14 @@ open class LibraryController( * @return true if the item should be selected, false otherwise. */ override fun onItemClick(view: View?, position: Int): Boolean { - val item = adapter.getItem(position) as? LibraryItem ?: return false + val item = adapter.getItem(position) as? LibraryMangaItem ?: return false return if (adapter.mode == SelectableAdapter.Mode.MULTI) { snack?.dismiss() lastClickPosition = position toggleSelection(position) false } else { - openManga(item.manga) + openManga(item.manga.manga) false } } @@ -1591,10 +1588,10 @@ open class LibraryController( */ override fun onItemLongClick(position: Int) { val item = adapter.getItem(position) - if (item !is LibraryItem) return + if (item !is LibraryMangaItem) return snack?.dismiss() if (libraryLayout == LibraryItem.LAYOUT_COVER_ONLY_GRID && actionMode == null) { - snack = view?.snack(item.manga.title) { + snack = view?.snack(item.manga.manga.title) { anchorView = activityBinding?.bottomNav view.elevation = 15f.dpToPx } @@ -1648,9 +1645,9 @@ open class LibraryController( } private fun setSelection(position: Int, selected: Boolean = true) { - val item = adapter.getItem(position) as? LibraryItem ?: return + val item = adapter.getItem(position) as? LibraryMangaItem ?: return - setSelection(item.manga, selected) + setSelection(item.manga.manga, selected) invalidateActionMode() } @@ -1674,7 +1671,7 @@ open class LibraryController( override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean { if (adapter.isSelected(fromPosition)) toggleSelection(fromPosition) - val item = adapter.getItem(fromPosition) as? LibraryItem ?: return false + val item = adapter.getItem(fromPosition) as? LibraryMangaItem ?: return false val newHeader = adapter.getSectionHeader(toPosition) as? LibraryHeaderItem if (toPosition < 1) return false return (adapter.getItem(toPosition) !is LibraryHeaderItem) && ( @@ -1696,11 +1693,11 @@ open class LibraryController( destroyActionModeIfNeeded() // if nothing moved if (lastItemPosition == null) return - val item = adapter.getItem(position) as? LibraryItem ?: return + val item = adapter.getItem(position) as? LibraryMangaItem ?: return val newHeader = adapter.getSectionHeader(position) as? LibraryHeaderItem val libraryItems = getSectionItems(adapter.getSectionHeader(position), item) - .filterIsInstance() - val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id } + .filterIsInstance() + val mangaIds = libraryItems.mapNotNull { (it as? LibraryMangaItem)?.manga?.manga?.id } if (newHeader?.category?.id == item.manga.category) { presenter.rearrangeCategory(item.manga.category, mangaIds) } else { @@ -1832,8 +1829,8 @@ open class LibraryController( if (category?.isDynamic == false && sortBy == LibrarySort.DragAndDrop.categoryValue) { val item = adapter.findCategoryHeader(catId) ?: return val libraryItems = adapter.getSectionItems(item) - .filterIsInstance() - val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id } + .filterIsInstance() + val mangaIds = libraryItems.mapNotNull { (it as? LibraryMangaItem)?.manga?.manga?.id } presenter.rearrangeCategory(catId, mangaIds) } else { presenter.sortCategory(catId, sortBy) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt index abbcdc7224..86287493a5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt @@ -64,23 +64,24 @@ class LibraryGridHolder( * @param item the manga item to bind. */ override fun onSetValues(item: LibraryItem) { + if (item !is LibraryMangaItem) throw IllegalStateException("Only LibraryMangaItem can use grid holder") // Update the title and subtitle of the manga. setCards(adapter.showOutline, binding.card, binding.unreadDownloadBadge.root) binding.playButton.transitionName = "library chapter $bindingAdapterPosition transition" - binding.constraintLayout.isVisible = !item.manga.isBlank() - binding.title.text = item.manga.title.highlightText(item.filter, color) - binding.behindTitle.text = item.manga.title - val mangaColor = item.manga.dominantCoverColors + binding.constraintLayout.isVisible = item.manga.manga.id != Long.MIN_VALUE + binding.title.text = item.manga.manga.title.highlightText(item.filter, color) + binding.behindTitle.text = item.manga.manga.title + val mangaColor = item.manga.manga.dominantCoverColors binding.coverConstraint.backgroundColor = mangaColor?.first ?: itemView.context.getResourceColor(R.attr.background) binding.behindTitle.setTextColor( mangaColor?.second ?: itemView.context.getResourceColor(R.attr.colorOnBackground), ) - val authorArtist = if (item.manga.author == item.manga.artist || item.manga.artist.isNullOrBlank()) { - item.manga.author?.trim() ?: "" + val authorArtist = if (item.manga.manga.author == item.manga.manga.artist || item.manga.manga.artist.isNullOrBlank()) { + item.manga.manga.author?.trim() ?: "" } else { listOfNotNull( - item.manga.author?.trim()?.takeIf { it.isNotBlank() }, - item.manga.artist?.trim()?.takeIf { it.isNotBlank() }, + item.manga.manga.author?.trim()?.takeIf { it.isNotBlank() }, + item.manga.manga.artist?.trim()?.takeIf { it.isNotBlank() }, ).joinToString(", ") } binding.subtitle.text = authorArtist.highlightText(item.filter, color) @@ -101,7 +102,7 @@ class LibraryGridHolder( // Update the cover. binding.coverThumbnail.dispose() - setCover(item.manga) + setCover(item.manga.manga) } override fun toggleActivation() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt index 73e10f7fd9..5760a4808f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt @@ -5,9 +5,6 @@ import androidx.core.graphics.ColorUtils import androidx.core.view.isVisible import com.google.android.material.card.MaterialCardView import eu.kanade.tachiyomi.R -import yokai.i18n.MR -import yokai.util.lang.getString -import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.system.getResourceColor @@ -17,9 +14,7 @@ import eu.kanade.tachiyomi.util.view.setCards * Generic class used to hold the displayed data of a manga in the library. * @param view the inflated view for this holder. * @param adapter the adapter handling this holder. - * @param listener a listener to react to the single tap and long tap events. */ - abstract class LibraryHolder( view: View, val adapter: LibraryCategoryAdapter, @@ -43,7 +38,7 @@ abstract class LibraryHolder( */ abstract fun onSetValues(item: LibraryItem) - fun setUnreadBadge(badge: LibraryBadge, item: LibraryItem) { + fun setUnreadBadge(badge: LibraryBadge, item: LibraryMangaItem) { val showTotal = item.header.category.sortingMode() == LibrarySort.TotalChapters badge.setUnreadDownload( when { @@ -54,7 +49,7 @@ abstract class LibraryHolder( }, when { item.downloadCount == -1 -> -1 - item.manga.isLocal() -> -2 + item.manga.manga.isLocal() -> -2 else -> item.downloadCount }, showTotal, @@ -63,7 +58,7 @@ abstract class LibraryHolder( ) } - fun setReadingButton(item: LibraryItem) { + fun setReadingButton(item: LibraryMangaItem) { itemView.findViewById(R.id.play_layout)?.isVisible = item.manga.unread > 0 && !item.hideReadingButton } @@ -80,8 +75,8 @@ abstract class LibraryHolder( override fun onLongClick(view: View?): Boolean { return if (adapter.isLongPressDragEnabled) { - val manga = (adapter.getItem(flexibleAdapterPosition) as? LibraryItem)?.manga - if (manga != null && !isDraggable && !manga.isBlank() && !manga.isHidden()) { + val manga = (adapter.getItem(flexibleAdapterPosition) as? LibraryMangaItem)?.manga + if (manga != null && !isDraggable) { adapter.mItemLongClickListener.onItemLongClick(flexibleAdapterPosition) toggleActivation() true diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index 50f63727e2..134e8241ae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -1,205 +1,48 @@ package eu.kanade.tachiyomi.ui.library import android.content.Context -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.ImageView -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.content.ContextCompat -import androidx.core.view.isVisible -import androidx.core.view.updateLayoutParams +import androidx.annotation.CallSuper import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.StaggeredGridLayoutManager import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractSectionableItem import eu.davidea.flexibleadapter.items.IFilterable import eu.davidea.flexibleadapter.items.IFlexible -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.LibraryManga -import eu.kanade.tachiyomi.data.database.models.seriesType import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.databinding.MangaGridItemBinding import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.util.system.dpToPx -import eu.kanade.tachiyomi.util.view.compatToolTipText -import eu.kanade.tachiyomi.widget.AutofitRecyclerView import uy.kohesive.injekt.injectLazy import yokai.domain.ui.UiPreferences -class LibraryItem( - val manga: LibraryManga, +abstract class LibraryItem( header: LibraryHeaderItem, - private val context: Context?, + internal val context: Context?, ) : AbstractSectionableItem(header), IFilterable { - var downloadCount = -1 - var unreadType = 2 - var sourceLanguage: String? = null var filter = "" - private val sourceManager: SourceManager by injectLazy() + internal val sourceManager: SourceManager by injectLazy() private val uiPreferences: UiPreferences by injectLazy() private val preferences: PreferencesHelper by injectLazy() - private val uniformSize: Boolean + internal val uniformSize: Boolean get() = uiPreferences.uniformGrid().get() - private val libraryLayout: Int + internal val libraryLayout: Int get() = preferences.libraryLayout().get() val hideReadingButton: Boolean get() = preferences.hideStartReadingButton().get() - override fun getLayoutRes(): Int { - return if (libraryLayout == LAYOUT_LIST || manga.isBlank()) { - R.layout.manga_list_item - } else { - R.layout.manga_grid_item - } - } - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LibraryHolder { - val parent = adapter.recyclerView - return if (parent is AutofitRecyclerView) { - val libraryLayout = libraryLayout - val isFixedSize = uniformSize - if (libraryLayout == LAYOUT_LIST || manga.isBlank()) { - LibraryListHolder(view, adapter as LibraryCategoryAdapter) - } else { - view.apply { - val isStaggered = parent.layoutManager is StaggeredGridLayoutManager - val binding = MangaGridItemBinding.bind(this) - binding.behindTitle.isVisible = libraryLayout == LAYOUT_COVER_ONLY_GRID - if (libraryLayout >= LAYOUT_COMFORTABLE_GRID) { - binding.textLayout.isVisible = libraryLayout == LAYOUT_COMFORTABLE_GRID - binding.card.setCardForegroundColor( - ContextCompat.getColorStateList( - context, - R.color.library_comfortable_grid_foreground, - ), - ) - } - if (isFixedSize) { - binding.constraintLayout.layoutParams = FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - ) - binding.coverThumbnail.maxHeight = Int.MAX_VALUE - binding.coverThumbnail.minimumHeight = 0 - binding.constraintLayout.minHeight = 0 - binding.coverThumbnail.scaleType = ImageView.ScaleType.CENTER_CROP - binding.coverThumbnail.adjustViewBounds = false - binding.coverThumbnail.updateLayoutParams { - height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT - dimensionRatio = "2:3" - } - } - if (libraryLayout != LAYOUT_COMFORTABLE_GRID) { - binding.card.updateLayoutParams { - bottomMargin = (if (isStaggered) 2 else 6).dpToPx - } - } - binding.setBGAndFG(libraryLayout) - } - val gridHolder = LibraryGridHolder( - view, - adapter as LibraryCategoryAdapter, - libraryLayout == LAYOUT_COMPACT_GRID, - isFixedSize, - ) - if (!isFixedSize) { - gridHolder.setFreeformCoverRatio(manga, parent) - } - gridHolder - } - } else { - LibraryListHolder(view, adapter as LibraryCategoryAdapter) - } - } - + @CallSuper override fun bindViewHolder( adapter: FlexibleAdapter>, holder: LibraryHolder, position: Int, payloads: MutableList?, ) { - if (holder is LibraryGridHolder && !holder.fixedSize) { - holder.setFreeformCoverRatio(manga, adapter.recyclerView as? AutofitRecyclerView) - } holder.onSetValues(this) (holder as? LibraryGridHolder)?.setSelected(adapter.isSelected(position)) - val layoutParams = holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams - layoutParams?.isFullSpan = manga.isBlank() - if (libraryLayout == LAYOUT_COVER_ONLY_GRID) { - holder.itemView.compatToolTipText = manga.title - } - } - - /** - * Returns true if this item is draggable. - */ - override fun isDraggable(): Boolean { - return !manga.isBlank() && header.category.isDragAndDrop - } - - override fun isEnabled(): Boolean { - return !manga.isBlank() - } - - override fun isSelectable(): Boolean { - return !manga.isBlank() - } - - /** - * Filters a manga depending on a query. - * - * @param constraint the query to apply. - * @return true if the manga should be included, false otherwise. - */ - override fun filter(constraint: String): Boolean { - filter = constraint - if (manga.isBlank() && manga.title.isBlank()) { - return constraint.isEmpty() - } - val sourceName by lazy { sourceManager.getOrStub(manga.source).name } - return manga.title.contains(constraint, true) || - (manga.author?.contains(constraint, true) ?: false) || - (manga.artist?.contains(constraint, true) ?: false) || - sourceName.contains(constraint, true) || - if (constraint.contains(",")) { - val genres = manga.genre?.split(", ") - constraint.split(",").all { containsGenre(it.trim(), genres) } - } else { - containsGenre(constraint, manga.genre?.split(", ")) - } - } - - private fun containsGenre(tag: String, genres: List?): Boolean { - if (tag.trim().isEmpty()) return true - context ?: return false - val seriesType by lazy { manga.seriesType(context, sourceManager) } - return if (tag.startsWith("-")) { - val realTag = tag.substringAfter("-") - genres?.find { - it.trim().equals(realTag, ignoreCase = true) || seriesType.equals(realTag, true) - } == null - } else { - genres?.find { - it.trim().equals(tag, ignoreCase = true) || seriesType.equals(tag, true) - } != null - } - } - - override fun equals(other: Any?): Boolean { - if (other is LibraryItem) { - return manga.id == other.manga.id && manga.category == other.manga.category - } - return false - } - - override fun hashCode(): Int { - return 31 * manga.id!!.hashCode() + header!!.hashCode() + (holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams)?.isFullSpan = this is LibraryPlaceholderItem } companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt index 38d17e926c..e3696c68e7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt @@ -39,22 +39,25 @@ class LibraryListHolder( setCards(adapter.showOutline, binding.card, binding.unreadDownloadBadge.root) binding.title.isVisible = true binding.constraintLayout.minHeight = 56.dpToPx - if (item.manga.isBlank()) { + if (item is LibraryPlaceholderItem) { binding.constraintLayout.minHeight = 0 binding.constraintLayout.updateLayoutParams { height = ViewGroup.MarginLayoutParams.WRAP_CONTENT } - if (item.manga.status == -1) { - binding.title.text = null - binding.title.isVisible = false - } else { - binding.title.text = itemView.context.getString( - if (adapter.hasActiveFilters && item.manga.realMangaCount >= 1) { - MR.strings.no_matches_for_filters_short - } else { - MR.strings.category_is_empty - }, - ) + when (item.type) { + is LibraryPlaceholderItem.Type.Blank -> { + binding.title.text = itemView.context.getString( + if (adapter.hasActiveFilters && item.type.mangaCount >= 1) { + MR.strings.no_matches_for_filters_short + } else { + MR.strings.category_is_empty + }, + ) + } + is LibraryPlaceholderItem.Type.Hidden -> { + binding.title.text = null + binding.title.isVisible = false + } } binding.title.textAlignment = View.TEXT_ALIGNMENT_CENTER binding.card.isVisible = false @@ -63,6 +66,9 @@ class LibraryListHolder( binding.subtitle.isVisible = false return } + + if (item !is LibraryMangaItem) error("${item::class.qualifiedName} is not a valid item") + binding.constraintLayout.updateLayoutParams { height = 52.dpToPx } @@ -71,16 +77,16 @@ class LibraryListHolder( binding.title.textAlignment = View.TEXT_ALIGNMENT_TEXT_START // Update the binding.title of the manga. - binding.title.text = item.manga.title.highlightText(item.filter, color) + binding.title.text = item.manga.manga.title.highlightText(item.filter, color) setUnreadBadge(binding.unreadDownloadBadge.badgeView, item) val authorArtist = - if (item.manga.author == item.manga.artist || item.manga.artist.isNullOrBlank()) { - item.manga.author?.trim() ?: "" + if (item.manga.manga.author == item.manga.manga.artist || item.manga.manga.artist.isNullOrBlank()) { + item.manga.manga.author?.trim() ?: "" } else { listOfNotNull( - item.manga.author?.trim()?.takeIf { it.isNotBlank() }, - item.manga.artist?.trim()?.takeIf { it.isNotBlank() }, + item.manga.manga.author?.trim()?.takeIf { it.isNotBlank() }, + item.manga.manga.artist?.trim()?.takeIf { it.isNotBlank() }, ).joinToString(", ") } @@ -95,7 +101,7 @@ class LibraryListHolder( // Update the cover. binding.coverThumbnail.dispose() - binding.coverThumbnail.loadManga(item.manga) + binding.coverThumbnail.loadManga(item.manga.manga) } override fun onActionStateChanged(position: Int, actionState: Int) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaItem.kt new file mode 100644 index 0000000000..28de0771f0 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaItem.kt @@ -0,0 +1,180 @@ +package eu.kanade.tachiyomi.ui.library + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.StaggeredGridLayoutManager +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.LibraryManga +import eu.kanade.tachiyomi.data.database.models.seriesType +import eu.kanade.tachiyomi.databinding.MangaGridItemBinding +import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.view.compatToolTipText +import eu.kanade.tachiyomi.widget.AutofitRecyclerView + +class LibraryMangaItem( + val manga: LibraryManga, + header: LibraryHeaderItem, + context: Context?, +) : LibraryItem(header, context) { + + var downloadCount = -1 + var unreadType = 2 + var sourceLanguage: String? = null + + override fun getLayoutRes(): Int { + return if (libraryLayout == LAYOUT_LIST) { + R.layout.manga_list_item + } else { + R.layout.manga_grid_item + } + } + + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LibraryHolder { + val listHolder by lazy { LibraryListHolder(view, adapter as LibraryCategoryAdapter) } + val parent = adapter.recyclerView + if (parent !is AutofitRecyclerView) return listHolder + + val libraryLayout = libraryLayout + val isFixedSize = uniformSize + + if (libraryLayout == LAYOUT_LIST) { return listHolder } + + view.apply { + val isStaggered = parent.layoutManager is StaggeredGridLayoutManager + val binding = MangaGridItemBinding.bind(this) + binding.behindTitle.isVisible = libraryLayout == LAYOUT_COVER_ONLY_GRID + if (libraryLayout >= LAYOUT_COMFORTABLE_GRID) { + binding.textLayout.isVisible = libraryLayout == LAYOUT_COMFORTABLE_GRID + binding.card.setCardForegroundColor( + ContextCompat.getColorStateList( + context, + R.color.library_comfortable_grid_foreground, + ), + ) + } + if (isFixedSize) { + binding.constraintLayout.layoutParams = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + ) + binding.coverThumbnail.maxHeight = Int.MAX_VALUE + binding.coverThumbnail.minimumHeight = 0 + binding.constraintLayout.minHeight = 0 + binding.coverThumbnail.scaleType = ImageView.ScaleType.CENTER_CROP + binding.coverThumbnail.adjustViewBounds = false + binding.coverThumbnail.updateLayoutParams { + height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT + dimensionRatio = "2:3" + } + } + if (libraryLayout != LAYOUT_COMFORTABLE_GRID) { + binding.card.updateLayoutParams { + bottomMargin = (if (isStaggered) 2 else 6).dpToPx + } + } + binding.setBGAndFG(libraryLayout) + } + val gridHolder = LibraryGridHolder( + view, + adapter as LibraryCategoryAdapter, + libraryLayout == LAYOUT_COMPACT_GRID, + isFixedSize, + ) + if (!isFixedSize) { + gridHolder.setFreeformCoverRatio(manga.manga, parent) + } + return gridHolder + } + + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: LibraryHolder, + position: Int, + payloads: MutableList?, + ) { + if (holder is LibraryGridHolder && !holder.fixedSize) { + holder.setFreeformCoverRatio(manga.manga, adapter.recyclerView as? AutofitRecyclerView) + } + super.bindViewHolder(adapter, holder, position, payloads) + if (libraryLayout == LAYOUT_COVER_ONLY_GRID) { + holder.itemView.compatToolTipText = manga.manga.title + } + } + + /** + * Returns true if this item is draggable. + */ + override fun isDraggable(): Boolean { + return header.category.isDragAndDrop + } + + override fun isEnabled(): Boolean { + return true + } + + override fun isSelectable(): Boolean { + return true + } + + /** + * Filters a manga depending on a query. + * + * @param constraint the query to apply. + * @return true if the manga should be included, false otherwise. + */ + override fun filter(constraint: String): Boolean { + filter = constraint + if (manga.manga.title.isBlank()) { + return constraint.isEmpty() + } + val sourceName by lazy { sourceManager.getOrStub(manga.manga.source).name } + return manga.manga.title.contains(constraint, true) || + (manga.manga.author?.contains(constraint, true) ?: false) || + (manga.manga.artist?.contains(constraint, true) ?: false) || + sourceName.contains(constraint, true) || + if (constraint.contains(",")) { + val genres = manga.manga.genre?.split(", ") + constraint.split(",").all { containsGenre(it.trim(), genres) } + } else { + containsGenre(constraint, manga.manga.genre?.split(", ")) + } + } + + private fun containsGenre(tag: String, genres: List?): Boolean { + if (tag.trim().isEmpty()) return true + context ?: return false + + val seriesType by lazy { manga.manga.seriesType(context, sourceManager) } + return if (tag.startsWith("-")) { + val realTag = tag.substringAfter("-") + genres?.find { + it.trim().equals(realTag, ignoreCase = true) || seriesType.equals(realTag, true) + } == null + } else { + genres?.find { + it.trim().equals(tag, ignoreCase = true) || seriesType.equals(tag, true) + } != null + } + } + + override fun equals(other: Any?): Boolean { + if (other is LibraryMangaItem) { + return manga.manga.id == other.manga.manga.id && manga.category == other.manga.category + } + return false + } + + override fun hashCode(): Int { + return 31 * manga.manga.id.hashCode() + header!!.hashCode() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPlaceholderItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPlaceholderItem.kt new file mode 100644 index 0000000000..d0b810a4a6 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPlaceholderItem.kt @@ -0,0 +1,57 @@ +package eu.kanade.tachiyomi.ui.library + +import android.content.Context +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.R + +/** + * Placeholder item to indicate if the category is hidden or empty/filtered out. + */ +class LibraryPlaceholderItem ( + val category: Int, + val type: Type, + header: LibraryHeaderItem, + context: Context?, +) : LibraryItem(header, context) { + + override fun getLayoutRes(): Int = R.layout.manga_list_item + + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LibraryHolder { + return LibraryListHolder(view, adapter as LibraryCategoryAdapter) + } + + override fun filter(constraint: String): Boolean { + filter = constraint + + if (type !is Type.Hidden || type.title.isBlank()) return constraint.isEmpty() + + return type.title.contains(constraint, true) + } + + override fun equals(other: Any?): Boolean { + if (other is LibraryPlaceholderItem) { + return category == other.category + } + return false + } + + override fun hashCode(): Int { + return 31 * Long.MIN_VALUE.hashCode() + header!!.hashCode() + } + + sealed class Type { + data class Hidden(val title: String, val hiddenItems: List) : Type() + data class Blank(val mangaCount: Int) : Type() + } + + companion object { + fun hidden(category: Int, header: LibraryHeaderItem, context: Context?, title: String, hiddenItems: List) = + LibraryPlaceholderItem(category, Type.Hidden(title, hiddenItems), header, context) + + fun blank(category: Int, header: LibraryHeaderItem, context: Context?, mangaCount: Int = 0) = + LibraryPlaceholderItem(category, Type.Blank(mangaCount), header, context) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index b2d43befea..f1a526adb3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -4,6 +4,8 @@ import eu.kanade.tachiyomi.core.preference.minusAssign import eu.kanade.tachiyomi.core.preference.plusAssign import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.models.Category +import eu.kanade.tachiyomi.data.database.models.Category.Companion.langSplitter +import eu.kanade.tachiyomi.data.database.models.Category.Companion.sourceSplitter import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter.Companion.copy import eu.kanade.tachiyomi.data.database.models.LibraryManga @@ -57,7 +59,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.retry -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext @@ -81,6 +82,9 @@ import yokai.i18n.MR import yokai.util.isLewd import yokai.util.lang.getString +typealias LibraryMap = Map> +typealias LibraryMutableMap = MutableMap> + /** * Presenter of [LibraryController]. */ @@ -120,20 +124,27 @@ class LibraryPresenter( var categories: List = emptyList() private set - private var removeArticles: Boolean = preferences.removeArticles().get() - /** All categories of the library, in case they are hidden because of hide categories is on */ private var allCategories: List = emptyList() - /** List of all manga to update the */ - // TODO: Store sectioned before flattening it out for "show all categories" - private var currentLibrary: Map> = mapOf() - var libraryItems: List = emptyList() - private var sectionedLibraryItems: MutableMap> = mutableMapOf() - var currentCategory = -1 + private var removeArticles: Boolean = preferences.removeArticles().get() + + /** List of all manga */ + var currentLibrary: LibraryMap = mapOf() private set - var allLibraryItems: List = emptyList() + val currentLibraryItems: List + get() = currentLibrary.values.flatten() + /** List of all manga to be displayed */ + private var libraryToDisplay: LibraryMutableMap = mutableMapOf() + val libraryItemsToDisplay: List + get() = libraryToDisplay.values.flatten() + + var currentCategoryId = -1 private set + var currentCategory: Category? + get() = allCategories.find { it.id == currentCategoryId } + set(value) { currentCategoryId = value?.id ?: 0 } + private var hiddenLibraryItems: List = emptyList() var forceShowAllCategories = false val showAllCategories @@ -170,16 +181,14 @@ class LibraryPresenter( fun isCategoryMoreThanOne(): Boolean = allCategories.size > 1 - fun findCurrentCategory() = allCategories.find { it.id == currentCategory } - /** Save the current list to speed up loading later */ override fun onDestroy() { val isSubController = controllerIsSubClass super.onDestroy() if (!isSubController) { - lastLibraryItems = libraryItems + lastDisplayedLibrary = libraryToDisplay lastCategories = categories - lastAllLibraryItems = allLibraryItems + lastLibrary = currentLibrary } } @@ -187,12 +196,12 @@ class LibraryPresenter( super.onCreate() if (!controllerIsSubClass) { - lastLibraryItems?.let { libraryItems = it } + lastDisplayedLibrary?.let { libraryToDisplay = it } lastCategories?.let { categories = it } - lastAllLibraryItems?.let { allLibraryItems = it } + lastLibrary?.let { currentLibrary = it } lastCategories = null - lastLibraryItems = null - lastAllLibraryItems = null + lastDisplayedLibrary = null + lastLibrary = null } subscribeLibrary() @@ -212,12 +221,16 @@ class LibraryPresenter( } fun getItemCountInCategories(categoryId: Int): Int { - val items = sectionedLibraryItems[categoryId] - return if (items?.firstOrNull()?.manga?.isHidden() == true || items?.firstOrNull()?.manga?.isBlank() == true) { - items.firstOrNull()?.manga?.read ?: 0 - } else { - sectionedLibraryItems[categoryId]?.size ?: 0 + val category = categories.find { it.id == categoryId } + val items = libraryToDisplay[category] + val firstItem = items?.firstOrNull() as? LibraryPlaceholderItem? + if (firstItem != null) { + if (firstItem.type !is LibraryPlaceholderItem.Type.Hidden) { + return 0 + } + return firstItem.type.hiddenItems.size } + return items?.size ?: 0 } private fun subscribeLibrary() { @@ -239,21 +252,23 @@ class LibraryPresenter( allCategories = data.allCategories val library = data.items - val hiddenItems = library.filter { it.manga.isHidden() }.mapNotNull { it.manga.items }.flatten() + val hiddenItems = data.hiddenItems - setDownloadCount(library) - setUnreadBadge(library) - setSourceLanguage(library) + library.forEach { (_, items) -> + setDownloadCount(items) + setUnreadBadge(items) + setSourceLanguage(items) + } setDownloadCount(hiddenItems) setUnreadBadge(hiddenItems) setSourceLanguage(hiddenItems) - allLibraryItems = library + currentLibrary = library hiddenLibraryItems = hiddenItems val mangaMap = library .applyFilters() .applySort() - val freshStart = libraryItems.isEmpty() + val freshStart = libraryToDisplay.isEmpty() sectionLibrary(mangaMap, freshStart) } } @@ -269,16 +284,16 @@ class LibraryPresenter( fun switchSection(order: Int) { preferences.lastUsedCategory().set(order) - val category = categories.find { it.order == order }?.id ?: return + val category = categories.find { it.order == order } ?: return currentCategory = category - view?.onNextLibraryUpdate(sectionedLibraryItems[currentCategory] ?: blankItem()) + view?.onNextLibraryUpdate(libraryToDisplay[category] ?: blankItem()) } - fun blankItem(id: Int = currentCategory, categories: List? = null): List { + fun blankItem(id: Int = currentCategoryId, categories: List? = null): List { val actualCategories = categories ?: this.categories return listOf( - LibraryItem( - LibraryManga.createBlank(id), + LibraryPlaceholderItem.blank( + id, LibraryHeaderItem({ actualCategories.getOrDefault(id) }, id), viewContext, ), @@ -286,20 +301,17 @@ class LibraryPresenter( } fun restoreLibrary() { - val items = libraryItems val show = showAllCategories || !libraryIsGrouped || categories.size == 1 - sectionedLibraryItems = items.groupBy { it.header.category.id!! }.toMutableMap() - if (!show && currentCategory == -1) { - currentCategory = categories.find { - it.order == preferences.lastUsedCategory().get() - }?.id ?: 0 + if (!show && currentCategoryId == -1) { + currentCategory = categories.find { it.order == preferences.lastUsedCategory().get() } } view?.onNextLibraryUpdate( if (!show) { - sectionedLibraryItems[currentCategory] - ?: sectionedLibraryItems[categories.first().id] ?: blankItem() + libraryToDisplay[currentCategory] + ?: libraryToDisplay[categories.first()] + ?: blankItem() } else { - libraryItems + libraryItemsToDisplay }, true, ) @@ -307,25 +319,29 @@ class LibraryPresenter( fun getMangaInCategories(catId: Int?): List? { catId ?: return null - return allLibraryItems.filter { it.header.category.id == catId }.map { it.manga } + return currentLibraryItems + .filterIsInstance() + .filter { it.header.category.id == catId } + .map { it.manga } } - private suspend fun sectionLibrary(items: List, freshStart: Boolean = false) { - libraryItems = items + private suspend fun sectionLibrary(items: LibraryMap, freshStart: Boolean = false) { val showAll = showAllCategories || !libraryIsGrouped || categories.size <= 1 - sectionedLibraryItems = items.groupBy { it.header.category.id ?: 0 }.toMutableMap() - if (!showAll && currentCategory == -1) { - currentCategory = categories.find { - it.order == preferences.lastUsedCategory().get() - }?.id ?: 0 + + libraryToDisplay = items.toMutableMap() + + if (!showAll && currentCategoryId == -1) { + currentCategory = categories.find { it.order == preferences.lastUsedCategory().get() } } + withUIContext { view?.onNextLibraryUpdate( if (!showAll) { - sectionedLibraryItems[currentCategory] - ?: sectionedLibraryItems[categories.first().id] ?: blankItem() + libraryToDisplay[currentCategory] + ?: libraryToDisplay[categories.first()] + ?: blankItem() } else { - libraryItems + libraryItemsToDisplay }, freshStart, ) @@ -337,7 +353,7 @@ class LibraryPresenter( * * @param items the items to filter. */ - private suspend fun List.applyFilters(): List { + private suspend fun LibraryMap.applyFilters(): LibraryMap { val filterPrefs = getPreferencesFlow().first() val showEmptyCategoriesWhileFiltering = preferences.showEmptyCategoriesWhileFiltering().get() @@ -353,57 +369,68 @@ class LibraryPresenter( filterPrefs.filterContentType == 0 ) hasActiveFilters = !filtersOff - val missingCategorySet = categories.mapNotNull { it.id }.toMutableSet() val realCount = mutableMapOf() - val filteredItems = this.filter f@{ item -> + val filteredItems = this.mapValues { (key, items) -> if (showEmptyCategoriesWhileFiltering) { - realCount[item.manga.category] = sectionedLibraryItems[item.manga.category]?.size ?: 0 + realCount[key.id ?: 0] = libraryToDisplay[key]?.size ?: 0 } - if (!showEmptyCategoriesWhileFiltering && item.manga.isHidden()) { - val subItems = sectionedLibraryItems[item.manga.category]?.takeUnless { it.size <= 1 } - ?: hiddenLibraryItems.filter { it.manga.category == item.manga.category } - if (subItems.isEmpty()) { - return@f filtersOff - } else { - return@f subItems.any { - matchesFilters( - it, - filterPrefs, - filterTrackers, - ) + items.filter f@{ item -> + if (item is LibraryMangaItem) { + return@f matchesFilters( + item, + filterPrefs, + filterTrackers, + ) + } + + if ( + !showEmptyCategoriesWhileFiltering + && item is LibraryPlaceholderItem + && item.type is LibraryPlaceholderItem.Type.Hidden + ) { + val subItems = (libraryToDisplay[key] ?: hiddenLibraryItems) + .filterIsInstance() + .filter { it.manga.category == item.category } + if (subItems.isEmpty()) { + return@f filtersOff + } else { + return@f subItems.any { + matchesFilters( + it, + filterPrefs, + filterTrackers, + ) + } } } - } else if (item.manga.isBlank() || item.manga.isHidden()) { - missingCategorySet.remove(item.manga.category) - return@f if (showAllCategories) { + + if (showAllCategories) { filtersOff || showEmptyCategoriesWhileFiltering } else { true } + }.ifEmpty { + if (showEmptyCategoriesWhileFiltering) { + val catId = key.id!! + listOf( + LibraryPlaceholderItem.blank( + catId, + LibraryHeaderItem({ this@LibraryPresenter.categories.getOrDefault(catId) }, catId), + viewContext, + realCount[catId] ?: 0, + ), + ) + } else { + emptyList() + } } - val matches = matchesFilters( - item, - filterPrefs, - filterTrackers, - ) - if (matches) { - missingCategorySet.remove(item.manga.category) - } - matches - }.toMutableList() - if (showEmptyCategoriesWhileFiltering) { - missingCategorySet.forEach { - filteredItems.add( - blankItem(it).first().apply { manga.realMangaCount = realCount[it] ?: 0 } - ) - } - } + }.toMutableMap() return filteredItems } private suspend fun matchesFilters( - item: LibraryItem, + item: LibraryMangaItem, filterPrefs: ItemPreferences, filterTrackers: String, ): Boolean { @@ -423,9 +450,9 @@ class LibraryPresenter( if (filterPrefs.filterMangaType > 0) { if (if (filterPrefs.filterMangaType == Manga.TYPE_MANHWA) { - item.manga.seriesType(sourceManager = sourceManager) !in arrayOf(filterPrefs.filterMangaType, Manga.TYPE_WEBTOON) + item.manga.manga.seriesType(sourceManager = sourceManager) !in arrayOf(filterPrefs.filterMangaType, Manga.TYPE_WEBTOON) } else { - filterPrefs.filterMangaType != item.manga.seriesType(sourceManager = sourceManager) + filterPrefs.filterMangaType != item.manga.manga.seriesType(sourceManager = sourceManager) } ) { return false @@ -433,51 +460,51 @@ class LibraryPresenter( } // Filter for completed status of manga - if (filterPrefs.filterCompleted == STATE_INCLUDE && item.manga.status != SManga.COMPLETED) return false - if (filterPrefs.filterCompleted == STATE_EXCLUDE && item.manga.status == SManga.COMPLETED) return false + if (filterPrefs.filterCompleted == STATE_INCLUDE && item.manga.manga.status != SManga.COMPLETED) return false + if (filterPrefs.filterCompleted == STATE_EXCLUDE && item.manga.manga.status == SManga.COMPLETED) return false if (!matchesFilterTracking(item, filterPrefs.filterTracked, filterTrackers)) return false // Filter for downloaded manga if (filterPrefs.filterDownloaded != STATE_IGNORE) { val isDownloaded = when { - item.manga.isLocal() -> true + item.manga.manga.isLocal() -> true item.downloadCount != -1 -> item.downloadCount > 0 - else -> downloadManager.getDownloadCount(item.manga) > 0 + else -> downloadManager.getDownloadCount(item.manga.manga) > 0 } return if (filterPrefs.filterDownloaded == STATE_INCLUDE) isDownloaded else !isDownloaded } // Filter for NSFW/SFW contents - if (filterPrefs.filterContentType == STATE_INCLUDE) return !item.manga.isLewd() - if (filterPrefs.filterContentType == STATE_EXCLUDE) return item.manga.isLewd() + if (filterPrefs.filterContentType == STATE_INCLUDE) return !item.manga.manga.isLewd() + if (filterPrefs.filterContentType == STATE_EXCLUDE) return item.manga.manga.isLewd() return true } private suspend fun matchesCustomFilters( - item: LibraryItem, + item: LibraryMangaItem, customFilters: FilteredLibraryController, filterTrackers: String, ): Boolean { val statuses = customFilters.filterStatus if (statuses.isNotEmpty()) { - if (item.manga.status !in statuses) return false + if (item.manga.manga.status !in statuses) return false } val seriesTypes = customFilters.filterMangaType if (seriesTypes.isNotEmpty()) { - if (item.manga.seriesType(sourceManager = sourceManager) !in seriesTypes) return false + if (item.manga.manga.seriesType(sourceManager = sourceManager) !in seriesTypes) return false } val languages = customFilters.filterLanguages if (languages.isNotEmpty()) { - if (getLanguage(item.manga) !in languages) return false + if (getLanguage(item.manga.manga) !in languages) return false } val sources = customFilters.filterSources if (sources.isNotEmpty()) { - if (item.manga.source !in sources) return false + if (item.manga.manga.source !in sources) return false } val trackingScore = customFilters.filterTrackingScore if (trackingScore > 0 || trackingScore == -1) { - val tracks = getTrack.awaitAllByMangaId(item.manga.id!!) + val tracks = getTrack.awaitAllByMangaId(item.manga.manga.id!!) val hasTrack = loggedServices.any { service -> tracks.any { it.sync_id == service.id } @@ -502,7 +529,7 @@ class LibraryPresenter( } val tags = customFilters.filterTags if (tags.isNotEmpty()) { - val genres = item.manga.getGenres() ?: return false + val genres = item.manga.manga.getGenres() ?: return false if (tags.none { tag -> genres.any { it.equals(tag, true) } }) return false } return true @@ -518,8 +545,8 @@ class LibraryPresenter( } private suspend fun LibraryManga.getStartYear(): Int { - if (getChapter.awaitAll(id!!, false).any { it.read }) { - val chapters = getHistory.awaitAllByMangaId(id!!).filter { it.last_read > 0 } + if (getChapter.awaitAll(manga.id!!, false).any { it.read }) { + val chapters = getHistory.awaitAllByMangaId(manga.id!!).filter { it.last_read > 0 } val date = chapters.minOfOrNull { it.last_read } ?: return -1 val cal = Calendar.getInstance().apply { timeInMillis = date } return if (date <= 0L) -1 else cal.get(Calendar.YEAR) @@ -536,13 +563,13 @@ class LibraryPresenter( } private suspend fun matchesFilterTracking( - item: LibraryItem, + item: LibraryMangaItem, filterTracked: Int, filterTrackers: String, ): Boolean { // Filter for tracked (or per tracked service) if (filterTracked != STATE_IGNORE) { - val tracks = getTrack.awaitAllByMangaId(item.manga.id!!) + val tracks = getTrack.awaitAllByMangaId(item.manga.manga.id!!) val hasTrack = loggedServices.any { service -> tracks.any { it.sync_id == service.id } @@ -585,19 +612,22 @@ class LibraryPresenter( if (!preferences.downloadBadge().get()) { // Unset download count if the preference is not enabled. for (item in itemList) { + if (item !is LibraryMangaItem) continue item.downloadCount = -1 } return } for (item in itemList) { - item.downloadCount = downloadManager.getDownloadCount(item.manga) + if (item !is LibraryMangaItem) continue + item.downloadCount = downloadManager.getDownloadCount(item.manga.manga) } } private fun setUnreadBadge(itemList: List) { val unreadType = preferences.unreadBadgeType().get() for (item in itemList) { + if (item !is LibraryMangaItem) continue item.unreadType = unreadType } } @@ -605,7 +635,8 @@ class LibraryPresenter( private fun setSourceLanguage(itemList: List) { val showLanguageBadges = preferences.languageBadge().get() for (item in itemList) { - item.sourceLanguage = if (showLanguageBadges) getLanguage(item.manga) else null + if (item !is LibraryMangaItem) continue + item.sourceLanguage = if (showLanguageBadges) getLanguage(item.manga.manga) else null } } @@ -626,88 +657,99 @@ class LibraryPresenter( * * @param itemList the map to sort. */ - private fun List.applySort(): List { + private fun LibraryMap.applySort(): LibraryMap { val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 -> - if (i1.header.category.id == i2.header.category.id) { - val category = i1.header.category - if (category.mangaOrder.isEmpty() && category.mangaSort == null) { - category.changeSortTo(preferences.librarySortingMode().get()) - if (category.id == 0) { - preferences.defaultMangaOrder() - .set(category.mangaSort.toString()) - } else if (!category.isDynamic) { - onCategoryUpdate( - CategoryUpdate( - id = category.id!!.toLong(), - mangaOrder = category.mangaOrderToString(), - ) - ) - } - } - val compare = when { - category.mangaSort != null -> { - var sort = when (category.sortingMode() ?: LibrarySort.Title) { - LibrarySort.Title -> sortAlphabetical(i1, i2) - LibrarySort.LatestChapter -> i2.manga.latestUpdate.compareTo(i1.manga.latestUpdate) - LibrarySort.Unread -> when { - i1.manga.unread == i2.manga.unread -> 0 - i1.manga.unread == 0 -> if (category.isAscending()) 1 else -1 - i2.manga.unread == 0 -> if (category.isAscending()) -1 else 1 - else -> i1.manga.unread.compareTo(i2.manga.unread) - } - LibrarySort.LastRead -> { - i1.manga.lastRead.compareTo(i2.manga.lastRead) - } - LibrarySort.TotalChapters -> { - i1.manga.totalChapters.compareTo(i2.manga.totalChapters) - } - LibrarySort.DateFetched -> { - i1.manga.lastFetch.compareTo(i2.manga.lastFetch) - } - LibrarySort.DateAdded -> i2.manga.date_added.compareTo(i1.manga.date_added) - LibrarySort.DragAndDrop -> { - if (category.isDynamic) { - val category1 = - allCategories.find { i1.manga.category == it.id }?.order - ?: 0 - val category2 = - allCategories.find { i2.manga.category == it.id }?.order - ?: 0 - category1.compareTo(category2) - } else { - sortAlphabetical(i1, i2) - } + val category = i1.header.category + val compare = when { + i1 is LibraryPlaceholderItem -> -1 + i2 is LibraryPlaceholderItem -> 1 + i1 !is LibraryMangaItem || i2 !is LibraryMangaItem -> 0 + category.mangaSort != null -> { + var sort = when (category.sortingMode() ?: LibrarySort.Title) { + LibrarySort.Title -> sortAlphabetical(i1, i2) + LibrarySort.LatestChapter -> i2.manga.latestUpdate.compareTo(i1.manga.latestUpdate) + LibrarySort.Unread -> when { + i1.manga.unread == i2.manga.unread -> 0 + i1.manga.unread == 0 -> if (category.isAscending()) 1 else -1 + i2.manga.unread == 0 -> if (category.isAscending()) -1 else 1 + else -> i1.manga.unread.compareTo(i2.manga.unread) + } + LibrarySort.LastRead -> { + i1.manga.lastRead.compareTo(i2.manga.lastRead) + } + LibrarySort.TotalChapters -> { + i1.manga.totalChapters.compareTo(i2.manga.totalChapters) + } + LibrarySort.DateFetched -> { + i1.manga.lastFetch.compareTo(i2.manga.lastFetch) + } + LibrarySort.DateAdded -> i2.manga.manga.date_added.compareTo(i1.manga.manga.date_added) + LibrarySort.DragAndDrop -> { + if (category.isDynamic) { + val category1 = + allCategories.find { i1.manga.category == it.id }?.order + ?: 0 + val category2 = + allCategories.find { i2.manga.category == it.id }?.order + ?: 0 + category1.compareTo(category2) + } else { + sortAlphabetical(i1, i2) } } - if (!category.isAscending()) sort *= -1 - sort } - category.mangaOrder.isNotEmpty() -> { - val order = category.mangaOrder - val index1 = order.indexOf(i1.manga.id!!) - val index2 = order.indexOf(i2.manga.id!!) - when { - index1 == index2 -> 0 - index1 == -1 -> -1 - index2 == -1 -> 1 - else -> index1.compareTo(index2) - } + if (!category.isAscending()) sort *= -1 + sort + } + category.mangaOrder.isNotEmpty() -> { + val order = category.mangaOrder + val index1 = order.indexOf(i1.manga.manga.id!!) + val index2 = order.indexOf(i2.manga.manga.id!!) + when { + index1 == index2 -> 0 + index1 == -1 -> -1 + index2 == -1 -> 1 + else -> index1.compareTo(index2) } - else -> 0 - } - if (compare == 0) { - sortAlphabetical(i1, i2) - } else { - compare } + else -> 0 + } + if (compare == 0 && i1 is LibraryMangaItem && i2 is LibraryMangaItem) { + sortAlphabetical(i1, i2) } else { - val category = i1.header.category.order - val category2 = i2.header.category.order - category.compareTo(category2) + compare } } - return this.sortedWith(Comparator(sortFn)) + return this.mapValues { (category, values) -> + // Making sure category has valid sort + if (category.mangaOrder.isEmpty() && category.mangaSort == null) { + category.changeSortTo(preferences.librarySortingMode().get()) + if (category.id == 0) { + preferences.defaultMangaOrder() + .set(category.mangaSort.toString()) + } else if (!category.isDynamic) { + onCategoryUpdate( + CategoryUpdate( + id = category.id!!.toLong(), + mangaOrder = category.mangaOrderToString(), + ) + ) + } + } + + values.sortedWith(Comparator(sortFn)) + }.toSortedMap { category, category2 -> + // Force default category to already be at the top. This also for some reason fixed a bug where Default + // category would disappear whenever a new category is added. + if (category.id == 0) { + -1 + } else if (category2.id == 0) { + 1 + } else { + category.order.compareTo(category2.order) + } + } } /** Gets the category by id @@ -726,11 +768,11 @@ class LibraryPresenter( * @param i1 the first manga * @param i2 the second manga to compare */ - private fun sortAlphabetical(i1: LibraryItem, i2: LibraryItem): Int { + private fun sortAlphabetical(i1: LibraryMangaItem, i2: LibraryMangaItem): Int { return if (removeArticles) { - i1.manga.title.removeArticles().compareTo(i2.manga.title.removeArticles(), true) + i1.manga.manga.title.removeArticles().compareTo(i2.manga.manga.title.removeArticles(), true) } else { - i1.manga.title.compareTo(i2.manga.title, true) + i1.manga.manga.title.compareTo(i2.manga.manga.title, true) } } @@ -769,33 +811,33 @@ class LibraryPresenter( ) } - private fun MutableList.addRemovedManga( - removedManga: Map>, - ): MutableList { - removedManga.keys.forEach { key -> - val manga = removedManga[key] ?: return@forEach - val headerItem = try { - manga.first().header - } catch (e: NoSuchElementException) { - return@forEach // No hidden manga to be handled - } - val mergedTitle = manga.joinToString("-") { - it.manga.title + "-" + it.manga.author - } - this.add( - LibraryItem( - LibraryManga.createHide( - headerItem.catId, - mergedTitle, - manga, - ), - headerItem, - viewContext, - ), - ) - } - return this - } +// private fun MutableList.addRemovedManga( +// removedManga: Map>, +// ): MutableList { +// removedManga.keys.forEach { key -> +// val manga = removedManga[key] ?: return@forEach +// val headerItem = try { +// manga.first().header +// } catch (e: NoSuchElementException) { +// return@forEach // No hidden manga to be handled +// } +// val mergedTitle = manga.joinToString("-") { +// it.manga.title + "-" + it.manga.manga.author +// } +// this.add( +// LibraryItem( +// LibraryManga.createHide( +// headerItem.catId, +// mergedTitle, +// manga, +// ), +// headerItem, +// viewContext, +// ), +// ) +// } +// return this +// } /** * Library's flow. @@ -854,15 +896,15 @@ class LibraryPresenter( } private fun getLibraryItems( - allCategories: List, + dbCategories: List, libraryManga: List, sortingMode: Int, isAscending: Boolean, showAll: Boolean, collapsedCategories: Set, defaultCategory: Category, - ): Triple, List, List> { - val categories = allCategories.toMutableList() + ): Triple, List> { + val categories = dbCategories.mapNotNull { if (it.id == null) null else it }.toMutableList() val hiddenItems = mutableListOf() val categoryAll = Category.createAll( @@ -871,88 +913,81 @@ class LibraryPresenter( isAscending, ) val catItemAll = LibraryHeaderItem({ categoryAll }, -1) - val categorySet = mutableSetOf() + + // NOTE: Don't call header.category, only header.catId val headerItems = ( - categories.mapNotNull { category -> - val id = category.id - if (id == null) { - null - } else { - id to LibraryHeaderItem({ categories.getOrDefault(id) }, id) - } - } + (-1 to catItemAll) + (0 to LibraryHeaderItem({ categories.getOrDefault(0) }, 0)) + categories.map { category -> + val id = category.id!! + id to LibraryHeaderItem({ this@LibraryPresenter.categories.getOrDefault(id) }, id) + } + (0 to LibraryHeaderItem({ this@LibraryPresenter.categories.getOrDefault(0) }, 0)) ).toMap() - // TODO: - - // val map = libraryManga.groupBy { - // categories.getOrDefault(it.category) - // } - - val items = if (libraryIsGrouped) { - libraryManga - } else { - libraryManga.distinctBy { it.id } - }.mapNotNull { - val headerItem = ( - if (!libraryIsGrouped) { - catItemAll - } else { - headerItems[it.category] - } - ) ?: return@mapNotNull null - categorySet.add(it.category) - LibraryItem(it, headerItem, viewContext) - }.toMutableList() - val categoriesHidden = if (forceShowAllCategories || controllerIsSubClass) { emptySet() } else { collapsedCategories.mapNotNull { it.toIntOrNull() }.toSet() } - if (categorySet.contains(0)) categories.add(0, defaultCategory) - if (libraryIsGrouped) { - categories.forEach { category -> - val catId = category.id ?: return@forEach - if (catId > 0 && !categorySet.contains(catId) && (catId !in categoriesHidden || !showAll)) { - val headerItem = headerItems[catId] - if (headerItem != null) { - items.add( - LibraryItem(LibraryManga.createBlank(catId), headerItem, viewContext), + val map = if (!libraryIsGrouped) + libraryManga + .asSequence() + .distinctBy { it.manga.id } + .map { LibraryMangaItem(it, catItemAll, viewContext) } + .groupBy { categoryAll } + else { + val rt = libraryManga + .asSequence() + .mapNotNull { + val headerItem = headerItems[it.category] ?: return@mapNotNull null + LibraryMangaItem(it, headerItem, viewContext) + } + .groupBy { it.header.catId } + + // Only show default category when needed + if (rt.containsKey(0)) categories.add(0, defaultCategory) + + // NOTE: Empty list means hide the category entirely + categories + .associateWith { rt[it.id].orEmpty() } + .mapValues { (key, values) -> + val catId = key.id!! // null check already handled by mapNotNull + val headerItem = headerItems[catId]!! // null check already handled by mapNotNull + + // Hide category if "Show all categories" is enabled and there's more than 1 category + if (catId in categoriesHidden && showAll && categories.size > 1) { + val mergedTitle = values.joinToString("-") { + it.manga.manga.title + "-" + it.manga.manga.author + } + libraryToDisplay[key] = values + hiddenItems.addAll(values) + return@mapValues listOf( + LibraryPlaceholderItem.hidden( + catId, + headerItem, + viewContext, + mergedTitle, + values, + ), ) } - } else if (catId in categoriesHidden && showAll && categories.size > 1) { - val mangaToRemove = items.filter { it.manga.category == catId } - val mergedTitle = mangaToRemove.joinToString("-") { - it.manga.title + "-" + it.manga.author - } - sectionedLibraryItems[catId] = mangaToRemove - hiddenItems.addAll(mangaToRemove) - items.removeAll(mangaToRemove) - val headerItem = headerItems[catId] - if (headerItem != null) { - items.add( - LibraryItem( - LibraryManga.createHide( - catId, - mergedTitle, - mangaToRemove, - ), + + // Making sure empty category is shown properly + values.ifEmpty { + listOf( + LibraryPlaceholderItem.blank( + catId, headerItem, viewContext, ), ) } } - } - } + }.toMutableMap() - categories.forEach { - it.isHidden = it.id in categoriesHidden && showAll && categories.size > 1 - } + categories.forEach { it.isHidden = it.id in categoriesHidden && showAll && categories.size > 1 } return Triple( - items, + map, if (!libraryIsGrouped) { arrayListOf(categoryAll) } else { @@ -968,12 +1003,13 @@ class LibraryPresenter( isAscending: Boolean, groupType: Int, collapsedDynamicCategories: Set, - ): Triple, List, List> { + ): Triple, List> { val tagItems: MutableMap = mutableMapOf() + val hiddenItems = mutableListOf() // internal function to make headers fun makeOrGetHeader(name: String, checkNameSwap: Boolean = false): LibraryHeaderItem { - tagItems.get(name)?.let { return it } + tagItems[name]?.let { return it } if (checkNameSwap && name.contains(" ")) { val swappedName = name.split(" ").reversed().joinToString(" ") if (tagItems.containsKey(swappedName)) { @@ -985,26 +1021,32 @@ class LibraryPresenter( return headerItem } + val hiddenDynamics = if (controllerIsSubClass) { + emptySet() + } else { + collapsedDynamicCategories + } + val unknown = context.getString(MR.strings.unknown) - val items = libraryManga.distinctBy { it.id }.map { manga -> + val items = libraryManga.distinctBy { it.manga.id }.map { manga -> when (groupType) { BY_TAG -> { - val tags = if (manga.genre.isNullOrBlank()) { + val tags = if (manga.manga.genre.isNullOrBlank()) { listOf(unknown) } else { - manga.genre?.split(",")?.mapNotNull { + manga.manga.genre?.split(",")?.mapNotNull { val tag = it.trim().capitalizeWords() tag.ifBlank { null } } ?: listOf(unknown) } tags.map { - LibraryItem(manga, makeOrGetHeader(it), viewContext) + LibraryMangaItem(manga, makeOrGetHeader(it), viewContext) } } BY_TRACK_STATUS -> { - val tracks = getTrack.awaitAllByMangaId(manga.id!!) + val tracks = getTrack.awaitAllByMangaId(manga.manga.id!!) val track = tracks.find { track -> - loggedServices.any { it.id == track?.sync_id } + loggedServices.any { it.id == track.sync_id } } val service = loggedServices.find { it.id == track?.sync_id } val status: String = if (track != null && service != null) { @@ -1016,12 +1058,12 @@ class LibraryPresenter( } else { view?.view?.context?.getString(MR.strings.not_tracked) ?: "" } - listOf(LibraryItem(manga, makeOrGetHeader(status), viewContext)) + listOf(LibraryMangaItem(manga, makeOrGetHeader(status), viewContext)) } BY_SOURCE -> { - val source = sourceManager.getOrStub(manga.source) + val source = sourceManager.getOrStub(manga.manga.source) listOf( - LibraryItem( + LibraryMangaItem( manga, makeOrGetHeader("${source.name}$sourceSplitter${source.id}"), viewContext, @@ -1029,26 +1071,26 @@ class LibraryPresenter( ) } BY_AUTHOR -> { - if (manga.artist.isNullOrBlank() && manga.author.isNullOrBlank()) { - listOf(LibraryItem(manga, makeOrGetHeader(unknown), viewContext)) + if (manga.manga.artist.isNullOrBlank() && manga.manga.author.isNullOrBlank()) { + listOf(LibraryMangaItem(manga, makeOrGetHeader(unknown), viewContext)) } else { listOfNotNull( - manga.author.takeUnless { it.isNullOrBlank() }, - manga.artist.takeUnless { it.isNullOrBlank() }, + manga.manga.author.takeUnless { it.isNullOrBlank() }, + manga.manga.artist.takeUnless { it.isNullOrBlank() }, ).map { it.split(",", "/", " x ", " - ", ignoreCase = true).mapNotNull { name -> val author = name.trim() author.ifBlank { null } } }.flatten().distinct().map { - LibraryItem(manga, makeOrGetHeader(it, true), viewContext) + LibraryMangaItem(manga, makeOrGetHeader(it, true), viewContext) } } } BY_LANGUAGE -> { - val lang = getLanguage(manga) + val lang = getLanguage(manga.manga) listOf( - LibraryItem( + LibraryMangaItem( manga, makeOrGetHeader( lang?.plus(langSplitter)?.plus( @@ -1063,15 +1105,11 @@ class LibraryPresenter( ), ) } - else -> listOf(LibraryItem(manga, makeOrGetHeader(context.mapStatus(manga.status)), viewContext)) // BY_STATUS + // BY_STATUS + else -> listOf(LibraryMangaItem(manga, makeOrGetHeader(context.mapStatus(manga.manga.status)), viewContext)) } - }.flatten().toMutableList() + }.flatten().groupBy { it.header.catId } - val hiddenDynamics = if (controllerIsSubClass) { - emptySet() - } else { - collapsedDynamicCategories - } val headers = tagItems.map { item -> Category.createCustom( item.key, @@ -1102,37 +1140,35 @@ class LibraryPresenter( if (!preferences.collapsedDynamicAtBottom().get()) return@let headers headers.filterNot { it.isHidden } + headers.filter { it.isHidden } } - headers.forEach { category -> - val catId = category.id ?: return@forEach - val headerItem = - tagItems[ - when { - category.sourceId != null -> "${category.name}$sourceSplitter${category.sourceId}" - category.langId != null -> "${category.langId}$langSplitter${category.name}" - else -> category.name - }, - ] - if (category.isHidden) { - val mangaToRemove = items.filter { it.header.catId == catId } - val mergedTitle = mangaToRemove.joinToString("-") { - it.manga.title + "-" + it.manga.author - } - sectionedLibraryItems[catId] = mangaToRemove - items.removeAll { it.header.catId == catId } - if (headerItem != null) { - items.add( - LibraryItem( - LibraryManga.createHide(catId, mergedTitle, mangaToRemove), - headerItem, - viewContext, - ), - ) + + val map = headers + .associateWith { items[it.id].orEmpty() } + .mapValues { (key, values) -> + val catId = key.id!! // null check already handled by mapNotNull + val headerItem = tagItems[key.dynamicHeaderKey()] + if (key.isHidden) { + val mergedTitle = values.joinToString("-") { + it.manga.manga.title + "-" + it.manga.manga.author + } + libraryToDisplay[key] = values + hiddenItems.addAll(values) + if (headerItem != null) { + return@mapValues listOf( + LibraryPlaceholderItem.hidden( + catId, + headerItem, + viewContext, + mergedTitle, + values, + ), + ) + } } + values } - } headers.forEachIndexed { index, category -> category.order = index } - return Triple(items, headers, listOf()) + return Triple(map, headers, hiddenItems) } private fun mapTrackingOrder(status: String): String { @@ -1165,7 +1201,7 @@ class LibraryPresenter( /** Requests the library to be filtered. */ fun requestFilterUpdate() { presenterScope.launch { - val mangaMap = allLibraryItems + val mangaMap = currentLibrary .applyFilters() .applySort() sectionLibrary(mangaMap) @@ -1174,11 +1210,11 @@ class LibraryPresenter( private fun requestBadgeUpdate(badgeUpdate: (List) -> Unit) { presenterScope.launch { - val mangaMap = allLibraryItems - badgeUpdate(mangaMap) - allLibraryItems = mangaMap - val current = libraryItems - badgeUpdate(current) + val mangaMap = currentLibrary + mangaMap.forEach { (_, items) -> badgeUpdate(items) } + currentLibrary = mangaMap + val current = libraryToDisplay + current.forEach { (_, items) -> badgeUpdate(items) } sectionLibrary(current) } } @@ -1201,7 +1237,7 @@ class LibraryPresenter( /** Requests the library to be sorted. */ private fun requestSortUpdate() { presenterScope.launch { - val mangaMap = libraryItems + val mangaMap = libraryToDisplay .applySort() sectionLibrary(mangaMap) } @@ -1338,7 +1374,7 @@ class LibraryPresenter( if (catId == 0) { emptyList() } else { - getCategories.awaitByMangaId(manga.id!!) + getCategories.awaitByMangaId(manga.manga.id!!) .filter { it.id != oldCatId } + listOf(category) } @@ -1346,11 +1382,11 @@ class LibraryPresenter( mc.add(cat.id!!.toLong()) } - setMangaCategories.await(manga.id!!, mc) + setMangaCategories.await(manga.manga.id!!, mc) if (category.mangaSort == null) { val ids = mangaIds.toMutableList() - if (!ids.contains(manga.id!!)) ids.add(manga.id!!) + if (!ids.contains(manga.manga.id!!)) ids.add(manga.manga.id!!) category.mangaOrder = ids if (category.id == 0) { preferences.defaultMangaOrder() @@ -1371,7 +1407,7 @@ class LibraryPresenter( /** Returns if manga is in a category by id */ fun mangaIsInCategory(manga: LibraryManga, catId: Int?): Boolean { // FIXME: Don't do blocking - val categories = runBlocking { getCategories.awaitByMangaId(manga.id!!) }.map { it.id } + val categories = runBlocking { getCategories.awaitByMangaId(manga.manga.id!!) }.map { it.id } return catId in categories } @@ -1511,11 +1547,9 @@ class LibraryPresenter( } companion object { - private var lastLibraryItems: List? = null + private var lastDisplayedLibrary: LibraryMutableMap? = null private var lastCategories: List? = null - private var lastAllLibraryItems: List? = null - private const val sourceSplitter = "◘•◘" - private const val langSplitter = "⨼⨦⨠" + private var lastLibrary: LibraryMap? = null private const val dynamicCategorySplitter = "▄╪\t▄╪\t▄" private val randomTags = arrayOf(0, 1, 2) @@ -1531,9 +1565,9 @@ class LibraryPresenter( private const val randomGroupOfTagsNegate = 2 fun onLowMemory() { - lastLibraryItems = null + lastDisplayedLibrary = null lastCategories = null - lastAllLibraryItems = null + lastLibrary = null } suspend fun setSearchSuggestion( @@ -1553,15 +1587,15 @@ class LibraryPresenter( preferences.librarySearchSuggestion().set( when (val value = random.nextInt(0, 5)) { randomSource -> { - val distinctSources = getLibraryManga.await().distinctBy { it.source } + val distinctSources = getLibraryManga.await().distinctBy { it.manga.source } val randomSource = sourceManager.get( - distinctSources.randomOrNull(random)?.source ?: 0L, + distinctSources.randomOrNull(random)?.manga?.source ?: 0L, )?.name randomSource?.chopByWords(30) } randomTitle -> { - getLibraryManga.await().randomOrNull(random)?.title?.chopByWords(30) + getLibraryManga.await().randomOrNull(random)?.manga?.title?.chopByWords(30) } in randomTags -> { val tags = RecentsPresenter.getRecentManga(true) @@ -1605,11 +1639,11 @@ class LibraryPresenter( ) { val libraryManga = getLibraryManga.await() libraryManga.forEach { manga -> - if (manga.id == null) return@forEach - if (manga.date_added == 0L) { - val chapters = getChapter.awaitAll(manga) - manga.date_added = chapters.minByOrNull { it.date_fetch }?.date_fetch ?: 0L - updateManga.await(MangaUpdate(manga.id!!, dateAdded = manga.date_added)) + if (manga.manga.id == null) return@forEach + if (manga.manga.date_added == 0L) { + val chapters = getChapter.awaitAll(manga.manga.id!!, manga.manga.filtered_scanlators?.isNotBlank() == true) + manga.manga.date_added = chapters.minByOrNull { it.date_fetch }?.date_fetch ?: 0L + updateManga.await(MangaUpdate(manga.manga.id!!, dateAdded = manga.manga.date_added)) } } } @@ -1631,15 +1665,15 @@ class LibraryPresenter( val getLibraryManga: GetLibraryManga by injectLazy() val libraryManga = getLibraryManga.await() libraryManga.forEach { manga -> - if (manga.id == null) return@forEach - if (manga.thumbnail_url?.startsWith("custom", ignoreCase = true) == true) { - val file = cc.getCoverFile(manga.thumbnail_url, !manga.favorite) + if (manga.manga.id == null) return@forEach + if (manga.manga.thumbnail_url?.startsWith("custom", ignoreCase = true) == true) { + val file = cc.getCoverFile(manga.manga.thumbnail_url, !manga.manga.favorite) if (file != null && file.exists()) { - file.renameTo(cc.getCustomCoverFile(manga)) + file.renameTo(cc.getCustomCoverFile(manga.manga)) } - manga.thumbnail_url = - manga.thumbnail_url!!.lowercase(Locale.ROOT).substringAfter("custom-") - updateManga.await(MangaUpdate(manga.id!!, thumbnailUrl = manga.thumbnail_url)) + manga.manga.thumbnail_url = + manga.manga.thumbnail_url!!.lowercase(Locale.ROOT).substringAfter("custom-") + updateManga.await(MangaUpdate(manga.manga.id!!, thumbnailUrl = manga.manga.thumbnail_url)) } } } @@ -1667,7 +1701,7 @@ class LibraryPresenter( data class LibraryData( val categories: List, val allCategories: List, - val items: List, + val items: LibraryMap, val hiddenItems: List, val removeArticles: Boolean, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt index 9950858541..94d55f515c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt @@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.library.LibraryGroup +import eu.kanade.tachiyomi.ui.library.LibraryMangaItem import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.launchUI @@ -368,11 +369,12 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri suspend fun checkForManhwa(sourceManager: SourceManager) { if (checked) return withIOContext { - val libraryManga = controller?.presenter?.allLibraryItems ?: return@withIOContext + val libraryManga = controller?.presenter?.currentLibraryItems ?: return@withIOContext checked = true var types = mutableSetOf() libraryManga.forEach { - when (it.manga.seriesType(sourceManager = sourceManager)) { + if (it !is LibraryMangaItem) return@forEach + when (it.manga.manga.seriesType(sourceManager = sourceManager)) { Manga.TYPE_MANHWA, Manga.TYPE_WEBTOON -> types.add(MR.strings.manhwa) Manga.TYPE_MANHUA -> types.add(MR.strings.manhua) Manga.TYPE_COMIC -> types.add(MR.strings.comic) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsController.kt index 054669930b..113df828cd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsController.kt @@ -28,9 +28,9 @@ import eu.kanade.tachiyomi.util.system.roundToTwoDecimal import eu.kanade.tachiyomi.util.view.compatToolTipText import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.withFadeTransaction +import kotlin.math.roundToInt import yokai.i18n.MR import yokai.util.lang.getString -import kotlin.math.roundToInt import android.R as AR class StatsController : BaseLegacyController() { @@ -61,7 +61,7 @@ class StatsController : BaseLegacyController() { } private fun handleGeneralStats() { - val mangaTracks = mangaDistinct.map { it to presenter.getTracks(it) } + val mangaTracks = mangaDistinct.map { it to presenter.getTracks(it.manga) } scoresList = getScoresList(mangaTracks) with(binding) { viewDetailLayout.isVisible = mangaDistinct.isNotEmpty() @@ -76,8 +76,8 @@ class StatsController : BaseLegacyController() { } statsTrackedMangaText.text = mangaTracks.count { it.second.isNotEmpty() }.toString() statsChaptersDownloadedText.text = mangaDistinct.sumOf { presenter.getDownloadCount(it) }.toString() - statsTotalTagsText.text = mangaDistinct.flatMap { it.getTags() }.distinct().count().toString() - statsMangaLocalText.text = mangaDistinct.count { it.isLocal() }.toString() + statsTotalTagsText.text = mangaDistinct.flatMap { it.manga.getTags() }.distinct().count().toString() + statsMangaLocalText.text = mangaDistinct.count { it.manga.isLocal() }.toString() statsGlobalUpdateMangaText.text = presenter.getGlobalUpdateManga().count().toString() statsSourcesText.text = presenter.getSources().count().toString() statsTrackersText.text = presenter.getLoggedTrackers().count().toString() @@ -105,7 +105,7 @@ class StatsController : BaseLegacyController() { val pieEntries = ArrayList() val mangaStatusDistributionList = statusMap.mapNotNull { (status, color) -> - val libraryCount = mangaDistinct.count { it.status == status } + val libraryCount = mangaDistinct.count { it.manga.status == status } if (status == SManga.UNKNOWN && libraryCount == 0) return@mapNotNull null pieEntries.add(PieEntry(libraryCount.toFloat(), activity!!.mapStatus(status))) StatusDistributionItem(activity!!.mapStatus(status), libraryCount, color) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsPresenter.kt index 71cb38068c..6d7c2f3dc2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsPresenter.kt @@ -65,19 +65,19 @@ class StatsPresenter( val includedCategories = prefs.libraryUpdateCategories().get().map(String::toInt) val excludedCategories = prefs.libraryUpdateCategoriesExclude().get().map(String::toInt) val restrictions = prefs.libraryUpdateMangaRestriction().get() - return libraryMangas.groupBy { it.id } + return libraryMangas.groupBy { it.manga.id } .filterNot { it.value.any { manga -> manga.category in excludedCategories } } .filter { includedCategories.isEmpty() || it.value.any { manga -> manga.category in includedCategories } } .filterNot { val manga = it.value.first() - (MANGA_NON_COMPLETED in restrictions && manga.status == SManga.COMPLETED) || + (MANGA_NON_COMPLETED in restrictions && manga.manga.status == SManga.COMPLETED) || (MANGA_HAS_UNREAD in restrictions && manga.unread != 0) || (MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasRead) } } fun getDownloadCount(manga: LibraryManga): Int { - return downloadManager.getDownloadCount(manga) + return downloadManager.getDownloadCount(manga.manga) } fun get10PointScore(track: Track): Float? { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsPresenter.kt index d47536ffb9..2dc2160d22 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsPresenter.kt @@ -153,7 +153,7 @@ class StatsDetailsPresenter( private suspend fun setupSeriesType() { currentStats = ArrayList() - val libraryFormat = mangasDistinct.filterByChip().groupBy { it.seriesType() } + val libraryFormat = mangasDistinct.filterByChip().groupBy { it.manga.seriesType() } libraryFormat.forEach { (seriesType, mangaList) -> currentStats?.add( @@ -173,7 +173,7 @@ class StatsDetailsPresenter( private suspend fun setupStatus() { currentStats = ArrayList() - val libraryFormat = mangasDistinct.filterByChip().groupBy { it.status } + val libraryFormat = mangasDistinct.filterByChip().groupBy { it.manga.status } libraryFormat.forEach { (status, mangaList) -> currentStats?.add( @@ -263,7 +263,7 @@ class StatsDetailsPresenter( private suspend fun setupTrackers() { currentStats = ArrayList() val libraryFormat = mangasDistinct.filterByChip() - .map { it to getTracks(it).ifEmpty { listOf(null) } } + .map { it to getTracks(it.manga).ifEmpty { listOf(null) } } .flatMap { it.second.map { track -> it.first to track } } val loggedServices = trackManager.services.filter { it.isLogged } @@ -292,7 +292,7 @@ class StatsDetailsPresenter( private suspend fun setupSources() { currentStats = ArrayList() - val libraryFormat = mangasDistinct.filterByChip().groupBy { it.source } + val libraryFormat = mangasDistinct.filterByChip().groupBy { it.manga.source } libraryFormat.forEach { (sourceId, mangaList) -> val source = sourceManager.getOrStub(sourceId) @@ -339,10 +339,10 @@ class StatsDetailsPresenter( private suspend fun setupTags() { currentStats = ArrayList() val mangaFiltered = mangasDistinct.filterByChip() - val tags = mangaFiltered.flatMap { it.getTags() }.distinctBy { it.uppercase() } + val tags = mangaFiltered.flatMap { it.manga.getTags() }.distinctBy { it.uppercase() } val libraryFormat = tags.map { tag -> tag to mangaFiltered.filter { - it.getTags().any { mangaTag -> mangaTag.equals(tag, true) } + it.manga.getTags().any { mangaTag -> mangaTag.equals(tag, true) } } } @@ -433,7 +433,7 @@ class StatsDetailsPresenter( this } else { filter { manga -> - context.mapSeriesType(manga.seriesType()) in selectedSeriesType + context.mapSeriesType(manga.manga.seriesType()) in selectedSeriesType } } } @@ -443,7 +443,7 @@ class StatsDetailsPresenter( this } else { filter { manga -> - context.mapStatus(manga.status) in selectedStatus + context.mapStatus(manga.manga.status) in selectedStatus } } } @@ -463,7 +463,7 @@ class StatsDetailsPresenter( this } else { filter { manga -> - manga.source in selectedSource.map { it.id } + manga.manga.source in selectedSource.map { it.id } } } } @@ -504,10 +504,10 @@ class StatsDetailsPresenter( * Get language name of a manga */ private fun LibraryManga.getLanguage(): String { - val code = if (isLocal()) { - LocalSource.getMangaLang(this) + val code = if (manga.isLocal()) { + LocalSource.getMangaLang(this.manga) } else { - sourceManager.get(source)?.lang + sourceManager.get(manga.source)?.lang } ?: return context.getString(MR.strings.unknown) return LocaleHelper.getLocalizedDisplayName(code) } @@ -516,7 +516,7 @@ class StatsDetailsPresenter( * Get mean score rounded to two decimal of a list of manga */ private suspend fun List.getMeanScoreRounded(): Double? { - val mangaTracks = this.map { it to getTracks(it) } + val mangaTracks = this.map { it to getTracks(it.manga) } val scoresList = mangaTracks.filter { it.second.isNotEmpty() } .mapNotNull { it.second.getMeanScoreByTracker() } return if (scoresList.isEmpty()) null else scoresList.average().roundToTwoDecimal() @@ -526,7 +526,7 @@ class StatsDetailsPresenter( * Get mean score rounded to int of a single manga */ private suspend fun LibraryManga.getMeanScoreToInt(): Int? { - val mangaTracks = getTracks(this) + val mangaTracks = getTracks(this.manga) val scoresList = mangaTracks.filter { it.score > 0 } .mapNotNull { it.get10PointScore() } return if (scoresList.isEmpty()) null else scoresList.average().roundToInt().coerceIn(1..10) @@ -550,8 +550,8 @@ class StatsDetailsPresenter( } private suspend fun LibraryManga.getStartYear(): Int? { - if (getChapter.awaitAll(id!!, false).any { it.read }) { - val chapters = getHistory.awaitAllByMangaId(id!!).filter { it.last_read > 0 } + if (getChapter.awaitAll(manga.id!!, false).any { it.read }) { + val chapters = getHistory.awaitAllByMangaId(manga.id!!).filter { it.last_read > 0 } val date = chapters.minOfOrNull { it.last_read } ?: return null val cal = Calendar.getInstance().apply { timeInMillis = date } return if (date <= 0L) null else cal.get(Calendar.YEAR) @@ -564,7 +564,7 @@ class StatsDetailsPresenter( } private fun getEnabledSources(): List { - return mangasDistinct.mapNotNull { sourceManager.get(it.source) } + return mangasDistinct.mapNotNull { sourceManager.get(it.manga.source) } .distinct().sortedBy { it.name } } @@ -589,7 +589,7 @@ class StatsDetailsPresenter( } private suspend fun List.getReadDuration(): Long { - return sumOf { manga -> getHistory.awaitAllByMangaId(manga.id!!).sumOf { it.time_read } } + return sumOf { manga -> getHistory.awaitAllByMangaId(manga.manga.id!!).sumOf { it.time_read } } } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index e2ec854e73..d2e6e31b04 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -335,7 +335,7 @@ class ReaderViewModel( val info = delegatedSource.fetchMangaFromChapterUrl(url) if (info != null) { val (sChapter, sManga, chapters) = info - val manga = Manga.create(sourceId).apply { copyFrom(sManga) } + val manga = Manga.create(sManga.url, sManga.title, sourceId).apply { copyFrom(sManga) } val chapter = Chapter.create().apply { copyFrom(sChapter) } val id = insertManga.await(manga) manga.id = id ?: manga.id diff --git a/app/src/main/java/yokai/domain/library/custom/model/CustomMangaInfo.kt b/app/src/main/java/yokai/domain/library/custom/model/CustomMangaInfo.kt index 9076e6c3b8..789dd4be01 100644 --- a/app/src/main/java/yokai/domain/library/custom/model/CustomMangaInfo.kt +++ b/app/src/main/java/yokai/domain/library/custom/model/CustomMangaInfo.kt @@ -12,8 +12,7 @@ data class CustomMangaInfo( val genre: String? = null, val status: Int? = null, ) { - fun toManga() = MangaImpl().apply { - id = this@CustomMangaInfo.mangaId + fun toManga() = MangaImpl(id = this.mangaId).apply { title = this@CustomMangaInfo.title ?: "" author = this@CustomMangaInfo.author artist = this@CustomMangaInfo.artist diff --git a/domain/src/commonMain/kotlin/eu/kanade/tachiyomi/domain/manga/models/Manga.kt b/domain/src/commonMain/kotlin/eu/kanade/tachiyomi/domain/manga/models/Manga.kt index da8d99f67b..05a84e0a2a 100644 --- a/domain/src/commonMain/kotlin/eu/kanade/tachiyomi/domain/manga/models/Manga.kt +++ b/domain/src/commonMain/kotlin/eu/kanade/tachiyomi/domain/manga/models/Manga.kt @@ -82,9 +82,6 @@ interface Manga : SManga { } } - fun isBlank() = id == Long.MIN_VALUE - fun isHidden() = status == -1 - fun setChapterOrder(sorting: Int, order: Int) { setChapterFlags(sorting, CHAPTER_SORTING_MASK) setChapterFlags(order, CHAPTER_SORT_MASK)