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
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
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.SManga
import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadata
import exh.source.EnhancedHttpSource
import kotlin.reflect.KClass
import rx.Completable
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 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.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup
import exh.debug.DebugToggles
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.TAG_TYPE_LIGHT
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.RaisedTag
import exh.metadata.nullIfBlank
import exh.metadata.parseHumanReadableByteCount
import exh.ui.login.LoginController
import exh.ui.metadata.adapters.EHentaiDescriptionAdapter
import exh.util.UriFilter
import exh.util.UriGroup
import exh.util.asObservableWithAsyncStacktrace
@ -477,6 +480,8 @@ class EHentai(
element.text().trim(),
if (element.hasClass("gtl")) {
TAG_TYPE_LIGHT
} else if (element.hasClass("gtw")) {
TAG_TYPE_WEAK
} else {
TAG_TYPE_NORMAL
}
@ -832,6 +837,10 @@ class EHentai(
return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/"
}
override fun getDescriptionAdapter(controller: MangaController): EHentaiDescriptionAdapter {
return EHentaiDescriptionAdapter(controller)
}
companion object {
private const val QUERY_PREFIX = "?f_apply=Apply+Filter"
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.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup
import exh.HITOMI_SOURCE_ID
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.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag
import exh.ui.metadata.adapters.HitomiDescriptionAdapter
import exh.util.urlImportFetchSearchManga
import java.text.SimpleDateFormat
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"
}
override fun getDescriptionAdapter(controller: MangaController): HitomiDescriptionAdapter {
return HitomiDescriptionAdapter(controller)
}
companion object {
private val INDEX_VERSION_CACHE_TIME_MS = 1000 * 60 * 10
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.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup
import exh.NHENTAI_SOURCE_ID
import exh.metadata.metadata.NHentaiSearchMetadata
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.ui.metadata.adapters.NHentaiDescriptionAdapter
import exh.util.urlImportFetchSearchManga
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
@ -195,7 +198,7 @@ class NHentai(val context: Context) : HttpSource(), LewdSource<NHentaiSearchMeta
tags.clear()
}?.forEach {
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]}/"
}
override fun getDescriptionAdapter(controller: MangaController): NHentaiDescriptionAdapter {
return NHentaiDescriptionAdapter(controller)
}
companion object {
private val GALLERY_JSON_REGEX = Regex(".parse\\(\"(.*)\"\\);")
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.ParsedHttpSource
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.chapter.ChapterRecognition
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.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag
import exh.ui.metadata.adapters.PervEdenDescriptionAdapter
import exh.util.UriFilter
import exh.util.UriGroup
import exh.util.urlImportFetchSearchManga
@ -358,6 +360,10 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang, val context: Con
return newUri.toString()
}
override fun getDescriptionAdapter(controller: MangaController): PervEdenDescriptionAdapter {
return PervEdenDescriptionAdapter(controller)
}
companion object {
val DATE_FORMAT = SimpleDateFormat("MMM d, yyyy", Locale.US).apply {
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.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup
import exh.EIGHTMUSES_SOURCE_ID
import exh.metadata.metadata.EightMusesSearchMetadata
import exh.metadata.metadata.base.RaisedTag
import exh.ui.metadata.adapters.EightMusesDescriptionAdapter
import exh.util.CachedField
import exh.util.NakedTrie
import exh.util.await
@ -397,4 +399,8 @@ class EightMuses(val context: Context) :
}
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.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup
import exh.HBROWSE_SOURCE_ID
import exh.metadata.metadata.HBrowseSearchMetadata
@ -25,6 +26,7 @@ import exh.metadata.metadata.base.RaisedTag
import exh.search.Namespace
import exh.search.SearchEngine
import exh.search.Text
import exh.ui.metadata.adapters.HBrowseDescriptionAdapter
import exh.util.await
import exh.util.dropBlank
import exh.util.urlImportFetchSearchManga
@ -475,6 +477,10 @@ class HBrowse(val context: Context) : HttpSource(), LewdSource<HBrowseSearchMeta
return "$baseUrl/${uri.pathSegments.first()}"
}
override fun getDescriptionAdapter(controller: MangaController): HBrowseDescriptionAdapter {
return HBrowseDescriptionAdapter(controller)
}
companion object {
private val PAGE_LIST_REGEX = Regex("list *= *(\\[.*]);")
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.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.metadata.HentaiCafeSearchMetadata
import exh.metadata.metadata.HentaiCafeSearchMetadata.Companion.TAG_TYPE_DEFAULT
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.HentaiCafeDescriptionAdapter
import exh.util.urlImportFetchSearchManga
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.nodes.Document
@ -111,4 +113,8 @@ class HentaiCafe(delegate: HttpSource, val context: Context) :
"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.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.metadata.PururinSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.PururinDescriptionAdapter
import exh.util.dropBlank
import exh.util.trimAll
import exh.util.urlImportFetchSearchManga
@ -89,10 +92,11 @@ class Pururin(delegate: HttpSource, val context: Context) :
else -> {
value.select("a").forEach { link ->
val searchUrl = Uri.parse(link.attr("href"))
val namespace = searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2]
tags += RaisedTag(
searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2],
namespace,
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? {
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.LewdSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.metadata.TsuminoSearchMetadata
import exh.metadata.metadata.TsuminoSearchMetadata.Companion.TAG_TYPE_DEFAULT
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.TsuminoDescriptionAdapter
import exh.util.dropBlank
import exh.util.trimAll
import exh.util.urlImportFetchSearchManga
@ -83,6 +85,12 @@ class Tsumino(delegate: HttpSource, val context: Context) :
input.getElementById("Rating")?.text()?.let {
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 {
@ -133,5 +141,12 @@ class Tsumino(delegate: HttpSource, val context: Context) :
companion object {
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.SourceManager
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.NucleusController
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.DownloadCustomChaptersDialog
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.MangaInfoItemAdapter
import eu.kanade.tachiyomi.ui.manga.track.TrackController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
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 exh.metadata.metadata.base.FlatMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.source.EnhancedHttpSource
import java.io.IOException
import kotlinx.android.synthetic.main.main_activity.root_coordinator
import kotlinx.coroutines.CancellationException
@ -133,6 +134,9 @@ class MangaController :
private val coverCache: CoverCache by injectLazy()
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 chaptersAdapter: ChaptersAdapter? = null
@ -201,13 +205,35 @@ class MangaController :
super.onViewCreated(view)
if (manga == null || source == null) return
val adapters: MutableList<RecyclerView.Adapter<out RecyclerView.ViewHolder>?> = mutableListOf()
// 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)
adapters += chaptersHeaderAdapter
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.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
binding.recycler.setHasFixedSize(true)
@ -360,11 +386,11 @@ class MangaController :
// SY -->
fun onNextMetaInfo(flatMetadata: FlatMetadata) {
presenter.meta = if (presenter.source is LewdSource<*, *>) {
flatMetadata.raise((presenter.source as LewdSource<*, *>).metaClass)
} else if (presenter.source is EnhancedHttpSource && (presenter.source as EnhancedHttpSource).enhancedSource is LewdSource<*, *>) {
flatMetadata.raise(((presenter.source as EnhancedHttpSource).enhancedSource as LewdSource<*, *>).metaClass)
} else null
val thisSourceAsLewdSource = presenter.source.getLewdSource()
if (thisSourceAsLewdSource != null) {
presenter.meta = flatMetadata.raise(thisSourceAsLewdSource.metaClass)
mangaMetaInfoAdapter?.notifyDataSetChanged()
}
}
// SY <--
@ -379,7 +405,8 @@ class MangaController :
fun onNextMangaInfo(manga: Manga, source: Source /* SY --> */, meta: RaisedSearchMetadata? /* SY <-- */) {
if (manga.initialized) {
// Update view.
mangaInfoAdapter?.update(manga, source /* SY --> */, meta /* SY <-- */)
mangaInfoAdapter?.update(manga, source)
mangaInfoItemAdapter?.update(manga, source, presenter.meta)
} else {
// Initialize manga.
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.Source
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.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
@ -116,7 +117,7 @@ class MangaPresenter(
super.onCreate(savedState)
// 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") })
}
// 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
import android.content.Context
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
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.data.database.models.Manga
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.MangaThumbnail
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.MangaInfoHeaderBinding
import eu.kanade.tachiyomi.source.Source
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.setTooltip
import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.util.view.visibleIf
import exh.MERGED_SOURCE_ID
import exh.isNamespaceSource
import exh.metadata.metadata.base.RaisedSearchMetadata
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.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class MangaInfoHeaderAdapter(
private val controller: MangaController,
private val fromSource: Boolean
private val controller: MangaController
) :
RecyclerView.Adapter<MangaInfoHeaderAdapter.HeaderViewHolder>() {
private var manga: Manga = controller.presenter.manga
private var source: Source = controller.presenter.source
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 lateinit var binding: MangaInfoHeaderBinding
private var initialLoad: Boolean = true
private var currentMangaThumbnail: MangaThumbnail? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
@ -80,18 +60,15 @@ class MangaInfoHeaderAdapter(
holder.bind()
}
val tagsAdapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter(null)
/**
* 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?) {
fun update(manga: Manga, source: Source) {
this.manga = manga
this.source = source
this.meta = meta
notifyDataSetChanged()
}
@ -226,15 +203,6 @@ class MangaInfoHeaderAdapter(
}
.launchIn(scope)
binding.mangaSummary.longClicks()
.onEach {
controller.activity?.copyToClipboard(
view.context.getString(R.string.description),
binding.mangaSummary.text.toString()
)
}
.launchIn(scope)
binding.mangaCover.longClicks()
.onEach {
controller.activity?.copyToClipboard(
@ -244,28 +212,6 @@ class MangaInfoHeaderAdapter(
}
.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)
}
@ -275,7 +221,6 @@ class MangaInfoHeaderAdapter(
* @param manga manga object containing information about manga.
* @param source the source of the manga.
*/
@ExperimentalCoroutinesApi
private fun setMangaInfo(manga: Manga, source: Source?) {
// Update full title TextView.
binding.mangaFullTitle.text = if (manga.title.isBlank()) {
@ -343,100 +288,6 @@ class MangaInfoHeaderAdapter(
.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.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
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.EX_DATE_FORMAT
import exh.metadata.ONGOING_SUFFIX
import exh.metadata.humanReadableByteCount
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.plusAssign
import java.util.Date
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -76,7 +77,7 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
}
// Build a nice looking description out of what we know
val titleDesc = StringBuilder()
/* val titleDesc = StringBuilder()
title?.let { titleDesc += "Title: $it\n" }
altTitle?.let { titleDesc += "Alternate Title: $it\n" }
@ -99,11 +100,36 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
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)
.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 {
@ -112,6 +138,7 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
const val TAG_TYPE_NORMAL = 0
const val TAG_TYPE_LIGHT = 1
const val TAG_TYPE_WEAK = 2
const val EH_GENRE_NAMESPACE = "genre"
private const val EH_ARTIST_NAMESPACE = "artist"

View File

@ -1,8 +1,9 @@
package exh.metadata.metadata
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.plusAssign
class EightMusesSearchMetadata : RaisedSearchMetadata() {
var path: List<String> = emptyList()
@ -26,14 +27,25 @@ class EightMusesSearchMetadata : RaisedSearchMetadata() {
manga.genre = tagsToGenreString()
val titleDesc = StringBuilder()
/*val titleDesc = StringBuilder()
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)
.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 {

View File

@ -1,9 +1,10 @@
package exh.metadata.metadata
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.EightMusesSearchMetadata.Companion.ARTIST_NAMESPACE
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.plusAssign
class HBrowseSearchMetadata : RaisedSearchMetadata() {
var hbId: Long? = null
@ -27,15 +28,25 @@ class HBrowseSearchMetadata : RaisedSearchMetadata() {
manga.artist = tags.ofNamespace(ARTIST_NAMESPACE).joinToString { it.name }
val titleDesc = StringBuilder()
manga.genre = tagsToGenreString()
/*val titleDesc = StringBuilder()
title?.let { titleDesc += "Title: $it\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)
.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 {

View File

@ -1,5 +1,7 @@
package exh.metadata.metadata
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.base.RaisedSearchMetadata
@ -30,16 +32,26 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() {
// Not available
manga.status = SManga.UNKNOWN
val detailsDesc = "Title: $title\n" +
"Artist: $artist\n"
val tagsDesc = tagsToDescription()
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)
.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 {

View File

@ -1,5 +1,7 @@
package exh.metadata.metadata
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.base.RaisedSearchMetadata
@ -37,17 +39,25 @@ class HitomiSearchMetadata : RaisedSearchMetadata() {
override fun copyTo(manga: SManga) {
thumbnailUrl?.let { manga.thumbnail_url = it }
val titleDesc = StringBuilder()
title?.let {
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"
}
val detailsDesc = StringBuilder()
manga.artist = artists.joinToString()
detailsDesc += "Artist(s): ${manga.artist}\n"
group?.let {
@ -74,16 +84,35 @@ class HitomiSearchMetadata : RaisedSearchMetadata() {
detailsDesc += "Upload date: ${EX_DATE_FORMAT.format(Date(it))}\n"
}
manga.status = SManga.UNKNOWN
val tagsDesc = tagsToDescription()*/
// Copy tags -> genres
manga.genre = tagsToGenreString()
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)
.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 {

View File

@ -1,12 +1,12 @@
package exh.metadata.metadata
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.EX_DATE_FORMAT
import exh.metadata.ONGOING_SUFFIX
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.nullIfBlank
import exh.plusAssign
import java.util.Date
import uy.kohesive.injekt.Injekt
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" }
japaneseTitle?.let { titleDesc += "Japanese Title: $it\n" }
shortTitle?.let { titleDesc += "Short Title: $it\n" }
@ -89,11 +89,27 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
favoritesCount?.let { detailsDesc += "Favorited: $it times\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)
.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 {
@ -106,7 +122,7 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
const val BASE_URL = "https://nhentai.net"
private const val NHENTAI_ARTIST_NAMESPACE = "artist"
private const val NHENTAI_CATEGORIES_NAMESPACE = "category"
const val NHENTAI_CATEGORIES_NAMESPACE = "category"
fun typeToExtension(t: String?) =
when (t) {

View File

@ -1,12 +1,13 @@
package exh.metadata.metadata
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga
import exh.PERV_EDEN_EN_SOURCE_ID
import exh.PERV_EDEN_IT_SOURCE_ID
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.RaisedTitle
import exh.plusAssign
class PervEdenSearchMetadata : RaisedSearchMetadata() {
var pvId: String? = null
@ -36,9 +37,28 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
url?.let { manga.url = it }
thumbnailUrl?.let { manga.thumbnail_url = it }
val titleDesc = StringBuilder()
title?.let {
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"
}
if (altTitles.isNotEmpty()) {
@ -50,7 +70,6 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
val detailsDesc = StringBuilder()
artist?.let {
manga.artist = it
detailsDesc += "Artist: $it\n"
}
@ -59,11 +78,6 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
}
status?.let {
manga.status = when (it) {
"Ongoing" -> SManga.ONGOING
"Completed", "Suspended" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
detailsDesc += "Status: $it\n"
}
@ -71,14 +85,30 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
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)
.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 {

View File

@ -1,8 +1,9 @@
package exh.metadata.metadata
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.plusAssign
class PururinSearchMetadata : RaisedSearchMetadata() {
var prId: Int? = null
@ -42,7 +43,7 @@ class PururinSearchMetadata : RaisedSearchMetadata() {
manga.genre = tagsToGenreString()
val titleDesc = StringBuilder()
/*val titleDesc = StringBuilder()
title?.let { titleDesc += "English Title: $it\n" }
altTitle?.let { titleDesc += "Japanese Title: $it\n" }
@ -52,11 +53,26 @@ class PururinSearchMetadata : RaisedSearchMetadata() {
fileSize?.let { detailsDesc += "Size: $it\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)
.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 {
@ -66,6 +82,7 @@ class PururinSearchMetadata : RaisedSearchMetadata() {
const val TAG_TYPE_DEFAULT = 0
private const val TAG_NAMESPACE_ARTIST = "artist"
const val TAG_NAMESPACE_CATEGORY = "category"
val BASE_URL = "https://pururin.io"
}

View File

@ -1,11 +1,14 @@
package exh.metadata.metadata
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.plusAssign
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class TsuminoSearchMetadata : RaisedSearchMetadata() {
var tmId: Int? = null
@ -20,6 +23,12 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
var ratingString: String? = null
var averageRating: Float? = null
var userRatings: Long? = null
var favorites: Long? = null
var category: String? = null
var collection: String? = null
@ -38,7 +47,10 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
manga.status = SManga.UNKNOWN
val titleDesc = "Title: $title\n"
// Copy tags -> genres
manga.genre = tagsToGenreString()
/*val titleDesc = "Title: $title\n"
val detailsDesc = StringBuilder()
uploader?.let { detailsDesc += "Uploader: $it\n" }
@ -59,14 +71,36 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
detailsDesc += "Character: $charactersString\n"
}
// Copy tags -> genres
manga.genre = tagsToGenreString()
val tagsDesc = tagsToDescription()*/
val tagsDesc = tagsToDescription()
manga.description = listOf(titleDesc, detailsDesc.toString(), tagsDesc.toString())
manga.description = "meta" /*listOf(titleDesc, detailsDesc.toString(), tagsDesc.toString())
.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 {
@ -76,6 +110,8 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
val BASE_URL = "https://www.tsumino.com"
val TSUMINO_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US)
fun tmIdFromUrl(url: String) =
Uri.parse(url).lastPathSegment

View File

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

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="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>