Add a special view to replace descriptions for integrated and delegated sources!

As the integrated and delegated websites don't actually have descriptions, just info, I decided to make a special view for them! with all the info you need available to you in front of your face, there is now no need to go searching through the description! This is likely the most work I have put into 1 feature in the whole time I have been developing TachiyomiSY!
This commit is contained in:
Jobobby04 2020-07-25 21:04:13 -04:00
parent a38cb2ab5f
commit 3e9c8dbfd2
55 changed files with 2635 additions and 362 deletions

View File

@ -1,14 +1,18 @@
package eu.kanade.tachiyomi.source.online package eu.kanade.tachiyomi.source.online
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source
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.source.model.SManga
import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadata import exh.metadata.metadata.base.insertFlatMetadata
import exh.source.EnhancedHttpSource
import kotlin.reflect.KClass import kotlin.reflect.KClass
import rx.Completable import rx.Completable
import rx.Single import rx.Single
@ -102,6 +106,24 @@ interface LewdSource<M : RaisedSearchMetadata, I> : CatalogueSource {
} }
} }
fun getDescriptionAdapter(controller: MangaController): RecyclerView.Adapter<*>?
val SManga.id get() = (this as? Manga)?.id val SManga.id get() = (this as? Manga)?.id
val SChapter.mangaId get() = (this as? Chapter)?.manga_id val SChapter.mangaId get() = (this as? Chapter)?.manga_id
companion object {
fun Source.isLewdSource() = (this is LewdSource<*, *> || (this is EnhancedHttpSource && this.enhancedSource is LewdSource<*, *>))
fun Source.getLewdSource(): LewdSource<*, *>? {
return if (!this.isLewdSource()) {
null
} else if (this is LewdSource<*, *>) {
this
} else if (this is EnhancedHttpSource && this.enhancedSource is LewdSource<*, *>) {
this.enhancedSource
} else {
null
}
}
}
} }

View File

@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.debug.DebugToggles import exh.debug.DebugToggles
import exh.eh.EHTags import exh.eh.EHTags
@ -36,11 +37,13 @@ import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.EHentaiSearchMetadata.Companion.EH_GENRE_NAMESPACE import exh.metadata.metadata.EHentaiSearchMetadata.Companion.EH_GENRE_NAMESPACE
import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_LIGHT import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_LIGHT
import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_NORMAL import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_NORMAL
import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_WEAK
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.metadata.nullIfBlank import exh.metadata.nullIfBlank
import exh.metadata.parseHumanReadableByteCount import exh.metadata.parseHumanReadableByteCount
import exh.ui.login.LoginController import exh.ui.login.LoginController
import exh.ui.metadata.adapters.EHentaiDescriptionAdapter
import exh.util.UriFilter import exh.util.UriFilter
import exh.util.UriGroup import exh.util.UriGroup
import exh.util.asObservableWithAsyncStacktrace import exh.util.asObservableWithAsyncStacktrace
@ -477,6 +480,8 @@ class EHentai(
element.text().trim(), element.text().trim(),
if (element.hasClass("gtl")) { if (element.hasClass("gtl")) {
TAG_TYPE_LIGHT TAG_TYPE_LIGHT
} else if (element.hasClass("gtw")) {
TAG_TYPE_WEAK
} else { } else {
TAG_TYPE_NORMAL TAG_TYPE_NORMAL
} }
@ -832,6 +837,10 @@ class EHentai(
return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/" return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/"
} }
override fun getDescriptionAdapter(controller: MangaController): EHentaiDescriptionAdapter {
return EHentaiDescriptionAdapter(controller)
}
companion object { companion object {
private const val QUERY_PREFIX = "?f_apply=Apply+Filter" private const val QUERY_PREFIX = "?f_apply=Apply+Filter"
private const val TR_SUFFIX = "TR" private const val TR_SUFFIX = "TR"

View File

@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.HITOMI_SOURCE_ID import exh.HITOMI_SOURCE_ID
import exh.hitomi.HitomiNozomi import exh.hitomi.HitomiNozomi
@ -27,6 +28,7 @@ import exh.metadata.metadata.HitomiSearchMetadata.Companion.LTN_BASE_URL
import exh.metadata.metadata.HitomiSearchMetadata.Companion.TAG_TYPE_DEFAULT import exh.metadata.metadata.HitomiSearchMetadata.Companion.TAG_TYPE_DEFAULT
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.ui.metadata.adapters.HitomiDescriptionAdapter
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -422,6 +424,10 @@ class Hitomi(val context: Context) : HttpSource(), LewdSource<HitomiSearchMetada
return "https://hitomi.la/manga/${uri.pathSegments[1].substringBefore('.')}.html" return "https://hitomi.la/manga/${uri.pathSegments[1].substringBefore('.')}.html"
} }
override fun getDescriptionAdapter(controller: MangaController): HitomiDescriptionAdapter {
return HitomiDescriptionAdapter(controller)
}
companion object { companion object {
private val INDEX_VERSION_CACHE_TIME_MS = 1000 * 60 * 10 private val INDEX_VERSION_CACHE_TIME_MS = 1000 * 60 * 10
private val PAGE_SIZE = 25 private val PAGE_SIZE = 25

View File

@ -21,11 +21,14 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.NHENTAI_SOURCE_ID import exh.NHENTAI_SOURCE_ID
import exh.metadata.metadata.NHentaiSearchMetadata import exh.metadata.metadata.NHentaiSearchMetadata
import exh.metadata.metadata.NHentaiSearchMetadata.Companion.TAG_TYPE_DEFAULT import exh.metadata.metadata.NHentaiSearchMetadata.Companion.TAG_TYPE_DEFAULT
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.ui.metadata.adapters.NHentaiDescriptionAdapter
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
@ -195,7 +198,7 @@ class NHentai(val context: Context) : HttpSource(), LewdSource<NHentaiSearchMeta
tags.clear() tags.clear()
}?.forEach { }?.forEach {
if (it.first != null && it.second != null) { if (it.first != null && it.second != null) {
tags.add(RaisedTag(it.first!!, it.second!!, TAG_TYPE_DEFAULT)) tags.add(RaisedTag(it.first!!, it.second!!, if (it.first == "category") TAG_TYPE_VIRTUAL else TAG_TYPE_DEFAULT))
} }
} }
} }
@ -366,6 +369,10 @@ class NHentai(val context: Context) : HttpSource(), LewdSource<NHentaiSearchMeta
return "$baseUrl/g/${uri.pathSegments[1]}/" return "$baseUrl/g/${uri.pathSegments[1]}/"
} }
override fun getDescriptionAdapter(controller: MangaController): NHentaiDescriptionAdapter {
return NHentaiDescriptionAdapter(controller)
}
companion object { companion object {
private val GALLERY_JSON_REGEX = Regex(".parse\\(\"(.*)\"\\);") private val GALLERY_JSON_REGEX = Regex(".parse\\(\"(.*)\"\\);")
private val UNICODE_ESCAPE_REGEX = Regex("\\\\u([0-9a-fA-F]{4})") private val UNICODE_ESCAPE_REGEX = Regex("\\\\u([0-9a-fA-F]{4})")

View File

@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
import exh.metadata.metadata.PervEdenLang import exh.metadata.metadata.PervEdenLang
@ -20,6 +21,7 @@ import exh.metadata.metadata.PervEdenSearchMetadata
import exh.metadata.metadata.PervEdenSearchMetadata.Companion.TAG_TYPE_DEFAULT import exh.metadata.metadata.PervEdenSearchMetadata.Companion.TAG_TYPE_DEFAULT
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.ui.metadata.adapters.PervEdenDescriptionAdapter
import exh.util.UriFilter import exh.util.UriFilter
import exh.util.UriGroup import exh.util.UriGroup
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
@ -358,6 +360,10 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang, val context: Con
return newUri.toString() return newUri.toString()
} }
override fun getDescriptionAdapter(controller: MangaController): PervEdenDescriptionAdapter {
return PervEdenDescriptionAdapter(controller)
}
companion object { companion object {
val DATE_FORMAT = SimpleDateFormat("MMM d, yyyy", Locale.US).apply { val DATE_FORMAT = SimpleDateFormat("MMM d, yyyy", Locale.US).apply {
timeZone = TimeZone.getTimeZone("GMT") timeZone = TimeZone.getTimeZone("GMT")

View File

@ -14,10 +14,12 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.EIGHTMUSES_SOURCE_ID import exh.EIGHTMUSES_SOURCE_ID
import exh.metadata.metadata.EightMusesSearchMetadata import exh.metadata.metadata.EightMusesSearchMetadata
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.ui.metadata.adapters.EightMusesDescriptionAdapter
import exh.util.CachedField import exh.util.CachedField
import exh.util.NakedTrie import exh.util.NakedTrie
import exh.util.await import exh.util.await
@ -397,4 +399,8 @@ class EightMuses(val context: Context) :
} }
return "/comics/album/${path.joinToString("/")}" return "/comics/album/${path.joinToString("/")}"
} }
override fun getDescriptionAdapter(controller: MangaController): EightMusesDescriptionAdapter {
return EightMusesDescriptionAdapter(controller)
}
} }

View File

@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.HBROWSE_SOURCE_ID import exh.HBROWSE_SOURCE_ID
import exh.metadata.metadata.HBrowseSearchMetadata import exh.metadata.metadata.HBrowseSearchMetadata
@ -25,6 +26,7 @@ import exh.metadata.metadata.base.RaisedTag
import exh.search.Namespace import exh.search.Namespace
import exh.search.SearchEngine import exh.search.SearchEngine
import exh.search.Text import exh.search.Text
import exh.ui.metadata.adapters.HBrowseDescriptionAdapter
import exh.util.await import exh.util.await
import exh.util.dropBlank import exh.util.dropBlank
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
@ -475,6 +477,10 @@ class HBrowse(val context: Context) : HttpSource(), LewdSource<HBrowseSearchMeta
return "$baseUrl/${uri.pathSegments.first()}" return "$baseUrl/${uri.pathSegments.first()}"
} }
override fun getDescriptionAdapter(controller: MangaController): HBrowseDescriptionAdapter {
return HBrowseDescriptionAdapter(controller)
}
companion object { companion object {
private val PAGE_LIST_REGEX = Regex("list *= *(\\[.*]);") private val PAGE_LIST_REGEX = Regex("list *= *(\\[.*]);")
private val TOTAL_PAGES_REGEX = Regex("totalPages *= *([0-9]*);") private val TOTAL_PAGES_REGEX = Regex("totalPages *= *([0-9]*);")

View File

@ -9,12 +9,14 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.metadata.HentaiCafeSearchMetadata import exh.metadata.metadata.HentaiCafeSearchMetadata
import exh.metadata.metadata.HentaiCafeSearchMetadata.Companion.TAG_TYPE_DEFAULT import exh.metadata.metadata.HentaiCafeSearchMetadata.Companion.TAG_TYPE_DEFAULT
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.HentaiCafeDescriptionAdapter
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -111,4 +113,8 @@ class HentaiCafe(delegate: HttpSource, val context: Context) :
"https://hentai.cafe/$lcFirstPathSegment" "https://hentai.cafe/$lcFirstPathSegment"
} }
} }
override fun getDescriptionAdapter(controller: MangaController): HentaiCafeDescriptionAdapter {
return HentaiCafeDescriptionAdapter(controller)
}
} }

View File

@ -9,10 +9,13 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.metadata.PururinSearchMetadata import exh.metadata.metadata.PururinSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.PururinDescriptionAdapter
import exh.util.dropBlank import exh.util.dropBlank
import exh.util.trimAll import exh.util.trimAll
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
@ -89,10 +92,11 @@ class Pururin(delegate: HttpSource, val context: Context) :
else -> { else -> {
value.select("a").forEach { link -> value.select("a").forEach { link ->
val searchUrl = Uri.parse(link.attr("href")) val searchUrl = Uri.parse(link.attr("href"))
val namespace = searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2]
tags += RaisedTag( tags += RaisedTag(
searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2], namespace,
searchUrl.lastPathSegment!!.substringBefore("."), searchUrl.lastPathSegment!!.substringBefore("."),
PururinSearchMetadata.TAG_TYPE_DEFAULT if (namespace != PururinSearchMetadata.TAG_NAMESPACE_CATEGORY) PururinSearchMetadata.TAG_TYPE_DEFAULT else TAG_TYPE_VIRTUAL
) )
} }
} }
@ -109,4 +113,8 @@ class Pururin(delegate: HttpSource, val context: Context) :
override fun mapUrlToMangaUrl(uri: Uri): String? { override fun mapUrlToMangaUrl(uri: Uri): String? {
return "${PururinSearchMetadata.BASE_URL}/gallery/${uri.pathSegments[1]}/${uri.lastPathSegment}" return "${PururinSearchMetadata.BASE_URL}/gallery/${uri.pathSegments[1]}/${uri.lastPathSegment}"
} }
override fun getDescriptionAdapter(controller: MangaController): PururinDescriptionAdapter {
return PururinDescriptionAdapter(controller)
}
} }

View File

@ -9,12 +9,14 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.metadata.TsuminoSearchMetadata import exh.metadata.metadata.TsuminoSearchMetadata
import exh.metadata.metadata.TsuminoSearchMetadata.Companion.TAG_TYPE_DEFAULT import exh.metadata.metadata.TsuminoSearchMetadata.Companion.TAG_TYPE_DEFAULT
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.TsuminoDescriptionAdapter
import exh.util.dropBlank import exh.util.dropBlank
import exh.util.trimAll import exh.util.trimAll
import exh.util.urlImportFetchSearchManga import exh.util.urlImportFetchSearchManga
@ -83,6 +85,12 @@ class Tsumino(delegate: HttpSource, val context: Context) :
input.getElementById("Rating")?.text()?.let { input.getElementById("Rating")?.text()?.let {
ratingString = it.trim() ratingString = it.trim()
val ratingString = ratingString
if (!ratingString.isNullOrBlank()) {
averageRating = RATING_FLOAT_REGEX.find(ratingString)?.groups?.get(1)?.value?.toFloatOrNull()
userRatings = RATING_USERS_REGEX.find(ratingString)?.groups?.get(1)?.value?.toLongOrNull()
favorites = RATING_FAVORITES_REGEX.find(ratingString)?.groups?.get(1)?.value?.toLongOrNull()
}
} }
input.getElementById("Category")?.children()?.first()?.text()?.let { input.getElementById("Category")?.children()?.first()?.text()?.let {
@ -133,5 +141,12 @@ class Tsumino(delegate: HttpSource, val context: Context) :
companion object { companion object {
val TM_DATE_FORMAT = SimpleDateFormat("yyyy MMM dd", Locale.US) val TM_DATE_FORMAT = SimpleDateFormat("yyyy MMM dd", Locale.US)
val RATING_FLOAT_REGEX = "([0-9].*) \\(".toRegex()
val RATING_USERS_REGEX = "\\(([0-9].*) users".toRegex()
val RATING_FAVORITES_REGEX = "/ ([0-9].*) favs".toRegex()
}
override fun getDescriptionAdapter(controller: MangaController): TsuminoDescriptionAdapter {
return TsuminoDescriptionAdapter(controller)
} }
} }

View File

@ -36,7 +36,7 @@ import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource.Companion.getLewdSource
import eu.kanade.tachiyomi.ui.base.controller.FabController import eu.kanade.tachiyomi.ui.base.controller.FabController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
@ -56,7 +56,9 @@ import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersSettingsSheet
import eu.kanade.tachiyomi.ui.manga.chapter.DeleteChaptersDialog import eu.kanade.tachiyomi.ui.manga.chapter.DeleteChaptersDialog
import eu.kanade.tachiyomi.ui.manga.chapter.DownloadCustomChaptersDialog import eu.kanade.tachiyomi.ui.manga.chapter.DownloadCustomChaptersDialog
import eu.kanade.tachiyomi.ui.manga.chapter.MangaChaptersHeaderAdapter import eu.kanade.tachiyomi.ui.manga.chapter.MangaChaptersHeaderAdapter
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoButtonsAdapter
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoHeaderAdapter import eu.kanade.tachiyomi.ui.manga.info.MangaInfoHeaderAdapter
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoItemAdapter
import eu.kanade.tachiyomi.ui.manga.track.TrackController import eu.kanade.tachiyomi.ui.manga.track.TrackController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.recent.history.HistoryController import eu.kanade.tachiyomi.ui.recent.history.HistoryController
@ -71,7 +73,6 @@ import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import exh.metadata.metadata.base.FlatMetadata import exh.metadata.metadata.base.FlatMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.source.EnhancedHttpSource
import java.io.IOException import java.io.IOException
import kotlinx.android.synthetic.main.main_activity.root_coordinator import kotlinx.android.synthetic.main.main_activity.root_coordinator
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
@ -133,6 +134,9 @@ class MangaController :
private val coverCache: CoverCache by injectLazy() private val coverCache: CoverCache by injectLazy()
private var mangaInfoAdapter: MangaInfoHeaderAdapter? = null private var mangaInfoAdapter: MangaInfoHeaderAdapter? = null
private var mangaInfoItemAdapter: MangaInfoItemAdapter? = null
private var mangaInfoButtonsAdapter: MangaInfoButtonsAdapter? = null
private var mangaMetaInfoAdapter: RecyclerView.Adapter<*>? = null
private var chaptersHeaderAdapter: MangaChaptersHeaderAdapter? = null private var chaptersHeaderAdapter: MangaChaptersHeaderAdapter? = null
private var chaptersAdapter: ChaptersAdapter? = null private var chaptersAdapter: ChaptersAdapter? = null
@ -201,13 +205,35 @@ class MangaController :
super.onViewCreated(view) super.onViewCreated(view)
if (manga == null || source == null) return if (manga == null || source == null) return
val adapters: MutableList<RecyclerView.Adapter<out RecyclerView.ViewHolder>?> = mutableListOf()
// Init RecyclerView and adapter // Init RecyclerView and adapter
mangaInfoAdapter = MangaInfoHeaderAdapter(this, fromSource) mangaInfoAdapter = MangaInfoHeaderAdapter(this)
adapters += mangaInfoAdapter
val thisSourceAsLewdSource = presenter.source.getLewdSource()
if (thisSourceAsLewdSource != null) {
mangaMetaInfoAdapter = thisSourceAsLewdSource.getDescriptionAdapter(this)
mangaMetaInfoAdapter?.let { adapters += it }
}
mangaInfoItemAdapter = MangaInfoItemAdapter(this, fromSource)
adapters += mangaInfoItemAdapter
if (!preferences.recommendsInOverflow().get() || smartSearchConfig != null) {
mangaInfoButtonsAdapter = MangaInfoButtonsAdapter(this)
adapters += mangaInfoButtonsAdapter
}
chaptersHeaderAdapter = MangaChaptersHeaderAdapter(this) chaptersHeaderAdapter = MangaChaptersHeaderAdapter(this)
adapters += chaptersHeaderAdapter
chaptersAdapter = ChaptersAdapter(this, view.context) chaptersAdapter = ChaptersAdapter(this, view.context)
binding.recycler.adapter = ConcatAdapter(mangaInfoAdapter, chaptersHeaderAdapter, chaptersAdapter) adapters += chaptersAdapter
binding.recycler.adapter = ConcatAdapter(adapters)
binding.recycler.layoutManager = LinearLayoutManager(view.context) binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL)) binding.recycler.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
binding.recycler.setHasFixedSize(true) binding.recycler.setHasFixedSize(true)
@ -360,11 +386,11 @@ class MangaController :
// SY --> // SY -->
fun onNextMetaInfo(flatMetadata: FlatMetadata) { fun onNextMetaInfo(flatMetadata: FlatMetadata) {
presenter.meta = if (presenter.source is LewdSource<*, *>) { val thisSourceAsLewdSource = presenter.source.getLewdSource()
flatMetadata.raise((presenter.source as LewdSource<*, *>).metaClass) if (thisSourceAsLewdSource != null) {
} else if (presenter.source is EnhancedHttpSource && (presenter.source as EnhancedHttpSource).enhancedSource is LewdSource<*, *>) { presenter.meta = flatMetadata.raise(thisSourceAsLewdSource.metaClass)
flatMetadata.raise(((presenter.source as EnhancedHttpSource).enhancedSource as LewdSource<*, *>).metaClass) mangaMetaInfoAdapter?.notifyDataSetChanged()
} else null }
} }
// SY <-- // SY <--
@ -379,7 +405,8 @@ class MangaController :
fun onNextMangaInfo(manga: Manga, source: Source /* SY --> */, meta: RaisedSearchMetadata? /* SY <-- */) { fun onNextMangaInfo(manga: Manga, source: Source /* SY --> */, meta: RaisedSearchMetadata? /* SY <-- */) {
if (manga.initialized) { if (manga.initialized) {
// Update view. // Update view.
mangaInfoAdapter?.update(manga, source /* SY --> */, meta /* SY <-- */) mangaInfoAdapter?.update(manga, source)
mangaInfoItemAdapter?.update(manga, source, presenter.meta)
} else { } else {
// Initialize manga. // Initialize manga.
fetchMangaInfoFromSource() fetchMangaInfoFromSource()

View File

@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.LewdSource import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.source.online.LewdSource.Companion.isLewdSource
import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
@ -116,7 +117,7 @@ class MangaPresenter(
super.onCreate(savedState) super.onCreate(savedState)
// SY --> // SY -->
if (manga.initialized && (source is LewdSource<*, *> || (source is EnhancedHttpSource && source.enhancedSource is LewdSource<*, *>))) { if (manga.initialized && source.isLewdSource()) {
getMangaMetaObservable().subscribeLatestCache({ view, flatMetadata -> if (flatMetadata != null) view.onNextMetaInfo(flatMetadata) else Timber.d("Invalid metadata") }) getMangaMetaObservable().subscribeLatestCache({ view, flatMetadata -> if (flatMetadata != null) view.onNextMetaInfo(flatMetadata) else Timber.d("Invalid metadata") })
} }
// SY <-- // SY <--

View File

@ -0,0 +1,64 @@
package eu.kanade.tachiyomi.ui.manga.info
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.MangaInfoButtonsBinding
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.util.view.visibleIf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class MangaInfoButtonsAdapter(
private val controller: MangaController
) :
RecyclerView.Adapter<MangaInfoButtonsAdapter.HeaderViewHolder>() {
private val preferences: PreferencesHelper by injectLazy()
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: MangaInfoButtonsBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
binding = MangaInfoButtonsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return HeaderViewHolder(binding.root)
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: HeaderViewHolder, position: Int) {
holder.bind()
}
inner class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
// EXH -->
if (controller.smartSearchConfig == null) {
binding.recommendBtn.visibleIf { !preferences.recommendsInOverflow().get() }
binding.recommendBtn.clicks()
.onEach { controller.openRecommends() }
.launchIn(scope)
} else {
if (controller.smartSearchConfig.origMangaId != null) {
binding.mergeBtn.visible()
}
binding.mergeBtn.clicks()
.onEach {
controller.mergeWithAnother()
}
.launchIn(scope)
}
// EXH <--
}
}
}

View File

@ -1,23 +1,17 @@
package eu.kanade.tachiyomi.ui.manga.info package eu.kanade.tachiyomi.ui.manga.info
import android.content.Context
import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import eu.davidea.flexibleadapter.FlexibleAdapter
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.data.glide.GlideApp import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.MangaThumbnail import eu.kanade.tachiyomi.data.glide.MangaThumbnail
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.MangaInfoHeaderBinding import eu.kanade.tachiyomi.databinding.MangaInfoHeaderBinding
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
@ -29,44 +23,30 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.setTooltip import eu.kanade.tachiyomi.util.view.setTooltip
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.util.view.visibleIf
import exh.MERGED_SOURCE_ID import exh.MERGED_SOURCE_ID
import exh.isNamespaceSource
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.util.SourceTagsUtil import exh.util.SourceTagsUtil
import exh.util.makeSearchChip
import exh.util.setChipsExtended
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks import reactivecircus.flowbinding.android.view.clicks
import reactivecircus.flowbinding.android.view.longClicks import reactivecircus.flowbinding.android.view.longClicks
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
class MangaInfoHeaderAdapter( class MangaInfoHeaderAdapter(
private val controller: MangaController, private val controller: MangaController
private val fromSource: Boolean
) : ) :
RecyclerView.Adapter<MangaInfoHeaderAdapter.HeaderViewHolder>() { RecyclerView.Adapter<MangaInfoHeaderAdapter.HeaderViewHolder>() {
private var manga: Manga = controller.presenter.manga private var manga: Manga = controller.presenter.manga
private var source: Source = controller.presenter.source private var source: Source = controller.presenter.source
private var trackCount: Int = 0 private var trackCount: Int = 0
// SY -->
private val preferences: PreferencesHelper by injectLazy()
private var meta: RaisedSearchMetadata? = controller.presenter.meta
// SY <--
private val scope = CoroutineScope(Job() + Dispatchers.Main) private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: MangaInfoHeaderBinding private lateinit var binding: MangaInfoHeaderBinding
private var initialLoad: Boolean = true
private var currentMangaThumbnail: MangaThumbnail? = null private var currentMangaThumbnail: MangaThumbnail? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
@ -80,18 +60,15 @@ class MangaInfoHeaderAdapter(
holder.bind() holder.bind()
} }
val tagsAdapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter(null)
/** /**
* Update the view with manga information. * Update the view with manga information.
* *
* @param manga manga object containing information about manga. * @param manga manga object containing information about manga.
* @param source the source of the manga. * @param source the source of the manga.
*/ */
fun update(manga: Manga, source: Source, meta: RaisedSearchMetadata?) { fun update(manga: Manga, source: Source) {
this.manga = manga this.manga = manga
this.source = source this.source = source
this.meta = meta
notifyDataSetChanged() notifyDataSetChanged()
} }
@ -226,15 +203,6 @@ class MangaInfoHeaderAdapter(
} }
.launchIn(scope) .launchIn(scope)
binding.mangaSummary.longClicks()
.onEach {
controller.activity?.copyToClipboard(
view.context.getString(R.string.description),
binding.mangaSummary.text.toString()
)
}
.launchIn(scope)
binding.mangaCover.longClicks() binding.mangaCover.longClicks()
.onEach { .onEach {
controller.activity?.copyToClipboard( controller.activity?.copyToClipboard(
@ -244,28 +212,6 @@ class MangaInfoHeaderAdapter(
} }
.launchIn(scope) .launchIn(scope)
// EXH -->
if (controller.smartSearchConfig == null) {
binding.recommendBtn.visibleIf { !preferences.recommendsInOverflow().get() }
binding.recommendBtn.clicks()
.onEach { controller.openRecommends() }
.launchIn(scope)
} else {
if (controller.smartSearchConfig.origMangaId != null) { binding.mergeBtn.visible() }
binding.mergeBtn.clicks()
.onEach {
controller.mergeWithAnother()
}
.launchIn(scope)
}
// EXH <--
// SY -->
binding.mangaNamespaceTagsRecycler.layoutManager = LinearLayoutManager(itemView.context)
binding.mangaNamespaceTagsRecycler.adapter = tagsAdapter
// SY <--
setMangaInfo(manga, source) setMangaInfo(manga, source)
} }
@ -275,7 +221,6 @@ class MangaInfoHeaderAdapter(
* @param manga manga object containing information about manga. * @param manga manga object containing information about manga.
* @param source the source of the manga. * @param source the source of the manga.
*/ */
@ExperimentalCoroutinesApi
private fun setMangaInfo(manga: Manga, source: Source?) { private fun setMangaInfo(manga: Manga, source: Source?) {
// Update full title TextView. // Update full title TextView.
binding.mangaFullTitle.text = if (manga.title.isBlank()) { binding.mangaFullTitle.text = if (manga.title.isBlank()) {
@ -343,100 +288,6 @@ class MangaInfoHeaderAdapter(
.into(it) .into(it)
} }
} }
// Manga info section
val hasInfoContent = !manga.description.isNullOrBlank() || !manga.genre.isNullOrBlank()
showMangaInfo(hasInfoContent)
if (hasInfoContent) {
// Update description TextView.
binding.mangaSummary.text = if (manga.description.isNullOrBlank()) {
view.context.getString(R.string.unknown)
} else {
manga.description
}
// Update genres list
if (!manga.genre.isNullOrBlank()) {
// SY -->
if (source != null && source.isNamespaceSource()) {
val genre = manga.getGenres()
if (!genre.isNullOrEmpty()) {
val namespaceTags = genre.map { SourceTagsUtil().parseTag(it) }
.groupBy { it.first }
.mapValues { values -> values.value.map { makeSearchChip(it.second, controller::performSearch, controller::performGlobalSearch, source.id, itemView.context, it.first) } }
.map { NamespaceTagsItem(it.key, it.value) }
tagsAdapter.updateDataSet(namespaceTags)
}
}
binding.mangaGenresTagsFullChips.setChipsExtended(manga.getGenres(), controller::performSearch, controller::performGlobalSearch, source?.id ?: 0)
binding.mangaGenresTagsCompactChips.setChipsExtended(manga.getGenres(), controller::performSearch, controller::performGlobalSearch, source?.id ?: 0)
// SY <--
} else {
binding.mangaGenresTagsWrapper.gone()
}
// Handle showing more or less info
merge(view.clicks(), binding.mangaSummary.clicks(), binding.mangaInfoToggle.clicks())
.onEach { toggleMangaInfo(view.context) }
.launchIn(scope)
// Expand manga info if navigated from source listing
if (initialLoad && fromSource) {
toggleMangaInfo(view.context)
initialLoad = false
}
}
}
private fun showMangaInfo(visible: Boolean) {
binding.mangaSummaryLabel.visibleIf { visible }
binding.mangaSummary.visibleIf { visible }
binding.mangaGenresTagsWrapper.visibleIf { visible }
binding.mangaInfoToggle.visibleIf { visible }
}
private fun toggleMangaInfo(context: Context) {
val isExpanded =
binding.mangaInfoToggle.text == context.getString(R.string.manga_info_collapse)
with(binding.mangaInfoToggle) {
text = if (isExpanded) {
context.getString(R.string.manga_info_expand)
} else {
context.getString(R.string.manga_info_collapse)
}
icon = if (isExpanded) {
context.getDrawable(R.drawable.ic_baseline_expand_more_24dp)
} else {
context.getDrawable(R.drawable.ic_baseline_expand_less_24dp)
}
}
with(binding.mangaSummary) {
maxLines =
if (isExpanded) {
2
} else {
Int.MAX_VALUE
}
ellipsize =
if (isExpanded) {
TextUtils.TruncateAt.END
} else {
null
}
}
binding.mangaGenresTagsCompact.visibleIf { isExpanded }
// SY -->
if (source.isNamespaceSource()) {
binding.mangaNamespaceTagsRecycler.visibleIf { !isExpanded }
} else {
binding.mangaGenresTagsFullChips.visibleIf { !isExpanded }
}
// SY <--
} }
/** /**

View File

@ -0,0 +1,214 @@
package eu.kanade.tachiyomi.ui.manga.info
import android.content.Context
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.MangaInfoItemBinding
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.util.view.visibleIf
import exh.isEhBasedSource
import exh.isNamespaceSource
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.util.SourceTagsUtil
import exh.util.makeSearchChip
import exh.util.setChipsExtended
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
import reactivecircus.flowbinding.android.view.longClicks
import uy.kohesive.injekt.api.get
class MangaInfoItemAdapter(
private val controller: MangaController,
private val fromSource: Boolean
) :
RecyclerView.Adapter<MangaInfoItemAdapter.HeaderViewHolder>() {
private var manga: Manga = controller.presenter.manga
private var source: Source = controller.presenter.source
private var meta: RaisedSearchMetadata? = controller.presenter.meta
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: MangaInfoItemBinding
private var initialLoad: Boolean = true
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
binding = MangaInfoItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return HeaderViewHolder(binding.root)
}
private var mangaTagsInfoAdapter: FlexibleAdapter<IFlexible<*>>? = FlexibleAdapter(null)
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: HeaderViewHolder, position: Int) {
holder.bind()
}
/**
* Update the view with manga information.
*
* @param manga manga object containing information about manga.
* @param source the source of the manga.
*/
fun update(manga: Manga, source: Source, meta: RaisedSearchMetadata?) {
this.manga = manga
this.source = source
this.meta = meta
notifyDataSetChanged()
}
inner class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
binding.mangaSummary.longClicks()
.onEach {
controller.activity?.copyToClipboard(
view.context.getString(R.string.description),
binding.mangaSummary.text.toString()
)
}
.launchIn(scope)
binding.genreGroups.layoutManager = LinearLayoutManager(itemView.context)
binding.genreGroups.adapter = mangaTagsInfoAdapter
setMangaInfo(manga, source)
}
/**
* Update the view with manga information.
*
* @param manga manga object containing information about manga.
* @param source the source of the manga.
*/
@ExperimentalCoroutinesApi
private fun setMangaInfo(manga: Manga, source: Source?) {
// Manga info section
val hasInfoContent = !manga.description.isNullOrBlank() || !manga.genre.isNullOrBlank()
showMangaInfo(hasInfoContent)
if (hasInfoContent) {
// Update description TextView.
binding.mangaSummary.text = if (manga.description.isNullOrBlank()) {
view.context.getString(R.string.unknown)
} else {
manga.description
}
if (binding.mangaSummary.text == "meta") {
binding.mangaSummary.gone()
binding.mangaSummaryLabel.setText(R.string.tags)
}
// Update genres list
if (!manga.genre.isNullOrBlank()) {
// SY -->
if (source != null && source.isNamespaceSource()) {
val metaTags = meta?.tags?.filterNot { it.type == TAG_TYPE_VIRTUAL }?.groupBy { it.namespace }
var namespaceTags: List<NamespaceTagsItem> = emptyList()
if (source.isEhBasedSource() && metaTags != null && metaTags.all { it.key != null }) {
namespaceTags = metaTags
.mapValues { values -> values.value.map { makeSearchChip(it.name, controller::performSearch, controller::performGlobalSearch, source.id, itemView.context, it.namespace, it.type) } }
.map { NamespaceTagsItem(it.key!!, it.value) }
} else {
val genre = manga.getGenres()
if (!genre.isNullOrEmpty()) {
namespaceTags = genre.map { SourceTagsUtil().parseTag(it) }
.groupBy { it.first }
.mapValues { values -> values.value.map { makeSearchChip(it.second, controller::performSearch, controller::performGlobalSearch, source.id, itemView.context, it.first) } }
.map { NamespaceTagsItem(it.key, it.value) }
}
}
mangaTagsInfoAdapter?.updateDataSet(namespaceTags)
}
binding.mangaGenresTagsFullChips.setChipsExtended(manga.getGenres(), controller::performSearch, controller::performGlobalSearch, source?.id ?: 0)
binding.mangaGenresTagsCompactChips.setChipsExtended(manga.getGenres(), controller::performSearch, controller::performGlobalSearch, source?.id ?: 0)
// SY <--
} else {
binding.mangaGenresTagsWrapper.gone()
}
// Handle showing more or less info
merge(view.clicks(), binding.mangaSummary.clicks(), binding.mangaInfoToggle.clicks())
.onEach { toggleMangaInfo(view.context) }
.launchIn(scope)
// Expand manga info if navigated from source listing
if (initialLoad && fromSource) {
toggleMangaInfo(view.context)
initialLoad = false
}
}
}
private fun showMangaInfo(visible: Boolean) {
binding.mangaSummaryLabel.visibleIf { visible }
binding.mangaSummary.visibleIf { visible }
binding.mangaGenresTagsWrapper.visibleIf { visible }
binding.mangaInfoToggle.visibleIf { visible }
}
private fun toggleMangaInfo(context: Context) {
val isExpanded =
binding.mangaInfoToggle.text == context.getString(R.string.manga_info_collapse)
with(binding.mangaInfoToggle) {
text = if (isExpanded) {
context.getString(R.string.manga_info_expand)
} else {
context.getString(R.string.manga_info_collapse)
}
icon = if (isExpanded) {
context.getDrawable(R.drawable.ic_baseline_expand_more_24dp)
} else {
context.getDrawable(R.drawable.ic_baseline_expand_less_24dp)
}
}
with(binding.mangaSummary) {
maxLines =
if (isExpanded) {
2
} else {
Int.MAX_VALUE
}
ellipsize =
if (isExpanded) {
TextUtils.TruncateAt.END
} else {
null
}
}
binding.mangaGenresTagsCompact.visibleIf { isExpanded }
// SY -->
if (!source.isNamespaceSource()) {
binding.mangaGenresTagsFullChips.visibleIf { !isExpanded }
} else {
binding.genreGroups.visibleIf { !isExpanded }
}
// SY <--
}
}
}

View File

@ -58,4 +58,4 @@ fun isLewdSource(source: Long) = source in 6900..6999 ||
fun Source.isEhBasedSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID fun Source.isEhBasedSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID
fun Source.isNamespaceSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID || id == NHENTAI_SOURCE_ID || id == HITOMI_SOURCE_ID || id == PURURIN_SOURCE_ID || id == TSUMINO_SOURCE_ID fun Source.isNamespaceSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID || id == NHENTAI_SOURCE_ID || id == HITOMI_SOURCE_ID || id == PURURIN_SOURCE_ID || id == TSUMINO_SOURCE_ID || id == EIGHTMUSES_SOURCE_ID || id == HBROWSE_SOURCE_ID

View File

@ -1,13 +1,14 @@
package exh.metadata.metadata package exh.metadata.metadata
import android.content.Context
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.EX_DATE_FORMAT import exh.metadata.EX_DATE_FORMAT
import exh.metadata.ONGOING_SUFFIX import exh.metadata.ONGOING_SUFFIX
import exh.metadata.humanReadableByteCount import exh.metadata.humanReadableByteCount
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.plusAssign
import java.util.Date import java.util.Date
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -76,7 +77,7 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
} }
// Build a nice looking description out of what we know // Build a nice looking description out of what we know
val titleDesc = StringBuilder() /* val titleDesc = StringBuilder()
title?.let { titleDesc += "Title: $it\n" } title?.let { titleDesc += "Title: $it\n" }
altTitle?.let { titleDesc += "Alternate Title: $it\n" } altTitle?.let { titleDesc += "Alternate Title: $it\n" }
@ -99,11 +100,36 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
detailsDesc += "\n" detailsDesc += "\n"
} }
val tagsDesc = tagsToDescription() val tagsDesc = tagsToDescription()*/
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) manga.description = "meta" /*listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank) .filter(String::isNotBlank)
.joinToString(separator = "\n") .joinToString(separator = "\n")*/
}
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
val pairs = mutableListOf<Pair<String, String>>()
gId?.let { pairs += Pair(context.getString(R.string.id), it) }
gToken?.let { pairs += Pair(context.getString(R.string.token), it) }
exh?.let { pairs += Pair(context.getString(R.string.is_exhentai_gallery), context.getString(if (it) android.R.string.yes else android.R.string.no)) }
thumbnailUrl?.let { pairs += Pair(context.getString(R.string.thumbnail_url), it) }
title?.let { pairs += Pair(context.getString(R.string.title), it) }
altTitle?.let { pairs += Pair(context.getString(R.string.alt_title), it) }
genre?.let { pairs += Pair(context.getString(R.string.genre), it) }
datePosted?.let { pairs += Pair(context.getString(R.string.date_posted), EX_DATE_FORMAT.format(Date(it))) }
parent?.let { pairs += Pair(context.getString(R.string.parent), it) }
visible?.let { pairs += Pair(context.getString(R.string.visible), it) }
language?.let { pairs += Pair(context.getString(R.string.language), it) }
translated?.let { pairs += Pair("Translated", context.getString(if (it) android.R.string.yes else android.R.string.no)) }
size?.let { pairs += Pair(context.getString(R.string.gallery_size), humanReadableByteCount(it, true)) }
length?.let { pairs += Pair(context.getString(R.string.page_count), it.toString()) }
favorites?.let { pairs += Pair(context.getString(R.string.total_favorites), it.toString()) }
ratingCount?.let { pairs += Pair(context.getString(R.string.total_ratings), it.toString()) }
averageRating?.let { pairs += Pair(context.getString(R.string.average_rating), it.toString()) }
aged.let { pairs += Pair(context.getString(R.string.aged), context.getString(if (it) android.R.string.yes else android.R.string.no)) }
lastUpdateCheck.let { pairs += Pair(context.getString(R.string.last_update_check), EX_DATE_FORMAT.format(Date(it))) }
return pairs
} }
companion object { companion object {
@ -112,6 +138,7 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
const val TAG_TYPE_NORMAL = 0 const val TAG_TYPE_NORMAL = 0
const val TAG_TYPE_LIGHT = 1 const val TAG_TYPE_LIGHT = 1
const val TAG_TYPE_WEAK = 2
const val EH_GENRE_NAMESPACE = "genre" const val EH_GENRE_NAMESPACE = "genre"
private const val EH_ARTIST_NAMESPACE = "artist" private const val EH_ARTIST_NAMESPACE = "artist"

View File

@ -1,8 +1,9 @@
package exh.metadata.metadata package exh.metadata.metadata
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.plusAssign
class EightMusesSearchMetadata : RaisedSearchMetadata() { class EightMusesSearchMetadata : RaisedSearchMetadata() {
var path: List<String> = emptyList() var path: List<String> = emptyList()
@ -26,14 +27,25 @@ class EightMusesSearchMetadata : RaisedSearchMetadata() {
manga.genre = tagsToGenreString() manga.genre = tagsToGenreString()
val titleDesc = StringBuilder() /*val titleDesc = StringBuilder()
title?.let { titleDesc += "Title: $it\n" } title?.let { titleDesc += "Title: $it\n" }
val tagsDesc = tagsToDescription() val tagsDesc = tagsToDescription()*/
manga.description = listOf(titleDesc.toString(), tagsDesc.toString()) manga.description = "meta" /*listOf(titleDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank) .filter(String::isNotBlank)
.joinToString(separator = "\n") .joinToString(separator = "\n")*/
}
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
val pairs = mutableListOf<Pair<String, String>>()
title?.let { pairs += Pair(context.getString(R.string.title), it) }
val path = path.joinToString("/", prefix = "/")
if (path.isNotBlank()) {
pairs += Pair(context.getString(R.string.path), path)
}
thumbnailUrl?.let { pairs += Pair(context.getString(R.string.thumbnail_url), it) }
return pairs
} }
companion object { companion object {

View File

@ -1,9 +1,10 @@
package exh.metadata.metadata package exh.metadata.metadata
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.EightMusesSearchMetadata.Companion.ARTIST_NAMESPACE import exh.metadata.metadata.EightMusesSearchMetadata.Companion.ARTIST_NAMESPACE
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.plusAssign
class HBrowseSearchMetadata : RaisedSearchMetadata() { class HBrowseSearchMetadata : RaisedSearchMetadata() {
var hbId: Long? = null var hbId: Long? = null
@ -27,15 +28,25 @@ class HBrowseSearchMetadata : RaisedSearchMetadata() {
manga.artist = tags.ofNamespace(ARTIST_NAMESPACE).joinToString { it.name } manga.artist = tags.ofNamespace(ARTIST_NAMESPACE).joinToString { it.name }
val titleDesc = StringBuilder() manga.genre = tagsToGenreString()
/*val titleDesc = StringBuilder()
title?.let { titleDesc += "Title: $it\n" } title?.let { titleDesc += "Title: $it\n" }
length?.let { titleDesc += "Length: $it page(s)\n" } length?.let { titleDesc += "Length: $it page(s)\n" }
val tagsDesc = tagsToDescription() val tagsDesc = tagsToDescription()*/
manga.description = listOf(titleDesc.toString(), tagsDesc.toString()) manga.description = "meta" /*listOf(titleDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank) .filter(String::isNotBlank)
.joinToString(separator = "\n") .joinToString(separator = "\n")*/
}
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
val pairs = mutableListOf<Pair<String, String>>()
hbId?.let { pairs += Pair(context.getString(R.string.id), it.toString()) }
title?.let { pairs += Pair(context.getString(R.string.title), it) }
length?.let { pairs += Pair(context.getString(R.string.page_count), it.toString()) }
return pairs
} }
companion object { companion object {

View File

@ -1,5 +1,7 @@
package exh.metadata.metadata package exh.metadata.metadata
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
@ -30,16 +32,26 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() {
// Not available // Not available
manga.status = SManga.UNKNOWN manga.status = SManga.UNKNOWN
val detailsDesc = "Title: $title\n" +
"Artist: $artist\n"
val tagsDesc = tagsToDescription()
manga.genre = tagsToGenreString() manga.genre = tagsToGenreString()
manga.description = listOf(detailsDesc, tagsDesc.toString()) /* val detailsDesc = "Title: $title\n" +
"Artist: $artist\n"
val tagsDesc = tagsToDescription()*/
manga.description = "meta" /*listOf(detailsDesc, tagsDesc.toString())
.filter(String::isNotBlank) .filter(String::isNotBlank)
.joinToString(separator = "\n") .joinToString(separator = "\n")*/
}
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
val pairs = mutableListOf<Pair<String, String>>()
hcId?.let { pairs += Pair(context.getString(R.string.id), it) }
readerId?.let { pairs += Pair(context.getString(R.string.reader_id), it) }
thumbnailUrl?.let { pairs += Pair(context.getString(R.string.thumbnail_url), it) }
title?.let { pairs += Pair(context.getString(R.string.title), it) }
artist?.let { pairs += Pair(context.getString(R.string.artist), it) }
return pairs
} }
companion object { companion object {

View File

@ -1,5 +1,7 @@
package exh.metadata.metadata package exh.metadata.metadata
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.EX_DATE_FORMAT import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
@ -37,17 +39,25 @@ class HitomiSearchMetadata : RaisedSearchMetadata() {
override fun copyTo(manga: SManga) { override fun copyTo(manga: SManga) {
thumbnailUrl?.let { manga.thumbnail_url = it } thumbnailUrl?.let { manga.thumbnail_url = it }
val titleDesc = StringBuilder()
title?.let { title?.let {
manga.title = it manga.title = it
}
// Copy tags -> genres
manga.genre = tagsToGenreString()
manga.artist = artists.joinToString()
manga.status = SManga.UNKNOWN
/*val titleDesc = StringBuilder()
title?.let {
titleDesc += "Title: $it\n" titleDesc += "Title: $it\n"
} }
val detailsDesc = StringBuilder() val detailsDesc = StringBuilder()
manga.artist = artists.joinToString()
detailsDesc += "Artist(s): ${manga.artist}\n" detailsDesc += "Artist(s): ${manga.artist}\n"
group?.let { group?.let {
@ -74,16 +84,35 @@ class HitomiSearchMetadata : RaisedSearchMetadata() {
detailsDesc += "Upload date: ${EX_DATE_FORMAT.format(Date(it))}\n" detailsDesc += "Upload date: ${EX_DATE_FORMAT.format(Date(it))}\n"
} }
manga.status = SManga.UNKNOWN val tagsDesc = tagsToDescription()*/
// Copy tags -> genres manga.description = "meta" /*listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
manga.genre = tagsToGenreString()
val tagsDesc = tagsToDescription()
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank) .filter(String::isNotBlank)
.joinToString(separator = "\n") .joinToString(separator = "\n")*/
}
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
val pairs = mutableListOf<Pair<String, String>>()
hlId?.let { pairs += Pair(context.getString(R.string.id), it) }
title?.let { pairs += Pair(context.getString(R.string.title), it) }
thumbnailUrl?.let { pairs += Pair(context.getString(R.string.thumbnail_url), it) }
val artists = artists.joinToString()
if (artists.isNotBlank()) {
pairs += Pair(context.getString(R.string.artist), artists)
}
group?.let { pairs += Pair(context.getString(R.string.group), it) }
type?.let { pairs += Pair(context.getString(R.string.genre), it) }
language?.let { pairs += Pair(context.getString(R.string.language), it) }
val series = series.joinToString()
if (series.isNotBlank()) {
pairs += Pair(context.getString(R.string.series), series)
}
val characters = characters.joinToString()
if (characters.isNotBlank()) {
pairs += Pair(context.getString(R.string.characters), characters)
}
uploadDate?.let { pairs += Pair(context.getString(R.string.date_posted), EX_DATE_FORMAT.format(Date(it))) }
return pairs
} }
companion object { companion object {

View File

@ -1,12 +1,12 @@
package exh.metadata.metadata package exh.metadata.metadata
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.EX_DATE_FORMAT import exh.metadata.EX_DATE_FORMAT
import exh.metadata.ONGOING_SUFFIX import exh.metadata.ONGOING_SUFFIX
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.nullIfBlank
import exh.plusAssign
import java.util.Date import java.util.Date
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -77,7 +77,7 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
} }
} }
val titleDesc = StringBuilder() /*val titleDesc = StringBuilder()
englishTitle?.let { titleDesc += "English Title: $it\n" } englishTitle?.let { titleDesc += "English Title: $it\n" }
japaneseTitle?.let { titleDesc += "Japanese Title: $it\n" } japaneseTitle?.let { titleDesc += "Japanese Title: $it\n" }
shortTitle?.let { titleDesc += "Short Title: $it\n" } shortTitle?.let { titleDesc += "Short Title: $it\n" }
@ -89,11 +89,27 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
favoritesCount?.let { detailsDesc += "Favorited: $it times\n" } favoritesCount?.let { detailsDesc += "Favorited: $it times\n" }
scanlator?.nullIfBlank()?.let { detailsDesc += "Scanlator: $it\n" } scanlator?.nullIfBlank()?.let { detailsDesc += "Scanlator: $it\n" }
val tagsDesc = tagsToDescription() val tagsDesc = tagsToDescription()*/
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) manga.description = "meta" /*listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank) .filter(String::isNotBlank)
.joinToString(separator = "\n") .joinToString(separator = "\n")*/
}
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
val pairs = mutableListOf<Pair<String, String>>()
nhId?.let { pairs += Pair(context.getString(R.string.id), it.toString()) }
uploadDate?.let { pairs += Pair(context.getString(R.string.date_posted), EX_DATE_FORMAT.format(Date(it * 1000))) }
favoritesCount?.let { pairs += Pair(context.getString(R.string.total_favorites), it.toString()) }
mediaId?.let { pairs += Pair(context.getString(R.string.media_id), it) }
japaneseTitle?.let { pairs += Pair(context.getString(R.string.japanese_title), it) }
englishTitle?.let { pairs += Pair(context.getString(R.string.english_title), it) }
shortTitle?.let { pairs += Pair(context.getString(R.string.short_title), it) }
coverImageType?.let { pairs += Pair(context.getString(R.string.cover_image_file_type), it) }
pageImageTypes.size.let { pairs += Pair(context.getString(R.string.page_count), it.toString()) }
thumbnailImageType?.let { pairs += Pair(context.getString(R.string.thumbnail_image_file_type), it) }
scanlator?.let { pairs += Pair(context.getString(R.string.scanlator), it) }
return pairs
} }
companion object { companion object {
@ -106,7 +122,7 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
const val BASE_URL = "https://nhentai.net" const val BASE_URL = "https://nhentai.net"
private const val NHENTAI_ARTIST_NAMESPACE = "artist" private const val NHENTAI_ARTIST_NAMESPACE = "artist"
private const val NHENTAI_CATEGORIES_NAMESPACE = "category" const val NHENTAI_CATEGORIES_NAMESPACE = "category"
fun typeToExtension(t: String?) = fun typeToExtension(t: String?) =
when (t) { when (t) {

View File

@ -1,12 +1,13 @@
package exh.metadata.metadata package exh.metadata.metadata
import android.content.Context
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.PERV_EDEN_EN_SOURCE_ID import exh.PERV_EDEN_EN_SOURCE_ID
import exh.PERV_EDEN_IT_SOURCE_ID import exh.PERV_EDEN_IT_SOURCE_ID
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.RaisedTitle import exh.metadata.metadata.base.RaisedTitle
import exh.plusAssign
class PervEdenSearchMetadata : RaisedSearchMetadata() { class PervEdenSearchMetadata : RaisedSearchMetadata() {
var pvId: String? = null var pvId: String? = null
@ -36,9 +37,28 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
url?.let { manga.url = it } url?.let { manga.url = it }
thumbnailUrl?.let { manga.thumbnail_url = it } thumbnailUrl?.let { manga.thumbnail_url = it }
val titleDesc = StringBuilder()
title?.let { title?.let {
manga.title = it manga.title = it
}
artist?.let {
manga.artist = it
}
status?.let {
manga.status = when (it) {
"Ongoing" -> SManga.ONGOING
"Completed", "Suspended" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
// Copy tags -> genres
manga.genre = tagsToGenreString()
/*val titleDesc = StringBuilder()
title?.let {
titleDesc += "Title: $it\n" titleDesc += "Title: $it\n"
} }
if (altTitles.isNotEmpty()) { if (altTitles.isNotEmpty()) {
@ -50,7 +70,6 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
val detailsDesc = StringBuilder() val detailsDesc = StringBuilder()
artist?.let { artist?.let {
manga.artist = it
detailsDesc += "Artist: $it\n" detailsDesc += "Artist: $it\n"
} }
@ -59,11 +78,6 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
} }
status?.let { status?.let {
manga.status = when (it) {
"Ongoing" -> SManga.ONGOING
"Completed", "Suspended" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
detailsDesc += "Status: $it\n" detailsDesc += "Status: $it\n"
} }
@ -71,14 +85,30 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
detailsDesc += "Rating: %.2\n".format(it) detailsDesc += "Rating: %.2\n".format(it)
} }
// Copy tags -> genres
manga.genre = tagsToGenreString()
val tagsDesc = tagsToDescription() val tagsDesc = tagsToDescription()*/
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) manga.description = "meta" /*listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank) .filter(String::isNotBlank)
.joinToString(separator = "\n") .joinToString(separator = "\n")*/
}
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
val pairs = mutableListOf<Pair<String, String>>()
pvId?.let { pairs += Pair(context.getString(R.string.id), it) }
url?.let { pairs += Pair(context.getString(R.string.url), it) }
thumbnailUrl?.let { pairs += Pair(context.getString(R.string.thumbnail_url), it) }
title?.let { pairs += Pair(context.getString(R.string.title), it) }
val altTitles = altTitles.joinToString()
if (altTitles.isNotBlank()) {
pairs += Pair(context.getString(R.string.alt_titles), altTitles)
}
artist?.let { pairs += Pair(context.getString(R.string.artist), it) }
type?.let { pairs += Pair(context.getString(R.string.genre), it) }
rating?.let { pairs += Pair(context.getString(R.string.average_rating), it.toString()) }
status?.let { pairs += Pair(context.getString(R.string.status), it) }
lang?.let { pairs += Pair(context.getString(R.string.language), it) }
return pairs
} }
companion object { companion object {

View File

@ -1,8 +1,9 @@
package exh.metadata.metadata package exh.metadata.metadata
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.plusAssign
class PururinSearchMetadata : RaisedSearchMetadata() { class PururinSearchMetadata : RaisedSearchMetadata() {
var prId: Int? = null var prId: Int? = null
@ -42,7 +43,7 @@ class PururinSearchMetadata : RaisedSearchMetadata() {
manga.genre = tagsToGenreString() manga.genre = tagsToGenreString()
val titleDesc = StringBuilder() /*val titleDesc = StringBuilder()
title?.let { titleDesc += "English Title: $it\n" } title?.let { titleDesc += "English Title: $it\n" }
altTitle?.let { titleDesc += "Japanese Title: $it\n" } altTitle?.let { titleDesc += "Japanese Title: $it\n" }
@ -52,11 +53,26 @@ class PururinSearchMetadata : RaisedSearchMetadata() {
fileSize?.let { detailsDesc += "Size: $it\n" } fileSize?.let { detailsDesc += "Size: $it\n" }
ratingCount?.let { detailsDesc += "Rating: $averageRating ($ratingCount)\n" } ratingCount?.let { detailsDesc += "Rating: $averageRating ($ratingCount)\n" }
val tagsDesc = tagsToDescription() val tagsDesc = tagsToDescription()*/
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) manga.description = "meta" /*listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank) .filter(String::isNotBlank)
.joinToString(separator = "\n") .joinToString(separator = "\n")*/
}
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
val pairs = mutableListOf<Pair<String, String>>()
prId?.let { pairs += Pair(context.getString(R.string.id), it.toString()) }
title?.let { pairs += Pair(context.getString(R.string.title), it) }
altTitle?.let { pairs += Pair(context.getString(R.string.alt_title), it) }
thumbnailUrl?.let { pairs += Pair(context.getString(R.string.thumbnail_url), it) }
uploaderDisp?.let { pairs += Pair(context.getString(R.string.uploader_capital), it) }
uploader?.let { pairs += Pair(context.getString(R.string.uploader), it) }
pages?.let { pairs += Pair(context.getString(R.string.page_count), it.toString()) }
fileSize?.let { pairs += Pair(context.getString(R.string.gallery_size), it) }
ratingCount?.let { pairs += Pair(context.getString(R.string.total_ratings), it.toString()) }
averageRating?.let { pairs += Pair(context.getString(R.string.average_rating), it.toString()) }
return pairs
} }
companion object { companion object {
@ -66,6 +82,7 @@ class PururinSearchMetadata : RaisedSearchMetadata() {
const val TAG_TYPE_DEFAULT = 0 const val TAG_TYPE_DEFAULT = 0
private const val TAG_NAMESPACE_ARTIST = "artist" private const val TAG_NAMESPACE_ARTIST = "artist"
const val TAG_NAMESPACE_CATEGORY = "category"
val BASE_URL = "https://pururin.io" val BASE_URL = "https://pururin.io"
} }

View File

@ -1,11 +1,14 @@
package exh.metadata.metadata package exh.metadata.metadata
import android.content.Context
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.EX_DATE_FORMAT import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.plusAssign import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale
class TsuminoSearchMetadata : RaisedSearchMetadata() { class TsuminoSearchMetadata : RaisedSearchMetadata() {
var tmId: Int? = null var tmId: Int? = null
@ -20,6 +23,12 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
var ratingString: String? = null var ratingString: String? = null
var averageRating: Float? = null
var userRatings: Long? = null
var favorites: Long? = null
var category: String? = null var category: String? = null
var collection: String? = null var collection: String? = null
@ -38,7 +47,10 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
manga.status = SManga.UNKNOWN manga.status = SManga.UNKNOWN
val titleDesc = "Title: $title\n" // Copy tags -> genres
manga.genre = tagsToGenreString()
/*val titleDesc = "Title: $title\n"
val detailsDesc = StringBuilder() val detailsDesc = StringBuilder()
uploader?.let { detailsDesc += "Uploader: $it\n" } uploader?.let { detailsDesc += "Uploader: $it\n" }
@ -59,14 +71,36 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
detailsDesc += "Character: $charactersString\n" detailsDesc += "Character: $charactersString\n"
} }
// Copy tags -> genres val tagsDesc = tagsToDescription()*/
manga.genre = tagsToGenreString()
val tagsDesc = tagsToDescription() manga.description = "meta" /*listOf(titleDesc, detailsDesc.toString(), tagsDesc.toString())
manga.description = listOf(titleDesc, detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank) .filter(String::isNotBlank)
.joinToString(separator = "\n") .joinToString(separator = "\n")*/
}
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
val pairs = mutableListOf<Pair<String, String>>()
tmId?.let { pairs += Pair(context.getString(R.string.id), it.toString()) }
title?.let { pairs += Pair(context.getString(R.string.title), it) }
uploader?.let { pairs += Pair(context.getString(R.string.uploader), it) }
uploadDate?.let { pairs += Pair(context.getString(R.string.date_posted), EX_DATE_FORMAT.format(Date(it))) }
length?.let { pairs += Pair(context.getString(R.string.page_count), it.toString()) }
ratingString?.let { pairs += Pair(context.getString(R.string.rating_string), it) }
averageRating?.let { pairs += Pair(context.getString(R.string.average_rating), it.toString()) }
userRatings?.let { pairs += Pair(context.getString(R.string.total_ratings), it.toString()) }
favorites?.let { pairs += Pair(context.getString(R.string.total_favorites), it.toString()) }
category?.let { pairs += Pair(context.getString(R.string.genre), it) }
collection?.let { pairs += Pair(context.getString(R.string.collection), it) }
group?.let { pairs += Pair(context.getString(R.string.group), it) }
val parodiesString = parody.joinToString()
if (parodiesString.isNotEmpty()) {
pairs += Pair(context.getString(R.string.parodies), parodiesString)
}
val charactersString = character.joinToString()
if (charactersString.isNotEmpty()) {
pairs += Pair(context.getString(R.string.characters), charactersString)
}
return pairs
} }
companion object { companion object {
@ -76,6 +110,8 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
val BASE_URL = "https://www.tsumino.com" val BASE_URL = "https://www.tsumino.com"
val TSUMINO_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US)
fun tmIdFromUrl(url: String) = fun tmIdFromUrl(url: String) =
Uri.parse(url).lastPathSegment Uri.parse(url).lastPathSegment

View File

@ -1,5 +1,6 @@
package exh.metadata.metadata.base package exh.metadata.metadata.base
import android.content.Context
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.forEach import exh.metadata.forEach
@ -112,6 +113,8 @@ abstract class RaisedSearchMetadata {
} }
} }
abstract fun getExtraInfoPairs(context: Context): List<Pair<String, String>>
companion object { companion object {
// Virtual tags allow searching of otherwise unindexed fields // Virtual tags allow searching of otherwise unindexed fields
const val TAG_TYPE_VIRTUAL = -2 const val TAG_TYPE_VIRTUAL = -2

View File

@ -0,0 +1,41 @@
package exh.ui.metadata
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.databinding.MetadataViewItemBinding
import kotlin.math.floor
class MetadataViewAdapter(private var data: List<Pair<String, String>>) :
RecyclerView.Adapter<MetadataViewAdapter.ViewHolder>() {
private lateinit var binding: MetadataViewItemBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MetadataViewAdapter.ViewHolder {
binding = MetadataViewItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding.root)
}
fun update(data: List<Pair<String, String>>) {
this.data = data
notifyDataSetChanged()
}
// binds the data to the TextView in each cell
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
// total number of cells
override fun getItemCount(): Int = data.size * 2
// stores and recycles views as they are scrolled off screen
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(position: Int) {
if (data.isEmpty()) return
val dataPosition = floor(position / 2F).toInt()
binding.infoText.text = if (position % 2 == 0) data[dataPosition].first else data[dataPosition].second
}
}
}

View File

@ -0,0 +1,87 @@
package exh.ui.metadata
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.MetadataViewControllerBinding
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.LewdSource.Companion.getLewdSource
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.metadata.base.FlatMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MetadataViewController : NucleusController<MetadataViewControllerBinding, MetadataViewPresenter> {
constructor(manga: Manga?) : super(
Bundle().apply {
putLong(MangaController.MANGA_EXTRA, manga?.id ?: 0)
}
) {
this.manga = manga
if (manga != null) {
source = Injekt.get<SourceManager>().getOrStub(manga.source)
}
}
constructor(mangaId: Long) : this(
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking()
)
@Suppress("unused")
constructor(bundle: Bundle) : this(bundle.getLong(MangaController.MANGA_EXTRA))
var data = emptyList<Pair<String, String>>()
var adapter: MetadataViewAdapter? = null
var manga: Manga? = null
private set
var source: Source? = null
private set
override fun getTitle(): String? {
return manga?.title
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = MetadataViewControllerBinding.inflate(inflater)
return binding.root
}
override fun createPresenter(): MetadataViewPresenter {
return MetadataViewPresenter(
manga!!,
source!!
)
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
if (manga == null || source == null) return
binding.recycler.layoutManager = GridLayoutManager(view.context, 2)
adapter = MetadataViewAdapter(data)
binding.recycler.adapter = adapter
binding.recycler.setHasFixedSize(true)
}
fun onNextMetaInfo(flatMetadata: FlatMetadata) {
val thisSourceAsLewdSource = presenter.source.getLewdSource()
if (thisSourceAsLewdSource != null) {
presenter.meta = flatMetadata.raise(thisSourceAsLewdSource.metaClass)
}
}
fun onNextMangaInfo(meta: RaisedSearchMetadata?) {
val context = view?.context ?: return
data = meta?.getExtraInfoPairs(context) ?: emptyList()
adapter?.update(data)
}
}

View File

@ -0,0 +1,48 @@
package exh.ui.metadata
import android.os.Bundle
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import exh.metadata.metadata.base.FlatMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MetadataViewPresenter(
val manga: Manga,
val source: Source,
val preferences: PreferencesHelper = Injekt.get(),
private val db: DatabaseHelper = Injekt.get()
) : BasePresenter<MetadataViewController>() {
var meta: RaisedSearchMetadata? = null
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
getMangaMetaObservable().subscribeLatestCache({ view, flatMetadata -> if (flatMetadata != null) view.onNextMetaInfo(flatMetadata) else Timber.d("Invalid metadata") })
getMangaObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache({ view, _ -> view.onNextMangaInfo(meta) })
}
private fun getMangaObservable(): Observable<Manga> {
return db.getManga(manga.url, manga.source).asRxObservable()
}
private fun getMangaMetaObservable(): Observable<FlatMetadata?> {
val mangaId = manga.id
return if (mangaId != null) {
db.getFlatMetadataForManga(mangaId).asRxObservable()
.observeOn(AndroidSchedulers.mainThread())
} else Observable.just(null)
}
}

View File

@ -0,0 +1,124 @@
package exh.ui.metadata.adapters
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterEhBinding
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.getResourceColor
import exh.metadata.EX_DATE_FORMAT
import exh.metadata.humanReadableByteCount
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.ui.metadata.MetadataViewController
import java.util.Date
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
class EHentaiDescriptionAdapter(
private val controller: MangaController
) :
RecyclerView.Adapter<EHentaiDescriptionAdapter.EHentaiDescriptionViewHolder>() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: DescriptionAdapterEhBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EHentaiDescriptionViewHolder {
binding = DescriptionAdapterEhBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return EHentaiDescriptionViewHolder(binding.root)
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: EHentaiDescriptionViewHolder, position: Int) {
holder.bind()
}
inner class EHentaiDescriptionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
val meta = controller.presenter.meta
if (meta == null || meta !is EHentaiSearchMetadata) return
val genre = meta.genre
if (genre != null) {
val pair = when (genre) {
"doujinshi" -> Pair("#fc4e4e", R.string.doujinshi)
"manga" -> Pair("#e78c1a", R.string.manga)
"artistcg" -> Pair("#dde500", R.string.artist_cg)
"gamecg" -> Pair("#05bf0b", R.string.game_cg)
"western" -> Pair("#14e723", R.string.western)
"non-h" -> Pair("#08d7e2", R.string.non_h)
"imageset" -> Pair("#5f5fff", R.string.image_set)
"cosplay" -> Pair("#9755f5", R.string.cosplay)
"asianporn" -> Pair("#fe93ff", R.string.asian_porn)
"misc" -> Pair("#9e9e9e", R.string.misc)
else -> Pair("", 0)
}
if (pair.first.isNotBlank()) {
binding.genre.setBackgroundColor(Color.parseColor(pair.first))
binding.genre.text = itemView.context.getString(pair.second)
} else binding.genre.text = genre
} else binding.genre.setText(R.string.unknown)
binding.visible.text = itemView.context.getString(R.string.is_visible, meta.visible ?: itemView.context.getString(R.string.unknown))
binding.favorites.text = (meta.favorites ?: 0).toString()
val drawable = itemView.context.getDrawable(R.drawable.ic_favorite_24dp)
drawable?.setTint(itemView.context.getResourceColor(R.attr.colorAccent))
binding.favorites.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
binding.whenPosted.text = EX_DATE_FORMAT.format(Date(meta.datePosted ?: 0))
binding.uploader.text = meta.uploader ?: itemView.context.getString(R.string.unknown)
binding.size.text = humanReadableByteCount(meta.size ?: 0, true)
binding.pages.text = itemView.context.getString(R.string.num_pages, meta.length ?: 0)
val language = meta.language ?: itemView.context.getString(R.string.unknown)
binding.language.text = if (meta.translated == true) {
itemView.context.getString(R.string.language_translated, language)
} else {
language
}
val ratingFloat = meta.averageRating?.toFloat()
val name = when (((ratingFloat ?: 100F) * 2).roundToInt()) {
0 -> R.string.rating0
1 -> R.string.rating1
2 -> R.string.rating2
3 -> R.string.rating3
4 -> R.string.rating4
5 -> R.string.rating5
6 -> R.string.rating6
7 -> R.string.rating7
8 -> R.string.rating8
9 -> R.string.rating9
10 -> R.string.rating10
else -> R.string.no_rating
}
binding.ratingBar.rating = ratingFloat ?: 0F
binding.rating.text = if (meta.ratingCount != null) {
itemView.context.getString(R.string.rating_view, itemView.context.getString(name), (ratingFloat ?: 0F).toString(), meta.ratingCount ?: 0)
} else {
itemView.context.getString(R.string.rating_view_no_count, itemView.context.getString(name), (ratingFloat ?: 0F).toString())
}
binding.moreInfo.clicks()
.onEach {
controller.router?.pushController(
MetadataViewController(
controller.manga
).withFadeTransaction()
)
}
.launchIn(scope)
}
}
}

View File

@ -0,0 +1,57 @@
package exh.ui.metadata.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapter8mBinding
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.metadata.EightMusesSearchMetadata
import exh.ui.metadata.MetadataViewController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
class EightMusesDescriptionAdapter(
private val controller: MangaController
) :
RecyclerView.Adapter<EightMusesDescriptionAdapter.EightMusesDescriptionViewHolder>() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: DescriptionAdapter8mBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EightMusesDescriptionViewHolder {
binding = DescriptionAdapter8mBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return EightMusesDescriptionViewHolder(binding.root)
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: EightMusesDescriptionViewHolder, position: Int) {
holder.bind()
}
inner class EightMusesDescriptionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
val meta = controller.presenter.meta
if (meta == null || meta !is EightMusesSearchMetadata) return
binding.title.text = meta.title ?: itemView.context.getString(R.string.unknown)
binding.moreInfo.clicks()
.onEach {
controller.router?.pushController(
MetadataViewController(
controller.manga
).withFadeTransaction()
)
}
.launchIn(scope)
}
}
}

View File

@ -0,0 +1,57 @@
package exh.ui.metadata.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterHbBinding
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.metadata.HBrowseSearchMetadata
import exh.ui.metadata.MetadataViewController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
class HBrowseDescriptionAdapter(
private val controller: MangaController
) :
RecyclerView.Adapter<HBrowseDescriptionAdapter.HBrowseDescriptionViewHolder>() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: DescriptionAdapterHbBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HBrowseDescriptionViewHolder {
binding = DescriptionAdapterHbBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return HBrowseDescriptionViewHolder(binding.root)
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: HBrowseDescriptionViewHolder, position: Int) {
holder.bind()
}
inner class HBrowseDescriptionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
val meta = controller.presenter.meta
if (meta == null || meta !is HBrowseSearchMetadata) return
binding.pages.text = itemView.context.getString(R.string.num_pages, meta.length ?: 0)
binding.moreInfo.clicks()
.onEach {
controller.router?.pushController(
MetadataViewController(
controller.manga
).withFadeTransaction()
)
}
.launchIn(scope)
}
}
}

View File

@ -0,0 +1,57 @@
package exh.ui.metadata.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterHcBinding
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.metadata.HentaiCafeSearchMetadata
import exh.ui.metadata.MetadataViewController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
class HentaiCafeDescriptionAdapter(
private val controller: MangaController
) :
RecyclerView.Adapter<HentaiCafeDescriptionAdapter.HentaiCafeDescriptionViewHolder>() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: DescriptionAdapterHcBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HentaiCafeDescriptionViewHolder {
binding = DescriptionAdapterHcBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return HentaiCafeDescriptionViewHolder(binding.root)
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: HentaiCafeDescriptionViewHolder, position: Int) {
holder.bind()
}
inner class HentaiCafeDescriptionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
val meta = controller.presenter.meta
if (meta == null || meta !is HentaiCafeSearchMetadata) return
binding.artist.text = meta.artist ?: itemView.context.getString(R.string.unknown)
binding.moreInfo.clicks()
.onEach {
controller.router?.pushController(
MetadataViewController(
controller.manga
).withFadeTransaction()
)
}
.launchIn(scope)
}
}
}

View File

@ -0,0 +1,84 @@
package exh.ui.metadata.adapters
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterHiBinding
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.HitomiSearchMetadata
import exh.ui.metadata.MetadataViewController
import java.util.Date
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
class HitomiDescriptionAdapter(
private val controller: MangaController
) :
RecyclerView.Adapter<HitomiDescriptionAdapter.HitomiDescriptionViewHolder>() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: DescriptionAdapterHiBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HitomiDescriptionViewHolder {
binding = DescriptionAdapterHiBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return HitomiDescriptionViewHolder(binding.root)
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: HitomiDescriptionViewHolder, position: Int) {
holder.bind()
}
inner class HitomiDescriptionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
val meta = controller.presenter.meta
if (meta == null || meta !is HitomiSearchMetadata) return
val genre = meta.type
if (genre != null) {
val pair = when (genre) {
"doujinshi" -> Pair("#fc4e4e", R.string.doujinshi)
"manga" -> Pair("#e78c1a", R.string.manga)
"artist CG" -> Pair("#dde500", R.string.artist_cg)
"game CG" -> Pair("#05bf0b", R.string.game_cg)
"western" -> Pair("#14e723", R.string.western)
"non-H" -> Pair("#08d7e2", R.string.non_h)
"image Set" -> Pair("#5f5fff", R.string.image_set)
"cosplay" -> Pair("#9755f5", R.string.cosplay)
"asian Porn" -> Pair("#fe93ff", R.string.asian_porn)
"misc" -> Pair("#9e9e9e", R.string.misc)
else -> Pair("", 0)
}
if (pair.first.isNotBlank()) {
binding.genre.setBackgroundColor(Color.parseColor(pair.first))
binding.genre.text = itemView.context.getString(pair.second)
} else binding.genre.text = genre
} else binding.genre.setText(R.string.unknown)
binding.whenPosted.text = EX_DATE_FORMAT.format(Date(meta.uploadDate ?: 0))
binding.group.text = meta.group ?: itemView.context.getString(R.string.unknown)
binding.language.text = meta.language ?: itemView.context.getString(R.string.unknown)
binding.moreInfo.clicks()
.onEach {
controller.router?.pushController(
MetadataViewController(
controller.manga
).withFadeTransaction()
)
}
.launchIn(scope)
}
}
}

View File

@ -0,0 +1,103 @@
package exh.ui.metadata.adapters
import android.annotation.SuppressLint
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterNhBinding
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.getResourceColor
import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.NHentaiSearchMetadata
import exh.ui.metadata.MetadataViewController
import java.util.Date
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
class NHentaiDescriptionAdapter(
private val controller: MangaController
) :
RecyclerView.Adapter<NHentaiDescriptionAdapter.NHentaiDescriptionViewHolder>() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: DescriptionAdapterNhBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NHentaiDescriptionViewHolder {
binding = DescriptionAdapterNhBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return NHentaiDescriptionViewHolder(binding.root)
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: NHentaiDescriptionViewHolder, position: Int) {
holder.bind()
}
inner class NHentaiDescriptionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
val meta = controller.presenter.meta
if (meta == null || meta !is NHentaiSearchMetadata) return
var category: String? = null
meta.tags.filter { it.namespace == NHentaiSearchMetadata.NHENTAI_CATEGORIES_NAMESPACE }.let {
if (it.isNotEmpty()) category = it.joinToString(transform = { it.name })
}
if (category != null) {
val pair = when (category) {
"doujinshi" -> Pair("#fc4e4e", R.string.doujinshi)
"manga" -> Pair("#e78c1a", R.string.manga)
"artistcg" -> Pair("#dde500", R.string.artist_cg)
"gamecg" -> Pair("#05bf0b", R.string.game_cg)
"western" -> Pair("#14e723", R.string.western)
"non-h" -> Pair("#08d7e2", R.string.non_h)
"imageset" -> Pair("#5f5fff", R.string.image_set)
"cosplay" -> Pair("#9755f5", R.string.cosplay)
"asianporn" -> Pair("#fe93ff", R.string.asian_porn)
"misc" -> Pair("#9e9e9e", R.string.misc)
else -> Pair("", 0)
}
if (pair.first.isNotBlank()) {
binding.genre.setBackgroundColor(Color.parseColor(pair.first))
binding.genre.text = itemView.context.getString(pair.second)
} else binding.genre.text = category
} else binding.genre.setText(R.string.unknown)
meta.favoritesCount?.let {
if (it == 0L) return@let
binding.favorites.text = it.toString()
val drawable = itemView.context.getDrawable(R.drawable.ic_favorite_24dp)
drawable?.setTint(itemView.context.getResourceColor(R.attr.colorAccent))
binding.favorites.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
}
binding.whenPosted.text = EX_DATE_FORMAT.format(Date((meta.uploadDate ?: 0) * 1000))
binding.pages.text = itemView.context.getString(R.string.num_pages, meta.pageImageTypes.size)
@SuppressLint("SetTextI18n")
binding.id.text = "#" + (meta.nhId ?: 0)
binding.moreInfo.clicks()
.onEach {
controller.router?.pushController(
MetadataViewController(
controller.manga
).withFadeTransaction()
)
}
.launchIn(scope)
}
}
}

View File

@ -0,0 +1,98 @@
package exh.ui.metadata.adapters
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterPeBinding
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.metadata.PervEdenSearchMetadata
import exh.ui.metadata.MetadataViewController
import java.util.Locale
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
class PervEdenDescriptionAdapter(
private val controller: MangaController
) :
RecyclerView.Adapter<PervEdenDescriptionAdapter.PervEdenDescriptionViewHolder>() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: DescriptionAdapterPeBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PervEdenDescriptionViewHolder {
binding = DescriptionAdapterPeBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PervEdenDescriptionViewHolder(binding.root)
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: PervEdenDescriptionViewHolder, position: Int) {
holder.bind()
}
inner class PervEdenDescriptionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
val meta = controller.presenter.meta
if (meta == null || meta !is PervEdenSearchMetadata) return
val genre = meta.type
if (genre != null) {
val pair = when (genre) {
"Doujinshi" -> Pair("#fc4e4e", R.string.doujinshi)
"Japanese Manga" -> Pair("#e78c1a", R.string.manga)
"Korean Manhwa" -> Pair("#dde500", R.string.manhwa)
"Chinese Manhua" -> Pair("#05bf0b", R.string.manhua)
"Comic" -> Pair("#14e723", R.string.comic)
else -> Pair("", 0)
}
if (pair.first.isNotBlank()) {
binding.genre.setBackgroundColor(Color.parseColor(pair.first))
binding.genre.text = itemView.context.getString(pair.second)
} else binding.genre.text = genre
} else binding.genre.setText(R.string.unknown)
val language = meta.lang
binding.language.text = if (language != null) {
val local = Locale(language)
local.displayName
} else itemView.context.getString(R.string.unknown)
val name = when (((meta.rating ?: 100F) * 2).roundToInt()) {
0 -> R.string.rating0
1 -> R.string.rating1
2 -> R.string.rating2
3 -> R.string.rating3
4 -> R.string.rating4
5 -> R.string.rating5
6 -> R.string.rating6
7 -> R.string.rating7
8 -> R.string.rating8
9 -> R.string.rating9
10 -> R.string.rating10
else -> R.string.no_rating
}
binding.ratingBar.rating = meta.rating ?: 0F
binding.rating.text = itemView.context.getString(R.string.rating_view_no_count, itemView.context.getString(name), (meta.rating ?: 0F).toString())
binding.moreInfo.clicks()
.onEach {
controller.router?.pushController(
MetadataViewController(
controller.manga
).withFadeTransaction()
)
}
.launchIn(scope)
}
}
}

View File

@ -0,0 +1,102 @@
package exh.ui.metadata.adapters
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterPuBinding
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.metadata.PururinSearchMetadata
import exh.metadata.metadata.PururinSearchMetadata.Companion.TAG_NAMESPACE_CATEGORY
import exh.ui.metadata.MetadataViewController
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
class PururinDescriptionAdapter(
private val controller: MangaController
) :
RecyclerView.Adapter<PururinDescriptionAdapter.PururinDescriptionViewHolder>() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: DescriptionAdapterPuBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PururinDescriptionViewHolder {
binding = DescriptionAdapterPuBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PururinDescriptionViewHolder(binding.root)
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: PururinDescriptionViewHolder, position: Int) {
holder.bind()
}
inner class PururinDescriptionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
val meta = controller.presenter.meta
if (meta == null || meta !is PururinSearchMetadata) return
val genre = meta.tags.find { it.namespace == TAG_NAMESPACE_CATEGORY }
if (genre != null) {
val pair = when (genre.name) {
"doujinshi" -> Pair("#fc4e4e", R.string.doujinshi)
"manga" -> Pair("#e78c1a", R.string.manga)
"artist-cg" -> Pair("#dde500", R.string.artist_cg)
"game-cg" -> Pair("#05bf0b", R.string.game_cg)
"artbook" -> Pair("#5f5fff", R.string.artbook)
"webtoon" -> Pair("#5f5fff", R.string.webtoon)
else -> Pair("", 0)
}
if (pair.first.isNotBlank()) {
binding.genre.setBackgroundColor(Color.parseColor(pair.first))
binding.genre.text = itemView.context.getString(pair.second)
} else binding.genre.text = genre.name
} else binding.genre.setText(R.string.unknown)
binding.uploader.text = meta.uploaderDisp ?: meta.uploader ?: ""
binding.size.text = meta.fileSize ?: itemView.context.getString(R.string.unknown)
binding.pages.text = itemView.context.getString(R.string.num_pages, meta.pages ?: 0)
val ratingFloat = meta.averageRating?.toFloat()
val name = when (((ratingFloat ?: 100F) * 2).roundToInt()) {
0 -> R.string.rating0
1 -> R.string.rating1
2 -> R.string.rating2
3 -> R.string.rating3
4 -> R.string.rating4
5 -> R.string.rating5
6 -> R.string.rating6
7 -> R.string.rating7
8 -> R.string.rating8
9 -> R.string.rating9
10 -> R.string.rating10
else -> R.string.no_rating
}
binding.ratingBar.rating = ratingFloat ?: 0F
binding.rating.text = if (meta.ratingCount != null) {
itemView.context.getString(R.string.rating_view, itemView.context.getString(name), (ratingFloat ?: 0F).toString(), meta.ratingCount ?: 0)
} else {
itemView.context.getString(R.string.rating_view_no_count, itemView.context.getString(name), (ratingFloat ?: 0F).toString())
}
binding.moreInfo.clicks()
.onEach {
controller.router?.pushController(
MetadataViewController(
controller.manga
).withFadeTransaction()
)
}
.launchIn(scope)
}
}
}

View File

@ -0,0 +1,107 @@
package exh.ui.metadata.adapters
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterTsBinding
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.getResourceColor
import exh.metadata.metadata.TsuminoSearchMetadata
import exh.ui.metadata.MetadataViewController
import java.util.Date
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
class TsuminoDescriptionAdapter(
private val controller: MangaController
) :
RecyclerView.Adapter<TsuminoDescriptionAdapter.TsuminoDescriptionViewHolder>() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var binding: DescriptionAdapterTsBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TsuminoDescriptionViewHolder {
binding = DescriptionAdapterTsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return TsuminoDescriptionViewHolder(binding.root)
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: TsuminoDescriptionViewHolder, position: Int) {
holder.bind()
}
inner class TsuminoDescriptionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
val meta = controller.presenter.meta
if (meta == null || meta !is TsuminoSearchMetadata) return
val genre = meta.category
if (genre != null) {
val pair = when (genre) {
"Doujinshi" -> Pair("#fc4e4e", R.string.doujinshi)
"Manga" -> Pair("#e78c1a", R.string.manga)
"Artist CG" -> Pair("#dde500", R.string.artist_cg)
"Game CG" -> Pair("#05bf0b", R.string.game_cg)
"Video" -> Pair("#14e723", R.string.video)
else -> Pair("", 0)
}
if (pair.first.isNotBlank()) {
binding.genre.setBackgroundColor(Color.parseColor(pair.first))
binding.genre.text = itemView.context.getString(pair.second)
} else binding.genre.text = genre
} else binding.genre.setText(R.string.unknown)
binding.favorites.text = (meta.favorites ?: 0).toString()
val drawable = itemView.context.getDrawable(R.drawable.ic_favorite_24dp)
drawable?.setTint(itemView.context.getResourceColor(R.attr.colorAccent))
binding.favorites.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
binding.whenPosted.text = TsuminoSearchMetadata.TSUMINO_DATE_FORMAT.format(Date(meta.uploadDate ?: 0))
binding.uploader.text = meta.uploader ?: itemView.context.getString(R.string.unknown)
binding.pages.text = itemView.context.getString(R.string.num_pages, meta.length ?: 0)
val name = when (((meta.averageRating ?: 100F) * 2).roundToInt()) {
0 -> R.string.rating0
1 -> R.string.rating1
2 -> R.string.rating2
3 -> R.string.rating3
4 -> R.string.rating4
5 -> R.string.rating5
6 -> R.string.rating6
7 -> R.string.rating7
8 -> R.string.rating8
9 -> R.string.rating9
10 -> R.string.rating10
else -> R.string.no_rating
}
binding.ratingBar.rating = meta.averageRating ?: 0F
binding.rating.text = if (meta.userRatings != null) {
itemView.context.getString(R.string.rating_view, itemView.context.getString(name), (meta.averageRating ?: 0F).toString(), meta.userRatings ?: 0L)
} else {
itemView.context.getString(R.string.rating_view_no_count, itemView.context.getString(name), (meta.averageRating ?: 0F).toString())
}
binding.moreInfo.clicks()
.onEach {
controller.router?.pushController(
MetadataViewController(
controller.manga
).withFadeTransaction()
)
}
.launchIn(scope)
}
}
}

View File

@ -8,6 +8,11 @@ import android.widget.FrameLayout
import androidx.annotation.Px import androidx.annotation.Px
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
import exh.EH_SOURCE_ID
import exh.EXH_SOURCE_ID
import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_LIGHT
import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_NORMAL
import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_WEAK
inline val View.marginTop: Int inline val View.marginTop: Int
get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin ?: 0 get() = (layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin ?: 0
@ -120,7 +125,7 @@ fun ChipGroup.setChipsExtended(items: List<String>?, onClick: (item: String) ->
} }
} }
fun makeSearchChip(item: String, onClick: (item: String) -> Unit = {}, onLongClick: (item: String) -> Unit = {}, sourceId: Long, context: Context, namespace: String? = null): Chip { fun makeSearchChip(item: String, onClick: (item: String) -> Unit = {}, onLongClick: (item: String) -> Unit = {}, sourceId: Long, context: Context, namespace: String? = null, type: Int? = null): Chip {
return Chip(context).apply { return Chip(context).apply {
text = item text = item
val search = (if (namespace != null) SourceTagsUtil().getWrappedTag(sourceId, namespace = namespace, tag = item) else SourceTagsUtil().getWrappedTag(sourceId, fullTag = item)) ?: item val search = (if (namespace != null) SourceTagsUtil().getWrappedTag(sourceId, namespace = namespace, tag = item) else SourceTagsUtil().getWrappedTag(sourceId, fullTag = item)) ?: item
@ -129,5 +134,13 @@ fun makeSearchChip(item: String, onClick: (item: String) -> Unit = {}, onLongCli
onLongClick(search) onLongClick(search)
false false
} }
if (sourceId == EXH_SOURCE_ID || sourceId == EH_SOURCE_ID) {
chipStrokeWidth = when (type) {
TAG_TYPE_NORMAL -> 5F
TAG_TYPE_LIGHT -> 3F
TAG_TYPE_WEAK -> 0F
else -> chipStrokeWidth
}
}
} }
} }

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="1dip"
android:color="?attr/colorOnSurface" />
</shape>

View File

@ -0,0 +1,35 @@
<?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="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/more_info"
style="@style/Theme.Widget.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/more_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -0,0 +1,146 @@
<?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="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:background="@drawable/rounded_rectangle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/genre"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp" />
</androidx.cardview.widget.CardView>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/pages"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/more_info"
style="@style/Theme.Widget.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/more_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/language"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/size"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/favorites"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/language" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/when_posted"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/size" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/visible"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/favorites" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/uploader"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/when_posted" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatRatingBar
android:id="@+id/rating_bar"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:numStars="5"
android:stepSize="0.1"
android:isIndicator="true"
android:focusable="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/rating"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rating_bar" />
</LinearLayout>

View File

@ -0,0 +1,34 @@
<?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="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/pages"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/more_info"
style="@style/Theme.Widget.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/more_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -0,0 +1,34 @@
<?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="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/artist"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/more_info"
style="@style/Theme.Widget.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/more_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -0,0 +1,80 @@
<?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="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:background="@drawable/rounded_rectangle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/genre"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp" />
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/more_info"
style="@style/Theme.Widget.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/more_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/language"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/when_posted"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/group"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -0,0 +1,90 @@
<?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="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:background="@drawable/rounded_rectangle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/genre"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp" />
</androidx.cardview.widget.CardView>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/pages"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/more_info"
style="@style/Theme.Widget.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/more_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/id"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/favorites"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/when_posted"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -0,0 +1,82 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:background="@drawable/rounded_rectangle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/genre"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp" />
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/more_info"
style="@style/Theme.Widget.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/more_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/language"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatRatingBar
android:id="@+id/rating_bar"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:numStars="5"
android:stepSize="0.1"
android:isIndicator="true"
android:focusable="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/rating"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rating_bar" />
</LinearLayout>

View File

@ -0,0 +1,107 @@
<?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="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:background="@drawable/rounded_rectangle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/genre"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp" />
</androidx.cardview.widget.CardView>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/pages"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/more_info"
style="@style/Theme.Widget.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/more_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/uploader"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/size"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatRatingBar
android:id="@+id/rating_bar"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:numStars="5"
android:stepSize="0.1"
android:isIndicator="true"
android:focusable="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/rating"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rating_bar" />
</LinearLayout>

View File

@ -0,0 +1,117 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:background="@drawable/rounded_rectangle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/genre"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp" />
</androidx.cardview.widget.CardView>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/pages"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/more_info"
style="@style/Theme.Widget.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/more_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/favorites"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/when_posted"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/uploader"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatRatingBar
android:id="@+id/rating_bar"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:numStars="5"
android:stepSize="0.1"
android:isIndicator="true"
android:focusable="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/rating"
style="@style/TextAppearance.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rating_bar" />
</LinearLayout>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/merge_btn"
style="@style/Theme.Widget.Button.FilledAccent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:layout_weight="1"
android:text="@string/merge_with_another_source"
android:visibility="gone"
tools:visibility="visible" />
<Button
android:id="@+id/recommend_btn"
style="@style/Theme.Widget.Button.FilledAccent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:layout_weight="1"
android:text="@string/az_recommends"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>

View File

@ -194,132 +194,4 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<TextView
android:id="@+id/manga_summary_label"
style="@style/TextAppearance.Regular.SubHeading"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="@string/manga_info_about_label"
android:textIsSelectable="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/manga_info_toggle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/manga_info_toggle"
style="@style/Theme.Widget.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="@string/manga_info_expand"
app:icon="@drawable/ic_baseline_expand_more_24dp"
app:iconTint="?attr/colorOnPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/manga_summary"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:maxLines="2"
android:textIsSelectable="false"
tools:text="Summary" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/manga_namespace_tags_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:visibility="gone"
tools:listitem="@layout/manga_info_genre_grouping"/>
<FrameLayout
android:id="@+id/manga_genres_tags_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_full_chips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:chipSpacingHorizontal="4dp" />
<HorizontalScrollView
android:id="@+id/manga_genres_tags_compact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_compact_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:chipSpacingHorizontal="4dp"
app:singleLine="true" />
</HorizontalScrollView>
</FrameLayout>
<Button
android:id="@+id/merge_btn"
style="@style/Theme.Widget.Button.FilledAccent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:text="@string/merge_with_another_source"
android:visibility="gone"
tools:visibility="visible" />
<Button
android:id="@+id/recommend_btn"
style="@style/Theme.Widget.Button.FilledAccent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:text="@string/az_recommends"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,110 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<TextView
android:id="@+id/manga_summary_label"
style="@style/TextAppearance.Regular.SubHeading"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="@string/manga_info_about_label"
android:textIsSelectable="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/manga_info_toggle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/manga_info_toggle"
style="@style/Theme.Widget.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="@string/manga_info_expand"
app:icon="@drawable/ic_baseline_expand_more_24dp"
app:iconTint="?attr/colorOnPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/manga_summary"
style="@style/TextAppearance.Regular.Body1.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:maxLines="2"
android:textIsSelectable="false"
android:visibility="gone"
tools:text="Summary"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/manga_genres_tags_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_full_chips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:visibility="gone"
app:chipSpacingHorizontal="4dp" />
<HorizontalScrollView
android:id="@+id/manga_genres_tags_compact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_compact_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
app:chipSpacingHorizontal="4dp"
app:singleLine="true" />
</HorizontalScrollView>
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/genre_groups"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:visibility="gone"
tools:visibility="visible"
tools:listitem="@layout/manga_info_genre_grouping" />
</LinearLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/metadata_view_item"/>
</FrameLayout>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/border">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/info_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:layout_margin="5dp"
style="@style/TextAppearance.Regular" />
</LinearLayout>

View File

@ -345,4 +345,80 @@
<string name="manga_id_is_null">Manga ID is null!</string> <string name="manga_id_is_null">Manga ID is null!</string>
<string name="loading_gallery">Loading gallery…</string> <string name="loading_gallery">Loading gallery…</string>
<!-- Rating 0-10 (0, 0.5, 1, 1.5 and so fourth) -->
<string name="rating10">Masterpiece</string>
<string name="rating9">Amazing</string>
<string name="rating8">Great</string>
<string name="rating7">Good</string>
<string name="rating6">Okay</string>
<string name="rating5">Mediocre</string>
<string name="rating4">Bad</string>
<string name="rating3">Awful</string>
<string name="rating2">Painful</string>
<string name="rating1">Unbearable</string>
<string name="rating0">Disaster</string>
<string name="no_rating">No rating</string>
<!-- Gallery types -->
<string name="doujinshi">Doujinshi</string>
<string name="artist_cg">Artist CG</string>
<string name="game_cg">Game CG</string>
<string name="western">Western</string>
<string name="non_h">Non-H</string>
<string name="image_set">Image Set</string>
<string name="cosplay">Cosplay</string>
<string name="asian_porn">Asian Porn</string>
<string name="misc">Misc</string>
<string name="artbook">Artbook</string>
<string name="video">Video</string>
<!-- More Info Menu -->
<string name="more_info">More Info</string>
<string name="alt_title">Alt Title</string>
<string name="id">Id</string>
<string name="token">Token</string>
<string name="is_exhentai_gallery">is Exhentai gallery</string>
<string name="thumbnail_url">Thumbnail url</string>
<string name="genre">Genre</string>
<string name="date_posted">Date posted</string>
<string name="page_count">Page count</string>
<string name="parent">Parent</string>
<string name="visible">Visible</string>
<string name="language">Language</string>
<string name="gallery_size">Gallery size</string>
<string name="total_favorites">Total favorites</string>
<string name="total_ratings">Total ratings</string>
<string name="average_rating">Average rating</string>
<string name="aged">Aged</string>
<string name="last_update_check">Last update check</string>
<string name="path">Path</string>
<string name="artist">Artist</string>
<string name="reader_id">Reader id</string>
<string name="series">Series</string>
<string name="characters">Characters</string>
<string name="group">Group</string>
<string name="media_id">Media id</string>
<string name="japanese_title">Japanese Title</string>
<string name="english_title">English Title</string>
<string name="short_title">Short Title</string>
<string name="cover_image_file_type">Cover image file type</string>
<string name="thumbnail_image_file_type">Thumbnail image file type</string>
<string name="scanlator">Scanlator</string>
<string name="url">Url</string>
<string name="alt_titles">Alt titles</string>
<string name="uploader_capital">Uploader Capitalized</string>
<string name="uploader">Uploader</string>
<string name="rating_string">Rating string</string>
<string name="collection">Collection</string>
<string name="parodies">Parodies</string>
<!-- Extra gallery info -->
<string name="num_pages">%1$d pages</string>
<string name="tags">Tags</string>
<string name="is_visible">Visible: %1$s</string>
<string name="rating_view">%1$s (%2$s, %3$d)</string>
<string name="rating_view_no_count">%1$s (%2$s)</string>
<string name="language_translated">%1$s TR</string>
</resources> </resources>