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 {
|
) = buildList {
|
||||||
if (!BuildConfig.DEBUG) add(CrashlyticsLogWriter())
|
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"
|
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,
|
@ProtoNumber(805) var customGenre: List<String>? = null,
|
||||||
) {
|
) {
|
||||||
fun getMangaImpl(): MangaImpl {
|
fun getMangaImpl(): MangaImpl {
|
||||||
return MangaImpl().apply {
|
return MangaImpl(
|
||||||
url = this@BackupManga.url
|
source = this.source,
|
||||||
|
url = this.url,
|
||||||
|
).apply {
|
||||||
title = this@BackupManga.title
|
title = this@BackupManga.title
|
||||||
artist = this@BackupManga.artist
|
artist = this@BackupManga.artist
|
||||||
author = this@BackupManga.author
|
author = this@BackupManga.author
|
||||||
|
@ -67,7 +69,6 @@ data class BackupManga(
|
||||||
status = this@BackupManga.status
|
status = this@BackupManga.status
|
||||||
thumbnail_url = this@BackupManga.thumbnailUrl
|
thumbnail_url = this@BackupManga.thumbnailUrl
|
||||||
favorite = this@BackupManga.favorite
|
favorite = this@BackupManga.favorite
|
||||||
source = this@BackupManga.source
|
|
||||||
date_added = this@BackupManga.dateAdded
|
date_added = this@BackupManga.dateAdded
|
||||||
viewer_flags = (
|
viewer_flags = (
|
||||||
this@BackupManga.viewer_flags
|
this@BackupManga.viewer_flags
|
||||||
|
|
|
@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.data.database.models
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.tachiyomi.ui.library.LibrarySort
|
import eu.kanade.tachiyomi.ui.library.LibrarySort
|
||||||
|
import java.io.Serializable
|
||||||
import yokai.i18n.MR
|
import yokai.i18n.MR
|
||||||
import yokai.util.lang.getString
|
import yokai.util.lang.getString
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
interface Category : Serializable {
|
interface Category : Serializable {
|
||||||
|
|
||||||
|
@ -56,7 +56,21 @@ interface Category : Serializable {
|
||||||
fun mangaOrderToString(): String =
|
fun mangaOrderToString(): String =
|
||||||
if (mangaSort != null) mangaSort.toString() else mangaOrder.joinToString("/")
|
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 {
|
companion object {
|
||||||
|
const val sourceSplitter = "◘•◘"
|
||||||
|
const val langSplitter = "⨼⨦⨠"
|
||||||
|
|
||||||
var lastCategoriesAddedTo = emptySet<Int>()
|
var lastCategoriesAddedTo = emptySet<Int>()
|
||||||
|
|
||||||
fun create(name: String): Category = CategoryImpl().apply {
|
fun create(name: String): Category = CategoryImpl().apply {
|
||||||
|
|
|
@ -32,10 +32,14 @@ class CategoryImpl : Category {
|
||||||
|
|
||||||
val category = other as Category
|
val category = other as Category
|
||||||
|
|
||||||
|
if (isDynamic && category.isDynamic) return dynamicHeaderKey() == category.dynamicHeaderKey()
|
||||||
|
|
||||||
return name == category.name
|
return name == category.name
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
|
if (isDynamic) return dynamicHeaderKey().hashCode()
|
||||||
|
|
||||||
return name.hashCode()
|
return name.hashCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package eu.kanade.tachiyomi.data.database.models
|
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 kotlin.math.roundToInt
|
||||||
import yokai.data.updateStrategyAdapter
|
|
||||||
|
|
||||||
data class LibraryManga(
|
data class LibraryManga(
|
||||||
|
val manga: Manga,
|
||||||
var unread: Int = 0,
|
var unread: Int = 0,
|
||||||
var read: Int = 0,
|
var read: Int = 0,
|
||||||
var category: Int = 0,
|
var category: Int = 0,
|
||||||
|
@ -13,41 +13,11 @@ data class LibraryManga(
|
||||||
var latestUpdate: Long = 0,
|
var latestUpdate: Long = 0,
|
||||||
var lastRead: Long = 0,
|
var lastRead: Long = 0,
|
||||||
var lastFetch: 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
|
val hasRead
|
||||||
get() = read > 0
|
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 {
|
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(
|
fun mapper(
|
||||||
// manga
|
// manga
|
||||||
id: Long,
|
id: Long,
|
||||||
|
@ -78,34 +48,37 @@ data class LibraryManga(
|
||||||
latestUpdate: Long,
|
latestUpdate: Long,
|
||||||
lastRead: Long,
|
lastRead: Long,
|
||||||
lastFetch: Long,
|
lastFetch: Long,
|
||||||
): LibraryManga = createBlank(categoryId.toInt()).apply {
|
): LibraryManga = LibraryManga(
|
||||||
this.id = id
|
manga = Manga.mapper(
|
||||||
this.source = source
|
id = id,
|
||||||
this.url = url
|
source = source,
|
||||||
this.artist = artist
|
url = url,
|
||||||
this.author = author
|
artist = artist,
|
||||||
this.description = description
|
author = author,
|
||||||
this.genre = genre
|
description = description,
|
||||||
this.title = title
|
genre = genre,
|
||||||
this.status = status.toInt()
|
title = title,
|
||||||
this.thumbnail_url = thumbnailUrl
|
status = status,
|
||||||
this.favorite = favorite
|
thumbnailUrl = thumbnailUrl,
|
||||||
this.last_update = lastUpdate ?: 0L
|
favorite = favorite,
|
||||||
this.initialized = initialized
|
lastUpdate = lastUpdate,
|
||||||
this.viewer_flags = viewerFlags.toInt()
|
initialized = initialized,
|
||||||
this.hide_title = hideTitle
|
viewerFlags = viewerFlags,
|
||||||
this.chapter_flags = chapterFlags.toInt()
|
hideTitle = hideTitle,
|
||||||
this.date_added = dateAdded ?: 0L
|
chapterFlags = chapterFlags,
|
||||||
this.filtered_scanlators = filteredScanlators
|
dateAdded = dateAdded,
|
||||||
this.update_strategy = updateStrategy.let(updateStrategyAdapter::decode)
|
filteredScanlators = filteredScanlators,
|
||||||
this.cover_last_modified = coverLastModified
|
updateStrategy = updateStrategy,
|
||||||
this.read = readCount.roundToInt()
|
coverLastModified = coverLastModified,
|
||||||
this.unread = maxOf((total - readCount).roundToInt(), 0)
|
),
|
||||||
this.totalChapters = total.toInt()
|
read = readCount.roundToInt(),
|
||||||
this.bookmarkCount = bookmarkCount.roundToInt()
|
unread = maxOf((total - readCount).roundToInt(), 0),
|
||||||
this.latestUpdate = latestUpdate
|
totalChapters = total.toInt(),
|
||||||
this.lastRead = lastRead
|
bookmarkCount = bookmarkCount.roundToInt(),
|
||||||
this.lastFetch = lastFetch
|
category = categoryId.toInt(),
|
||||||
}
|
latestUpdate = latestUpdate,
|
||||||
|
lastRead = lastRead,
|
||||||
|
lastFetch = lastFetch,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,15 +182,13 @@ var Manga.vibrantCoverColor: Int?
|
||||||
id?.let { MangaCoverMetadata.setVibrantColor(it, value) }
|
id?.let { MangaCoverMetadata.setVibrantColor(it, value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Manga.Companion.create(source: Long) = MangaImpl().apply {
|
fun Manga.Companion.create(url: String, title: String, source: Long = 0) =
|
||||||
this.source = source
|
MangaImpl(
|
||||||
}
|
source = source,
|
||||||
|
url = url,
|
||||||
fun Manga.Companion.create(pathUrl: String, title: String, source: Long = 0) = MangaImpl().apply {
|
).apply {
|
||||||
url = pathUrl
|
this.title = title
|
||||||
this.title = title
|
}
|
||||||
this.source = source
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Manga.Companion.mapper(
|
fun Manga.Companion.mapper(
|
||||||
id: Long,
|
id: Long,
|
||||||
|
@ -213,14 +211,12 @@ fun Manga.Companion.mapper(
|
||||||
filteredScanlators: String?,
|
filteredScanlators: String?,
|
||||||
updateStrategy: Long,
|
updateStrategy: Long,
|
||||||
coverLastModified: Long,
|
coverLastModified: Long,
|
||||||
) = create(source).apply {
|
) = create(url, title, source).apply {
|
||||||
this.id = id
|
this.id = id
|
||||||
this.url = url
|
|
||||||
this.artist = artist
|
this.artist = artist
|
||||||
this.author = author
|
this.author = author
|
||||||
this.description = description
|
this.description = description
|
||||||
this.genre = genre
|
this.genre = genre
|
||||||
this.title = title
|
|
||||||
this.status = status.toInt()
|
this.status = status.toInt()
|
||||||
this.thumbnail_url = thumbnailUrl
|
this.thumbnail_url = thumbnailUrl
|
||||||
this.favorite = favorite
|
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()) {
|
data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History, var extraChapters: List<ChapterHistory> = emptyList()) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun createBlank() = MangaChapterHistory(MangaImpl(), ChapterImpl(), HistoryImpl())
|
fun createBlank() = MangaChapterHistory(
|
||||||
|
MangaImpl(null, -1, ""),
|
||||||
|
ChapterImpl(),
|
||||||
|
HistoryImpl(),
|
||||||
|
)
|
||||||
|
|
||||||
fun mapper(
|
fun mapper(
|
||||||
// manga
|
// manga
|
||||||
|
|
|
@ -8,13 +8,11 @@ import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
open class MangaImpl : Manga {
|
open class MangaImpl(
|
||||||
|
override var id: Long? = null,
|
||||||
override var id: Long? = null
|
override var source: Long = -1,
|
||||||
|
override var url: String = "",
|
||||||
override var source: Long = -1
|
) : Manga {
|
||||||
|
|
||||||
override lateinit var url: String
|
|
||||||
|
|
||||||
private val customMangaManager: CustomMangaManager by injectLazy()
|
private val customMangaManager: CustomMangaManager by injectLazy()
|
||||||
|
|
||||||
|
@ -107,7 +105,7 @@ open class MangaImpl : Manga {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return if (::url.isInitialized) {
|
return if (url.isNotBlank()) {
|
||||||
url.hashCode()
|
url.hashCode()
|
||||||
} else {
|
} else {
|
||||||
(id ?: 0L).hashCode()
|
(id ?: 0L).hashCode()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
import eu.kanade.tachiyomi.domain.manga.models.Manga
|
import eu.kanade.tachiyomi.domain.manga.models.Manga
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
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.interactor.RelinkCustomManga
|
||||||
import yokai.domain.library.custom.model.CustomMangaInfo
|
import yokai.domain.library.custom.model.CustomMangaInfo
|
||||||
import yokai.domain.library.custom.model.CustomMangaInfo.Companion.getMangaInfo
|
import yokai.domain.library.custom.model.CustomMangaInfo.Companion.getMangaInfo
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
|
|
||||||
class CustomMangaManager(val context: Context) {
|
class CustomMangaManager(val context: Context) {
|
||||||
private val scope = CoroutineScope(Dispatchers.IO)
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
@ -176,8 +176,7 @@ class CustomMangaManager(val context: Context) {
|
||||||
val status: Int? = null,
|
val status: Int? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun toManga() = MangaImpl().apply {
|
fun toManga() = MangaImpl(id = this.id).apply {
|
||||||
id = this@MangaJson.id
|
|
||||||
title = this@MangaJson.title ?: ""
|
title = this@MangaJson.title ?: ""
|
||||||
author = this@MangaJson.author
|
author = this@MangaJson.author
|
||||||
artist = this@MangaJson.artist
|
artist = this@MangaJson.artist
|
||||||
|
@ -272,9 +271,6 @@ class CustomMangaManager(val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mangaFromComicInfoObject(id: Long, comicInfo: ComicInfo) = MangaImpl().apply {
|
private fun mangaFromComicInfoObject(id: Long, comicInfo: ComicInfo) =
|
||||||
this.id = id
|
MangaImpl(id = id).apply { this.copyFromComicInfo(comicInfo) }
|
||||||
this.copyFromComicInfo(comicInfo)
|
|
||||||
this.title = comicInfo.series?.value ?: ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,16 +173,17 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
|
|
||||||
val mangaList = (
|
val mangaList = (
|
||||||
if (savedMangasList != null) {
|
if (savedMangasList != null) {
|
||||||
val mangas = getLibraryManga.await().filter {
|
val mangas =
|
||||||
it.id in savedMangasList
|
getLibraryManga.await()
|
||||||
}.distinctBy { it.id }
|
.filter { it.manga.id in savedMangasList }
|
||||||
|
.distinctBy { it.manga.id }
|
||||||
val categoryId = inputData.getInt(KEY_CATEGORY, -1)
|
val categoryId = inputData.getInt(KEY_CATEGORY, -1)
|
||||||
if (categoryId > -1) categoryIds.add(categoryId)
|
if (categoryId > -1) categoryIds.add(categoryId)
|
||||||
mangas
|
mangas
|
||||||
} else {
|
} else {
|
||||||
getMangaToUpdate()
|
getMangaToUpdate()
|
||||||
}
|
}
|
||||||
).sortedBy { it.title }
|
).sortedBy { it.manga.title }
|
||||||
|
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
try {
|
try {
|
||||||
|
@ -227,7 +228,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
private suspend fun updateChaptersJob(mangaToAdd: List<LibraryManga>) {
|
private suspend fun updateChaptersJob(mangaToAdd: List<LibraryManga>) {
|
||||||
// Initialize the variables holding the progress of the updates.
|
// Initialize the variables holding the progress of the updates.
|
||||||
mangaToUpdate.addAll(mangaToAdd)
|
mangaToUpdate.addAll(mangaToAdd)
|
||||||
mangaToUpdateMap.putAll(mangaToAdd.groupBy { it.source })
|
mangaToUpdateMap.putAll(mangaToAdd.groupBy { it.manga.source })
|
||||||
checkIfMassiveUpdate()
|
checkIfMassiveUpdate()
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
val list = mangaToUpdateMap.keys.map { source ->
|
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 {
|
private suspend fun updateDetails(mangaToUpdate: List<LibraryManga>) = coroutineScope {
|
||||||
// Initialize the variables holding the progress of the updates.
|
// Initialize the variables holding the progress of the updates.
|
||||||
val count = AtomicInteger(0)
|
val count = AtomicInteger(0)
|
||||||
val asyncList = mangaToUpdate.groupBy { it.source }.values.map { list ->
|
val asyncList = mangaToUpdate.groupBy { it.manga.source }.values.map { list ->
|
||||||
async {
|
async {
|
||||||
requestSemaphore.withPermit {
|
requestSemaphore.withPermit {
|
||||||
list.forEach { manga ->
|
list.forEach { manga ->
|
||||||
ensureActive()
|
ensureActive()
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource ?: return@async
|
val source = sourceManager.get(manga.manga.source) as? HttpSource ?: return@async
|
||||||
notifier.showProgressNotification(
|
notifier.showProgressNotification(
|
||||||
manga,
|
manga.manga,
|
||||||
count.andIncrement,
|
count.andIncrement,
|
||||||
mangaToUpdate.size,
|
mangaToUpdate.size,
|
||||||
)
|
)
|
||||||
ensureActive()
|
ensureActive()
|
||||||
val networkManga = try {
|
val networkManga = try {
|
||||||
source.getMangaDetails(manga.copy())
|
source.getMangaDetails(manga.manga.copy())
|
||||||
} catch (e: java.lang.Exception) {
|
} catch (e: java.lang.Exception) {
|
||||||
Logger.e(e)
|
Logger.e(e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
if (networkManga != null) {
|
if (networkManga != null) {
|
||||||
manga.prepareCoverUpdate(coverCache, networkManga, false)
|
manga.manga.prepareCoverUpdate(coverCache, networkManga, false)
|
||||||
val thumbnailUrl = manga.thumbnail_url
|
val thumbnailUrl = manga.manga.thumbnail_url
|
||||||
manga.copyFrom(networkManga)
|
manga.manga.copyFrom(networkManga)
|
||||||
manga.initialized = true
|
manga.manga.initialized = true
|
||||||
val request: ImageRequest =
|
val request: ImageRequest =
|
||||||
if (thumbnailUrl != manga.thumbnail_url) {
|
if (thumbnailUrl != manga.manga.thumbnail_url) {
|
||||||
// load new covers in background
|
// load new covers in background
|
||||||
ImageRequest.Builder(context).data(manga.cover())
|
ImageRequest.Builder(context).data(manga.manga.cover())
|
||||||
.memoryCachePolicy(CachePolicy.DISABLED).build()
|
.memoryCachePolicy(CachePolicy.DISABLED).build()
|
||||||
} else {
|
} else {
|
||||||
ImageRequest.Builder(context).data(manga.cover())
|
ImageRequest.Builder(context).data(manga.manga.cover())
|
||||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
.diskCachePolicy(CachePolicy.WRITE_ONLY)
|
.diskCachePolicy(CachePolicy.WRITE_ONLY)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
context.imageLoader.execute(request)
|
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 }
|
val loggedServices = trackManager.services.filter { it.isLogged }
|
||||||
|
|
||||||
mangaToUpdate.forEach { manga ->
|
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 ->
|
tracks.forEach { track ->
|
||||||
val service = trackManager.getService(track.sync_id)
|
val service = trackManager.getService(track.sync_id)
|
||||||
|
@ -324,7 +325,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
val newTrack = service.refresh(track)
|
val newTrack = service.refresh(track)
|
||||||
insertTrack.await(newTrack)
|
insertTrack.await(newTrack)
|
||||||
|
|
||||||
syncChaptersWithTrackServiceTwoWay(getChapter.awaitAll(manga.id!!, false), track, service)
|
syncChaptersWithTrackServiceTwoWay(getChapter.awaitAll(manga.manga.id!!, false), track, service)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.e(e)
|
Logger.e(e)
|
||||||
}
|
}
|
||||||
|
@ -376,7 +377,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
|
|
||||||
private fun checkIfMassiveUpdate() {
|
private fun checkIfMassiveUpdate() {
|
||||||
val largestSourceSize = mangaToUpdate
|
val largestSourceSize = mangaToUpdate
|
||||||
.groupBy { it.source }
|
.groupBy { it.manga.source }
|
||||||
.filterKeys { sourceManager.get(it) !is UnmeteredSource }
|
.filterKeys { sourceManager.get(it) !is UnmeteredSource }
|
||||||
.maxOfOrNull { it.value.size } ?: 0
|
.maxOfOrNull { it.value.size } ?: 0
|
||||||
if (largestSourceSize > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
|
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
|
val httpSource = sourceManager.get(source) as? HttpSource ?: return false
|
||||||
while (count < mangaToUpdateMap[source]!!.size) {
|
while (count < mangaToUpdateMap[source]!!.size) {
|
||||||
val manga = mangaToUpdateMap[source]!![count]
|
val manga = mangaToUpdateMap[source]!![count]
|
||||||
val shouldDownload = manga.shouldDownloadNewChapters(preferences)
|
val shouldDownload = manga.manga.shouldDownloadNewChapters(preferences)
|
||||||
if (updateMangaChapters(manga, this.count.andIncrement, httpSource, shouldDownload)) {
|
if (updateMangaChapters(manga, this.count.andIncrement, httpSource, shouldDownload)) {
|
||||||
hasDownloads = true
|
hasDownloads = true
|
||||||
}
|
}
|
||||||
|
@ -410,15 +411,15 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
try {
|
try {
|
||||||
var hasDownloads = false
|
var hasDownloads = false
|
||||||
ensureActive()
|
ensureActive()
|
||||||
notifier.showProgressNotification(manga, progress, mangaToUpdate.size)
|
notifier.showProgressNotification(manga.manga, progress, mangaToUpdate.size)
|
||||||
val fetchedChapters = source.getChapterList(manga.copy())
|
val fetchedChapters = source.getChapterList(manga.manga.copy())
|
||||||
|
|
||||||
if (fetchedChapters.isNotEmpty()) {
|
if (fetchedChapters.isNotEmpty()) {
|
||||||
val newChapters = syncChaptersWithSource(fetchedChapters, manga, source)
|
val newChapters = syncChaptersWithSource(fetchedChapters, manga.manga, source)
|
||||||
if (newChapters.first.isNotEmpty()) {
|
if (newChapters.first.isNotEmpty()) {
|
||||||
if (shouldDownload) {
|
if (shouldDownload) {
|
||||||
downloadChapters(
|
downloadChapters(
|
||||||
manga,
|
manga.manga,
|
||||||
newChapters.first.sortedBy { it.chapter_number },
|
newChapters.first.sortedBy { it.chapter_number },
|
||||||
)
|
)
|
||||||
hasDownloads = true
|
hasDownloads = true
|
||||||
|
@ -428,24 +429,24 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
}
|
}
|
||||||
if (deleteRemoved && newChapters.second.isNotEmpty()) {
|
if (deleteRemoved && newChapters.second.isNotEmpty()) {
|
||||||
val removedChapters = newChapters.second.filter {
|
val removedChapters = newChapters.second.filter {
|
||||||
downloadManager.isChapterDownloaded(it, manga) &&
|
downloadManager.isChapterDownloaded(it, manga.manga) &&
|
||||||
newChapters.first.none { newChapter ->
|
newChapters.first.none { newChapter ->
|
||||||
newChapter.chapter_number == it.chapter_number && it.scanlator.isNullOrBlank()
|
newChapter.chapter_number == it.chapter_number && it.scanlator.isNullOrBlank()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (removedChapters.isNotEmpty()) {
|
if (removedChapters.isNotEmpty()) {
|
||||||
downloadManager.deleteChapters(removedChapters, manga, source)
|
downloadManager.deleteChapters(removedChapters, manga.manga, source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newChapters.first.size + newChapters.second.size > 0) {
|
if (newChapters.first.size + newChapters.second.size > 0) {
|
||||||
sendUpdate(manga.id)
|
sendUpdate(manga.manga.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return@coroutineScope hasDownloads
|
return@coroutineScope hasDownloads
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e !is CancellationException) {
|
if (e !is CancellationException) {
|
||||||
failedUpdates[manga] = e.message
|
failedUpdates[manga.manga] = e.message
|
||||||
Logger.e { "Failed updating: ${manga.title}: $e" }
|
Logger.e { "Failed updating: ${manga.manga.title}: $e" }
|
||||||
}
|
}
|
||||||
return@coroutineScope false
|
return@coroutineScope false
|
||||||
}
|
}
|
||||||
|
@ -461,17 +462,17 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
val restrictions = preferences.libraryUpdateMangaRestriction().get()
|
val restrictions = preferences.libraryUpdateMangaRestriction().get()
|
||||||
return mangaToAdd.filter { manga ->
|
return mangaToAdd.filter { manga ->
|
||||||
when {
|
when {
|
||||||
MANGA_NON_COMPLETED in restrictions && manga.status == SManga.COMPLETED -> {
|
MANGA_NON_COMPLETED in restrictions && manga.manga.status == SManga.COMPLETED -> {
|
||||||
skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_completed)
|
skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_completed)
|
||||||
}
|
}
|
||||||
MANGA_HAS_UNREAD in restrictions && manga.unread != 0 -> {
|
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 -> {
|
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 -> {
|
manga.manga.update_strategy != UpdateStrategy.ALWAYS_UPDATE -> {
|
||||||
skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_not_always_update)
|
skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_not_always_update)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
return@filter true
|
return@filter true
|
||||||
|
@ -503,10 +504,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
preferences.libraryUpdateCategories().get().map(String::toInt)
|
preferences.libraryUpdateCategories().get().map(String::toInt)
|
||||||
if (categoriesToUpdate.isNotEmpty()) {
|
if (categoriesToUpdate.isNotEmpty()) {
|
||||||
categoryIds.addAll(categoriesToUpdate)
|
categoryIds.addAll(categoriesToUpdate)
|
||||||
libraryManga.filter { it.category in categoriesToUpdate }.distinctBy { it.id }
|
libraryManga.filter { it.category in categoriesToUpdate }.distinctBy { it.manga.id }
|
||||||
} else {
|
} else {
|
||||||
categoryIds.addAll(getCategories.await().mapNotNull { it.id } + 0)
|
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>) {
|
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)
|
categoryIds.add(categoryId)
|
||||||
addManga(mangas)
|
addManga(mangas)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addCategory(categoryId: Int) {
|
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)
|
categoryIds.add(categoryId)
|
||||||
addManga(mangas)
|
addManga(mangas)
|
||||||
}
|
}
|
||||||
|
@ -579,7 +580,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
val distinctManga = mangaToAdd.filter { it !in mangaToUpdate }
|
val distinctManga = mangaToAdd.filter { it !in mangaToUpdate }
|
||||||
mangaToUpdate.addAll(distinctManga)
|
mangaToUpdate.addAll(distinctManga)
|
||||||
checkIfMassiveUpdate()
|
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
|
// if added queue items is a new source not in the async list or an async list has
|
||||||
// finished running
|
// finished running
|
||||||
if (mangaToUpdateMap[it.key].isNullOrEmpty()) {
|
if (mangaToUpdateMap[it.key].isNullOrEmpty()) {
|
||||||
|
@ -727,9 +728,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
if (mangaToUse != null) {
|
if (mangaToUse != null) {
|
||||||
builder.putLongArray(
|
builder.putLongArray(
|
||||||
KEY_MANGAS,
|
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()
|
val inputData = builder.build()
|
||||||
|
|
|
@ -185,14 +185,14 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||||
val manga = it.key
|
val manga = it.key
|
||||||
val chapters = it.value
|
val chapters = it.value
|
||||||
val chapterNames = chapters.map { chapter ->
|
val chapterNames = chapters.map { chapter ->
|
||||||
chapter.preferredChapterName(context, manga, preferences)
|
chapter.preferredChapterName(context, manga.manga, preferences)
|
||||||
}
|
}
|
||||||
notifications.add(
|
notifications.add(
|
||||||
Pair(
|
Pair(
|
||||||
context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||||
setSmallIcon(R.drawable.ic_yokai)
|
setSmallIcon(R.drawable.ic_yokai)
|
||||||
try {
|
try {
|
||||||
val request = ImageRequest.Builder(context).data(manga.cover())
|
val request = ImageRequest.Builder(context).data(manga.manga.cover())
|
||||||
.networkCachePolicy(CachePolicy.DISABLED)
|
.networkCachePolicy(CachePolicy.DISABLED)
|
||||||
.diskCachePolicy(CachePolicy.ENABLED)
|
.diskCachePolicy(CachePolicy.ENABLED)
|
||||||
.transformations(CircleCropTransformation())
|
.transformations(CircleCropTransformation())
|
||||||
|
@ -205,7 +205,7 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
}
|
}
|
||||||
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||||
setContentTitle(manga.title)
|
setContentTitle(manga.manga.title)
|
||||||
color = ContextCompat.getColor(context, R.color.secondaryTachiyomi)
|
color = ContextCompat.getColor(context, R.color.secondaryTachiyomi)
|
||||||
val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) {
|
val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) {
|
||||||
"${chapterNames.take(MAX_CHAPTERS - 1).joinToString(", ")}, " +
|
"${chapterNames.take(MAX_CHAPTERS - 1).joinToString(", ")}, " +
|
||||||
|
@ -224,7 +224,7 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||||
setContentIntent(
|
setContentIntent(
|
||||||
NotificationReceiver.openChapterPendingActivity(
|
NotificationReceiver.openChapterPendingActivity(
|
||||||
context,
|
context,
|
||||||
manga,
|
manga.manga,
|
||||||
chapters.first(),
|
chapters.first(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -233,7 +233,7 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||||
context.getString(MR.strings.mark_as_read),
|
context.getString(MR.strings.mark_as_read),
|
||||||
NotificationReceiver.markAsReadPendingBroadcast(
|
NotificationReceiver.markAsReadPendingBroadcast(
|
||||||
context,
|
context,
|
||||||
manga,
|
manga.manga,
|
||||||
chapters,
|
chapters,
|
||||||
Notifications.ID_NEW_CHAPTERS,
|
Notifications.ID_NEW_CHAPTERS,
|
||||||
),
|
),
|
||||||
|
@ -243,13 +243,13 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||||
context.getString(MR.strings.view_chapters),
|
context.getString(MR.strings.view_chapters),
|
||||||
NotificationReceiver.openChapterPendingActivity(
|
NotificationReceiver.openChapterPendingActivity(
|
||||||
context,
|
context,
|
||||||
manga,
|
manga.manga,
|
||||||
Notifications.ID_NEW_CHAPTERS,
|
Notifications.ID_NEW_CHAPTERS,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
setAutoCancel(true)
|
setAutoCancel(true)
|
||||||
},
|
},
|
||||||
manga.id.hashCode(),
|
manga.manga.id.hashCode(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -281,13 +281,13 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||||
NotificationCompat.BigTextStyle()
|
NotificationCompat.BigTextStyle()
|
||||||
.bigText(
|
.bigText(
|
||||||
updates.keys.joinToString("\n") {
|
updates.keys.joinToString("\n") {
|
||||||
it.title.chop(45)
|
it.manga.title.chop(45)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (!preferences.hideNotificationContent().get()) {
|
} else if (!preferences.hideNotificationContent().get()) {
|
||||||
setContentText(updates.keys.first().title.chop(45))
|
setContentText(updates.keys.first().manga.title.chop(45))
|
||||||
}
|
}
|
||||||
priority = NotificationCompat.PRIORITY_HIGH
|
priority = NotificationCompat.PRIORITY_HIGH
|
||||||
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
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.isLTR
|
||||||
import eu.kanade.tachiyomi.util.system.timeSpanFromNow
|
import eu.kanade.tachiyomi.util.system.timeSpanFromNow
|
||||||
import eu.kanade.tachiyomi.util.system.withDefContext
|
import eu.kanade.tachiyomi.util.system.withDefContext
|
||||||
|
import java.util.*
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import uy.kohesive.injekt.injectLazy
|
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.category.interactor.GetCategories
|
||||||
import yokai.domain.chapter.interactor.GetChapter
|
import yokai.domain.chapter.interactor.GetChapter
|
||||||
import yokai.domain.history.interactor.GetHistory
|
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.
|
* Adapter storing a list of manga in a certain category.
|
||||||
|
@ -117,8 +117,8 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
|
||||||
*/
|
*/
|
||||||
fun indexOf(manga: Manga): Int {
|
fun indexOf(manga: Manga): Int {
|
||||||
return currentItems.indexOfFirst {
|
return currentItems.indexOfFirst {
|
||||||
if (it is LibraryItem) {
|
if (it is LibraryMangaItem) {
|
||||||
it.manga.id == manga.id
|
it.manga.manga.id == manga.id
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,7 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
|
||||||
*/
|
*/
|
||||||
fun allIndexOf(manga: Manga): List<Int> {
|
fun allIndexOf(manga: Manga): List<Int> {
|
||||||
return currentItems.mapIndexedNotNull { index, it ->
|
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
|
index
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
@ -164,7 +164,7 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
|
||||||
} else {
|
} else {
|
||||||
val filteredManga = withDefContext { mangas.filter { it.filter(s) } }
|
val filteredManga = withDefContext { mangas.filter { it.filter(s) } }
|
||||||
if (filteredManga.isEmpty() && controller?.presenter?.showAllCategories == false) {
|
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) }
|
val blankItem = catId?.let { controller.presenter.blankItem(it) }
|
||||||
updateDataSet(blankItem ?: emptyList())
|
updateDataSet(blankItem ?: emptyList())
|
||||||
} else {
|
} else {
|
||||||
|
@ -202,18 +202,19 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
|
||||||
vibrateOnCategoryChange(item.category.name)
|
vibrateOnCategoryChange(item.category.name)
|
||||||
item.category.name
|
item.category.name
|
||||||
}
|
}
|
||||||
is LibraryItem -> {
|
is LibraryPlaceholderItem -> {
|
||||||
val text = if (item.manga.isBlank()) {
|
item.header?.category?.name.orEmpty()
|
||||||
return item.header?.category?.name.orEmpty()
|
}
|
||||||
} else {
|
is LibraryMangaItem -> {
|
||||||
|
val text =
|
||||||
when (getSort(position)) {
|
when (getSort(position)) {
|
||||||
LibrarySort.DragAndDrop -> {
|
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
|
// 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)
|
category ?: context.getString(MR.strings.default_value)
|
||||||
} else {
|
} else {
|
||||||
val title = item.manga.title
|
val title = item.manga.manga.title
|
||||||
if (preferences.removeArticles().get()) {
|
if (preferences.removeArticles().get()) {
|
||||||
title.removeArticles().chop(15)
|
title.removeArticles().chop(15)
|
||||||
} else {
|
} else {
|
||||||
|
@ -222,14 +223,14 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LibrarySort.DateFetched -> {
|
LibrarySort.DateFetched -> {
|
||||||
val id = item.manga.id ?: return ""
|
val id = item.manga.manga.id ?: return ""
|
||||||
// FIXME: Don't do blocking
|
// FIXME: Don't do blocking
|
||||||
val history = runBlocking { getChapter.awaitAll(id, false) }
|
val history = runBlocking { getChapter.awaitAll(id, false) }
|
||||||
val last = history.maxOfOrNull { it.date_fetch }
|
val last = history.maxOfOrNull { it.date_fetch }
|
||||||
context.timeSpanFromNow(MR.strings.fetched_, last ?: 0)
|
context.timeSpanFromNow(MR.strings.fetched_, last ?: 0)
|
||||||
}
|
}
|
||||||
LibrarySort.LastRead -> {
|
LibrarySort.LastRead -> {
|
||||||
val id = item.manga.id ?: return ""
|
val id = item.manga.manga.id ?: return ""
|
||||||
// FIXME: Don't do blocking
|
// FIXME: Don't do blocking
|
||||||
val history = runBlocking { getHistory.awaitAllByMangaId(id) }
|
val history = runBlocking { getHistory.awaitAllByMangaId(id) }
|
||||||
val last = history.maxOfOrNull { it.last_read }
|
val last = history.maxOfOrNull { it.last_read }
|
||||||
|
@ -256,21 +257,20 @@ class LibraryCategoryAdapter(val controller: LibraryController?) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LibrarySort.LatestChapter -> {
|
LibrarySort.LatestChapter -> {
|
||||||
context.timeSpanFromNow(MR.strings.updated_, item.manga.last_update)
|
context.timeSpanFromNow(MR.strings.updated_, item.manga.manga.last_update)
|
||||||
}
|
}
|
||||||
LibrarySort.DateAdded -> {
|
LibrarySort.DateAdded -> {
|
||||||
context.timeSpanFromNow(MR.strings.added_, item.manga.date_added)
|
context.timeSpanFromNow(MR.strings.added_, item.manga.manga.date_added)
|
||||||
}
|
}
|
||||||
LibrarySort.Title -> {
|
LibrarySort.Title -> {
|
||||||
val title = if (preferences.removeArticles().get()) {
|
val title = if (preferences.removeArticles().get()) {
|
||||||
item.manga.title.removeArticles()
|
item.manga.manga.title.removeArticles()
|
||||||
} else {
|
} else {
|
||||||
item.manga.title
|
item.manga.manga.title
|
||||||
}
|
}
|
||||||
getFirstLetter(title)
|
getFirstLetter(title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!isSingleCategory) {
|
if (!isSingleCategory) {
|
||||||
vibrateOnCategoryChange(item.header?.category?.name.orEmpty())
|
vibrateOnCategoryChange(item.header?.category?.name.orEmpty())
|
||||||
}
|
}
|
||||||
|
|
|
@ -451,7 +451,7 @@ open class LibraryController(
|
||||||
|
|
||||||
private fun setActiveCategory() {
|
private fun setActiveCategory() {
|
||||||
val currentCategory = presenter.categories.indexOfFirst {
|
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) {
|
if (currentCategory > -1) {
|
||||||
binding.categoryRecycler.setCategories(currentCategory)
|
binding.categoryRecycler.setCategories(currentCategory)
|
||||||
|
@ -521,14 +521,13 @@ open class LibraryController(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openRandomManga(global: Boolean) {
|
private fun openRandomManga(global: Boolean) {
|
||||||
val items = if (global) {
|
val items =
|
||||||
presenter.allLibraryItems
|
if (global) { presenter.currentLibraryItems } else { adapter.currentItems }
|
||||||
} else {
|
.filterIsInstance<LibraryMangaItem>()
|
||||||
adapter.currentItems
|
.filter { !it.manga.manga.initialized || it.manga.unread > 0 }
|
||||||
}.filter { (it is LibraryItem && !it.manga.isBlank() && !it.manga.isHidden() && (!it.manga.initialized || it.manga.unread > 0)) }
|
|
||||||
if (items.isNotEmpty()) {
|
if (items.isNotEmpty()) {
|
||||||
val item = items.random() as LibraryItem
|
val item = items.random() as LibraryMangaItem
|
||||||
openManga(item.manga)
|
openManga(item.manga.manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,7 +661,7 @@ open class LibraryController(
|
||||||
createActionModeIfNeeded()
|
createActionModeIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (presenter.libraryItems.isNotEmpty() && !isSubClass) {
|
if (presenter.libraryItemsToDisplay.isNotEmpty() && !isSubClass) {
|
||||||
presenter.restoreLibrary()
|
presenter.restoreLibrary()
|
||||||
if (justStarted) {
|
if (justStarted) {
|
||||||
val activityBinding = activityBinding ?: return
|
val activityBinding = activityBinding ?: return
|
||||||
|
@ -706,7 +705,7 @@ open class LibraryController(
|
||||||
if (!LibraryUpdateJob.isRunning(context)) {
|
if (!LibraryUpdateJob.isRunning(context)) {
|
||||||
when {
|
when {
|
||||||
!presenter.showAllCategories && presenter.groupType == BY_DEFAULT -> {
|
!presenter.showAllCategories && presenter.groupType == BY_DEFAULT -> {
|
||||||
presenter.findCurrentCategory()?.let {
|
presenter.currentCategory?.let {
|
||||||
updateLibrary(it)
|
updateLibrary(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -904,7 +903,7 @@ open class LibraryController(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val newOffset =
|
val newOffset =
|
||||||
presenter.categories.indexOfFirst { presenter.currentCategory == it.id } +
|
presenter.categories.indexOfFirst { presenter.currentCategoryId == it.id } +
|
||||||
(if (next) 1 else -1)
|
(if (next) 1 else -1)
|
||||||
if (if (!next) {
|
if (if (!next) {
|
||||||
newOffset > -1
|
newOffset > -1
|
||||||
|
@ -1013,7 +1012,7 @@ open class LibraryController(
|
||||||
override fun getSpanSize(position: Int): Int {
|
override fun getSpanSize(position: Int): Int {
|
||||||
if (libraryLayout == LibraryItem.LAYOUT_LIST) return managerSpanCount
|
if (libraryLayout == LibraryItem.LAYOUT_LIST) return managerSpanCount
|
||||||
val item = this@LibraryController.mAdapter?.getItem(position)
|
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
|
managerSpanCount
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
|
@ -1459,7 +1458,7 @@ open class LibraryController(
|
||||||
adapter.removeAllScrollableHeaders()
|
adapter.removeAllScrollableHeaders()
|
||||||
}
|
}
|
||||||
adapter.setFilter(query)
|
adapter.setFilter(query)
|
||||||
if (presenter.allLibraryItems.isEmpty()) return true
|
if (presenter.currentLibraryItems.isEmpty()) return true
|
||||||
viewScope.launchUI {
|
viewScope.launchUI {
|
||||||
adapter.performFilterAsync()
|
adapter.performFilterAsync()
|
||||||
}
|
}
|
||||||
|
@ -1478,7 +1477,6 @@ open class LibraryController(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setSelection(manga: Manga, selected: Boolean) {
|
private fun setSelection(manga: Manga, selected: Boolean) {
|
||||||
if (manga.isBlank()) return
|
|
||||||
val currentMode = adapter.mode
|
val currentMode = adapter.mode
|
||||||
if (selected) {
|
if (selected) {
|
||||||
if (selectedMangas.add(manga)) {
|
if (selectedMangas.add(manga)) {
|
||||||
|
@ -1528,7 +1526,7 @@ open class LibraryController(
|
||||||
toggleSelection(position)
|
toggleSelection(position)
|
||||||
return
|
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 activity = activity ?: return
|
||||||
val chapter = presenter.getFirstUnread(manga) ?: return
|
val chapter = presenter.getFirstUnread(manga) ?: return
|
||||||
activity.apply {
|
activity.apply {
|
||||||
|
@ -1544,9 +1542,8 @@ open class LibraryController(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleSelection(position: Int) {
|
private fun toggleSelection(position: Int) {
|
||||||
val item = adapter.getItem(position) as? LibraryItem ?: return
|
val item = adapter.getItem(position) as? LibraryMangaItem ?: return
|
||||||
if (item.manga.isBlank()) return
|
setSelection(item.manga.manga, !adapter.isSelected(position))
|
||||||
setSelection(item.manga, !adapter.isSelected(position))
|
|
||||||
invalidateActionMode()
|
invalidateActionMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1562,14 +1559,14 @@ open class LibraryController(
|
||||||
* @return true if the item should be selected, false otherwise.
|
* @return true if the item should be selected, false otherwise.
|
||||||
*/
|
*/
|
||||||
override fun onItemClick(view: View?, position: Int): Boolean {
|
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) {
|
return if (adapter.mode == SelectableAdapter.Mode.MULTI) {
|
||||||
snack?.dismiss()
|
snack?.dismiss()
|
||||||
lastClickPosition = position
|
lastClickPosition = position
|
||||||
toggleSelection(position)
|
toggleSelection(position)
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
openManga(item.manga)
|
openManga(item.manga.manga)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1591,10 +1588,10 @@ open class LibraryController(
|
||||||
*/
|
*/
|
||||||
override fun onItemLongClick(position: Int) {
|
override fun onItemLongClick(position: Int) {
|
||||||
val item = adapter.getItem(position)
|
val item = adapter.getItem(position)
|
||||||
if (item !is LibraryItem) return
|
if (item !is LibraryMangaItem) return
|
||||||
snack?.dismiss()
|
snack?.dismiss()
|
||||||
if (libraryLayout == LibraryItem.LAYOUT_COVER_ONLY_GRID && actionMode == null) {
|
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
|
anchorView = activityBinding?.bottomNav
|
||||||
view.elevation = 15f.dpToPx
|
view.elevation = 15f.dpToPx
|
||||||
}
|
}
|
||||||
|
@ -1648,9 +1645,9 @@ open class LibraryController(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setSelection(position: Int, selected: Boolean = true) {
|
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()
|
invalidateActionMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1674,7 +1671,7 @@ open class LibraryController(
|
||||||
|
|
||||||
override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean {
|
override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean {
|
||||||
if (adapter.isSelected(fromPosition)) toggleSelection(fromPosition)
|
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
|
val newHeader = adapter.getSectionHeader(toPosition) as? LibraryHeaderItem
|
||||||
if (toPosition < 1) return false
|
if (toPosition < 1) return false
|
||||||
return (adapter.getItem(toPosition) !is LibraryHeaderItem) && (
|
return (adapter.getItem(toPosition) !is LibraryHeaderItem) && (
|
||||||
|
@ -1696,11 +1693,11 @@ open class LibraryController(
|
||||||
destroyActionModeIfNeeded()
|
destroyActionModeIfNeeded()
|
||||||
// if nothing moved
|
// if nothing moved
|
||||||
if (lastItemPosition == null) return
|
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 newHeader = adapter.getSectionHeader(position) as? LibraryHeaderItem
|
||||||
val libraryItems = getSectionItems(adapter.getSectionHeader(position), item)
|
val libraryItems = getSectionItems(adapter.getSectionHeader(position), item)
|
||||||
.filterIsInstance<LibraryItem>()
|
.filterIsInstance<LibraryMangaItem>()
|
||||||
val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id }
|
val mangaIds = libraryItems.mapNotNull { (it as? LibraryMangaItem)?.manga?.manga?.id }
|
||||||
if (newHeader?.category?.id == item.manga.category) {
|
if (newHeader?.category?.id == item.manga.category) {
|
||||||
presenter.rearrangeCategory(item.manga.category, mangaIds)
|
presenter.rearrangeCategory(item.manga.category, mangaIds)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1832,8 +1829,8 @@ open class LibraryController(
|
||||||
if (category?.isDynamic == false && sortBy == LibrarySort.DragAndDrop.categoryValue) {
|
if (category?.isDynamic == false && sortBy == LibrarySort.DragAndDrop.categoryValue) {
|
||||||
val item = adapter.findCategoryHeader(catId) ?: return
|
val item = adapter.findCategoryHeader(catId) ?: return
|
||||||
val libraryItems = adapter.getSectionItems(item)
|
val libraryItems = adapter.getSectionItems(item)
|
||||||
.filterIsInstance<LibraryItem>()
|
.filterIsInstance<LibraryMangaItem>()
|
||||||
val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id }
|
val mangaIds = libraryItems.mapNotNull { (it as? LibraryMangaItem)?.manga?.manga?.id }
|
||||||
presenter.rearrangeCategory(catId, mangaIds)
|
presenter.rearrangeCategory(catId, mangaIds)
|
||||||
} else {
|
} else {
|
||||||
presenter.sortCategory(catId, sortBy)
|
presenter.sortCategory(catId, sortBy)
|
||||||
|
|
|
@ -64,23 +64,24 @@ class LibraryGridHolder(
|
||||||
* @param item the manga item to bind.
|
* @param item the manga item to bind.
|
||||||
*/
|
*/
|
||||||
override fun onSetValues(item: LibraryItem) {
|
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.
|
// Update the title and subtitle of the manga.
|
||||||
setCards(adapter.showOutline, binding.card, binding.unreadDownloadBadge.root)
|
setCards(adapter.showOutline, binding.card, binding.unreadDownloadBadge.root)
|
||||||
binding.playButton.transitionName = "library chapter $bindingAdapterPosition transition"
|
binding.playButton.transitionName = "library chapter $bindingAdapterPosition transition"
|
||||||
binding.constraintLayout.isVisible = !item.manga.isBlank()
|
binding.constraintLayout.isVisible = item.manga.manga.id != Long.MIN_VALUE
|
||||||
binding.title.text = item.manga.title.highlightText(item.filter, color)
|
binding.title.text = item.manga.manga.title.highlightText(item.filter, color)
|
||||||
binding.behindTitle.text = item.manga.title
|
binding.behindTitle.text = item.manga.manga.title
|
||||||
val mangaColor = item.manga.dominantCoverColors
|
val mangaColor = item.manga.manga.dominantCoverColors
|
||||||
binding.coverConstraint.backgroundColor = mangaColor?.first ?: itemView.context.getResourceColor(R.attr.background)
|
binding.coverConstraint.backgroundColor = mangaColor?.first ?: itemView.context.getResourceColor(R.attr.background)
|
||||||
binding.behindTitle.setTextColor(
|
binding.behindTitle.setTextColor(
|
||||||
mangaColor?.second ?: itemView.context.getResourceColor(R.attr.colorOnBackground),
|
mangaColor?.second ?: itemView.context.getResourceColor(R.attr.colorOnBackground),
|
||||||
)
|
)
|
||||||
val authorArtist = if (item.manga.author == item.manga.artist || item.manga.artist.isNullOrBlank()) {
|
val authorArtist = if (item.manga.manga.author == item.manga.manga.artist || item.manga.manga.artist.isNullOrBlank()) {
|
||||||
item.manga.author?.trim() ?: ""
|
item.manga.manga.author?.trim() ?: ""
|
||||||
} else {
|
} else {
|
||||||
listOfNotNull(
|
listOfNotNull(
|
||||||
item.manga.author?.trim()?.takeIf { it.isNotBlank() },
|
item.manga.manga.author?.trim()?.takeIf { it.isNotBlank() },
|
||||||
item.manga.artist?.trim()?.takeIf { it.isNotBlank() },
|
item.manga.manga.artist?.trim()?.takeIf { it.isNotBlank() },
|
||||||
).joinToString(", ")
|
).joinToString(", ")
|
||||||
}
|
}
|
||||||
binding.subtitle.text = authorArtist.highlightText(item.filter, color)
|
binding.subtitle.text = authorArtist.highlightText(item.filter, color)
|
||||||
|
@ -101,7 +102,7 @@ class LibraryGridHolder(
|
||||||
|
|
||||||
// Update the cover.
|
// Update the cover.
|
||||||
binding.coverThumbnail.dispose()
|
binding.coverThumbnail.dispose()
|
||||||
setCover(item.manga)
|
setCover(item.manga.manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toggleActivation() {
|
override fun toggleActivation() {
|
||||||
|
|
|
@ -5,9 +5,6 @@ import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import eu.kanade.tachiyomi.R
|
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.ui.base.holder.BaseFlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.util.isLocal
|
import eu.kanade.tachiyomi.util.isLocal
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
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.
|
* Generic class used to hold the displayed data of a manga in the library.
|
||||||
* @param view the inflated view for this holder.
|
* @param view the inflated view for this holder.
|
||||||
* @param adapter the adapter handling 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(
|
abstract class LibraryHolder(
|
||||||
view: View,
|
view: View,
|
||||||
val adapter: LibraryCategoryAdapter,
|
val adapter: LibraryCategoryAdapter,
|
||||||
|
@ -43,7 +38,7 @@ abstract class LibraryHolder(
|
||||||
*/
|
*/
|
||||||
abstract fun onSetValues(item: LibraryItem)
|
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
|
val showTotal = item.header.category.sortingMode() == LibrarySort.TotalChapters
|
||||||
badge.setUnreadDownload(
|
badge.setUnreadDownload(
|
||||||
when {
|
when {
|
||||||
|
@ -54,7 +49,7 @@ abstract class LibraryHolder(
|
||||||
},
|
},
|
||||||
when {
|
when {
|
||||||
item.downloadCount == -1 -> -1
|
item.downloadCount == -1 -> -1
|
||||||
item.manga.isLocal() -> -2
|
item.manga.manga.isLocal() -> -2
|
||||||
else -> item.downloadCount
|
else -> item.downloadCount
|
||||||
},
|
},
|
||||||
showTotal,
|
showTotal,
|
||||||
|
@ -63,7 +58,7 @@ abstract class LibraryHolder(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setReadingButton(item: LibraryItem) {
|
fun setReadingButton(item: LibraryMangaItem) {
|
||||||
itemView.findViewById<View>(R.id.play_layout)?.isVisible =
|
itemView.findViewById<View>(R.id.play_layout)?.isVisible =
|
||||||
item.manga.unread > 0 && !item.hideReadingButton
|
item.manga.unread > 0 && !item.hideReadingButton
|
||||||
}
|
}
|
||||||
|
@ -80,8 +75,8 @@ abstract class LibraryHolder(
|
||||||
|
|
||||||
override fun onLongClick(view: View?): Boolean {
|
override fun onLongClick(view: View?): Boolean {
|
||||||
return if (adapter.isLongPressDragEnabled) {
|
return if (adapter.isLongPressDragEnabled) {
|
||||||
val manga = (adapter.getItem(flexibleAdapterPosition) as? LibraryItem)?.manga
|
val manga = (adapter.getItem(flexibleAdapterPosition) as? LibraryMangaItem)?.manga
|
||||||
if (manga != null && !isDraggable && !manga.isBlank() && !manga.isHidden()) {
|
if (manga != null && !isDraggable) {
|
||||||
adapter.mItemLongClickListener.onItemLongClick(flexibleAdapterPosition)
|
adapter.mItemLongClickListener.onItemLongClick(flexibleAdapterPosition)
|
||||||
toggleActivation()
|
toggleActivation()
|
||||||
true
|
true
|
||||||
|
|
|
@ -1,205 +1,48 @@
|
||||||
package eu.kanade.tachiyomi.ui.library
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import androidx.annotation.CallSuper
|
||||||
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.RecyclerView
|
||||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
|
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
|
||||||
import eu.davidea.flexibleadapter.items.IFilterable
|
import eu.davidea.flexibleadapter.items.IFilterable
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
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.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.databinding.MangaGridItemBinding
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
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 uy.kohesive.injekt.injectLazy
|
||||||
import yokai.domain.ui.UiPreferences
|
import yokai.domain.ui.UiPreferences
|
||||||
|
|
||||||
class LibraryItem(
|
abstract class LibraryItem(
|
||||||
val manga: LibraryManga,
|
|
||||||
header: LibraryHeaderItem,
|
header: LibraryHeaderItem,
|
||||||
private val context: Context?,
|
internal val context: Context?,
|
||||||
) : AbstractSectionableItem<LibraryHolder, LibraryHeaderItem>(header), IFilterable<String> {
|
) : AbstractSectionableItem<LibraryHolder, LibraryHeaderItem>(header), IFilterable<String> {
|
||||||
|
|
||||||
var downloadCount = -1
|
|
||||||
var unreadType = 2
|
|
||||||
var sourceLanguage: String? = null
|
|
||||||
var filter = ""
|
var filter = ""
|
||||||
|
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
internal val sourceManager: SourceManager by injectLazy()
|
||||||
private val uiPreferences: UiPreferences by injectLazy()
|
private val uiPreferences: UiPreferences by injectLazy()
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
private val uniformSize: Boolean
|
internal val uniformSize: Boolean
|
||||||
get() = uiPreferences.uniformGrid().get()
|
get() = uiPreferences.uniformGrid().get()
|
||||||
|
|
||||||
private val libraryLayout: Int
|
internal val libraryLayout: Int
|
||||||
get() = preferences.libraryLayout().get()
|
get() = preferences.libraryLayout().get()
|
||||||
|
|
||||||
val hideReadingButton: Boolean
|
val hideReadingButton: Boolean
|
||||||
get() = preferences.hideStartReadingButton().get()
|
get() = preferences.hideStartReadingButton().get()
|
||||||
|
|
||||||
override fun getLayoutRes(): Int {
|
@CallSuper
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindViewHolder(
|
override fun bindViewHolder(
|
||||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||||
holder: LibraryHolder,
|
holder: LibraryHolder,
|
||||||
position: Int,
|
position: Int,
|
||||||
payloads: MutableList<Any?>?,
|
payloads: MutableList<Any?>?,
|
||||||
) {
|
) {
|
||||||
if (holder is LibraryGridHolder && !holder.fixedSize) {
|
|
||||||
holder.setFreeformCoverRatio(manga, adapter.recyclerView as? AutofitRecyclerView)
|
|
||||||
}
|
|
||||||
holder.onSetValues(this)
|
holder.onSetValues(this)
|
||||||
(holder as? LibraryGridHolder)?.setSelected(adapter.isSelected(position))
|
(holder as? LibraryGridHolder)?.setSelected(adapter.isSelected(position))
|
||||||
val layoutParams = holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams
|
(holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams)?.isFullSpan = this is LibraryPlaceholderItem
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -39,22 +39,25 @@ class LibraryListHolder(
|
||||||
setCards(adapter.showOutline, binding.card, binding.unreadDownloadBadge.root)
|
setCards(adapter.showOutline, binding.card, binding.unreadDownloadBadge.root)
|
||||||
binding.title.isVisible = true
|
binding.title.isVisible = true
|
||||||
binding.constraintLayout.minHeight = 56.dpToPx
|
binding.constraintLayout.minHeight = 56.dpToPx
|
||||||
if (item.manga.isBlank()) {
|
if (item is LibraryPlaceholderItem) {
|
||||||
binding.constraintLayout.minHeight = 0
|
binding.constraintLayout.minHeight = 0
|
||||||
binding.constraintLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.constraintLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
height = ViewGroup.MarginLayoutParams.WRAP_CONTENT
|
height = ViewGroup.MarginLayoutParams.WRAP_CONTENT
|
||||||
}
|
}
|
||||||
if (item.manga.status == -1) {
|
when (item.type) {
|
||||||
binding.title.text = null
|
is LibraryPlaceholderItem.Type.Blank -> {
|
||||||
binding.title.isVisible = false
|
binding.title.text = itemView.context.getString(
|
||||||
} else {
|
if (adapter.hasActiveFilters && item.type.mangaCount >= 1) {
|
||||||
binding.title.text = itemView.context.getString(
|
MR.strings.no_matches_for_filters_short
|
||||||
if (adapter.hasActiveFilters && item.manga.realMangaCount >= 1) {
|
} else {
|
||||||
MR.strings.no_matches_for_filters_short
|
MR.strings.category_is_empty
|
||||||
} 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.title.textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||||
binding.card.isVisible = false
|
binding.card.isVisible = false
|
||||||
|
@ -63,6 +66,9 @@ class LibraryListHolder(
|
||||||
binding.subtitle.isVisible = false
|
binding.subtitle.isVisible = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item !is LibraryMangaItem) error("${item::class.qualifiedName} is not a valid item")
|
||||||
|
|
||||||
binding.constraintLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.constraintLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
height = 52.dpToPx
|
height = 52.dpToPx
|
||||||
}
|
}
|
||||||
|
@ -71,16 +77,16 @@ class LibraryListHolder(
|
||||||
binding.title.textAlignment = View.TEXT_ALIGNMENT_TEXT_START
|
binding.title.textAlignment = View.TEXT_ALIGNMENT_TEXT_START
|
||||||
|
|
||||||
// Update the binding.title of the manga.
|
// 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)
|
setUnreadBadge(binding.unreadDownloadBadge.badgeView, item)
|
||||||
|
|
||||||
val authorArtist =
|
val authorArtist =
|
||||||
if (item.manga.author == item.manga.artist || item.manga.artist.isNullOrBlank()) {
|
if (item.manga.manga.author == item.manga.manga.artist || item.manga.manga.artist.isNullOrBlank()) {
|
||||||
item.manga.author?.trim() ?: ""
|
item.manga.manga.author?.trim() ?: ""
|
||||||
} else {
|
} else {
|
||||||
listOfNotNull(
|
listOfNotNull(
|
||||||
item.manga.author?.trim()?.takeIf { it.isNotBlank() },
|
item.manga.manga.author?.trim()?.takeIf { it.isNotBlank() },
|
||||||
item.manga.artist?.trim()?.takeIf { it.isNotBlank() },
|
item.manga.manga.artist?.trim()?.takeIf { it.isNotBlank() },
|
||||||
).joinToString(", ")
|
).joinToString(", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +101,7 @@ class LibraryListHolder(
|
||||||
|
|
||||||
// Update the cover.
|
// Update the cover.
|
||||||
binding.coverThumbnail.dispose()
|
binding.coverThumbnail.dispose()
|
||||||
binding.coverThumbnail.loadManga(item.manga)
|
binding.coverThumbnail.loadManga(item.manga.manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActionStateChanged(position: Int, actionState: Int) {
|
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.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
import eu.kanade.tachiyomi.ui.library.LibraryController
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryGroup
|
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.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import eu.kanade.tachiyomi.util.system.launchUI
|
import eu.kanade.tachiyomi.util.system.launchUI
|
||||||
|
@ -368,11 +369,12 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
|
||||||
suspend fun checkForManhwa(sourceManager: SourceManager) {
|
suspend fun checkForManhwa(sourceManager: SourceManager) {
|
||||||
if (checked) return
|
if (checked) return
|
||||||
withIOContext {
|
withIOContext {
|
||||||
val libraryManga = controller?.presenter?.allLibraryItems ?: return@withIOContext
|
val libraryManga = controller?.presenter?.currentLibraryItems ?: return@withIOContext
|
||||||
checked = true
|
checked = true
|
||||||
var types = mutableSetOf<StringResource>()
|
var types = mutableSetOf<StringResource>()
|
||||||
libraryManga.forEach {
|
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_MANHWA, Manga.TYPE_WEBTOON -> types.add(MR.strings.manhwa)
|
||||||
Manga.TYPE_MANHUA -> types.add(MR.strings.manhua)
|
Manga.TYPE_MANHUA -> types.add(MR.strings.manhua)
|
||||||
Manga.TYPE_COMIC -> types.add(MR.strings.comic)
|
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.compatToolTipText
|
||||||
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
||||||
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
||||||
|
import kotlin.math.roundToInt
|
||||||
import yokai.i18n.MR
|
import yokai.i18n.MR
|
||||||
import yokai.util.lang.getString
|
import yokai.util.lang.getString
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import android.R as AR
|
import android.R as AR
|
||||||
|
|
||||||
class StatsController : BaseLegacyController<StatsControllerBinding>() {
|
class StatsController : BaseLegacyController<StatsControllerBinding>() {
|
||||||
|
@ -61,7 +61,7 @@ class StatsController : BaseLegacyController<StatsControllerBinding>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleGeneralStats() {
|
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)
|
scoresList = getScoresList(mangaTracks)
|
||||||
with(binding) {
|
with(binding) {
|
||||||
viewDetailLayout.isVisible = mangaDistinct.isNotEmpty()
|
viewDetailLayout.isVisible = mangaDistinct.isNotEmpty()
|
||||||
|
@ -76,8 +76,8 @@ class StatsController : BaseLegacyController<StatsControllerBinding>() {
|
||||||
}
|
}
|
||||||
statsTrackedMangaText.text = mangaTracks.count { it.second.isNotEmpty() }.toString()
|
statsTrackedMangaText.text = mangaTracks.count { it.second.isNotEmpty() }.toString()
|
||||||
statsChaptersDownloadedText.text = mangaDistinct.sumOf { presenter.getDownloadCount(it) }.toString()
|
statsChaptersDownloadedText.text = mangaDistinct.sumOf { presenter.getDownloadCount(it) }.toString()
|
||||||
statsTotalTagsText.text = mangaDistinct.flatMap { it.getTags() }.distinct().count().toString()
|
statsTotalTagsText.text = mangaDistinct.flatMap { it.manga.getTags() }.distinct().count().toString()
|
||||||
statsMangaLocalText.text = mangaDistinct.count { it.isLocal() }.toString()
|
statsMangaLocalText.text = mangaDistinct.count { it.manga.isLocal() }.toString()
|
||||||
statsGlobalUpdateMangaText.text = presenter.getGlobalUpdateManga().count().toString()
|
statsGlobalUpdateMangaText.text = presenter.getGlobalUpdateManga().count().toString()
|
||||||
statsSourcesText.text = presenter.getSources().count().toString()
|
statsSourcesText.text = presenter.getSources().count().toString()
|
||||||
statsTrackersText.text = presenter.getLoggedTrackers().count().toString()
|
statsTrackersText.text = presenter.getLoggedTrackers().count().toString()
|
||||||
|
@ -105,7 +105,7 @@ class StatsController : BaseLegacyController<StatsControllerBinding>() {
|
||||||
val pieEntries = ArrayList<PieEntry>()
|
val pieEntries = ArrayList<PieEntry>()
|
||||||
|
|
||||||
val mangaStatusDistributionList = statusMap.mapNotNull { (status, color) ->
|
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
|
if (status == SManga.UNKNOWN && libraryCount == 0) return@mapNotNull null
|
||||||
pieEntries.add(PieEntry(libraryCount.toFloat(), activity!!.mapStatus(status)))
|
pieEntries.add(PieEntry(libraryCount.toFloat(), activity!!.mapStatus(status)))
|
||||||
StatusDistributionItem(activity!!.mapStatus(status), libraryCount, color)
|
StatusDistributionItem(activity!!.mapStatus(status), libraryCount, color)
|
||||||
|
|
|
@ -65,19 +65,19 @@ class StatsPresenter(
|
||||||
val includedCategories = prefs.libraryUpdateCategories().get().map(String::toInt)
|
val includedCategories = prefs.libraryUpdateCategories().get().map(String::toInt)
|
||||||
val excludedCategories = prefs.libraryUpdateCategoriesExclude().get().map(String::toInt)
|
val excludedCategories = prefs.libraryUpdateCategoriesExclude().get().map(String::toInt)
|
||||||
val restrictions = prefs.libraryUpdateMangaRestriction().get()
|
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 } }
|
.filterNot { it.value.any { manga -> manga.category in excludedCategories } }
|
||||||
.filter { includedCategories.isEmpty() || it.value.any { manga -> manga.category in includedCategories } }
|
.filter { includedCategories.isEmpty() || it.value.any { manga -> manga.category in includedCategories } }
|
||||||
.filterNot {
|
.filterNot {
|
||||||
val manga = it.value.first()
|
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_HAS_UNREAD in restrictions && manga.unread != 0) ||
|
||||||
(MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasRead)
|
(MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasRead)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDownloadCount(manga: LibraryManga): Int {
|
fun getDownloadCount(manga: LibraryManga): Int {
|
||||||
return downloadManager.getDownloadCount(manga)
|
return downloadManager.getDownloadCount(manga.manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun get10PointScore(track: Track): Float? {
|
fun get10PointScore(track: Track): Float? {
|
||||||
|
|
|
@ -153,7 +153,7 @@ class StatsDetailsPresenter(
|
||||||
|
|
||||||
private suspend fun setupSeriesType() {
|
private suspend fun setupSeriesType() {
|
||||||
currentStats = ArrayList()
|
currentStats = ArrayList()
|
||||||
val libraryFormat = mangasDistinct.filterByChip().groupBy { it.seriesType() }
|
val libraryFormat = mangasDistinct.filterByChip().groupBy { it.manga.seriesType() }
|
||||||
|
|
||||||
libraryFormat.forEach { (seriesType, mangaList) ->
|
libraryFormat.forEach { (seriesType, mangaList) ->
|
||||||
currentStats?.add(
|
currentStats?.add(
|
||||||
|
@ -173,7 +173,7 @@ class StatsDetailsPresenter(
|
||||||
|
|
||||||
private suspend fun setupStatus() {
|
private suspend fun setupStatus() {
|
||||||
currentStats = ArrayList()
|
currentStats = ArrayList()
|
||||||
val libraryFormat = mangasDistinct.filterByChip().groupBy { it.status }
|
val libraryFormat = mangasDistinct.filterByChip().groupBy { it.manga.status }
|
||||||
|
|
||||||
libraryFormat.forEach { (status, mangaList) ->
|
libraryFormat.forEach { (status, mangaList) ->
|
||||||
currentStats?.add(
|
currentStats?.add(
|
||||||
|
@ -263,7 +263,7 @@ class StatsDetailsPresenter(
|
||||||
private suspend fun setupTrackers() {
|
private suspend fun setupTrackers() {
|
||||||
currentStats = ArrayList()
|
currentStats = ArrayList()
|
||||||
val libraryFormat = mangasDistinct.filterByChip()
|
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 } }
|
.flatMap { it.second.map { track -> it.first to track } }
|
||||||
val loggedServices = trackManager.services.filter { it.isLogged }
|
val loggedServices = trackManager.services.filter { it.isLogged }
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ class StatsDetailsPresenter(
|
||||||
|
|
||||||
private suspend fun setupSources() {
|
private suspend fun setupSources() {
|
||||||
currentStats = ArrayList()
|
currentStats = ArrayList()
|
||||||
val libraryFormat = mangasDistinct.filterByChip().groupBy { it.source }
|
val libraryFormat = mangasDistinct.filterByChip().groupBy { it.manga.source }
|
||||||
|
|
||||||
libraryFormat.forEach { (sourceId, mangaList) ->
|
libraryFormat.forEach { (sourceId, mangaList) ->
|
||||||
val source = sourceManager.getOrStub(sourceId)
|
val source = sourceManager.getOrStub(sourceId)
|
||||||
|
@ -339,10 +339,10 @@ class StatsDetailsPresenter(
|
||||||
private suspend fun setupTags() {
|
private suspend fun setupTags() {
|
||||||
currentStats = ArrayList()
|
currentStats = ArrayList()
|
||||||
val mangaFiltered = mangasDistinct.filterByChip()
|
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 ->
|
val libraryFormat = tags.map { tag ->
|
||||||
tag to mangaFiltered.filter {
|
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
|
this
|
||||||
} else {
|
} else {
|
||||||
filter { manga ->
|
filter { manga ->
|
||||||
context.mapSeriesType(manga.seriesType()) in selectedSeriesType
|
context.mapSeriesType(manga.manga.seriesType()) in selectedSeriesType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -443,7 +443,7 @@ class StatsDetailsPresenter(
|
||||||
this
|
this
|
||||||
} else {
|
} else {
|
||||||
filter { manga ->
|
filter { manga ->
|
||||||
context.mapStatus(manga.status) in selectedStatus
|
context.mapStatus(manga.manga.status) in selectedStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -463,7 +463,7 @@ class StatsDetailsPresenter(
|
||||||
this
|
this
|
||||||
} else {
|
} else {
|
||||||
filter { manga ->
|
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
|
* Get language name of a manga
|
||||||
*/
|
*/
|
||||||
private fun LibraryManga.getLanguage(): String {
|
private fun LibraryManga.getLanguage(): String {
|
||||||
val code = if (isLocal()) {
|
val code = if (manga.isLocal()) {
|
||||||
LocalSource.getMangaLang(this)
|
LocalSource.getMangaLang(this.manga)
|
||||||
} else {
|
} else {
|
||||||
sourceManager.get(source)?.lang
|
sourceManager.get(manga.source)?.lang
|
||||||
} ?: return context.getString(MR.strings.unknown)
|
} ?: return context.getString(MR.strings.unknown)
|
||||||
return LocaleHelper.getLocalizedDisplayName(code)
|
return LocaleHelper.getLocalizedDisplayName(code)
|
||||||
}
|
}
|
||||||
|
@ -516,7 +516,7 @@ class StatsDetailsPresenter(
|
||||||
* Get mean score rounded to two decimal of a list of manga
|
* Get mean score rounded to two decimal of a list of manga
|
||||||
*/
|
*/
|
||||||
private suspend fun List<LibraryManga>.getMeanScoreRounded(): Double? {
|
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() }
|
val scoresList = mangaTracks.filter { it.second.isNotEmpty() }
|
||||||
.mapNotNull { it.second.getMeanScoreByTracker() }
|
.mapNotNull { it.second.getMeanScoreByTracker() }
|
||||||
return if (scoresList.isEmpty()) null else scoresList.average().roundToTwoDecimal()
|
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
|
* Get mean score rounded to int of a single manga
|
||||||
*/
|
*/
|
||||||
private suspend fun LibraryManga.getMeanScoreToInt(): Int? {
|
private suspend fun LibraryManga.getMeanScoreToInt(): Int? {
|
||||||
val mangaTracks = getTracks(this)
|
val mangaTracks = getTracks(this.manga)
|
||||||
val scoresList = mangaTracks.filter { it.score > 0 }
|
val scoresList = mangaTracks.filter { it.score > 0 }
|
||||||
.mapNotNull { it.get10PointScore() }
|
.mapNotNull { it.get10PointScore() }
|
||||||
return if (scoresList.isEmpty()) null else scoresList.average().roundToInt().coerceIn(1..10)
|
return if (scoresList.isEmpty()) null else scoresList.average().roundToInt().coerceIn(1..10)
|
||||||
|
@ -550,8 +550,8 @@ class StatsDetailsPresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun LibraryManga.getStartYear(): Int? {
|
private suspend fun LibraryManga.getStartYear(): Int? {
|
||||||
if (getChapter.awaitAll(id!!, false).any { it.read }) {
|
if (getChapter.awaitAll(manga.id!!, false).any { it.read }) {
|
||||||
val chapters = getHistory.awaitAllByMangaId(id!!).filter { it.last_read > 0 }
|
val chapters = getHistory.awaitAllByMangaId(manga.id!!).filter { it.last_read > 0 }
|
||||||
val date = chapters.minOfOrNull { it.last_read } ?: return null
|
val date = chapters.minOfOrNull { it.last_read } ?: return null
|
||||||
val cal = Calendar.getInstance().apply { timeInMillis = date }
|
val cal = Calendar.getInstance().apply { timeInMillis = date }
|
||||||
return if (date <= 0L) null else cal.get(Calendar.YEAR)
|
return if (date <= 0L) null else cal.get(Calendar.YEAR)
|
||||||
|
@ -564,7 +564,7 @@ class StatsDetailsPresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getEnabledSources(): List<Source> {
|
private fun getEnabledSources(): List<Source> {
|
||||||
return mangasDistinct.mapNotNull { sourceManager.get(it.source) }
|
return mangasDistinct.mapNotNull { sourceManager.get(it.manga.source) }
|
||||||
.distinct().sortedBy { it.name }
|
.distinct().sortedBy { it.name }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,7 +589,7 @@ class StatsDetailsPresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun List<LibraryManga>.getReadDuration(): Long {
|
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)
|
val info = delegatedSource.fetchMangaFromChapterUrl(url)
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
val (sChapter, sManga, chapters) = info
|
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 chapter = Chapter.create().apply { copyFrom(sChapter) }
|
||||||
val id = insertManga.await(manga)
|
val id = insertManga.await(manga)
|
||||||
manga.id = id ?: manga.id
|
manga.id = id ?: manga.id
|
||||||
|
|
|
@ -12,8 +12,7 @@ data class CustomMangaInfo(
|
||||||
val genre: String? = null,
|
val genre: String? = null,
|
||||||
val status: Int? = null,
|
val status: Int? = null,
|
||||||
) {
|
) {
|
||||||
fun toManga() = MangaImpl().apply {
|
fun toManga() = MangaImpl(id = this.mangaId).apply {
|
||||||
id = this@CustomMangaInfo.mangaId
|
|
||||||
title = this@CustomMangaInfo.title ?: ""
|
title = this@CustomMangaInfo.title ?: ""
|
||||||
author = this@CustomMangaInfo.author
|
author = this@CustomMangaInfo.author
|
||||||
artist = this@CustomMangaInfo.artist
|
artist = this@CustomMangaInfo.artist
|
||||||
|
|
|
@ -82,9 +82,6 @@ interface Manga : SManga {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isBlank() = id == Long.MIN_VALUE
|
|
||||||
fun isHidden() = status == -1
|
|
||||||
|
|
||||||
fun setChapterOrder(sorting: Int, order: Int) {
|
fun setChapterOrder(sorting: Int, order: Int) {
|
||||||
setChapterFlags(sorting, CHAPTER_SORTING_MASK)
|
setChapterFlags(sorting, CHAPTER_SORTING_MASK)
|
||||||
setChapterFlags(order, CHAPTER_SORT_MASK)
|
setChapterFlags(order, CHAPTER_SORT_MASK)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue