mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 16:11:51 +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.database.DatabaseHelper
|
||||
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.track.TrackManager
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
@ -41,6 +42,8 @@ class AppModule(val app: Application) : InjektModule {
|
||||
|
||||
addSingletonFactory { DownloadManager(app) }
|
||||
|
||||
addSingletonFactory { CustomMangaManager(app) }
|
||||
|
||||
addSingletonFactory { TrackManager(app) }
|
||||
|
||||
addSingletonFactory { Gson() }
|
||||
@ -56,5 +59,7 @@ class AppModule(val app: Application) : InjektModule {
|
||||
GlobalScope.launch { get<DatabaseHelper>() }
|
||||
|
||||
GlobalScope.launch { get<DownloadManager>() }
|
||||
|
||||
GlobalScope.launch { get<CustomMangaManager>() }
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,9 @@ object Migrations {
|
||||
BackupCreatorJob.setupTask()
|
||||
ExtensionUpdateJob.setupTask()
|
||||
}
|
||||
if (oldVersion < 66) {
|
||||
LibraryPresenter.updateCustoms()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -15,7 +15,7 @@ object MangaTypeAdapter {
|
||||
write {
|
||||
beginArray()
|
||||
value(it.url)
|
||||
value(it.title)
|
||||
value(it.originalTitle)
|
||||
value(it.source)
|
||||
value(max(0, it.viewer))
|
||||
value(it.chapter_flags)
|
||||
|
@ -6,6 +6,7 @@ import coil.Coil
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
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.system.executeOnIO
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
@ -29,11 +30,16 @@ import java.io.InputStream
|
||||
*/
|
||||
class CoverCache(val context: Context) {
|
||||
|
||||
/**
|
||||
* Cache directory used for cache management.
|
||||
*/
|
||||
private val cacheDir = context.getExternalFilesDir("covers")
|
||||
?: File(context.filesDir, "covers").also { it.mkdirs() }
|
||||
companion object {
|
||||
private const val COVERS_DIR = "covers"
|
||||
private const val CUSTOM_COVERS_DIR = "covers/custom"
|
||||
}
|
||||
|
||||
/** 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 {
|
||||
return Formatter.formatFileSize(context, DiskUtil.getDirectorySize(cacheDir))
|
||||
@ -50,7 +56,7 @@ class CoverCache(val context: Context) {
|
||||
val files = cacheDir.listFiles()?.iterator() ?: return@launch
|
||||
while (files.hasNext()) {
|
||||
val file = files.next()
|
||||
if (file.name !in urls) {
|
||||
if (file.isFile && file.name !in urls) {
|
||||
deletedSize += file.length()
|
||||
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.
|
||||
*
|
||||
@ -75,19 +120,11 @@ class CoverCache(val context: Context) {
|
||||
return File(cacheDir, manga.key())
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the given stream to this cache.
|
||||
*
|
||||
* @param thumbnailUrl url of the thumbnail.
|
||||
* @param inputStream the stream to copy.
|
||||
* @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) }
|
||||
fun deleteFromCache(name: String?) {
|
||||
if (name.isNullOrEmpty()) return
|
||||
val file = getCoverFile(MangaImpl().apply { thumbnail_url = name })
|
||||
Coil.imageLoader(context).invalidate(file.name)
|
||||
if (file.exists()) file.delete()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,13 +133,21 @@ class CoverCache(val context: Context) {
|
||||
* @param thumbnailUrl the thumbnail url.
|
||||
* @return status of deletion.
|
||||
*/
|
||||
fun deleteFromCache(manga: Manga, deleteMemoryCache: Boolean = true) {
|
||||
fun deleteFromCache(
|
||||
manga: Manga,
|
||||
deleteCustom: Boolean = true
|
||||
) {
|
||||
// Check if url is empty.
|
||||
if (manga.thumbnail_url.isNullOrEmpty()) return
|
||||
|
||||
// Remove file
|
||||
val file = getCoverFile(manga)
|
||||
if (deleteMemoryCache) Coil.imageLoader(context).invalidate(file.name)
|
||||
if (deleteCustom) deleteCustomCover(manga)
|
||||
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_SOURCE, obj.source)
|
||||
put(COL_URL, obj.url)
|
||||
put(COL_ARTIST, obj.artist)
|
||||
put(COL_AUTHOR, obj.author)
|
||||
put(COL_DESCRIPTION, obj.description)
|
||||
put(COL_GENRE, obj.genre)
|
||||
put(COL_TITLE, obj.title)
|
||||
put(COL_ARTIST, obj.originalArtist)
|
||||
put(COL_AUTHOR, obj.originalAuthor)
|
||||
put(COL_DESCRIPTION, obj.originalDescription)
|
||||
put(COL_GENRE, obj.originalGenre)
|
||||
put(COL_TITLE, obj.originalTitle)
|
||||
put(COL_STATUS, obj.status)
|
||||
put(COL_THUMBNAIL_URL, obj.thumbnail_url)
|
||||
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.api.get
|
||||
import java.util.Locale
|
||||
import kotlin.random.Random
|
||||
|
||||
interface Manga : SManga {
|
||||
|
||||
@ -149,15 +148,6 @@ interface Manga : SManga {
|
||||
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
|
||||
var displayMode: Int
|
||||
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.DownloadProvider
|
||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlin.collections.set
|
||||
|
||||
open class MangaImpl : Manga {
|
||||
|
||||
@ -14,15 +14,34 @@ open class MangaImpl : Manga {
|
||||
|
||||
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
|
||||
|
||||
@ -42,14 +61,25 @@ open class MangaImpl : Manga {
|
||||
|
||||
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) {
|
||||
if (other is MangaImpl && (other as MangaImpl)::title.isInitialized &&
|
||||
!other.title.isBlank() && other.title != title) {
|
||||
val oldTitle = title
|
||||
title = other.title
|
||||
if (other is MangaImpl && other::ogTitle.isInitialized &&
|
||||
!other.title.isBlank() && other.ogTitle != ogTitle) {
|
||||
val oldTitle = ogTitle
|
||||
title = other.ogTitle
|
||||
val db: DownloadManager by injectLazy()
|
||||
val provider = DownloadProvider(db.context)
|
||||
provider.renameMangaFolder(oldTitle, title, source)
|
||||
provider.renameMangaFolder(oldTitle, ogTitle, source)
|
||||
}
|
||||
super.copyFrom(other)
|
||||
}
|
||||
@ -64,7 +94,7 @@ open class MangaImpl : Manga {
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
if (::url.isInitialized) return url.hashCode()
|
||||
else return (id ?: 0L).hashCode()
|
||||
return if (::url.isInitialized) url.hashCode()
|
||||
else (id ?: 0L).hashCode()
|
||||
}
|
||||
}
|
||||
|
@ -26,11 +26,11 @@ class MangaInfoPutResolver(val reset: Boolean = false) : PutResolver<Manga>() {
|
||||
.build()
|
||||
|
||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||
put(MangaTable.COL_TITLE, manga.title)
|
||||
put(MangaTable.COL_GENRE, manga.genre)
|
||||
put(MangaTable.COL_AUTHOR, manga.author)
|
||||
put(MangaTable.COL_ARTIST, manga.artist)
|
||||
put(MangaTable.COL_DESCRIPTION, manga.description)
|
||||
put(MangaTable.COL_TITLE, manga.originalTitle)
|
||||
put(MangaTable.COL_GENRE, manga.originalGenre)
|
||||
put(MangaTable.COL_AUTHOR, manga.originalAuthor)
|
||||
put(MangaTable.COL_ARTIST, manga.originalArtist)
|
||||
put(MangaTable.COL_DESCRIPTION, manga.originalDescription)
|
||||
}
|
||||
|
||||
fun resetToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||
|
@ -138,11 +138,11 @@ class DownloadCache(
|
||||
val trueMangaDirs = mangaDirs.mapNotNull { mangaDir ->
|
||||
val manga = sourceMangas.firstOrNull()?.find {
|
||||
DiskUtil.buildValidFilename(
|
||||
it.title
|
||||
it.originalTitle
|
||||
).toLowerCase() == mangaDir.key.toLowerCase() && it.source == sourceValue.key
|
||||
} ?: sourceMangas.lastOrNull()?.find {
|
||||
DiskUtil.buildValidFilename(
|
||||
it.title
|
||||
it.originalTitle
|
||||
).toLowerCase() == mangaDir.key.toLowerCase() && it.source == sourceValue.key
|
||||
}
|
||||
val id = manga?.id ?: return@mapNotNull null
|
||||
|
@ -185,7 +185,7 @@ class DownloadProvider(private val context: Context) {
|
||||
* @param manga the manga to query.
|
||||
*/
|
||||
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 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 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 {
|
||||
val cover = data.thumbnail_url
|
||||
return when (getResourceType(cover)) {
|
||||
Type.File -> fileLoader(data)
|
||||
Type.URL -> httpLoader(data, options)
|
||||
Type.CUSTOM -> customLoader(data, options)
|
||||
Type.File -> fileLoader(data)
|
||||
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 {
|
||||
val customCoverFile = coverCache.getCustomCoverFile(manga)
|
||||
if (customCoverFile.exists() && options.parameters.value(realCover) != true) {
|
||||
return fileLoader(customCoverFile)
|
||||
}
|
||||
val coverFile = coverCache.getCoverFile(manga)
|
||||
if (coverFile.exists()) {
|
||||
return fileLoader(coverFile)
|
||||
@ -158,14 +156,13 @@ class MangaFetcher() : Fetcher<Manga> {
|
||||
private fun getResourceType(cover: String?): Type? {
|
||||
return when {
|
||||
cover.isNullOrEmpty() -> null
|
||||
cover.startsWith("http") -> Type.URL
|
||||
cover.startsWith("Custom-") -> Type.CUSTOM
|
||||
cover.startsWith("http") || cover.startsWith("Custom-", true) -> Type.URL
|
||||
cover.startsWith("/") || cover.startsWith("file://") -> Type.File
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
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.transform.CircleCropTransformation
|
||||
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.models.Category
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
@ -73,6 +74,7 @@ import java.util.concurrent.atomic.AtomicInteger
|
||||
*/
|
||||
class LibraryUpdateService(
|
||||
val db: DatabaseHelper = Injekt.get(),
|
||||
val coverCache: CoverCache = Injekt.get(),
|
||||
val sourceManager: SourceManager = Injekt.get(),
|
||||
val preferences: PreferencesHelper = Injekt.get(),
|
||||
val downloadManager: DownloadManager = Injekt.get(),
|
||||
@ -533,15 +535,14 @@ class LibraryUpdateService(
|
||||
val thumbnailUrl = manga.thumbnail_url
|
||||
manga.copyFrom(networkManga)
|
||||
manga.initialized = true
|
||||
if (thumbnailUrl != manga.thumbnail_url) {
|
||||
coverCache.deleteFromCache(thumbnailUrl)
|
||||
// load new covers in background
|
||||
if (!manga.hasCustomCover()) {
|
||||
val request = LoadRequest.Builder(this@LibraryUpdateService)
|
||||
.data(manga)
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.build()
|
||||
val request =
|
||||
LoadRequest.Builder(this@LibraryUpdateService).data(manga)
|
||||
.memoryCachePolicy(CachePolicy.DISABLED).build()
|
||||
Coil.imageLoader(this@LibraryUpdateService).execute(request)
|
||||
}
|
||||
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
|
@ -23,22 +23,31 @@ interface SManga : Serializable {
|
||||
|
||||
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) {
|
||||
if (other.author != null)
|
||||
author = other.author
|
||||
author = other.originalAuthor
|
||||
|
||||
if (other.artist != null)
|
||||
artist = other.artist
|
||||
artist = other.originalArtist
|
||||
|
||||
if (other.description != null)
|
||||
description = other.description
|
||||
description = other.originalDescription
|
||||
|
||||
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
|
||||
|
||||
status = other.status
|
||||
|
@ -37,6 +37,7 @@ import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.ArrayList
|
||||
import java.util.Comparator
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 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.view.View
|
||||
import coil.api.loadAny
|
||||
import coil.request.Parameters
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
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.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 uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
@ -24,6 +28,8 @@ class EditMangaDialog : DialogController {
|
||||
|
||||
private var customCoverUri: Uri? = null
|
||||
|
||||
private var willResetCover = false
|
||||
|
||||
private val infoController
|
||||
get() = targetController as MangaDetailsController
|
||||
|
||||
@ -68,22 +74,58 @@ class EditMangaDialog : DialogController {
|
||||
view.manga_artist.append(manga.artist ?: "")
|
||||
view.manga_description.append(manga.description ?: "")
|
||||
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.cover_layout.setOnClickListener {
|
||||
infoController.changeCover()
|
||||
}
|
||||
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() {
|
||||
if (manga.genre.isNullOrBlank() || manga.source == LocalSource.ID) dialogView?.manga_genres_tags?.setTags(
|
||||
emptyList()
|
||||
)
|
||||
else dialogView?.manga_genres_tags?.setTags(manga.genre?.split(", "))
|
||||
else dialogView?.manga_genres_tags?.setTags(manga.originalGenre?.split(", "))
|
||||
}
|
||||
|
||||
fun updateCover(uri: Uri) {
|
||||
willResetCover = false
|
||||
dialogView!!.manga_cover.loadAny(uri)
|
||||
customCoverUri = uri
|
||||
}
|
||||
@ -97,7 +139,7 @@ class EditMangaDialog : DialogController {
|
||||
infoController.presenter.updateManga(dialogView?.title?.text.toString(),
|
||||
dialogView?.manga_author?.text.toString(), dialogView?.manga_artist?.text.toString(),
|
||||
customCoverUri, dialogView?.manga_description?.text.toString(),
|
||||
dialogView?.manga_genres_tags?.tags)
|
||||
dialogView?.manga_genres_tags?.tags, willResetCover)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
@ -43,7 +43,6 @@ import coil.request.LoadRequest
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.checkbox.checkBoxPrompt
|
||||
import com.afollestad.materialdialogs.checkbox.isCheckPromptChecked
|
||||
import com.afollestad.materialdialogs.list.listItems
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
@ -307,7 +306,7 @@ class MangaDetailsController : BaseController,
|
||||
fun setPaletteColor() {
|
||||
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 ->
|
||||
val bitmap = (drawable as BitmapDrawable).bitmap
|
||||
// Generate the Palette on a background thread.
|
||||
@ -393,8 +392,8 @@ class MangaDetailsController : BaseController,
|
||||
presenter.refreshTracking()
|
||||
refreshTracker = null
|
||||
}
|
||||
// reset the covers and palette cause user might have set a custom cover
|
||||
presenter.forceUpdateCovers(false)
|
||||
// fetch cover again in case the user set a new cover while reading
|
||||
setPaletteColor()
|
||||
val isCurrentController = router?.backstack?.lastOrNull()?.controller() ==
|
||||
this
|
||||
if (isCurrentController) {
|
||||
@ -693,10 +692,6 @@ class MangaDetailsController : BaseController,
|
||||
inflater.inflate(R.menu.manga_details, menu)
|
||||
val editItem = menu.findItem(R.id.action_edit)
|
||||
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 &&
|
||||
manga?.source != LocalSource.ID
|
||||
menu.findItem(R.id.action_mark_all_as_read).isVisible =
|
||||
@ -745,29 +740,10 @@ class MangaDetailsController : BaseController,
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_edit -> {
|
||||
if (manga?.source == LocalSource.ID) {
|
||||
editMangaDialog = EditMangaDialog(
|
||||
this, presenter.manga
|
||||
)
|
||||
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()
|
||||
}
|
||||
}
|
||||
editMangaDialog = EditMangaDialog(
|
||||
this, presenter.manga
|
||||
)
|
||||
editMangaDialog?.showDialog(router)
|
||||
}
|
||||
R.id.action_open_in_web_view -> openInWebView()
|
||||
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.model.Download
|
||||
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.LibraryUpdateService
|
||||
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.fetchMangaDetailsAsync
|
||||
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.track.TrackItem
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
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.system.executeOnIO
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -43,6 +44,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.OutputStream
|
||||
@ -60,6 +62,8 @@ class MangaDetailsPresenter(
|
||||
|
||||
private var scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||
|
||||
private val customMangaManager: CustomMangaManager by injectLazy()
|
||||
|
||||
var isLockedFromSearch = false
|
||||
var hasRequested = false
|
||||
var isLoading = false
|
||||
@ -405,11 +409,10 @@ class MangaDetailsPresenter(
|
||||
manga.copyFrom(networkManga)
|
||||
manga.initialized = true
|
||||
|
||||
if (shouldUpdateCover(thumbnailUrl, networkManga)) {
|
||||
coverCache.deleteFromCache(manga, false)
|
||||
manga.thumbnail_url = networkManga.thumbnail_url
|
||||
if (thumbnailUrl != networkManga.thumbnail_url) {
|
||||
coverCache.deleteFromCache(thumbnailUrl)
|
||||
withContext(Dispatchers.Main) {
|
||||
forceUpdateCovers()
|
||||
controller.setPaletteColor()
|
||||
}
|
||||
}
|
||||
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.
|
||||
*/
|
||||
@ -666,6 +656,7 @@ class MangaDetailsPresenter(
|
||||
coverCache.deleteFromCache(manga)
|
||||
db.resetMangaInfo(manga).executeAsBlocking()
|
||||
downloadManager.deleteManga(manga, source)
|
||||
customMangaManager.saveMangaInfo(CustomMangaManager.MangaJson(manga.id!!))
|
||||
asyncUpdateMangaAndChapters(true)
|
||||
}
|
||||
|
||||
@ -718,36 +709,41 @@ class MangaDetailsPresenter(
|
||||
artist: String?,
|
||||
uri: Uri?,
|
||||
description: String?,
|
||||
tags: Array<String>?
|
||||
tags: Array<String>?,
|
||||
resetCover: Boolean = false
|
||||
) {
|
||||
if (manga.source == LocalSource.ID) {
|
||||
manga.title = if (title.isNullOrBlank()) manga.url else title.trim()
|
||||
manga.author = author?.trim()
|
||||
manga.artist = artist?.trim()
|
||||
manga.description = description?.trim()
|
||||
manga.author = author?.trimOrNull()
|
||||
manga.artist = artist?.trimOrNull()
|
||||
manga.description = description?.trimOrNull()
|
||||
val tagsString = tags?.joinToString(", ") { it.capitalize() }
|
||||
manga.genre = if (tags.isNullOrEmpty()) null else tagsString?.trim()
|
||||
LocalSource(downloadManager.context).updateMangaInfo(manga)
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remvoe custom cover
|
||||
*/
|
||||
fun clearCustomCover() {
|
||||
if (manga.hasCustomCover()) {
|
||||
coverCache.deleteFromCache(manga)
|
||||
manga.removeCustomThumbnailUrl()
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
forceUpdateCovers()
|
||||
if (uri != null) {
|
||||
editCoverWithStream(uri)
|
||||
} else if (resetCover) {
|
||||
coverCache.deleteCustomCover(manga)
|
||||
controller.setPaletteColor()
|
||||
}
|
||||
}
|
||||
|
||||
fun forceUpdateCovers(deleteCache: Boolean = true) {
|
||||
if (deleteCache) coverCache.deleteFromCache(manga)
|
||||
controller.setPaletteColor()
|
||||
controller.updateHeader()
|
||||
}
|
||||
|
||||
fun editCoverWithStream(uri: Uri): Boolean {
|
||||
@ -755,16 +751,13 @@ class MangaDetailsPresenter(
|
||||
downloadManager.context.contentResolver.openInputStream(uri) ?: return false
|
||||
if (manga.source == LocalSource.ID) {
|
||||
LocalSource.updateCover(downloadManager.context, manga, inputStream)
|
||||
forceUpdateCovers()
|
||||
controller.setPaletteColor()
|
||||
return true
|
||||
}
|
||||
|
||||
if (manga.favorite) {
|
||||
coverCache.deleteFromCache(manga)
|
||||
manga.setCustomThumbnailUrl()
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
coverCache.copyToCache(manga, inputStream)
|
||||
forceUpdateCovers(false)
|
||||
coverCache.setCustomCoverToCache(manga, inputStream)
|
||||
controller.setPaletteColor()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -137,7 +137,7 @@ class MangaHeaderHolder(
|
||||
title.text = manga.title
|
||||
|
||||
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())
|
||||
|
||||
@ -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() {
|
||||
sub_item_group.visible()
|
||||
if (!showMoreButton) more_button_group.gone()
|
||||
|
@ -7,7 +7,6 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.util.system.HashCode
|
||||
|
||||
class MangaHeaderItem(val manga: Manga, var startExpanded: Boolean) :
|
||||
AbstractFlexibleItem<MangaHeaderHolder>() {
|
||||
@ -46,6 +45,6 @@ class MangaHeaderItem(val manga: Manga, var startExpanded: Boolean) :
|
||||
}
|
||||
|
||||
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
|
||||
SetAsCoverResult.Success
|
||||
} else {
|
||||
manga.thumbnail_url ?: throw Exception("Image url not found")
|
||||
if (manga.favorite) {
|
||||
coverCache.deleteFromCache(manga)
|
||||
manga.setCustomThumbnailUrl()
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
coverCache.copyToCache(manga, stream())
|
||||
coverCache.setCustomCoverToCache(manga, stream())
|
||||
SetAsCoverResult.Success
|
||||
} else {
|
||||
SetAsCoverResult.AddToLibraryFirst
|
||||
|
@ -74,7 +74,7 @@ object ChapterRecognition {
|
||||
}
|
||||
|
||||
// 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.
|
||||
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.
|
||||
* 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_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:layout_marginBottom="10dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/manga_cover"
|
||||
android:layout_width="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:minWidth="50dp"
|
||||
android:minWidth="75dp"
|
||||
android:layout_height="150dp"
|
||||
android:contentDescription="@string/cover_of_image"
|
||||
android:background="@drawable/image_border_background"
|
||||
android:src="@mipmap/ic_launcher"/>
|
||||
</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
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
@ -85,7 +97,7 @@
|
||||
android:id="@+id/reset_tags"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Theme.Widget.Button.Primary"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:textAllCaps="false"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
|
@ -380,7 +380,7 @@
|
||||
<string name="newest_first">Newest to oldest</string>
|
||||
<string name="oldest_first">Oldest to newest</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="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>
|
||||
|
Loading…
Reference in New Issue
Block a user