mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 19:31:49 +01:00
Edit info for online manga + Custom covers update
Yes you read that right. It's back! Oh god it's back Instead of modifying the db, an external json file is made holding the custom info for your library (meaning it's even easier to remove should I so choose) Reworking to just override the variable and use said var instead of having the current/original logic that existed before Custom covers are now saved in a new folder, likewise to upstream Also like upstream, custom covers can be added to manga without covers (closes #49) (I'm so sorry Carlos)
This commit is contained in:
parent
0809a7b7ff
commit
d3ec230d4b
@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.data.cache.ChapterCache
|
|||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
|
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
@ -41,6 +42,8 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
|
|
||||||
addSingletonFactory { DownloadManager(app) }
|
addSingletonFactory { DownloadManager(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { CustomMangaManager(app) }
|
||||||
|
|
||||||
addSingletonFactory { TrackManager(app) }
|
addSingletonFactory { TrackManager(app) }
|
||||||
|
|
||||||
addSingletonFactory { Gson() }
|
addSingletonFactory { Gson() }
|
||||||
@ -56,5 +59,7 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
GlobalScope.launch { get<DatabaseHelper>() }
|
GlobalScope.launch { get<DatabaseHelper>() }
|
||||||
|
|
||||||
GlobalScope.launch { get<DownloadManager>() }
|
GlobalScope.launch { get<DownloadManager>() }
|
||||||
|
|
||||||
|
GlobalScope.launch { get<CustomMangaManager>() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,9 @@ object Migrations {
|
|||||||
BackupCreatorJob.setupTask()
|
BackupCreatorJob.setupTask()
|
||||||
ExtensionUpdateJob.setupTask()
|
ExtensionUpdateJob.setupTask()
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 66) {
|
||||||
|
LibraryPresenter.updateCustoms()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -15,7 +15,7 @@ object MangaTypeAdapter {
|
|||||||
write {
|
write {
|
||||||
beginArray()
|
beginArray()
|
||||||
value(it.url)
|
value(it.url)
|
||||||
value(it.title)
|
value(it.originalTitle)
|
||||||
value(it.source)
|
value(it.source)
|
||||||
value(max(0, it.viewer))
|
value(max(0, it.viewer))
|
||||||
value(it.chapter_flags)
|
value(it.chapter_flags)
|
||||||
|
@ -6,6 +6,7 @@ import coil.Coil
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
@ -29,11 +30,16 @@ import java.io.InputStream
|
|||||||
*/
|
*/
|
||||||
class CoverCache(val context: Context) {
|
class CoverCache(val context: Context) {
|
||||||
|
|
||||||
/**
|
companion object {
|
||||||
* Cache directory used for cache management.
|
private const val COVERS_DIR = "covers"
|
||||||
*/
|
private const val CUSTOM_COVERS_DIR = "covers/custom"
|
||||||
private val cacheDir = context.getExternalFilesDir("covers")
|
}
|
||||||
?: File(context.filesDir, "covers").also { it.mkdirs() }
|
|
||||||
|
/** Cache directory used for cache management.*/
|
||||||
|
private val cacheDir = getCacheDir(COVERS_DIR)
|
||||||
|
|
||||||
|
/** Cache directory used for custom cover cache management.*/
|
||||||
|
private val customCoverCacheDir = getCacheDir(CUSTOM_COVERS_DIR)
|
||||||
|
|
||||||
fun getChapterCacheSize(): String {
|
fun getChapterCacheSize(): String {
|
||||||
return Formatter.formatFileSize(context, DiskUtil.getDirectorySize(cacheDir))
|
return Formatter.formatFileSize(context, DiskUtil.getDirectorySize(cacheDir))
|
||||||
@ -50,7 +56,7 @@ class CoverCache(val context: Context) {
|
|||||||
val files = cacheDir.listFiles()?.iterator() ?: return@launch
|
val files = cacheDir.listFiles()?.iterator() ?: return@launch
|
||||||
while (files.hasNext()) {
|
while (files.hasNext()) {
|
||||||
val file = files.next()
|
val file = files.next()
|
||||||
if (file.name !in urls) {
|
if (file.isFile && file.name !in urls) {
|
||||||
deletedSize += file.length()
|
deletedSize += file.length()
|
||||||
file.delete()
|
file.delete()
|
||||||
}
|
}
|
||||||
@ -65,6 +71,45 @@ class CoverCache(val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the custom cover from cache.
|
||||||
|
*
|
||||||
|
* @param manga the manga.
|
||||||
|
* @return cover image.
|
||||||
|
*/
|
||||||
|
fun getCustomCoverFile(manga: Manga): File {
|
||||||
|
return File(customCoverCacheDir, DiskUtil.hashKeyForDisk(manga.id.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the given stream as the manga's custom cover to cache.
|
||||||
|
*
|
||||||
|
* @param manga the manga.
|
||||||
|
* @param inputStream the stream to copy.
|
||||||
|
* @throws IOException if there's any error.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun setCustomCoverToCache(manga: Manga, inputStream: InputStream) {
|
||||||
|
getCustomCoverFile(manga).outputStream().use {
|
||||||
|
inputStream.copyTo(it)
|
||||||
|
Coil.imageLoader(context).invalidate(manga.key())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete custom cover of the manga from the cache
|
||||||
|
*
|
||||||
|
* @param manga the manga.
|
||||||
|
* @return whether the cover was deleted.
|
||||||
|
*/
|
||||||
|
fun deleteCustomCover(manga: Manga): Boolean {
|
||||||
|
val result = getCustomCoverFile(manga).let {
|
||||||
|
it.exists() && it.delete()
|
||||||
|
}
|
||||||
|
Coil.imageLoader(context).invalidate(manga.key())
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the cover from cache.
|
* Returns the cover from cache.
|
||||||
*
|
*
|
||||||
@ -75,19 +120,11 @@ class CoverCache(val context: Context) {
|
|||||||
return File(cacheDir, manga.key())
|
return File(cacheDir, manga.key())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun deleteFromCache(name: String?) {
|
||||||
* Copy the given stream to this cache.
|
if (name.isNullOrEmpty()) return
|
||||||
*
|
val file = getCoverFile(MangaImpl().apply { thumbnail_url = name })
|
||||||
* @param thumbnailUrl url of the thumbnail.
|
Coil.imageLoader(context).invalidate(file.name)
|
||||||
* @param inputStream the stream to copy.
|
if (file.exists()) file.delete()
|
||||||
* @throws IOException if there's any error.
|
|
||||||
*/
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun copyToCache(manga: Manga, inputStream: InputStream) {
|
|
||||||
// Get destination file.
|
|
||||||
val destFile = getCoverFile(manga)
|
|
||||||
|
|
||||||
destFile.outputStream().use { inputStream.copyTo(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,13 +133,21 @@ class CoverCache(val context: Context) {
|
|||||||
* @param thumbnailUrl the thumbnail url.
|
* @param thumbnailUrl the thumbnail url.
|
||||||
* @return status of deletion.
|
* @return status of deletion.
|
||||||
*/
|
*/
|
||||||
fun deleteFromCache(manga: Manga, deleteMemoryCache: Boolean = true) {
|
fun deleteFromCache(
|
||||||
|
manga: Manga,
|
||||||
|
deleteCustom: Boolean = true
|
||||||
|
) {
|
||||||
// Check if url is empty.
|
// Check if url is empty.
|
||||||
if (manga.thumbnail_url.isNullOrEmpty()) return
|
if (manga.thumbnail_url.isNullOrEmpty()) return
|
||||||
|
|
||||||
// Remove file
|
// Remove file
|
||||||
val file = getCoverFile(manga)
|
val file = getCoverFile(manga)
|
||||||
if (deleteMemoryCache) Coil.imageLoader(context).invalidate(file.name)
|
if (deleteCustom) deleteCustomCover(manga)
|
||||||
if (file.exists()) file.delete()
|
if (file.exists()) file.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getCacheDir(dir: String): File {
|
||||||
|
return context.getExternalFilesDir(dir)
|
||||||
|
?: File(context.filesDir, dir).also { it.mkdirs() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,11 +52,11 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
|
|||||||
put(COL_ID, obj.id)
|
put(COL_ID, obj.id)
|
||||||
put(COL_SOURCE, obj.source)
|
put(COL_SOURCE, obj.source)
|
||||||
put(COL_URL, obj.url)
|
put(COL_URL, obj.url)
|
||||||
put(COL_ARTIST, obj.artist)
|
put(COL_ARTIST, obj.originalArtist)
|
||||||
put(COL_AUTHOR, obj.author)
|
put(COL_AUTHOR, obj.originalAuthor)
|
||||||
put(COL_DESCRIPTION, obj.description)
|
put(COL_DESCRIPTION, obj.originalDescription)
|
||||||
put(COL_GENRE, obj.genre)
|
put(COL_GENRE, obj.originalGenre)
|
||||||
put(COL_TITLE, obj.title)
|
put(COL_TITLE, obj.originalTitle)
|
||||||
put(COL_STATUS, obj.status)
|
put(COL_STATUS, obj.status)
|
||||||
put(COL_THUMBNAIL_URL, obj.thumbnail_url)
|
put(COL_THUMBNAIL_URL, obj.thumbnail_url)
|
||||||
put(COL_FAVORITE, obj.favorite)
|
put(COL_FAVORITE, obj.favorite)
|
||||||
|
@ -9,7 +9,6 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
interface Manga : SManga {
|
interface Manga : SManga {
|
||||||
|
|
||||||
@ -149,15 +148,6 @@ interface Manga : SManga {
|
|||||||
return DiskUtil.hashKeyForDisk(thumbnail_url.orEmpty())
|
return DiskUtil.hashKeyForDisk(thumbnail_url.orEmpty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCustomThumbnailUrl() {
|
|
||||||
removeCustomThumbnailUrl()
|
|
||||||
thumbnail_url = "Custom-${Random.nextInt(0, 1000)}-J2K-${thumbnail_url ?: id!!}"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeCustomThumbnailUrl() {
|
|
||||||
thumbnail_url = thumbnail_url?.substringAfter("-J2K-")?.substringAfter("Custom-")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to display the chapter's title one way or another
|
// Used to display the chapter's title one way or another
|
||||||
var displayMode: Int
|
var displayMode: Int
|
||||||
get() = chapter_flags and DISPLAY_MASK
|
get() = chapter_flags and DISPLAY_MASK
|
||||||
|
@ -2,9 +2,9 @@ package eu.kanade.tachiyomi.data.database.models
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
||||||
|
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import kotlin.collections.set
|
|
||||||
|
|
||||||
open class MangaImpl : Manga {
|
open class MangaImpl : Manga {
|
||||||
|
|
||||||
@ -14,15 +14,34 @@ open class MangaImpl : Manga {
|
|||||||
|
|
||||||
override lateinit var url: String
|
override lateinit var url: String
|
||||||
|
|
||||||
override lateinit var title: String
|
private val customMangaManager: CustomMangaManager by injectLazy()
|
||||||
|
|
||||||
override var artist: String? = null
|
override var title: String
|
||||||
|
get() = if (favorite) {
|
||||||
|
val customTitle = customMangaManager.getManga(this)?.title
|
||||||
|
if (customTitle.isNullOrBlank()) ogTitle else customTitle
|
||||||
|
} else {
|
||||||
|
ogTitle
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
ogTitle = value
|
||||||
|
}
|
||||||
|
|
||||||
override var author: String? = null
|
override var author: String?
|
||||||
|
get() = if (favorite) customMangaManager.getManga(this)?.author ?: ogAuthor else ogAuthor
|
||||||
|
set(value) { ogAuthor = value }
|
||||||
|
|
||||||
override var description: String? = null
|
override var artist: String?
|
||||||
|
get() = if (favorite) customMangaManager.getManga(this)?.artist ?: ogArtist else ogArtist
|
||||||
|
set(value) { ogArtist = value }
|
||||||
|
|
||||||
override var genre: String? = null
|
override var description: String?
|
||||||
|
get() = if (favorite) customMangaManager.getManga(this)?.description ?: ogDesc else ogDesc
|
||||||
|
set(value) { ogDesc = value }
|
||||||
|
|
||||||
|
override var genre: String?
|
||||||
|
get() = if (favorite) customMangaManager.getManga(this)?.genre ?: ogGenre else ogGenre
|
||||||
|
set(value) { ogGenre = value }
|
||||||
|
|
||||||
override var status: Int = 0
|
override var status: Int = 0
|
||||||
|
|
||||||
@ -42,14 +61,25 @@ open class MangaImpl : Manga {
|
|||||||
|
|
||||||
override var date_added: Long = 0
|
override var date_added: Long = 0
|
||||||
|
|
||||||
|
lateinit var ogTitle: String
|
||||||
|
private set
|
||||||
|
var ogAuthor: String? = null
|
||||||
|
private set
|
||||||
|
var ogArtist: String? = null
|
||||||
|
private set
|
||||||
|
var ogDesc: String? = null
|
||||||
|
private set
|
||||||
|
var ogGenre: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
override fun copyFrom(other: SManga) {
|
override fun copyFrom(other: SManga) {
|
||||||
if (other is MangaImpl && (other as MangaImpl)::title.isInitialized &&
|
if (other is MangaImpl && other::ogTitle.isInitialized &&
|
||||||
!other.title.isBlank() && other.title != title) {
|
!other.title.isBlank() && other.ogTitle != ogTitle) {
|
||||||
val oldTitle = title
|
val oldTitle = ogTitle
|
||||||
title = other.title
|
title = other.ogTitle
|
||||||
val db: DownloadManager by injectLazy()
|
val db: DownloadManager by injectLazy()
|
||||||
val provider = DownloadProvider(db.context)
|
val provider = DownloadProvider(db.context)
|
||||||
provider.renameMangaFolder(oldTitle, title, source)
|
provider.renameMangaFolder(oldTitle, ogTitle, source)
|
||||||
}
|
}
|
||||||
super.copyFrom(other)
|
super.copyFrom(other)
|
||||||
}
|
}
|
||||||
@ -64,7 +94,7 @@ open class MangaImpl : Manga {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
if (::url.isInitialized) return url.hashCode()
|
return if (::url.isInitialized) url.hashCode()
|
||||||
else return (id ?: 0L).hashCode()
|
else (id ?: 0L).hashCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,11 @@ class MangaInfoPutResolver(val reset: Boolean = false) : PutResolver<Manga>() {
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||||
put(MangaTable.COL_TITLE, manga.title)
|
put(MangaTable.COL_TITLE, manga.originalTitle)
|
||||||
put(MangaTable.COL_GENRE, manga.genre)
|
put(MangaTable.COL_GENRE, manga.originalGenre)
|
||||||
put(MangaTable.COL_AUTHOR, manga.author)
|
put(MangaTable.COL_AUTHOR, manga.originalAuthor)
|
||||||
put(MangaTable.COL_ARTIST, manga.artist)
|
put(MangaTable.COL_ARTIST, manga.originalArtist)
|
||||||
put(MangaTable.COL_DESCRIPTION, manga.description)
|
put(MangaTable.COL_DESCRIPTION, manga.originalDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetToContentValues(manga: Manga) = ContentValues(1).apply {
|
fun resetToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||||
|
@ -138,11 +138,11 @@ class DownloadCache(
|
|||||||
val trueMangaDirs = mangaDirs.mapNotNull { mangaDir ->
|
val trueMangaDirs = mangaDirs.mapNotNull { mangaDir ->
|
||||||
val manga = sourceMangas.firstOrNull()?.find {
|
val manga = sourceMangas.firstOrNull()?.find {
|
||||||
DiskUtil.buildValidFilename(
|
DiskUtil.buildValidFilename(
|
||||||
it.title
|
it.originalTitle
|
||||||
).toLowerCase() == mangaDir.key.toLowerCase() && it.source == sourceValue.key
|
).toLowerCase() == mangaDir.key.toLowerCase() && it.source == sourceValue.key
|
||||||
} ?: sourceMangas.lastOrNull()?.find {
|
} ?: sourceMangas.lastOrNull()?.find {
|
||||||
DiskUtil.buildValidFilename(
|
DiskUtil.buildValidFilename(
|
||||||
it.title
|
it.originalTitle
|
||||||
).toLowerCase() == mangaDir.key.toLowerCase() && it.source == sourceValue.key
|
).toLowerCase() == mangaDir.key.toLowerCase() && it.source == sourceValue.key
|
||||||
}
|
}
|
||||||
val id = manga?.id ?: return@mapNotNull null
|
val id = manga?.id ?: return@mapNotNull null
|
||||||
|
@ -185,7 +185,7 @@ class DownloadProvider(private val context: Context) {
|
|||||||
* @param manga the manga to query.
|
* @param manga the manga to query.
|
||||||
*/
|
*/
|
||||||
fun getMangaDirName(manga: Manga): String {
|
fun getMangaDirName(manga: Manga): String {
|
||||||
return DiskUtil.buildValidFilename(manga.title)
|
return DiskUtil.buildValidFilename(manga.originalTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,7 +28,11 @@ import uy.kohesive.injekt.api.get
|
|||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class MangaFetcher() : Fetcher<Manga> {
|
class MangaFetcher : Fetcher<Manga> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val realCover = "real_cover"
|
||||||
|
}
|
||||||
|
|
||||||
private val coverCache: CoverCache by injectLazy()
|
private val coverCache: CoverCache by injectLazy()
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
@ -46,23 +50,17 @@ class MangaFetcher() : Fetcher<Manga> {
|
|||||||
override suspend fun fetch(pool: BitmapPool, data: Manga, size: Size, options: Options): FetchResult {
|
override suspend fun fetch(pool: BitmapPool, data: Manga, size: Size, options: Options): FetchResult {
|
||||||
val cover = data.thumbnail_url
|
val cover = data.thumbnail_url
|
||||||
return when (getResourceType(cover)) {
|
return when (getResourceType(cover)) {
|
||||||
Type.File -> fileLoader(data)
|
|
||||||
Type.URL -> httpLoader(data, options)
|
Type.URL -> httpLoader(data, options)
|
||||||
Type.CUSTOM -> customLoader(data, options)
|
Type.File -> fileLoader(data)
|
||||||
null -> error("Invalid image")
|
null -> error("Invalid image")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun customLoader(manga: Manga, options: Options): FetchResult {
|
|
||||||
val coverFile = coverCache.getCoverFile(manga)
|
|
||||||
if (coverFile.exists()) {
|
|
||||||
return fileLoader(coverFile)
|
|
||||||
}
|
|
||||||
manga.thumbnail_url = manga.thumbnail_url!!.substringAfter("-J2K-").substringAfter("CUSTOM-")
|
|
||||||
return httpLoader(manga, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun httpLoader(manga: Manga, options: Options): FetchResult {
|
private suspend fun httpLoader(manga: Manga, options: Options): FetchResult {
|
||||||
|
val customCoverFile = coverCache.getCustomCoverFile(manga)
|
||||||
|
if (customCoverFile.exists() && options.parameters.value(realCover) != true) {
|
||||||
|
return fileLoader(customCoverFile)
|
||||||
|
}
|
||||||
val coverFile = coverCache.getCoverFile(manga)
|
val coverFile = coverCache.getCoverFile(manga)
|
||||||
if (coverFile.exists()) {
|
if (coverFile.exists()) {
|
||||||
return fileLoader(coverFile)
|
return fileLoader(coverFile)
|
||||||
@ -158,14 +156,13 @@ class MangaFetcher() : Fetcher<Manga> {
|
|||||||
private fun getResourceType(cover: String?): Type? {
|
private fun getResourceType(cover: String?): Type? {
|
||||||
return when {
|
return when {
|
||||||
cover.isNullOrEmpty() -> null
|
cover.isNullOrEmpty() -> null
|
||||||
cover.startsWith("http") -> Type.URL
|
cover.startsWith("http") || cover.startsWith("Custom-", true) -> Type.URL
|
||||||
cover.startsWith("Custom-") -> Type.CUSTOM
|
|
||||||
cover.startsWith("/") || cover.startsWith("file://") -> Type.File
|
cover.startsWith("/") || cover.startsWith("file://") -> Type.File
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class Type {
|
private enum class Type {
|
||||||
File, CUSTOM, URL;
|
File, URL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,111 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.library
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.github.salomonbrys.kotson.nullLong
|
||||||
|
import com.github.salomonbrys.kotson.nullString
|
||||||
|
import com.github.salomonbrys.kotson.set
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||||
|
import java.io.File
|
||||||
|
import java.util.Scanner
|
||||||
|
|
||||||
|
class CustomMangaManager(val context: Context) {
|
||||||
|
|
||||||
|
private val editJson = File(context.getExternalFilesDir(null), "edits.json")
|
||||||
|
|
||||||
|
private var customMangaMap = mutableMapOf<Long, Manga>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
fetchCustomData()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getManga(manga: Manga): Manga? = customMangaMap[manga.id]
|
||||||
|
|
||||||
|
private fun fetchCustomData() {
|
||||||
|
if (!editJson.exists() || !editJson.isFile) return
|
||||||
|
|
||||||
|
val json = try {
|
||||||
|
Gson().fromJson(
|
||||||
|
Scanner(editJson).useDelimiter("\\Z").next(), JsonObject::class.java
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
} ?: return
|
||||||
|
|
||||||
|
val mangasJson = json.get("mangas").asJsonArray ?: return
|
||||||
|
customMangaMap = mangasJson.mapNotNull { element ->
|
||||||
|
val mangaObject = element.asJsonObject ?: return@mapNotNull null
|
||||||
|
val id = mangaObject["id"]?.nullLong ?: return@mapNotNull null
|
||||||
|
val manga = MangaImpl().apply {
|
||||||
|
this.id = id
|
||||||
|
title = mangaObject["title"]?.nullString ?: ""
|
||||||
|
author = mangaObject["author"]?.nullString
|
||||||
|
artist = mangaObject["artist"]?.nullString
|
||||||
|
description = mangaObject["description"]?.nullString
|
||||||
|
genre = mangaObject["genre"]?.asJsonArray?.mapNotNull { it.nullString }
|
||||||
|
?.joinToString(", ")
|
||||||
|
}
|
||||||
|
id to manga
|
||||||
|
}.toMap().toMutableMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveMangaInfo(manga: MangaJson) {
|
||||||
|
if (manga.title == null && manga.author == null && manga.artist == null && manga.description == null && manga.genre == null) {
|
||||||
|
customMangaMap.remove(manga.id)
|
||||||
|
} else {
|
||||||
|
customMangaMap[manga.id] = MangaImpl().apply {
|
||||||
|
id = manga.id
|
||||||
|
title = manga.title ?: ""
|
||||||
|
author = manga.author
|
||||||
|
artist = manga.artist
|
||||||
|
description = manga.description
|
||||||
|
genre = manga.genre?.joinToString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saveCustomInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveCustomInfo() {
|
||||||
|
val jsonElements = customMangaMap.values.map { it.toJson() }
|
||||||
|
if (jsonElements.isNotEmpty()) {
|
||||||
|
val gson = GsonBuilder().create()
|
||||||
|
val root = JsonObject()
|
||||||
|
val mangaEntries = gson.toJsonTree(jsonElements)
|
||||||
|
|
||||||
|
root["mangas"] = mangaEntries
|
||||||
|
editJson.delete()
|
||||||
|
editJson.writeText(gson.toJson(root))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Manga.toJson(): MangaJson {
|
||||||
|
return MangaJson(
|
||||||
|
id!!, title, author, artist, description, genre?.split(", ")?.toTypedArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MangaJson(
|
||||||
|
val id: Long,
|
||||||
|
val title: String? = null,
|
||||||
|
val author: String? = null,
|
||||||
|
val artist: String? = null,
|
||||||
|
val description: String? = null,
|
||||||
|
val genre: Array<String>? = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
other as MangaJson
|
||||||
|
if (id != other.id) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return id.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ import coil.request.GetRequest
|
|||||||
import coil.request.LoadRequest
|
import coil.request.LoadRequest
|
||||||
import coil.transform.CircleCropTransformation
|
import coil.transform.CircleCropTransformation
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
@ -73,6 +74,7 @@ import java.util.concurrent.atomic.AtomicInteger
|
|||||||
*/
|
*/
|
||||||
class LibraryUpdateService(
|
class LibraryUpdateService(
|
||||||
val db: DatabaseHelper = Injekt.get(),
|
val db: DatabaseHelper = Injekt.get(),
|
||||||
|
val coverCache: CoverCache = Injekt.get(),
|
||||||
val sourceManager: SourceManager = Injekt.get(),
|
val sourceManager: SourceManager = Injekt.get(),
|
||||||
val preferences: PreferencesHelper = Injekt.get(),
|
val preferences: PreferencesHelper = Injekt.get(),
|
||||||
val downloadManager: DownloadManager = Injekt.get(),
|
val downloadManager: DownloadManager = Injekt.get(),
|
||||||
@ -533,15 +535,14 @@ class LibraryUpdateService(
|
|||||||
val thumbnailUrl = manga.thumbnail_url
|
val thumbnailUrl = manga.thumbnail_url
|
||||||
manga.copyFrom(networkManga)
|
manga.copyFrom(networkManga)
|
||||||
manga.initialized = true
|
manga.initialized = true
|
||||||
|
if (thumbnailUrl != manga.thumbnail_url) {
|
||||||
|
coverCache.deleteFromCache(thumbnailUrl)
|
||||||
// load new covers in background
|
// load new covers in background
|
||||||
if (!manga.hasCustomCover()) {
|
val request =
|
||||||
val request = LoadRequest.Builder(this@LibraryUpdateService)
|
LoadRequest.Builder(this@LibraryUpdateService).data(manga)
|
||||||
.data(manga)
|
.memoryCachePolicy(CachePolicy.DISABLED).build()
|
||||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
|
||||||
.build()
|
|
||||||
Coil.imageLoader(this@LibraryUpdateService).execute(request)
|
Coil.imageLoader(this@LibraryUpdateService).execute(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
db.insertManga(manga).executeAsBlocking()
|
db.insertManga(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,22 +23,31 @@ interface SManga : Serializable {
|
|||||||
|
|
||||||
var initialized: Boolean
|
var initialized: Boolean
|
||||||
|
|
||||||
fun hasCustomCover() = thumbnail_url?.startsWith("Custom-") == true
|
val originalTitle: String
|
||||||
|
get() = (this as? MangaImpl)?.ogTitle ?: title
|
||||||
|
val originalAuthor: String?
|
||||||
|
get() = (this as? MangaImpl)?.ogAuthor ?: author
|
||||||
|
val originalArtist: String?
|
||||||
|
get() = (this as? MangaImpl)?.ogArtist ?: artist
|
||||||
|
val originalDescription: String?
|
||||||
|
get() = (this as? MangaImpl)?.ogDesc ?: description
|
||||||
|
val originalGenre: String?
|
||||||
|
get() = (this as? MangaImpl)?.ogGenre ?: genre
|
||||||
|
|
||||||
fun copyFrom(other: SManga) {
|
fun copyFrom(other: SManga) {
|
||||||
if (other.author != null)
|
if (other.author != null)
|
||||||
author = other.author
|
author = other.originalAuthor
|
||||||
|
|
||||||
if (other.artist != null)
|
if (other.artist != null)
|
||||||
artist = other.artist
|
artist = other.originalArtist
|
||||||
|
|
||||||
if (other.description != null)
|
if (other.description != null)
|
||||||
description = other.description
|
description = other.originalDescription
|
||||||
|
|
||||||
if (other.genre != null)
|
if (other.genre != null)
|
||||||
genre = other.genre
|
genre = other.originalGenre
|
||||||
|
|
||||||
if (other.thumbnail_url != null && !hasCustomCover())
|
if (other.thumbnail_url != null)
|
||||||
thumbnail_url = other.thumbnail_url
|
thumbnail_url = other.thumbnail_url
|
||||||
|
|
||||||
status = other.status
|
status = other.status
|
||||||
|
@ -37,6 +37,7 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import java.util.Comparator
|
import java.util.Comparator
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of [LibraryController].
|
* Presenter of [LibraryController].
|
||||||
@ -874,5 +875,24 @@ class LibraryPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateCustoms() {
|
||||||
|
val db: DatabaseHelper = Injekt.get()
|
||||||
|
val cc: CoverCache = Injekt.get()
|
||||||
|
db.inTransaction {
|
||||||
|
val libraryManga = db.getLibraryMangas().executeAsBlocking()
|
||||||
|
libraryManga.forEach { manga ->
|
||||||
|
if (manga.thumbnail_url?.startsWith("custom", ignoreCase = true) == true) {
|
||||||
|
val file = cc.getCoverFile(manga)
|
||||||
|
if (file.exists()) {
|
||||||
|
file.renameTo(cc.getCustomCoverFile(manga))
|
||||||
|
}
|
||||||
|
manga.thumbnail_url =
|
||||||
|
manga.thumbnail_url!!.toLowerCase(Locale.ROOT).substringAfter("custom-")
|
||||||
|
db.insertManga(manga).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,17 @@ import android.net.Uri
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import coil.api.loadAny
|
import coil.api.loadAny
|
||||||
|
import coil.request.Parameters
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.customview.customView
|
import com.afollestad.materialdialogs.customview.customView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.download.coil.MangaFetcher
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
|
import eu.kanade.tachiyomi.util.lang.chop
|
||||||
|
import eu.kanade.tachiyomi.util.view.visibleIf
|
||||||
import kotlinx.android.synthetic.main.edit_manga_dialog.view.*
|
import kotlinx.android.synthetic.main.edit_manga_dialog.view.*
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -24,6 +28,8 @@ class EditMangaDialog : DialogController {
|
|||||||
|
|
||||||
private var customCoverUri: Uri? = null
|
private var customCoverUri: Uri? = null
|
||||||
|
|
||||||
|
private var willResetCover = false
|
||||||
|
|
||||||
private val infoController
|
private val infoController
|
||||||
get() = targetController as MangaDetailsController
|
get() = targetController as MangaDetailsController
|
||||||
|
|
||||||
@ -68,22 +74,58 @@ class EditMangaDialog : DialogController {
|
|||||||
view.manga_artist.append(manga.artist ?: "")
|
view.manga_artist.append(manga.artist ?: "")
|
||||||
view.manga_description.append(manga.description ?: "")
|
view.manga_description.append(manga.description ?: "")
|
||||||
view.manga_genres_tags.setTags(manga.genre?.split(", ") ?: emptyList())
|
view.manga_genres_tags.setTags(manga.genre?.split(", ") ?: emptyList())
|
||||||
|
} else {
|
||||||
|
if (manga.title != manga.originalTitle) {
|
||||||
|
view.title.append(manga.title)
|
||||||
|
}
|
||||||
|
if (manga.author != manga.originalAuthor) {
|
||||||
|
view.manga_author.append(manga.author ?: "")
|
||||||
|
}
|
||||||
|
if (manga.artist != manga.originalArtist) {
|
||||||
|
view.manga_artist.append(manga.artist ?: "")
|
||||||
|
}
|
||||||
|
if (manga.description != manga.originalDescription) {
|
||||||
|
view.manga_description.append(manga.description ?: "")
|
||||||
|
}
|
||||||
|
view.manga_genres_tags.setTags(manga.genre?.split(", ") ?: emptyList())
|
||||||
|
|
||||||
|
view.title.hint = "${resources?.getString(R.string.title)}: ${manga.originalTitle}"
|
||||||
|
if (manga.originalAuthor != null) {
|
||||||
|
view.manga_author.hint = "${resources?.getString(R.string.author)}: ${manga.originalAuthor}"
|
||||||
|
}
|
||||||
|
if (manga.originalArtist != null) {
|
||||||
|
view.manga_artist.hint = "${resources?.getString(R.string.artist)}: ${manga.originalArtist}"
|
||||||
|
}
|
||||||
|
if (manga.originalDescription != null) {
|
||||||
|
view.manga_description.hint =
|
||||||
|
"${resources?.getString(R.string.description)}: ${manga.originalDescription?.replace(
|
||||||
|
"\n", " "
|
||||||
|
)?.chop(20)}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
view.manga_genres_tags.clearFocus()
|
view.manga_genres_tags.clearFocus()
|
||||||
view.cover_layout.setOnClickListener {
|
view.cover_layout.setOnClickListener {
|
||||||
infoController.changeCover()
|
infoController.changeCover()
|
||||||
}
|
}
|
||||||
view.reset_tags.setOnClickListener { resetTags() }
|
view.reset_tags.setOnClickListener { resetTags() }
|
||||||
|
view.reset_cover.visibleIf(!isLocal)
|
||||||
|
view.reset_cover.setOnClickListener {
|
||||||
|
view.manga_cover.loadAny(manga, builder = {
|
||||||
|
parameters(Parameters.Builder().set(MangaFetcher.realCover, true).build())
|
||||||
|
})
|
||||||
|
willResetCover = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetTags() {
|
private fun resetTags() {
|
||||||
if (manga.genre.isNullOrBlank() || manga.source == LocalSource.ID) dialogView?.manga_genres_tags?.setTags(
|
if (manga.genre.isNullOrBlank() || manga.source == LocalSource.ID) dialogView?.manga_genres_tags?.setTags(
|
||||||
emptyList()
|
emptyList()
|
||||||
)
|
)
|
||||||
else dialogView?.manga_genres_tags?.setTags(manga.genre?.split(", "))
|
else dialogView?.manga_genres_tags?.setTags(manga.originalGenre?.split(", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateCover(uri: Uri) {
|
fun updateCover(uri: Uri) {
|
||||||
|
willResetCover = false
|
||||||
dialogView!!.manga_cover.loadAny(uri)
|
dialogView!!.manga_cover.loadAny(uri)
|
||||||
customCoverUri = uri
|
customCoverUri = uri
|
||||||
}
|
}
|
||||||
@ -97,7 +139,7 @@ class EditMangaDialog : DialogController {
|
|||||||
infoController.presenter.updateManga(dialogView?.title?.text.toString(),
|
infoController.presenter.updateManga(dialogView?.title?.text.toString(),
|
||||||
dialogView?.manga_author?.text.toString(), dialogView?.manga_artist?.text.toString(),
|
dialogView?.manga_author?.text.toString(), dialogView?.manga_artist?.text.toString(),
|
||||||
customCoverUri, dialogView?.manga_description?.text.toString(),
|
customCoverUri, dialogView?.manga_description?.text.toString(),
|
||||||
dialogView?.manga_genres_tags?.tags)
|
dialogView?.manga_genres_tags?.tags, willResetCover)
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
@ -43,7 +43,6 @@ import coil.request.LoadRequest
|
|||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.checkbox.checkBoxPrompt
|
import com.afollestad.materialdialogs.checkbox.checkBoxPrompt
|
||||||
import com.afollestad.materialdialogs.checkbox.isCheckPromptChecked
|
import com.afollestad.materialdialogs.checkbox.isCheckPromptChecked
|
||||||
import com.afollestad.materialdialogs.list.listItems
|
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||||
import com.bluelinelabs.conductor.ControllerChangeType
|
import com.bluelinelabs.conductor.ControllerChangeType
|
||||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||||
@ -307,7 +306,7 @@ class MangaDetailsController : BaseController,
|
|||||||
fun setPaletteColor() {
|
fun setPaletteColor() {
|
||||||
val view = view ?: return
|
val view = view ?: return
|
||||||
|
|
||||||
val request = LoadRequest.Builder(view.context).data(manga).allowHardware(false)
|
val request = LoadRequest.Builder(view.context).data(presenter.manga).allowHardware(false)
|
||||||
.target { drawable ->
|
.target { drawable ->
|
||||||
val bitmap = (drawable as BitmapDrawable).bitmap
|
val bitmap = (drawable as BitmapDrawable).bitmap
|
||||||
// Generate the Palette on a background thread.
|
// Generate the Palette on a background thread.
|
||||||
@ -393,8 +392,8 @@ class MangaDetailsController : BaseController,
|
|||||||
presenter.refreshTracking()
|
presenter.refreshTracking()
|
||||||
refreshTracker = null
|
refreshTracker = null
|
||||||
}
|
}
|
||||||
// reset the covers and palette cause user might have set a custom cover
|
// fetch cover again in case the user set a new cover while reading
|
||||||
presenter.forceUpdateCovers(false)
|
setPaletteColor()
|
||||||
val isCurrentController = router?.backstack?.lastOrNull()?.controller() ==
|
val isCurrentController = router?.backstack?.lastOrNull()?.controller() ==
|
||||||
this
|
this
|
||||||
if (isCurrentController) {
|
if (isCurrentController) {
|
||||||
@ -693,10 +692,6 @@ class MangaDetailsController : BaseController,
|
|||||||
inflater.inflate(R.menu.manga_details, menu)
|
inflater.inflate(R.menu.manga_details, menu)
|
||||||
val editItem = menu.findItem(R.id.action_edit)
|
val editItem = menu.findItem(R.id.action_edit)
|
||||||
editItem.isVisible = presenter.manga.favorite && !presenter.isLockedFromSearch
|
editItem.isVisible = presenter.manga.favorite && !presenter.isLockedFromSearch
|
||||||
editItem.title = view?.context?.getString(
|
|
||||||
if (manga?.source == LocalSource.ID)
|
|
||||||
R.string.edit else R.string.edit_cover
|
|
||||||
)
|
|
||||||
menu.findItem(R.id.action_download).isVisible = !presenter.isLockedFromSearch &&
|
menu.findItem(R.id.action_download).isVisible = !presenter.isLockedFromSearch &&
|
||||||
manga?.source != LocalSource.ID
|
manga?.source != LocalSource.ID
|
||||||
menu.findItem(R.id.action_mark_all_as_read).isVisible =
|
menu.findItem(R.id.action_mark_all_as_read).isVisible =
|
||||||
@ -745,29 +740,10 @@ class MangaDetailsController : BaseController,
|
|||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.action_edit -> {
|
R.id.action_edit -> {
|
||||||
if (manga?.source == LocalSource.ID) {
|
editMangaDialog = EditMangaDialog(
|
||||||
editMangaDialog = EditMangaDialog(
|
this, presenter.manga
|
||||||
this, presenter.manga
|
)
|
||||||
)
|
editMangaDialog?.showDialog(router)
|
||||||
editMangaDialog?.showDialog(router)
|
|
||||||
} else {
|
|
||||||
if (manga?.hasCustomCover() == true) {
|
|
||||||
MaterialDialog(activity!!).listItems(items = listOf(
|
|
||||||
view!!.context.getString(
|
|
||||||
R.string.edit_cover
|
|
||||||
), view!!.context.getString(
|
|
||||||
R.string.reset_cover
|
|
||||||
)
|
|
||||||
), waitForPositiveButton = false, selection = { _, index, _ ->
|
|
||||||
when (index) {
|
|
||||||
0 -> changeCover()
|
|
||||||
else -> presenter.clearCustomCover()
|
|
||||||
}
|
|
||||||
}).show()
|
|
||||||
} else {
|
|
||||||
changeCover()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
R.id.action_open_in_web_view -> openInWebView()
|
R.id.action_open_in_web_view -> openInWebView()
|
||||||
R.id.action_refresh_tracking -> presenter.refreshTracking(true)
|
R.id.action_refresh_tracking -> presenter.refreshTracking(true)
|
||||||
|
@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
||||||
|
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryServiceListener
|
import eu.kanade.tachiyomi.data.library.LibraryServiceListener
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
@ -26,11 +27,11 @@ import eu.kanade.tachiyomi.source.Source
|
|||||||
import eu.kanade.tachiyomi.source.fetchChapterListAsync
|
import eu.kanade.tachiyomi.source.fetchChapterListAsync
|
||||||
import eu.kanade.tachiyomi.source.fetchMangaDetailsAsync
|
import eu.kanade.tachiyomi.source.fetchMangaDetailsAsync
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
||||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
||||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||||
|
import eu.kanade.tachiyomi.util.lang.trimOrNull
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -43,6 +44,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@ -60,6 +62,8 @@ class MangaDetailsPresenter(
|
|||||||
|
|
||||||
private var scope = CoroutineScope(Job() + Dispatchers.Default)
|
private var scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
|
|
||||||
|
private val customMangaManager: CustomMangaManager by injectLazy()
|
||||||
|
|
||||||
var isLockedFromSearch = false
|
var isLockedFromSearch = false
|
||||||
var hasRequested = false
|
var hasRequested = false
|
||||||
var isLoading = false
|
var isLoading = false
|
||||||
@ -405,11 +409,10 @@ class MangaDetailsPresenter(
|
|||||||
manga.copyFrom(networkManga)
|
manga.copyFrom(networkManga)
|
||||||
manga.initialized = true
|
manga.initialized = true
|
||||||
|
|
||||||
if (shouldUpdateCover(thumbnailUrl, networkManga)) {
|
if (thumbnailUrl != networkManga.thumbnail_url) {
|
||||||
coverCache.deleteFromCache(manga, false)
|
coverCache.deleteFromCache(thumbnailUrl)
|
||||||
manga.thumbnail_url = networkManga.thumbnail_url
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
forceUpdateCovers()
|
controller.setPaletteColor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.insertManga(manga).executeAsBlocking()
|
db.insertManga(manga).executeAsBlocking()
|
||||||
@ -460,19 +463,6 @@ class MangaDetailsPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldUpdateCover(thumbnailUrl: String?, networkManga: SManga): Boolean {
|
|
||||||
val refreshCovers = preferences.refreshCoversToo().getOrDefault()
|
|
||||||
if (thumbnailUrl == networkManga.thumbnail_url && !refreshCovers) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (thumbnailUrl != networkManga.thumbnail_url && !manga.hasCustomCover()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (manga.hasCustomCover()) return false
|
|
||||||
|
|
||||||
return refreshCovers
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests an updated list of chapters from the source.
|
* Requests an updated list of chapters from the source.
|
||||||
*/
|
*/
|
||||||
@ -666,6 +656,7 @@ class MangaDetailsPresenter(
|
|||||||
coverCache.deleteFromCache(manga)
|
coverCache.deleteFromCache(manga)
|
||||||
db.resetMangaInfo(manga).executeAsBlocking()
|
db.resetMangaInfo(manga).executeAsBlocking()
|
||||||
downloadManager.deleteManga(manga, source)
|
downloadManager.deleteManga(manga, source)
|
||||||
|
customMangaManager.saveMangaInfo(CustomMangaManager.MangaJson(manga.id!!))
|
||||||
asyncUpdateMangaAndChapters(true)
|
asyncUpdateMangaAndChapters(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -718,36 +709,41 @@ class MangaDetailsPresenter(
|
|||||||
artist: String?,
|
artist: String?,
|
||||||
uri: Uri?,
|
uri: Uri?,
|
||||||
description: String?,
|
description: String?,
|
||||||
tags: Array<String>?
|
tags: Array<String>?,
|
||||||
|
resetCover: Boolean = false
|
||||||
) {
|
) {
|
||||||
if (manga.source == LocalSource.ID) {
|
if (manga.source == LocalSource.ID) {
|
||||||
manga.title = if (title.isNullOrBlank()) manga.url else title.trim()
|
manga.title = if (title.isNullOrBlank()) manga.url else title.trim()
|
||||||
manga.author = author?.trim()
|
manga.author = author?.trimOrNull()
|
||||||
manga.artist = artist?.trim()
|
manga.artist = artist?.trimOrNull()
|
||||||
manga.description = description?.trim()
|
manga.description = description?.trimOrNull()
|
||||||
val tagsString = tags?.joinToString(", ") { it.capitalize() }
|
val tagsString = tags?.joinToString(", ") { it.capitalize() }
|
||||||
manga.genre = if (tags.isNullOrEmpty()) null else tagsString?.trim()
|
manga.genre = if (tags.isNullOrEmpty()) null else tagsString?.trim()
|
||||||
LocalSource(downloadManager.context).updateMangaInfo(manga)
|
LocalSource(downloadManager.context).updateMangaInfo(manga)
|
||||||
db.updateMangaInfo(manga).executeAsBlocking()
|
db.updateMangaInfo(manga).executeAsBlocking()
|
||||||
|
} else {
|
||||||
|
val genre = if (!tags.isNullOrEmpty() && tags.joinToString(", ") != manga.genre) {
|
||||||
|
tags.map { it.capitalize() }.toTypedArray()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val manga = CustomMangaManager.MangaJson(
|
||||||
|
manga.id!!,
|
||||||
|
title?.trimOrNull(),
|
||||||
|
author?.trimOrNull(),
|
||||||
|
artist?.trimOrNull(),
|
||||||
|
description?.trimOrNull(),
|
||||||
|
genre
|
||||||
|
)
|
||||||
|
customMangaManager.saveMangaInfo(manga)
|
||||||
}
|
}
|
||||||
if (uri != null) editCoverWithStream(uri)
|
if (uri != null) {
|
||||||
}
|
editCoverWithStream(uri)
|
||||||
|
} else if (resetCover) {
|
||||||
/**
|
coverCache.deleteCustomCover(manga)
|
||||||
* Remvoe custom cover
|
controller.setPaletteColor()
|
||||||
*/
|
|
||||||
fun clearCustomCover() {
|
|
||||||
if (manga.hasCustomCover()) {
|
|
||||||
coverCache.deleteFromCache(manga)
|
|
||||||
manga.removeCustomThumbnailUrl()
|
|
||||||
db.insertManga(manga).executeAsBlocking()
|
|
||||||
forceUpdateCovers()
|
|
||||||
}
|
}
|
||||||
}
|
controller.updateHeader()
|
||||||
|
|
||||||
fun forceUpdateCovers(deleteCache: Boolean = true) {
|
|
||||||
if (deleteCache) coverCache.deleteFromCache(manga)
|
|
||||||
controller.setPaletteColor()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun editCoverWithStream(uri: Uri): Boolean {
|
fun editCoverWithStream(uri: Uri): Boolean {
|
||||||
@ -755,16 +751,13 @@ class MangaDetailsPresenter(
|
|||||||
downloadManager.context.contentResolver.openInputStream(uri) ?: return false
|
downloadManager.context.contentResolver.openInputStream(uri) ?: return false
|
||||||
if (manga.source == LocalSource.ID) {
|
if (manga.source == LocalSource.ID) {
|
||||||
LocalSource.updateCover(downloadManager.context, manga, inputStream)
|
LocalSource.updateCover(downloadManager.context, manga, inputStream)
|
||||||
forceUpdateCovers()
|
controller.setPaletteColor()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manga.favorite) {
|
if (manga.favorite) {
|
||||||
coverCache.deleteFromCache(manga)
|
coverCache.setCustomCoverToCache(manga, inputStream)
|
||||||
manga.setCustomThumbnailUrl()
|
controller.setPaletteColor()
|
||||||
db.insertManga(manga).executeAsBlocking()
|
|
||||||
coverCache.copyToCache(manga, inputStream)
|
|
||||||
forceUpdateCovers(false)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -137,7 +137,7 @@ class MangaHeaderHolder(
|
|||||||
title.text = manga.title
|
title.text = manga.title
|
||||||
|
|
||||||
if (manga.genre.isNullOrBlank().not()) manga_genres_tags.setTags(
|
if (manga.genre.isNullOrBlank().not()) manga_genres_tags.setTags(
|
||||||
manga.genre?.split(", ")?.map(String::trim)
|
manga.genre?.split(",")?.map(String::trim)
|
||||||
)
|
)
|
||||||
else manga_genres_tags.setTags(emptyList())
|
else manga_genres_tags.setTags(emptyList())
|
||||||
|
|
||||||
@ -323,14 +323,6 @@ class MangaHeaderHolder(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isCached(manga: Manga): Boolean {
|
|
||||||
if (manga.source == LocalSource.ID) return true
|
|
||||||
manga.thumbnail_url?.let {
|
|
||||||
return adapter.delegate.mangaPresenter().coverCache.getCoverFile(manga).exists()
|
|
||||||
}
|
|
||||||
return manga.initialized
|
|
||||||
}
|
|
||||||
|
|
||||||
fun expand() {
|
fun expand() {
|
||||||
sub_item_group.visible()
|
sub_item_group.visible()
|
||||||
if (!showMoreButton) more_button_group.gone()
|
if (!showMoreButton) more_button_group.gone()
|
||||||
|
@ -7,7 +7,6 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.util.system.HashCode
|
|
||||||
|
|
||||||
class MangaHeaderItem(val manga: Manga, var startExpanded: Boolean) :
|
class MangaHeaderItem(val manga: Manga, var startExpanded: Boolean) :
|
||||||
AbstractFlexibleItem<MangaHeaderHolder>() {
|
AbstractFlexibleItem<MangaHeaderHolder>() {
|
||||||
@ -46,6 +45,6 @@ class MangaHeaderItem(val manga: Manga, var startExpanded: Boolean) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return HashCode.generate(manga.id, manga.title)
|
return -(manga.id).hashCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -540,12 +540,8 @@ class ReaderPresenter(
|
|||||||
R.string.cover_updated
|
R.string.cover_updated
|
||||||
SetAsCoverResult.Success
|
SetAsCoverResult.Success
|
||||||
} else {
|
} else {
|
||||||
manga.thumbnail_url ?: throw Exception("Image url not found")
|
|
||||||
if (manga.favorite) {
|
if (manga.favorite) {
|
||||||
coverCache.deleteFromCache(manga)
|
coverCache.setCustomCoverToCache(manga, stream())
|
||||||
manga.setCustomThumbnailUrl()
|
|
||||||
db.insertManga(manga).executeAsBlocking()
|
|
||||||
coverCache.copyToCache(manga, stream())
|
|
||||||
SetAsCoverResult.Success
|
SetAsCoverResult.Success
|
||||||
} else {
|
} else {
|
||||||
SetAsCoverResult.AddToLibraryFirst
|
SetAsCoverResult.AddToLibraryFirst
|
||||||
|
@ -74,7 +74,7 @@ object ChapterRecognition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove manga title from chapter title.
|
// Remove manga title from chapter title.
|
||||||
val nameWithoutManga = name.replace(manga.title.toLowerCase(), "").trim()
|
val nameWithoutManga = name.replace(manga.originalTitle.toLowerCase(), "").trim()
|
||||||
|
|
||||||
// Check if first value is number after title remove.
|
// Check if first value is number after title remove.
|
||||||
if (updateChapter(withoutManga.find(nameWithoutManga), chapter))
|
if (updateChapter(withoutManga.find(nameWithoutManga), chapter))
|
||||||
|
@ -22,6 +22,11 @@ fun String.removeArticles(): String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.trimOrNull(): String? {
|
||||||
|
val trimmed = trim()
|
||||||
|
return if (trimmed.isBlank()) null else trimmed
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the given string to have at most [count] characters using [replacement] near the center.
|
* Replaces the given string to have at most [count] characters using [replacement] near the center.
|
||||||
* If [replacement] is longer than [count] an exception will be thrown when `length > count`.
|
* If [replacement] is longer than [count] an exception will be thrown when `length > count`.
|
||||||
|
@ -10,19 +10,31 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
android:layout_marginBottom="10dp">
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/manga_cover"
|
android:id="@+id/manga_cover"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
android:minWidth="50dp"
|
android:minWidth="75dp"
|
||||||
android:layout_height="150dp"
|
android:layout_height="150dp"
|
||||||
android:contentDescription="@string/cover_of_image"
|
android:contentDescription="@string/cover_of_image"
|
||||||
android:background="@drawable/image_border_background"
|
android:background="@drawable/image_border_background"
|
||||||
android:src="@mipmap/ic_launcher"/>
|
android:src="@mipmap/ic_launcher"/>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/reset_cover"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/Theme.Widget.Button.Primary"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="@string/reset_cover" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -85,7 +97,7 @@
|
|||||||
android:id="@+id/reset_tags"
|
android:id="@+id/reset_tags"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/Theme.Widget.Button.Primary"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:textAllCaps="false"
|
android:textAllCaps="false"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
|
@ -380,7 +380,7 @@
|
|||||||
<string name="newest_first">Newest to oldest</string>
|
<string name="newest_first">Newest to oldest</string>
|
||||||
<string name="oldest_first">Oldest to newest</string>
|
<string name="oldest_first">Oldest to newest</string>
|
||||||
<string name="clear_tags">Clear Tags</string>
|
<string name="clear_tags">Clear Tags</string>
|
||||||
<string name="edit_cover">Edit cover</string>
|
<string name="reset_tags">Reset Tags</string>
|
||||||
<string name="reset_cover">Reset cover</string>
|
<string name="reset_cover">Reset cover</string>
|
||||||
<string name="failed_to_update_cover">Failed to update cover</string>
|
<string name="failed_to_update_cover">Failed to update cover</string>
|
||||||
<string name="must_be_in_library_to_edit">Manga must be in your library to edit</string>
|
<string name="must_be_in_library_to_edit">Manga must be in your library to edit</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user