mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
refactor(library): Store sectioned library instead of flatten version of it (#336)
* refactor(library): Store sectioned first before flattening it out * fix: Fix build, also rename some variables and move stuff around * chore: Replace findCurrentCategory with currentCategory getter * fix: Empty category can't be collapsed * chore: Disable file log for debug build * fix: Entry always displayed on default category * refactor: Specify id, source, and url directly from MangaImpl constructor * refactor: Make LibraryManga not extend MangaImpl * refactor: Separate placeholder from LibraryManga * fix: Default category should always be at the very beginning * fix: Accidentally made the entries invisible * fix: Default category disappear everytime a new category is added
This commit is contained in:
parent
e415fd4ef2
commit
cae0332ef9
27 changed files with 899 additions and 801 deletions
|
@ -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"
|
||||
|
|
|
@ -57,8 +57,10 @@ data class BackupManga(
|
|||
@ProtoNumber(805) var customGenre: List<String>? = 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
|
||||
|
|
|
@ -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<Int>()
|
||||
|
||||
fun create(name: String): Category = CategoryImpl().apply {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<LibraryItem>? = 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<LibraryItem>): 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<ChapterHistory> = emptyList()) {
|
||||
|
||||
companion object {
|
||||
fun createBlank() = MangaChapterHistory(MangaImpl(), ChapterImpl(), HistoryImpl())
|
||||
fun createBlank() = MangaChapterHistory(
|
||||
MangaImpl(null, -1, ""),
|
||||
ChapterImpl(),
|
||||
HistoryImpl(),
|
||||
)
|
||||
|
||||
fun mapper(
|
||||
// manga
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
@ -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<LibraryManga>) {
|
||||
// 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<LibraryManga>) = 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<LibraryManga>) {
|
||||
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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<Int> {
|
||||
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())
|
||||
}
|
||||
|
|
|
@ -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<LibraryMangaItem>()
|
||||
.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<LibraryItem>()
|
||||
val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id }
|
||||
.filterIsInstance<LibraryMangaItem>()
|
||||
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<LibraryItem>()
|
||||
val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id }
|
||||
.filterIsInstance<LibraryMangaItem>()
|
||||
val mangaIds = libraryItems.mapNotNull { (it as? LibraryMangaItem)?.manga?.manga?.id }
|
||||
presenter.rearrangeCategory(catId, mangaIds)
|
||||
} else {
|
||||
presenter.sortCategory(catId, sortBy)
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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<View>(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
|
||||
|
|
|
@ -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<LibraryHolder, LibraryHeaderItem>(header), IFilterable<String> {
|
||||
|
||||
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<IFlexible<RecyclerView.ViewHolder>>): 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<ConstraintLayout.LayoutParams> {
|
||||
height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
|
||||
dimensionRatio = "2:3"
|
||||
}
|
||||
}
|
||||
if (libraryLayout != LAYOUT_COMFORTABLE_GRID) {
|
||||
binding.card.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
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<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: LibraryHolder,
|
||||
position: Int,
|
||||
payloads: MutableList<Any?>?,
|
||||
) {
|
||||
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<String>?): 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 {
|
||||
|
|
|
@ -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<ViewGroup.MarginLayoutParams> {
|
||||
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<ViewGroup.MarginLayoutParams> {
|
||||
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) {
|
||||
|
|
|
@ -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<IFlexible<RecyclerView.ViewHolder>>): 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<ConstraintLayout.LayoutParams> {
|
||||
height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT
|
||||
dimensionRatio = "2:3"
|
||||
}
|
||||
}
|
||||
if (libraryLayout != LAYOUT_COMFORTABLE_GRID) {
|
||||
binding.card.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
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<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: LibraryHolder,
|
||||
position: Int,
|
||||
payloads: MutableList<Any?>?,
|
||||
) {
|
||||
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<String>?): 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()
|
||||
}
|
||||
}
|
|
@ -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<IFlexible<RecyclerView.ViewHolder>>): 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<LibraryMangaItem>) : Type()
|
||||
data class Blank(val mangaCount: Int) : Type()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun hidden(category: Int, header: LibraryHeaderItem, context: Context?, title: String, hiddenItems: List<LibraryMangaItem>) =
|
||||
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)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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<StringResource>()
|
||||
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)
|
||||
|
|
|
@ -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<StatsControllerBinding>() {
|
||||
|
@ -61,7 +61,7 @@ class StatsController : BaseLegacyController<StatsControllerBinding>() {
|
|||
}
|
||||
|
||||
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<StatsControllerBinding>() {
|
|||
}
|
||||
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<StatsControllerBinding>() {
|
|||
val pieEntries = ArrayList<PieEntry>()
|
||||
|
||||
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)
|
||||
|
|
|
@ -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? {
|
||||
|
|
|
@ -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<LibraryManga>.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<Source> {
|
||||
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<LibraryManga>.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 } }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue