Add special view for browsing E/Exhentai! All the important info is now in front of your face when browsing, it is on by default and can be toggled off in the E-Hentai settings. Let me know if you find any errors

This commit is contained in:
Jobobby04
2020-07-28 16:55:33 -04:00
parent 032504f128
commit a6cba5c87d
24 changed files with 463 additions and 141 deletions
@@ -272,4 +272,6 @@ object PreferenceKeys {
const val recommendsInOverflow = "recommends_in_overflow"
const val hitomiAlwaysWebp = "hitomi_always_webp"
const val enhancedEHentaiView = "enhanced_e_hentai_view"
}
@@ -380,4 +380,6 @@ class PreferencesHelper(val context: Context) {
fun recommendsInOverflow() = flowPrefs.getBoolean(Keys.recommendsInOverflow, false)
fun hitomiAlwaysWebp() = flowPrefs.getBoolean(Keys.hitomiAlwaysWebp, true)
fun enhancedEHentaiView() = flowPrefs.getBoolean(Keys.enhancedEHentaiView, true)
}
@@ -1,3 +1,9 @@
package eu.kanade.tachiyomi.source.model
data class MangasPage(val mangas: List<SManga>, val hasNextPage: Boolean)
import exh.metadata.metadata.base.RaisedSearchMetadata
/* SY --> */ open /* SY <-- */ class MangasPage(val mangas: List<SManga>, val hasNextPage: Boolean)
// SY -->
class MetadataMangasPage(mangas: List<SManga>, hasNextPage: Boolean, val mangasMetadata: List<RaisedSearchMetadata>) : MangasPage(mangas, hasNextPage)
// SY <--
@@ -18,7 +18,7 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
@@ -96,9 +96,9 @@ class EHentai(
/**
* Gallery list entry
*/
data class ParsedManga(val fav: Int, val manga: Manga)
data class ParsedManga(val fav: Int, val manga: Manga, val metadata: EHentaiSearchMetadata)
fun extendedGenericMangaParse(doc: Document) = with(doc) {
private fun extendedGenericMangaParse(doc: Document) = with(doc) {
// Parse mangas (supports compact + extended layout)
val parsedMangas = select(".itg > tbody > tr").filter {
// Do not parse header and ads
@@ -110,6 +110,8 @@ class EHentai(
val infoElement = it.selectFirst(".gl3e")
val favElement = column2.children().find { it.attr("style").startsWith("border-color") }
val infoElements = infoElement?.select("div")
val parsedTags = mutableListOf<RaisedTag>()
ParsedManga(
fav = FAVORITES_BORDER_HEX_COLORS.indexOf(
@@ -122,14 +124,10 @@ class EHentai(
// Get image
thumbnail_url = thumbnailElement.attr("src")
val tags = mutableListOf<RaisedTag>()
val infoElements = infoElement?.select("div")
if (infoElements != null) {
linkElement.select("div div")?.getOrNull(1)?.select("tr")?.forEach { row ->
val namespace = row.select(".tc").text().removeSuffix(":")
tags.addAll(
parsedTags.addAll(
row.select("div").map { element ->
RaisedTag(
namespace,
@@ -143,46 +141,61 @@ class EHentai(
}
)
}
getGenre(infoElements[1])?.let { tags += it }
getDateTag(infoElements[2])?.let { tags += it }
getRating(infoElements[3])?.let { tags += it }
getAuthor(infoElements[4])?.let { author = it }
} else {
val tagElement = it.selectFirst(".gl3c > a")
val tagElements = tagElement.select("div")
tagElements.forEach { element ->
if (element.className() == "gt") {
val namespace = element.attr("title").substringBefore(":").trimOrNull() ?: "misc"
tags += RaisedTag(
parsedTags += RaisedTag(
namespace,
element.attr("title").substringAfter(":").trim(),
TAG_TYPE_NORMAL
)
}
}
}
val genre = it.selectFirst(".gl1c div")
getGenre(genreString = genre?.text()?.nullIfBlank()?.toLowerCase()?.replace(" ", ""))?.let { tags += it }
genre = parsedTags.toGenreString()
},
metadata = EHentaiSearchMetadata().apply {
tags += parsedTags
if (infoElements != null) {
getGenre(infoElements.getOrNull(1))?.let { genre = it }
getDateTag(infoElements.getOrNull(2))?.let { datePosted = it }
getRating(infoElements.getOrNull(3))?.let { averageRating = it }
getUploader(infoElements.getOrNull(4))?.let { uploader = it }
getPageCount(infoElements.getOrNull(5))?.let { length = it }
} else {
val parsedGenre = it.selectFirst(".gl1c div")
getGenre(genreString = parsedGenre?.text()?.nullIfBlank()?.toLowerCase()?.replace(" ", ""))?.let { genre = it }
val info = it.selectFirst(".gl2c")
val extraInfo = it.selectFirst(".gl4c")
val infoList = info.select("div div")
getDateTag(infoList[8])?.let { tags += it }
getDateTag(infoList.getOrNull(8))?.let { datePosted = it }
getRating(infoList[9])?.let { tags += it }
getRating(infoList.getOrNull(9))?.let { averageRating = it }
val extraInfoList = extraInfo.select("div")
getAuthor(extraInfoList[1])?.let { author = it }
}
if (extraInfoList.getOrNull(2) == null) {
getUploader(extraInfoList.getOrNull(0))?.let { uploader = it }
genre = tags.toGenreString()
getPageCount(extraInfoList.getOrNull(1))?.let { length = it }
} else {
getUploader(extraInfoList.getOrNull(1))?.let { uploader = it }
getPageCount(extraInfoList.getOrNull(2))?.let { length = it }
}
}
}
)
}
@@ -202,68 +215,54 @@ class EHentai(
Pair(parsedMangas, hasNextPage)
}
private fun getGenre(element: Element? = null, genreString: String? = null): RaisedTag? {
val attr = element?.attr("onclick")
private fun getGenre(element: Element? = null, genreString: String? = null): String? {
return element?.attr("onclick")
?.nullIfBlank()
?.substringAfterLast('/')
?.removeSuffix("'")
?.trim()
?.substringAfterLast('/')
?.removeSuffix("'") ?: genreString
return if (attr != null) {
RaisedTag(
EH_GENRE_NAMESPACE,
attr,
TAG_TYPE_NORMAL
)
} else null
}
private fun getDateTag(element: Element?): RaisedTag? {
private fun getDateTag(element: Element?): Long? {
val text = element?.text()?.nullIfBlank()
return if (text != null) {
val date = EX_DATE_FORMAT.parse(text)
if (date != null) {
RaisedTag(
EH_DATE_POSTED_NAMESPACE,
date.time.toString(),
TAG_TYPE_NORMAL
)
} else null
date?.time
} else null
}
private fun getRating(element: Element?): RaisedTag? {
private fun getRating(element: Element?): Double? {
val ratingStyle = element?.attr("style")?.nullIfBlank()
return if (ratingStyle != null) {
val matches = "([0-9]*)px".toRegex().findAll(ratingStyle).mapNotNull { it.groupValues.getOrNull(1)?.toIntOrNull() }.toList()
val matches = RATING_REGEX.findAll(ratingStyle).mapNotNull { it.groupValues.getOrNull(1)?.toIntOrNull() }.toList()
if (matches.size == 2) {
var rate = 5 - matches[0] / 16
RaisedTag(
EH_RATING_NAMESPACE,
if (matches[1] == 21) {
rate--
"$rate.5"
} else rate.toString(),
TAG_TYPE_NORMAL
)
if (matches[1] == 21) {
rate--
rate + 0.5
} else rate.toDouble()
} else null
} else null
}
private fun getAuthor(element: Element?): String? {
return element?.select("a")
?.attr("href")
?.nullIfBlank()
?.trim()
?.substringAfterLast('/')
private fun getUploader(element: Element?): String? {
return element?.select("a")?.text()?.trimOrNull()
}
private fun getPageCount(element: Element?): Int? {
val pageCount = element?.text()?.trimOrNull()
return if (pageCount != null) {
PAGE_COUNT_REGEX.find(pageCount)?.value?.toIntOrNull()
} else null
}
/**
* Parse a list of galleries
*/
fun genericMangaParse(response: Response) = extendedGenericMangaParse(response.asJsoup()).let {
MangasPage(it.first.map { it.manga }, it.second)
MetadataMangasPage(it.first.map { it.manga }, it.second, it.first.map { it.metadata })
}
override fun fetchChapterList(manga: SManga) = fetchChapterList(manga) {}
@@ -675,7 +674,7 @@ class EHentai(
page++
} while (parsed.second)
return Pair(result as List<ParsedManga>, favNames!!)
return Pair(result.toList(), favNames!!)
}
fun spPref() = if (exh) {
@@ -965,8 +964,8 @@ class EHentai(
private const val QUERY_PREFIX = "?f_apply=Apply+Filter"
private const val TR_SUFFIX = "TR"
private const val REVERSE_PARAM = "TEH_REVERSE"
private const val EH_DATE_POSTED_NAMESPACE = "date_posted"
private const val EH_RATING_NAMESPACE = "rating"
private val PAGE_COUNT_REGEX = "[0-9]*".toRegex()
private val RATING_REGEX = "([0-9]*)px".toRegex()
private const val EH_API_BASE = "https://api.e-hentai.org/api.php"
private val JSON = "application/json; charset=utf-8".toMediaTypeOrNull()!!
@@ -54,6 +54,7 @@ import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import eu.kanade.tachiyomi.widget.EmptyView
import exh.EXHSavedSearch
import exh.isEhBasedSource
import kotlinx.android.parcel.Parcelize
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.drop
@@ -348,7 +349,7 @@ open class BrowseSourceController(bundle: Bundle) :
binding.catalogueView.removeView(oldRecycler)
}
val recycler = if (preferences.sourceDisplayMode().get() == DisplayMode.LIST) {
val recycler = if (preferences.sourceDisplayMode().get() == DisplayMode.LIST /* SY --> */ || (preferences.enhancedEHentaiView().get() && presenter.source.isEhBasedSource()) /* SY <-- */) {
RecyclerView(view.context).apply {
id = R.id.recycler
layoutManager = LinearLayoutManager(context)
@@ -439,6 +440,11 @@ open class BrowseSourceController(bundle: Bundle) :
DisplayMode.LIST -> R.id.action_list
}
menu.findItem(displayItem).isChecked = true
// SY -->
if (preferences.enhancedEHentaiView().get() && presenter.source.isEhBasedSource()) {
menu.findItem(R.id.action_display_mode).isVisible = false
}
// SY <--
}
override fun onPrepareOptionsMenu(menu: Menu) {
@@ -38,9 +38,9 @@ import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateItem
import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem
import eu.kanade.tachiyomi.util.removeCovers
import exh.EXHSavedSearch
import exh.isEhBasedSource
import java.lang.RuntimeException
import java.util.Date
import kotlinx.coroutines.flow.subscribe
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
@@ -165,9 +165,13 @@ open class BrowseSourcePresenter(
pagerSubscription?.let { remove(it) }
pagerSubscription = pager.results()
.observeOn(Schedulers.io())
.map { pair -> pair.first to pair.second.map { networkToLocalManga(it, sourceId) } }
// SY -->
.map { triple -> Triple(triple.first, triple.second.map { networkToLocalManga(it, sourceId) }, triple.third) }
// SY <--
.doOnNext { initializeMangas(it.second) }
.map { pair -> pair.first to pair.second.map { SourceItem(it, sourceDisplayMode) } }
// SY -->
.map { triple -> triple.first to triple.second.mapIndexed { index, manga -> SourceItem(manga, sourceDisplayMode, if (prefs.enhancedEHentaiView().get() && source.isEhBasedSource()) triple.third?.getOrNull(index) else null) } }
// SY <--
.observeOn(AndroidSchedulers.mainThread())
.subscribeReplay(
{ view, (page, mangas) ->
@@ -2,7 +2,9 @@ package eu.kanade.tachiyomi.ui.browse.source.browse
import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.base.RaisedSearchMetadata
import rx.Observable
/**
@@ -13,9 +15,9 @@ abstract class Pager(var currentPage: Int = 1) {
var hasNextPage = true
private set
protected val results: PublishRelay<Pair<Int, List<SManga>>> = PublishRelay.create()
protected val results: PublishRelay< /* SY --> */ Triple /* SY <-- */ <Int, List<SManga> /* SY --> */, List<RaisedSearchMetadata>? /* SY <-- */ >> = PublishRelay.create()
fun results(): Observable<Pair<Int, List<SManga>>> {
fun results(): Observable< /* SY --> */ Triple /* SY <-- */ <Int, List<SManga> /* SY --> */, List<RaisedSearchMetadata>?> /* SY <-- */> {
return results.asObservable()
}
@@ -25,6 +27,11 @@ abstract class Pager(var currentPage: Int = 1) {
val page = currentPage
currentPage++
hasNextPage = mangasPage.hasNextPage && mangasPage.mangas.isNotEmpty()
results.call(Pair(page, mangasPage.mangas))
// SY -->
val mangasMetadata = if (mangasPage is MetadataMangasPage) {
mangasPage.mangasMetadata
} else null
// SY <--
results.call( /* SY <-- */ Triple /* SY <-- */ (page, mangasPage.mangas /* SY --> */, mangasMetadata /* SY <-- */))
}
}
@@ -0,0 +1,114 @@
package eu.kanade.tachiyomi.ui.browse.source.browse
import android.graphics.Color
import android.view.View
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import eu.davidea.flexibleadapter.FlexibleAdapter
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.toMangaThumbnail
import eu.kanade.tachiyomi.util.system.getResourceColor
import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.util.SourceTagsUtil
import exh.util.SourceTagsUtil.Companion.getLocaleSourceUtil
import java.util.Date
import kotlinx.android.synthetic.main.source_enhanced_ehentai_list_item.date_posted
import kotlinx.android.synthetic.main.source_enhanced_ehentai_list_item.genre
import kotlinx.android.synthetic.main.source_enhanced_ehentai_list_item.language
import kotlinx.android.synthetic.main.source_enhanced_ehentai_list_item.rating_bar
import kotlinx.android.synthetic.main.source_enhanced_ehentai_list_item.thumbnail
import kotlinx.android.synthetic.main.source_enhanced_ehentai_list_item.title
import kotlinx.android.synthetic.main.source_enhanced_ehentai_list_item.uploader
/**
* Class used to hold the displayed data of a manga in the catalogue, like the cover or the title.
* All the elements from the layout file "item_catalogue_list" are available in this class.
*
* @param view the inflated view for this holder.
* @param adapter the adapter handling this holder.
* @constructor creates a new catalogue holder.
*/
class SourceEnhancedEHentaiListHolder(private val view: View, adapter: FlexibleAdapter<*>) :
SourceHolder(view, adapter) {
private val favoriteColor = view.context.getResourceColor(R.attr.colorOnSurface, 0.38f)
private val unfavoriteColor = view.context.getResourceColor(R.attr.colorOnSurface)
/**
* Method called from [CatalogueAdapter.onBindViewHolder]. It updates the data for this
* holder with the given manga.
*
* @param manga the manga to bind.
*/
override fun onSetValues(manga: Manga) {
title.text = manga.title
title.setTextColor(if (manga.favorite) favoriteColor else unfavoriteColor)
// Set alpha of thumbnail.
thumbnail.alpha = if (manga.favorite) 0.3f else 1.0f
setImage(manga)
}
fun onSetMetadataValues(manga: Manga, metadata: RaisedSearchMetadata) {
if (metadata !is EHentaiSearchMetadata) return
if (metadata.uploader != null) {
uploader.text = metadata.uploader
}
val pair = when (metadata.genre) {
"doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"artistcg" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.artist_cg)
"gamecg" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.game_cg)
"western" -> Pair(SourceTagsUtil.WESTERN_COLOR, R.string.western)
"non-h" -> Pair(SourceTagsUtil.NON_H_COLOR, R.string.non_h)
"imageset" -> Pair(SourceTagsUtil.IMAGE_SET_COLOR, R.string.image_set)
"cosplay" -> Pair(SourceTagsUtil.COSPLAY_COLOR, R.string.cosplay)
"asianporn" -> Pair(SourceTagsUtil.ASIAN_PORN_COLOR, R.string.asian_porn)
"misc" -> Pair(SourceTagsUtil.MISC_COLOR, R.string.misc)
else -> Pair("", 0)
}
if (pair.first.isNotBlank()) {
genre.setBackgroundColor(Color.parseColor(pair.first))
genre.text = view.context.getString(pair.second)
} else genre.text = metadata.genre
metadata.datePosted?.let { date_posted.text = EX_DATE_FORMAT.format(Date(it)) }
metadata.averageRating?.let { rating_bar.rating = it.toFloat() }
val locale = getLocaleSourceUtil(metadata.tags.firstOrNull { it.namespace == "language" }?.name)
val pageCount = metadata.length
language.text = if (locale != null && pageCount != null) {
view.resources.getQuantityString(R.plurals.browse_language_and_pages, pageCount, pageCount, locale.toLanguageTag().toUpperCase())
} else if (pageCount != null) {
view.resources.getQuantityString(R.plurals.num_pages, pageCount, pageCount)
} else locale?.toLanguageTag()?.toUpperCase()
}
override fun setImage(manga: Manga) {
GlideApp.with(view.context).clear(thumbnail)
if (!manga.thumbnail_url.isNullOrEmpty()) {
val radius = view.context.resources.getDimensionPixelSize(R.dimen.card_radius)
val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius))
GlideApp.with(view.context)
.load(manga.toMangaThumbnail())
.diskCacheStrategy(DiskCacheStrategy.DATA)
.apply(requestOptions)
.dontAnimate()
.placeholder(android.R.color.transparent)
.into(thumbnail)
}
}
}
@@ -14,25 +14,26 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import exh.metadata.metadata.base.RaisedSearchMetadata
import kotlinx.android.synthetic.main.source_compact_grid_item.view.card
import kotlinx.android.synthetic.main.source_compact_grid_item.view.gradient
class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMode>) :
class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMode> /* SY --> */, private val metadata: RaisedSearchMetadata? = null /* SY <-- */) :
AbstractFlexibleItem<SourceHolder>() {
override fun getLayoutRes(): Int {
return when (displayMode.get()) {
return /* SY --> */ if (metadata == null) /* SY <-- */ when (displayMode.get()) {
DisplayMode.COMPACT_GRID -> R.layout.source_compact_grid_item
DisplayMode.COMFORTABLE_GRID -> R.layout.source_comfortable_grid_item
DisplayMode.LIST -> R.layout.source_list_item
}
} /* SY --> */ else R.layout.source_enhanced_ehentai_list_item /* SY <-- */
}
override fun createViewHolder(
view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
): SourceHolder {
return when (displayMode.get()) {
return /* SY --> */ if (metadata == null) /* SY <-- */ when (displayMode.get()) {
DisplayMode.COMPACT_GRID -> {
val parent = adapter.recyclerView as AutofitRecyclerView
val coverHeight = parent.itemWidth / 3 * 4
@@ -59,7 +60,11 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
DisplayMode.LIST -> {
SourceListHolder(view, adapter)
}
// SY -->
} else {
SourceEnhancedEHentaiListHolder(view, adapter)
}
// SY <--
}
override fun bindViewHolder(
@@ -69,6 +74,11 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
payloads: List<Any?>?
) {
holder.onSetValues(manga)
// SY -->
if (metadata != null) {
(holder as? SourceEnhancedEHentaiListHolder)?.onSetMetadataValues(manga, metadata)
}
// SY <--
}
override fun equals(other: Any?): Boolean {
@@ -20,7 +20,7 @@ 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.SourceTagsUtil.Companion.getRaisedTags
import exh.util.makeSearchChip
import exh.util.setChipsExtended
import kotlinx.coroutines.CoroutineScope
@@ -128,11 +128,11 @@ class MangaInfoItemAdapter(
.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()
val genre = manga.getRaisedTags()
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) } }
namespaceTags = genre
.groupBy { it.namespace }
.mapValues { values -> values.value.map { makeSearchChip(it.name, controller::performSearch, controller::performGlobalSearch, source.id, itemView.context, it.namespace) } }
.map { NamespaceTagsItem(it.key, it.value) }
}
}
@@ -10,7 +10,7 @@ import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
open class NamespaceTagsItem(val namespace: String, val tags: List<Chip>) : AbstractFlexibleItem<NamespaceTagsItem.Holder>() {
open class NamespaceTagsItem(val namespace: String?, val tags: List<Chip>) : AbstractFlexibleItem<NamespaceTagsItem.Holder>() {
override fun getLayoutRes(): Int {
return R.layout.manga_info_genre_grouping
@@ -22,7 +22,7 @@ open class NamespaceTagsItem(val namespace: String, val tags: List<Chip>) : Abst
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
val namespaceChip = Chip(holder.itemView.context)
namespaceChip.text = namespace
namespaceChip.text = namespace ?: holder.itemView.context.getString(R.string.unknown)
holder.namespaceChipGroup.addView(namespaceChip)
tags.forEach {
@@ -518,6 +518,13 @@ class SettingsEhController : SettingsController() {
onChange { preferences.imageQuality().reconfigure() }
}.dependency = PreferenceKeys.eh_enableExHentai
switchPreference {
titleRes = R.string.pref_enhanced_e_hentai_view
summaryRes = R.string.pref_enhanced_e_hentai_view_summary
key = PreferenceKeys.enhancedEHentaiView
defaultValue = true
}
}
preferenceCategory {
@@ -14,6 +14,7 @@ import exh.metadata.EX_DATE_FORMAT
import exh.metadata.humanReadableByteCount
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.ui.metadata.MetadataViewController
import exh.util.SourceTagsUtil
import java.util.Date
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
@@ -50,16 +51,16 @@ class EHentaiDescriptionAdapter(
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)
"doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"artistcg" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.artist_cg)
"gamecg" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.game_cg)
"western" -> Pair(SourceTagsUtil.WESTERN_COLOR, R.string.western)
"non-h" -> Pair(SourceTagsUtil.NON_H_COLOR, R.string.non_h)
"imageset" -> Pair(SourceTagsUtil.IMAGE_SET_COLOR, R.string.image_set)
"cosplay" -> Pair(SourceTagsUtil.COSPLAY_COLOR, R.string.cosplay)
"asianporn" -> Pair(SourceTagsUtil.ASIAN_PORN_COLOR, R.string.asian_porn)
"misc" -> Pair(SourceTagsUtil.MISC_COLOR, R.string.misc)
else -> Pair("", 0)
}
@@ -80,7 +81,7 @@ class EHentaiDescriptionAdapter(
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)
binding.pages.text = itemView.resources.getQuantityString(R.plurals.num_pages, meta.length ?: 0, 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)
@@ -41,7 +41,7 @@ class HBrowseDescriptionAdapter(
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.pages.text = itemView.resources.getQuantityString(R.plurals.num_pages, meta.length ?: 0, meta.length ?: 0)
binding.moreInfo.clicks()
.onEach {
@@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.HitomiSearchMetadata
import exh.ui.metadata.MetadataViewController
import exh.util.SourceTagsUtil
import java.util.Date
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -47,16 +48,16 @@ class HitomiDescriptionAdapter(
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)
"doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"artist CG" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.artist_cg)
"game CG" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.game_cg)
"western" -> Pair(SourceTagsUtil.WESTERN_COLOR, R.string.western)
"non-H" -> Pair(SourceTagsUtil.NON_H_COLOR, R.string.non_h)
"image Set" -> Pair(SourceTagsUtil.IMAGE_SET_COLOR, R.string.image_set)
"cosplay" -> Pair(SourceTagsUtil.COSPLAY_COLOR, R.string.cosplay)
"asian Porn" -> Pair(SourceTagsUtil.ASIAN_PORN_COLOR, R.string.asian_porn)
"misc" -> Pair(SourceTagsUtil.MISC_COLOR, R.string.misc)
else -> Pair("", 0)
}
@@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.NHentaiSearchMetadata
import exh.ui.metadata.MetadataViewController
import exh.util.SourceTagsUtil
import java.util.Date
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -54,16 +55,16 @@ class NHentaiDescriptionAdapter(
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)
"doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"artistcg" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.artist_cg)
"gamecg" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.game_cg)
"western" -> Pair(SourceTagsUtil.WESTERN_COLOR, R.string.western)
"non-h" -> Pair(SourceTagsUtil.NON_H_COLOR, R.string.non_h)
"imageset" -> Pair(SourceTagsUtil.IMAGE_SET_COLOR, R.string.image_set)
"cosplay" -> Pair(SourceTagsUtil.COSPLAY_COLOR, R.string.cosplay)
"asianporn" -> Pair(SourceTagsUtil.ASIAN_PORN_COLOR, R.string.asian_porn)
"misc" -> Pair(SourceTagsUtil.MISC_COLOR, R.string.misc)
else -> Pair("", 0)
}
@@ -85,7 +86,7 @@ class NHentaiDescriptionAdapter(
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)
binding.pages.text = itemView.resources.getQuantityString(R.plurals.num_pages, meta.pageImageTypes.size, meta.pageImageTypes.size)
@SuppressLint("SetTextI18n")
binding.id.text = "#" + (meta.nhId ?: 0)
@@ -11,6 +11,7 @@ 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 exh.util.SourceTagsUtil
import java.util.Locale
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
@@ -47,11 +48,11 @@ class PervEdenDescriptionAdapter(
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)
"Doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"Japanese Manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"Korean Manhwa" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.manhwa)
"Chinese Manhua" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.manhua)
"Comic" -> Pair(SourceTagsUtil.WESTERN_COLOR, R.string.comic)
else -> Pair("", 0)
}
@@ -12,6 +12,7 @@ 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 exh.util.SourceTagsUtil
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -47,12 +48,12 @@ class PururinDescriptionAdapter(
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)
"doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"artist-cg" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.artist_cg)
"game-cg" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.game_cg)
"artbook" -> Pair(SourceTagsUtil.IMAGE_SET_COLOR, R.string.artbook)
"webtoon" -> Pair(SourceTagsUtil.NON_H_COLOR, R.string.webtoon)
else -> Pair("", 0)
}
@@ -64,7 +65,7 @@ class PururinDescriptionAdapter(
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)
binding.pages.text = itemView.resources.getQuantityString(R.plurals.num_pages, meta.pages ?: 0, meta.pages ?: 0)
val ratingFloat = meta.averageRating?.toFloat()
val name = when (((ratingFloat ?: 100F) * 2).roundToInt()) {
@@ -12,6 +12,7 @@ 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 exh.util.SourceTagsUtil
import java.util.Date
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
@@ -48,11 +49,11 @@ class TsuminoDescriptionAdapter(
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)
"Doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"Manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"Artist CG" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.artist_cg)
"Game CG" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.game_cg)
"Video" -> Pair(SourceTagsUtil.WESTERN_COLOR, R.string.video)
else -> Pair("", 0)
}
@@ -70,7 +71,7 @@ class TsuminoDescriptionAdapter(
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)
binding.pages.text = itemView.resources.getQuantityString(R.plurals.num_pages, meta.length ?: 0, meta.length ?: 0)
val name = when (((meta.averageRating ?: 100F) * 2).roundToInt()) {
0 -> R.string.rating0
+46 -9
View File
@@ -1,30 +1,31 @@
package exh.util
import eu.kanade.tachiyomi.data.database.models.Manga
import exh.EH_SOURCE_ID
import exh.EXH_SOURCE_ID
import exh.HITOMI_SOURCE_ID
import exh.NHENTAI_SOURCE_ID
import exh.PURURIN_SOURCE_ID
import exh.TSUMINO_SOURCE_ID
import exh.metadata.metadata.base.RaisedTag
import java.util.Locale
class SourceTagsUtil {
fun getWrappedTag(sourceId: Long, namespace: String? = null, tag: String? = null, fullTag: String? = null): String? {
return if (sourceId == EXH_SOURCE_ID || sourceId == EH_SOURCE_ID || sourceId == NHENTAI_SOURCE_ID || sourceId == HITOMI_SOURCE_ID) {
val parsed = if (fullTag != null) parseTag(fullTag) else if (namespace != null && tag != null) Pair(namespace, tag) else null
if (parsed != null) {
val parsed = if (fullTag != null) parseTag(fullTag) else if (namespace != null && tag != null) RaisedTag(namespace, tag, TAG_TYPE_DEFAULT) else null
if (parsed?.namespace != null) {
when (sourceId) {
HITOMI_SOURCE_ID -> wrapTagHitomi(parsed.first, parsed.second.substringBefore('|').trim())
NHENTAI_SOURCE_ID -> wrapTagNHentai(parsed.first, parsed.second.substringBefore('|').trim())
PURURIN_SOURCE_ID -> parsed.second.substringBefore('|').trim()
TSUMINO_SOURCE_ID -> parsed.second.substringBefore('|').trim()
else -> wrapTag(parsed.first, parsed.second.substringBefore('|').trim())
HITOMI_SOURCE_ID -> wrapTagHitomi(parsed.namespace, parsed.name.substringBefore('|').trim())
NHENTAI_SOURCE_ID -> wrapTagNHentai(parsed.namespace, parsed.name.substringBefore('|').trim())
PURURIN_SOURCE_ID -> parsed.name.substringBefore('|').trim()
TSUMINO_SOURCE_ID -> parsed.name.substringBefore('|').trim()
else -> wrapTag(parsed.namespace, parsed.name.substringBefore('|').trim())
}
} else null
} else null
}
fun parseTag(tag: String) = tag.substringBefore(':').trim() to tag.substringAfter(':').trim()
private fun wrapTag(namespace: String, tag: String) = if (tag.contains(' ')) {
"$namespace:\"$tag$\""
} else {
@@ -46,4 +47,40 @@ class SourceTagsUtil {
} else {
"$namespace:$tag"
}
companion object {
fun Manga.getRaisedTags(): List<RaisedTag>? = this.getGenres()?.map { parseTag(it) }
fun parseTag(tag: String) = RaisedTag(tag.substringBefore(':').trimOrNull(), (tag.substringAfter(':').trimOrNull() ?: tag), TAG_TYPE_DEFAULT)
const val DOUJINSHI_COLOR = "#f44336"
const val MANGA_COLOR = "#ff9800"
const val ARTIST_CG_COLOR = "#fbc02d"
const val GAME_CG_COLOR = "#4caf50"
const val WESTERN_COLOR = "#8bc34a"
const val NON_H_COLOR = "#2196f3"
const val IMAGE_SET_COLOR = "#3f51b5"
const val COSPLAY_COLOR = "#9c27b0"
const val ASIAN_PORN_COLOR = "#9575cd"
const val MISC_COLOR = "#f06292"
fun getLocaleSourceUtil(language: String?) = when (language) {
"english", "eng" -> Locale("en")
"chinese" -> Locale("zh")
"spanish" -> Locale("es")
"korean" -> Locale("ko")
"russian" -> Locale("ru")
"french" -> Locale("fr")
"portuguese" -> Locale("pt")
"thai" -> Locale("th")
"german" -> Locale("de")
"italian" -> Locale("it")
"vietnamese" -> Locale("vi")
"polish" -> Locale("pl")
"hungarian" -> Locale("hu")
"dutch" -> Locale("nl")
else -> null
}
private const val TAG_TYPE_DEFAULT = 1
}
}