Very basic manga info edit, currently will break everything if used, tags and cover edit not working

This commit is contained in:
Jobobby04 2020-07-11 14:53:59 -04:00
parent bbf1c4ffd9
commit 044c638079
14 changed files with 613 additions and 16 deletions

View File

@ -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
@ -42,6 +43,8 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { DownloadManager(app) }
addSingletonFactory { CustomMangaManager(app) }
addSingletonFactory { TrackManager(app) }
addSingletonFactory { Gson() }
@ -63,5 +66,7 @@ class AppModule(val app: Application) : InjektModule {
GlobalScope.launch { get<DatabaseHelper>() }
GlobalScope.launch { get<DownloadManager>() }
GlobalScope.launch { get<CustomMangaManager>() }
}
}

View File

@ -1,5 +1,8 @@
package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.data.library.CustomMangaManager
import uy.kohesive.injekt.injectLazy
open class MangaImpl : Manga {
override var id: Long? = null
@ -8,17 +11,34 @@ open class MangaImpl : Manga {
override lateinit var url: String
// SY -->
override var title: String = ""
// SY <--
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
@ -36,6 +56,17 @@ open class MangaImpl : Manga {
override var cover_last_modified: 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 equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || javaClass != other.javaClass) return false

View File

@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaCoverLastModifiedPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaInfoPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaTitlePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaViewerPutResolver
@ -84,6 +85,16 @@ interface MangaQueries : DbProvider {
.build()
)
.prepare()
fun updateMangaInfo(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaInfoPutResolver())
.prepare()
fun resetMangaInfo(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaInfoPutResolver(true))
.prepare()
// SY <--
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()

View File

@ -0,0 +1,44 @@
package eu.kanade.tachiyomi.data.database.resolvers
import android.content.ContentValues
import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.inTransactionReturn
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
class MangaInfoPutResolver(val reset: Boolean = false) : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
val contentValues = if (reset) resetToContentValues(manga) else mapToContentValues(manga)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
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 {
val splitter = "▒ ▒∩▒"
put(MangaTable.COL_TITLE, manga.title.split(splitter).last())
put(MangaTable.COL_GENRE, manga.genre?.split(splitter)?.lastOrNull())
put(MangaTable.COL_AUTHOR, manga.author?.split(splitter)?.lastOrNull())
put(MangaTable.COL_ARTIST, manga.artist?.split(splitter)?.lastOrNull())
put(MangaTable.COL_DESCRIPTION, manga.description?.split(splitter)?.lastOrNull())
}
}

View File

@ -22,7 +22,7 @@ import uy.kohesive.injekt.injectLazy
*
* @param context the application context.
*/
class DownloadManager(private val context: Context) {
class DownloadManager(val context: Context) {
/**
* The sources manager.

View File

@ -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()
}
}
}

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.source
import android.content.Context
import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Filter
@ -135,6 +136,44 @@ class LocalSource(private val context: Context) : CatalogueSource {
return Observable.just(MangasPage(mangas, false))
}
fun updateMangaInfo(manga: SManga) {
val directory = getBaseDirectories(context).mapNotNull { File(it, manga.url) }.find {
it.exists()
} ?: return
val gson = GsonBuilder().setPrettyPrinting().create()
val existingFileName = directory.listFiles()?.find { it.extension == "json" }?.name
val file = File(directory, existingFileName ?: "info.json")
file.writeText(gson.toJson(manga.toJson()))
}
fun SManga.toJson(): MangaJson {
return MangaJson(title, author, artist, description, genre?.split(", ")?.toTypedArray())
}
data class MangaJson(
val title: String,
val author: String?,
val artist: String?,
val description: String?,
val genre: Array<String>?
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MangaJson
if (title != other.title) return false
return true
}
override fun hashCode(): Int {
return title.hashCode()
}
}
override fun fetchLatestUpdates(page: Int) = fetchSearchManga(page, "", LATEST_FILTERS)
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.source.model
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import java.io.Serializable
interface SManga : Serializable {
@ -22,27 +23,38 @@ interface SManga : Serializable {
var initialized: Boolean
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) {
// EXH -->
if (other.title.isNotBlank()) {
title = other.title
title = other.originalTitle
}
// EXH <--
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) {
@ -61,9 +73,6 @@ interface SManga : Serializable {
const val ONGOING = 1
const val COMPLETED = 2
const val LICENSED = 3
// SY -->
const val RECOMMENDS = 69 // nice
// SY <--
fun create(): SManga {
return SMangaImpl()

View File

@ -0,0 +1,179 @@
package eu.kanade.tachiyomi.ui.manga
import android.app.Dialog
import android.os.Bundle
import android.view.View
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.bumptech.glide.load.engine.DiskCacheStrategy
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.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
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.setChips
import kotlinx.android.synthetic.main.edit_manga_dialog.view.manga_artist
import kotlinx.android.synthetic.main.edit_manga_dialog.view.manga_author
import kotlinx.android.synthetic.main.edit_manga_dialog.view.manga_cover
import kotlinx.android.synthetic.main.edit_manga_dialog.view.manga_description
import kotlinx.android.synthetic.main.edit_manga_dialog.view.manga_genres_tags
import kotlinx.android.synthetic.main.edit_manga_dialog.view.reset_tags
import kotlinx.android.synthetic.main.edit_manga_dialog.view.title
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class EditMangaDialog : DialogController {
private var dialogView: View? = null
private val manga: Manga
// private var customCoverUri: Uri? = null
private var willResetCover = false
private val infoController
get() = targetController as MangaAllInOneController
constructor(target: MangaAllInOneController, manga: Manga) : super(
Bundle()
.apply {
putLong(KEY_MANGA, manga.id!!)
}
) {
targetController = target
this.manga = manga
}
@Suppress("unused")
constructor(bundle: Bundle) : super(bundle) {
manga = Injekt.get<DatabaseHelper>().getManga(bundle.getLong(KEY_MANGA))
.executeAsBlocking()!!
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val dialog = MaterialDialog(activity!!).apply {
customView(viewRes = R.layout.edit_manga_dialog, scrollable = true)
negativeButton(android.R.string.cancel)
positiveButton(R.string.action_save) { onPositiveButtonClick() }
}
dialogView = dialog.view
onViewCreated(dialog.view)
dialog.setOnShowListener {
val dView = (it as? MaterialDialog)?.view
dView?.contentLayout?.scrollView?.scrollTo(0, 0)
}
return dialog
}
fun onViewCreated(view: View) {
val mangaThumbnail = manga.toMangaThumbnail()
GlideApp.with(view.context)
.load(mangaThumbnail)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(view.manga_cover)
// view.manga_cover.loadAny(manga)
val isLocal = manga.source == LocalSource.ID
if (isLocal) {
if (manga.title != manga.url) {
view.title.append(manga.title)
}
view.title.hint = "${resources?.getString(R.string.title)}: ${manga.url}"
view.manga_author.append(manga.author ?: "")
view.manga_artist.append(manga.artist ?: "")
view.manga_description.append(manga.description ?: "")
view.manga_genres_tags.setChips(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.setChips(manga.genre?.split(", ") ?: emptyList())
view.title.hint = "${resources?.getString(R.string.title)}: ${manga.originalTitle}"
if (manga.originalAuthor != null) {
view.manga_author.hint = "Author: ${manga.originalAuthor}"
}
if (manga.originalArtist != null) {
view.manga_artist.hint = "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?.setChips(
emptyList()
)
else dialogView?.manga_genres_tags?.setChips(manga.originalGenre?.split(", "))
}
/* fun updateCover(uri: Uri) {
willResetCover = false
dialogView!!.manga_cover.loadAny(uri)
customCoverUri = uri
}*/
override fun onDestroyView(view: View) {
super.onDestroyView(view)
dialogView = null
}
private fun onPositiveButtonClick() {
infoController.presenter.updateMangaInfo(
dialogView?.title?.text.toString(),
dialogView?.manga_author?.text.toString(), dialogView?.manga_artist?.text.toString(),
dialogView?.manga_description?.text.toString()
)
// ,
// dialogView?.manga_genres_tags?.tags)
}
private fun getAllChips() {
dialogView?.manga_genres_tags?.childCount
/*for (i in 0 until dialogView?.manga_genres_tags?.childCount) {
val child: View = getChildAt(i)
if (child is Chip) {
if (child.isChecked) {
checkedIds.add(child.getId())
}
}
}*/
}
private companion object {
const val KEY_MANGA = "manga_id"
}
}

View File

@ -161,6 +161,8 @@ class MangaAllInOneController :
val smartSearchConfig: SourceController.SmartSearchConfig? = args.getParcelable(SMART_SEARCH_CONFIG_EXTRA)
override val coroutineContext: CoroutineContext = Job() + Dispatchers.Main
private var editMangaDialog: EditMangaDialog? = null
// EXH <--
val fromSource = args.getBoolean(FROM_SOURCE_EXTRA, false)
@ -666,6 +668,8 @@ class MangaAllInOneController :
else -> throw NotImplementedError("Unimplemented sorting method")
}
menu.findItem(sortingItem).isChecked = true
if (presenter.manga.favorite) menu.findItem(R.id.action_edit).isVisible = true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -720,6 +724,12 @@ class MangaAllInOneController :
presenter.removeFilters()
activity?.invalidateOptionsMenu()
}
R.id.action_edit -> {
editMangaDialog = EditMangaDialog(
this, presenter.manga
)
editMangaDialog?.showDialog(router)
}
R.id.action_sort -> presenter.revertSortOrder()
}
return super.onOptionsItemSelected(item)

View File

@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
@ -31,6 +32,7 @@ import exh.debug.DebugToggles
import exh.eh.EHentaiUpdateHelper
import exh.isEhBasedSource
import exh.util.await
import exh.util.trimOrNull
import java.util.Date
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -76,6 +78,8 @@ class MangaAllInOnePresenter(
private val scope = CoroutineScope(Job() + Dispatchers.Default)
private val customMangaManager: CustomMangaManager by injectLazy()
/**
* Whether the chapter list has been requested to the source.
*/
@ -196,6 +200,47 @@ class MangaAllInOnePresenter(
}
}
fun updateMangaInfo(
title: String?,
author: String?,
artist: String?,
description: String?
// tags: Array<String>?
) {
if (manga.source == LocalSource.ID) {
manga.title = if (title.isNullOrBlank()) manga.url else title.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)
} else if (resetCover) {
coverCache.deleteCustomCover(manga)
controller.setPaletteColor()
}*/
// controller.updateHeader()
}
/**
* Fetch manga information from source.
*/

View File

@ -7,3 +7,8 @@ fun List<String>.dropEmpty() = filter { it.isNotEmpty() }
fun String.removeArticles(): String {
return this.replace(Regex("^(an|a|the) ", RegexOption.IGNORE_CASE), "")
}
fun String.trimOrNull(): String? {
val trimmed = trim()
return if (trimmed.isBlank()) null else trimmed
}

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/cover_layout"
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="75dp"
android:layout_height="150dp"
android:contentDescription="@string/description_cover"
android:background="@drawable/rounded_rectangle"
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"
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"
android:layout_height="wrap_content"
android:hint="@string/title"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:inputType="text"
android:maxLines="1"/>
<EditText
android:id="@+id/manga_author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Artist"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:inputType="text"
android:maxLines="1"/>
<EditText
android:id="@+id/manga_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Author"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:inputType="text"
android:maxLines="1"/>
<EditText
android:id="@+id/manga_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:hint="@string/description"
android:inputType="text|textMultiLine"
android:scrollHorizontally="false" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp" />
<Button
android:id="@+id/reset_tags"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:textAllCaps="false"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:text="Clear Tags" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/divider"/>
</LinearLayout>

View File

@ -95,4 +95,11 @@
android:title="@string/download_all" />
</menu>
</item>
<item
android:id="@+id/action_edit"
android:icon="@drawable/ic_edit_24dp"
android:title="@string/action_edit"
android:visible="false"
app:showAsAction="never" />
</menu>