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

View File

@ -341,6 +341,9 @@ dependencies {
// Humanize (EH) // Humanize (EH)
implementation 'com.github.mfornos:humanize-slim:1.2.2' implementation 'com.github.mfornos:humanize-slim:1.2.2'
// RatingBar (SY)
implementation 'me.zhanghai.android.materialratingbar:library:1.3.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
final def markwon_version = '4.1.0' final def markwon_version = '4.1.0'

View File

@ -272,4 +272,6 @@ object PreferenceKeys {
const val recommendsInOverflow = "recommends_in_overflow" const val recommendsInOverflow = "recommends_in_overflow"
const val hitomiAlwaysWebp = "hitomi_always_webp" const val hitomiAlwaysWebp = "hitomi_always_webp"
const val enhancedEHentaiView = "enhanced_e_hentai_view"
} }

View File

@ -380,4 +380,6 @@ class PreferencesHelper(val context: Context) {
fun recommendsInOverflow() = flowPrefs.getBoolean(Keys.recommendsInOverflow, false) fun recommendsInOverflow() = flowPrefs.getBoolean(Keys.recommendsInOverflow, false)
fun hitomiAlwaysWebp() = flowPrefs.getBoolean(Keys.hitomiAlwaysWebp, true) fun hitomiAlwaysWebp() = flowPrefs.getBoolean(Keys.hitomiAlwaysWebp, true)
fun enhancedEHentaiView() = flowPrefs.getBoolean(Keys.enhancedEHentaiView, true)
} }

View File

@ -1,3 +1,9 @@
package eu.kanade.tachiyomi.source.model 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 <--

View File

@ -18,7 +18,7 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList 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.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
@ -96,9 +96,9 @@ class EHentai(
/** /**
* Gallery list entry * 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) // Parse mangas (supports compact + extended layout)
val parsedMangas = select(".itg > tbody > tr").filter { val parsedMangas = select(".itg > tbody > tr").filter {
// Do not parse header and ads // Do not parse header and ads
@ -110,6 +110,8 @@ class EHentai(
val infoElement = it.selectFirst(".gl3e") val infoElement = it.selectFirst(".gl3e")
val favElement = column2.children().find { it.attr("style").startsWith("border-color") } val favElement = column2.children().find { it.attr("style").startsWith("border-color") }
val infoElements = infoElement?.select("div")
val parsedTags = mutableListOf<RaisedTag>()
ParsedManga( ParsedManga(
fav = FAVORITES_BORDER_HEX_COLORS.indexOf( fav = FAVORITES_BORDER_HEX_COLORS.indexOf(
@ -122,14 +124,10 @@ class EHentai(
// Get image // Get image
thumbnail_url = thumbnailElement.attr("src") thumbnail_url = thumbnailElement.attr("src")
val tags = mutableListOf<RaisedTag>()
val infoElements = infoElement?.select("div")
if (infoElements != null) { if (infoElements != null) {
linkElement.select("div div")?.getOrNull(1)?.select("tr")?.forEach { row -> linkElement.select("div div")?.getOrNull(1)?.select("tr")?.forEach { row ->
val namespace = row.select(".tc").text().removeSuffix(":") val namespace = row.select(".tc").text().removeSuffix(":")
tags.addAll( parsedTags.addAll(
row.select("div").map { element -> row.select("div").map { element ->
RaisedTag( RaisedTag(
namespace, 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 { } else {
val tagElement = it.selectFirst(".gl3c > a") val tagElement = it.selectFirst(".gl3c > a")
val tagElements = tagElement.select("div") val tagElements = tagElement.select("div")
tagElements.forEach { element -> tagElements.forEach { element ->
if (element.className() == "gt") { if (element.className() == "gt") {
val namespace = element.attr("title").substringBefore(":").trimOrNull() ?: "misc" val namespace = element.attr("title").substringBefore(":").trimOrNull() ?: "misc"
tags += RaisedTag( parsedTags += RaisedTag(
namespace, namespace,
element.attr("title").substringAfter(":").trim(), element.attr("title").substringAfter(":").trim(),
TAG_TYPE_NORMAL TAG_TYPE_NORMAL
) )
} }
} }
}
val genre = it.selectFirst(".gl1c div") genre = parsedTags.toGenreString()
getGenre(genreString = genre?.text()?.nullIfBlank()?.toLowerCase()?.replace(" ", ""))?.let { tags += it } },
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 info = it.selectFirst(".gl2c")
val extraInfo = it.selectFirst(".gl4c") val extraInfo = it.selectFirst(".gl4c")
val infoList = info.select("div div") 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") 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) Pair(parsedMangas, hasNextPage)
} }
private fun getGenre(element: Element? = null, genreString: String? = null): RaisedTag? { private fun getGenre(element: Element? = null, genreString: String? = null): String? {
val attr = element?.attr("onclick") return element?.attr("onclick")
?.nullIfBlank() ?.nullIfBlank()
?.substringAfterLast('/') ?.substringAfterLast('/')
?.removeSuffix("'") ?.removeSuffix("'")
?.trim() ?.trim()
?.substringAfterLast('/') ?.substringAfterLast('/')
?.removeSuffix("'") ?: genreString ?.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() val text = element?.text()?.nullIfBlank()
return if (text != null) { return if (text != null) {
val date = EX_DATE_FORMAT.parse(text) val date = EX_DATE_FORMAT.parse(text)
if (date != null) { date?.time
RaisedTag(
EH_DATE_POSTED_NAMESPACE,
date.time.toString(),
TAG_TYPE_NORMAL
)
} else null
} else null } else null
} }
private fun getRating(element: Element?): RaisedTag? { private fun getRating(element: Element?): Double? {
val ratingStyle = element?.attr("style")?.nullIfBlank() val ratingStyle = element?.attr("style")?.nullIfBlank()
return if (ratingStyle != null) { 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) { if (matches.size == 2) {
var rate = 5 - matches[0] / 16 var rate = 5 - matches[0] / 16
RaisedTag( if (matches[1] == 21) {
EH_RATING_NAMESPACE, rate--
if (matches[1] == 21) { rate + 0.5
rate-- } else rate.toDouble()
"$rate.5"
} else rate.toString(),
TAG_TYPE_NORMAL
)
} else null } else null
} else null } else null
} }
private fun getAuthor(element: Element?): String? { private fun getUploader(element: Element?): String? {
return element?.select("a") return element?.select("a")?.text()?.trimOrNull()
?.attr("href") }
?.nullIfBlank()
?.trim() private fun getPageCount(element: Element?): Int? {
?.substringAfterLast('/') val pageCount = element?.text()?.trimOrNull()
return if (pageCount != null) {
PAGE_COUNT_REGEX.find(pageCount)?.value?.toIntOrNull()
} else null
} }
/** /**
* Parse a list of galleries * Parse a list of galleries
*/ */
fun genericMangaParse(response: Response) = extendedGenericMangaParse(response.asJsoup()).let { 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) {} override fun fetchChapterList(manga: SManga) = fetchChapterList(manga) {}
@ -675,7 +674,7 @@ class EHentai(
page++ page++
} while (parsed.second) } while (parsed.second)
return Pair(result as List<ParsedManga>, favNames!!) return Pair(result.toList(), favNames!!)
} }
fun spPref() = if (exh) { fun spPref() = if (exh) {
@ -965,8 +964,8 @@ class EHentai(
private const val QUERY_PREFIX = "?f_apply=Apply+Filter" private const val QUERY_PREFIX = "?f_apply=Apply+Filter"
private const val TR_SUFFIX = "TR" private const val TR_SUFFIX = "TR"
private const val REVERSE_PARAM = "TEH_REVERSE" private const val REVERSE_PARAM = "TEH_REVERSE"
private const val EH_DATE_POSTED_NAMESPACE = "date_posted" private val PAGE_COUNT_REGEX = "[0-9]*".toRegex()
private const val EH_RATING_NAMESPACE = "rating" private val RATING_REGEX = "([0-9]*)px".toRegex()
private const val EH_API_BASE = "https://api.e-hentai.org/api.php" private const val EH_API_BASE = "https://api.e-hentai.org/api.php"
private val JSON = "application/json; charset=utf-8".toMediaTypeOrNull()!! private val JSON = "application/json; charset=utf-8".toMediaTypeOrNull()!!

View File

@ -54,6 +54,7 @@ import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import eu.kanade.tachiyomi.widget.EmptyView import eu.kanade.tachiyomi.widget.EmptyView
import exh.EXHSavedSearch import exh.EXHSavedSearch
import exh.isEhBasedSource
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
@ -348,7 +349,7 @@ open class BrowseSourceController(bundle: Bundle) :
binding.catalogueView.removeView(oldRecycler) 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 { RecyclerView(view.context).apply {
id = R.id.recycler id = R.id.recycler
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
@ -439,6 +440,11 @@ open class BrowseSourceController(bundle: Bundle) :
DisplayMode.LIST -> R.id.action_list DisplayMode.LIST -> R.id.action_list
} }
menu.findItem(displayItem).isChecked = true 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) { override fun onPrepareOptionsMenu(menu: Menu) {

View File

@ -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.ui.browse.source.filter.TriStateSectionItem
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
import exh.EXHSavedSearch import exh.EXHSavedSearch
import exh.isEhBasedSource
import java.lang.RuntimeException import java.lang.RuntimeException
import java.util.Date import java.util.Date
import kotlinx.coroutines.flow.subscribe
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@ -165,9 +165,13 @@ open class BrowseSourcePresenter(
pagerSubscription?.let { remove(it) } pagerSubscription?.let { remove(it) }
pagerSubscription = pager.results() pagerSubscription = pager.results()
.observeOn(Schedulers.io()) .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) } .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()) .observeOn(AndroidSchedulers.mainThread())
.subscribeReplay( .subscribeReplay(
{ view, (page, mangas) -> { view, (page, mangas) ->

View File

@ -2,7 +2,9 @@ package eu.kanade.tachiyomi.ui.browse.source.browse
import com.jakewharton.rxrelay.PublishRelay import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.base.RaisedSearchMetadata
import rx.Observable import rx.Observable
/** /**
@ -13,9 +15,9 @@ abstract class Pager(var currentPage: Int = 1) {
var hasNextPage = true var hasNextPage = true
private set 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() return results.asObservable()
} }
@ -25,6 +27,11 @@ abstract class Pager(var currentPage: Int = 1) {
val page = currentPage val page = currentPage
currentPage++ currentPage++
hasNextPage = mangasPage.hasNextPage && mangasPage.mangas.isNotEmpty() 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 <-- */))
} }
} }

View File

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

View File

@ -14,25 +14,26 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
import eu.kanade.tachiyomi.widget.AutofitRecyclerView 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.card
import kotlinx.android.synthetic.main.source_compact_grid_item.view.gradient 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>() { AbstractFlexibleItem<SourceHolder>() {
override fun getLayoutRes(): Int { 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.COMPACT_GRID -> R.layout.source_compact_grid_item
DisplayMode.COMFORTABLE_GRID -> R.layout.source_comfortable_grid_item DisplayMode.COMFORTABLE_GRID -> R.layout.source_comfortable_grid_item
DisplayMode.LIST -> R.layout.source_list_item DisplayMode.LIST -> R.layout.source_list_item
} } /* SY --> */ else R.layout.source_enhanced_ehentai_list_item /* SY <-- */
} }
override fun createViewHolder( override fun createViewHolder(
view: View, view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>> adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
): SourceHolder { ): SourceHolder {
return when (displayMode.get()) { return /* SY --> */ if (metadata == null) /* SY <-- */ when (displayMode.get()) {
DisplayMode.COMPACT_GRID -> { DisplayMode.COMPACT_GRID -> {
val parent = adapter.recyclerView as AutofitRecyclerView val parent = adapter.recyclerView as AutofitRecyclerView
val coverHeight = parent.itemWidth / 3 * 4 val coverHeight = parent.itemWidth / 3 * 4
@ -59,7 +60,11 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
DisplayMode.LIST -> { DisplayMode.LIST -> {
SourceListHolder(view, adapter) SourceListHolder(view, adapter)
} }
// SY -->
} else {
SourceEnhancedEHentaiListHolder(view, adapter)
} }
// SY <--
} }
override fun bindViewHolder( override fun bindViewHolder(
@ -69,6 +74,11 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
payloads: List<Any?>? payloads: List<Any?>?
) { ) {
holder.onSetValues(manga) holder.onSetValues(manga)
// SY -->
if (metadata != null) {
(holder as? SourceEnhancedEHentaiListHolder)?.onSetMetadataValues(manga, metadata)
}
// SY <--
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

View File

@ -20,7 +20,7 @@ import exh.isEhBasedSource
import exh.isNamespaceSource import exh.isNamespaceSource
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL 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.makeSearchChip
import exh.util.setChipsExtended import exh.util.setChipsExtended
import kotlinx.coroutines.CoroutineScope 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) } } .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) } .map { NamespaceTagsItem(it.key!!, it.value) }
} else { } else {
val genre = manga.getGenres() val genre = manga.getRaisedTags()
if (!genre.isNullOrEmpty()) { if (!genre.isNullOrEmpty()) {
namespaceTags = genre.map { SourceTagsUtil().parseTag(it) } namespaceTags = genre
.groupBy { it.first } .groupBy { it.namespace }
.mapValues { values -> values.value.map { makeSearchChip(it.second, controller::performSearch, controller::performGlobalSearch, source.id, itemView.context, it.first) } } .mapValues { values -> values.value.map { makeSearchChip(it.name, controller::performSearch, controller::performGlobalSearch, source.id, itemView.context, it.namespace) } }
.map { NamespaceTagsItem(it.key, it.value) } .map { NamespaceTagsItem(it.key, it.value) }
} }
} }

View File

@ -10,7 +10,7 @@ import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R 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 { override fun getLayoutRes(): Int {
return R.layout.manga_info_genre_grouping 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?>?) { override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
val namespaceChip = Chip(holder.itemView.context) val namespaceChip = Chip(holder.itemView.context)
namespaceChip.text = namespace namespaceChip.text = namespace ?: holder.itemView.context.getString(R.string.unknown)
holder.namespaceChipGroup.addView(namespaceChip) holder.namespaceChipGroup.addView(namespaceChip)
tags.forEach { tags.forEach {

View File

@ -518,6 +518,13 @@ class SettingsEhController : SettingsController() {
onChange { preferences.imageQuality().reconfigure() } onChange { preferences.imageQuality().reconfigure() }
}.dependency = PreferenceKeys.eh_enableExHentai }.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 { preferenceCategory {

View File

@ -14,6 +14,7 @@ import exh.metadata.EX_DATE_FORMAT
import exh.metadata.humanReadableByteCount import exh.metadata.humanReadableByteCount
import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.EHentaiSearchMetadata
import exh.ui.metadata.MetadataViewController import exh.ui.metadata.MetadataViewController
import exh.util.SourceTagsUtil
import java.util.Date import java.util.Date
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -50,16 +51,16 @@ class EHentaiDescriptionAdapter(
val genre = meta.genre val genre = meta.genre
if (genre != null) { if (genre != null) {
val pair = when (genre) { val pair = when (genre) {
"doujinshi" -> Pair("#fc4e4e", R.string.doujinshi) "doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"manga" -> Pair("#e78c1a", R.string.manga) "manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"artistcg" -> Pair("#dde500", R.string.artist_cg) "artistcg" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.artist_cg)
"gamecg" -> Pair("#05bf0b", R.string.game_cg) "gamecg" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.game_cg)
"western" -> Pair("#14e723", R.string.western) "western" -> Pair(SourceTagsUtil.WESTERN_COLOR, R.string.western)
"non-h" -> Pair("#08d7e2", R.string.non_h) "non-h" -> Pair(SourceTagsUtil.NON_H_COLOR, R.string.non_h)
"imageset" -> Pair("#5f5fff", R.string.image_set) "imageset" -> Pair(SourceTagsUtil.IMAGE_SET_COLOR, R.string.image_set)
"cosplay" -> Pair("#9755f5", R.string.cosplay) "cosplay" -> Pair(SourceTagsUtil.COSPLAY_COLOR, R.string.cosplay)
"asianporn" -> Pair("#fe93ff", R.string.asian_porn) "asianporn" -> Pair(SourceTagsUtil.ASIAN_PORN_COLOR, R.string.asian_porn)
"misc" -> Pair("#9e9e9e", R.string.misc) "misc" -> Pair(SourceTagsUtil.MISC_COLOR, R.string.misc)
else -> Pair("", 0) else -> Pair("", 0)
} }
@ -80,7 +81,7 @@ class EHentaiDescriptionAdapter(
binding.uploader.text = meta.uploader ?: itemView.context.getString(R.string.unknown) binding.uploader.text = meta.uploader ?: itemView.context.getString(R.string.unknown)
binding.size.text = humanReadableByteCount(meta.size ?: 0, true) 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) val language = meta.language ?: itemView.context.getString(R.string.unknown)
binding.language.text = if (meta.translated == true) { binding.language.text = if (meta.translated == true) {
itemView.context.getString(R.string.language_translated, language) itemView.context.getString(R.string.language_translated, language)

View File

@ -41,7 +41,7 @@ class HBrowseDescriptionAdapter(
val meta = controller.presenter.meta val meta = controller.presenter.meta
if (meta == null || meta !is HBrowseSearchMetadata) return 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() binding.moreInfo.clicks()
.onEach { .onEach {

View File

@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.EX_DATE_FORMAT import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.HitomiSearchMetadata import exh.metadata.metadata.HitomiSearchMetadata
import exh.ui.metadata.MetadataViewController import exh.ui.metadata.MetadataViewController
import exh.util.SourceTagsUtil
import java.util.Date import java.util.Date
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -47,16 +48,16 @@ class HitomiDescriptionAdapter(
val genre = meta.type val genre = meta.type
if (genre != null) { if (genre != null) {
val pair = when (genre) { val pair = when (genre) {
"doujinshi" -> Pair("#fc4e4e", R.string.doujinshi) "doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"manga" -> Pair("#e78c1a", R.string.manga) "manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"artist CG" -> Pair("#dde500", R.string.artist_cg) "artist CG" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.artist_cg)
"game CG" -> Pair("#05bf0b", R.string.game_cg) "game CG" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.game_cg)
"western" -> Pair("#14e723", R.string.western) "western" -> Pair(SourceTagsUtil.WESTERN_COLOR, R.string.western)
"non-H" -> Pair("#08d7e2", R.string.non_h) "non-H" -> Pair(SourceTagsUtil.NON_H_COLOR, R.string.non_h)
"image Set" -> Pair("#5f5fff", R.string.image_set) "image Set" -> Pair(SourceTagsUtil.IMAGE_SET_COLOR, R.string.image_set)
"cosplay" -> Pair("#9755f5", R.string.cosplay) "cosplay" -> Pair(SourceTagsUtil.COSPLAY_COLOR, R.string.cosplay)
"asian Porn" -> Pair("#fe93ff", R.string.asian_porn) "asian Porn" -> Pair(SourceTagsUtil.ASIAN_PORN_COLOR, R.string.asian_porn)
"misc" -> Pair("#9e9e9e", R.string.misc) "misc" -> Pair(SourceTagsUtil.MISC_COLOR, R.string.misc)
else -> Pair("", 0) else -> Pair("", 0)
} }

View File

@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
import exh.metadata.EX_DATE_FORMAT import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.NHentaiSearchMetadata import exh.metadata.metadata.NHentaiSearchMetadata
import exh.ui.metadata.MetadataViewController import exh.ui.metadata.MetadataViewController
import exh.util.SourceTagsUtil
import java.util.Date import java.util.Date
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -54,16 +55,16 @@ class NHentaiDescriptionAdapter(
if (category != null) { if (category != null) {
val pair = when (category) { val pair = when (category) {
"doujinshi" -> Pair("#fc4e4e", R.string.doujinshi) "doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"manga" -> Pair("#e78c1a", R.string.manga) "manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"artistcg" -> Pair("#dde500", R.string.artist_cg) "artistcg" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.artist_cg)
"gamecg" -> Pair("#05bf0b", R.string.game_cg) "gamecg" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.game_cg)
"western" -> Pair("#14e723", R.string.western) "western" -> Pair(SourceTagsUtil.WESTERN_COLOR, R.string.western)
"non-h" -> Pair("#08d7e2", R.string.non_h) "non-h" -> Pair(SourceTagsUtil.NON_H_COLOR, R.string.non_h)
"imageset" -> Pair("#5f5fff", R.string.image_set) "imageset" -> Pair(SourceTagsUtil.IMAGE_SET_COLOR, R.string.image_set)
"cosplay" -> Pair("#9755f5", R.string.cosplay) "cosplay" -> Pair(SourceTagsUtil.COSPLAY_COLOR, R.string.cosplay)
"asianporn" -> Pair("#fe93ff", R.string.asian_porn) "asianporn" -> Pair(SourceTagsUtil.ASIAN_PORN_COLOR, R.string.asian_porn)
"misc" -> Pair("#9e9e9e", R.string.misc) "misc" -> Pair(SourceTagsUtil.MISC_COLOR, R.string.misc)
else -> Pair("", 0) else -> Pair("", 0)
} }
@ -85,7 +86,7 @@ class NHentaiDescriptionAdapter(
binding.whenPosted.text = EX_DATE_FORMAT.format(Date((meta.uploadDate ?: 0) * 1000)) 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") @SuppressLint("SetTextI18n")
binding.id.text = "#" + (meta.nhId ?: 0) binding.id.text = "#" + (meta.nhId ?: 0)

View File

@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.metadata.PervEdenSearchMetadata import exh.metadata.metadata.PervEdenSearchMetadata
import exh.ui.metadata.MetadataViewController import exh.ui.metadata.MetadataViewController
import exh.util.SourceTagsUtil
import java.util.Locale import java.util.Locale
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -47,11 +48,11 @@ class PervEdenDescriptionAdapter(
val genre = meta.type val genre = meta.type
if (genre != null) { if (genre != null) {
val pair = when (genre) { val pair = when (genre) {
"Doujinshi" -> Pair("#fc4e4e", R.string.doujinshi) "Doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"Japanese Manga" -> Pair("#e78c1a", R.string.manga) "Japanese Manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"Korean Manhwa" -> Pair("#dde500", R.string.manhwa) "Korean Manhwa" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.manhwa)
"Chinese Manhua" -> Pair("#05bf0b", R.string.manhua) "Chinese Manhua" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.manhua)
"Comic" -> Pair("#14e723", R.string.comic) "Comic" -> Pair(SourceTagsUtil.WESTERN_COLOR, R.string.comic)
else -> Pair("", 0) else -> Pair("", 0)
} }

View File

@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.metadata.PururinSearchMetadata import exh.metadata.metadata.PururinSearchMetadata
import exh.metadata.metadata.PururinSearchMetadata.Companion.TAG_NAMESPACE_CATEGORY import exh.metadata.metadata.PururinSearchMetadata.Companion.TAG_NAMESPACE_CATEGORY
import exh.ui.metadata.MetadataViewController import exh.ui.metadata.MetadataViewController
import exh.util.SourceTagsUtil
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -47,12 +48,12 @@ class PururinDescriptionAdapter(
val genre = meta.tags.find { it.namespace == TAG_NAMESPACE_CATEGORY } val genre = meta.tags.find { it.namespace == TAG_NAMESPACE_CATEGORY }
if (genre != null) { if (genre != null) {
val pair = when (genre.name) { val pair = when (genre.name) {
"doujinshi" -> Pair("#fc4e4e", R.string.doujinshi) "doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"manga" -> Pair("#e78c1a", R.string.manga) "manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"artist-cg" -> Pair("#dde500", R.string.artist_cg) "artist-cg" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.artist_cg)
"game-cg" -> Pair("#05bf0b", R.string.game_cg) "game-cg" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.game_cg)
"artbook" -> Pair("#5f5fff", R.string.artbook) "artbook" -> Pair(SourceTagsUtil.IMAGE_SET_COLOR, R.string.artbook)
"webtoon" -> Pair("#5f5fff", R.string.webtoon) "webtoon" -> Pair(SourceTagsUtil.NON_H_COLOR, R.string.webtoon)
else -> Pair("", 0) else -> Pair("", 0)
} }
@ -64,7 +65,7 @@ class PururinDescriptionAdapter(
binding.uploader.text = meta.uploaderDisp ?: meta.uploader ?: "" binding.uploader.text = meta.uploaderDisp ?: meta.uploader ?: ""
binding.size.text = meta.fileSize ?: itemView.context.getString(R.string.unknown) 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 ratingFloat = meta.averageRating?.toFloat()
val name = when (((ratingFloat ?: 100F) * 2).roundToInt()) { val name = when (((ratingFloat ?: 100F) * 2).roundToInt()) {

View File

@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import exh.metadata.metadata.TsuminoSearchMetadata import exh.metadata.metadata.TsuminoSearchMetadata
import exh.ui.metadata.MetadataViewController import exh.ui.metadata.MetadataViewController
import exh.util.SourceTagsUtil
import java.util.Date import java.util.Date
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -48,11 +49,11 @@ class TsuminoDescriptionAdapter(
val genre = meta.category val genre = meta.category
if (genre != null) { if (genre != null) {
val pair = when (genre) { val pair = when (genre) {
"Doujinshi" -> Pair("#fc4e4e", R.string.doujinshi) "Doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
"Manga" -> Pair("#e78c1a", R.string.manga) "Manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
"Artist CG" -> Pair("#dde500", R.string.artist_cg) "Artist CG" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.artist_cg)
"Game CG" -> Pair("#05bf0b", R.string.game_cg) "Game CG" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.game_cg)
"Video" -> Pair("#14e723", R.string.video) "Video" -> Pair(SourceTagsUtil.WESTERN_COLOR, R.string.video)
else -> Pair("", 0) else -> Pair("", 0)
} }
@ -70,7 +71,7 @@ class TsuminoDescriptionAdapter(
binding.whenPosted.text = TsuminoSearchMetadata.TSUMINO_DATE_FORMAT.format(Date(meta.uploadDate ?: 0)) binding.whenPosted.text = TsuminoSearchMetadata.TSUMINO_DATE_FORMAT.format(Date(meta.uploadDate ?: 0))
binding.uploader.text = meta.uploader ?: itemView.context.getString(R.string.unknown) 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()) { val name = when (((meta.averageRating ?: 100F) * 2).roundToInt()) {
0 -> R.string.rating0 0 -> R.string.rating0

View File

@ -1,30 +1,31 @@
package exh.util package exh.util
import eu.kanade.tachiyomi.data.database.models.Manga
import exh.EH_SOURCE_ID import exh.EH_SOURCE_ID
import exh.EXH_SOURCE_ID import exh.EXH_SOURCE_ID
import exh.HITOMI_SOURCE_ID import exh.HITOMI_SOURCE_ID
import exh.NHENTAI_SOURCE_ID import exh.NHENTAI_SOURCE_ID
import exh.PURURIN_SOURCE_ID import exh.PURURIN_SOURCE_ID
import exh.TSUMINO_SOURCE_ID import exh.TSUMINO_SOURCE_ID
import exh.metadata.metadata.base.RaisedTag
import java.util.Locale
class SourceTagsUtil { class SourceTagsUtil {
fun getWrappedTag(sourceId: Long, namespace: String? = null, tag: String? = null, fullTag: String? = null): String? { 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) { 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 val parsed = if (fullTag != null) parseTag(fullTag) else if (namespace != null && tag != null) RaisedTag(namespace, tag, TAG_TYPE_DEFAULT) else null
if (parsed != null) { if (parsed?.namespace != null) {
when (sourceId) { when (sourceId) {
HITOMI_SOURCE_ID -> wrapTagHitomi(parsed.first, parsed.second.substringBefore('|').trim()) HITOMI_SOURCE_ID -> wrapTagHitomi(parsed.namespace, parsed.name.substringBefore('|').trim())
NHENTAI_SOURCE_ID -> wrapTagNHentai(parsed.first, parsed.second.substringBefore('|').trim()) NHENTAI_SOURCE_ID -> wrapTagNHentai(parsed.namespace, parsed.name.substringBefore('|').trim())
PURURIN_SOURCE_ID -> parsed.second.substringBefore('|').trim() PURURIN_SOURCE_ID -> parsed.name.substringBefore('|').trim()
TSUMINO_SOURCE_ID -> parsed.second.substringBefore('|').trim() TSUMINO_SOURCE_ID -> parsed.name.substringBefore('|').trim()
else -> wrapTag(parsed.first, parsed.second.substringBefore('|').trim()) else -> wrapTag(parsed.namespace, parsed.name.substringBefore('|').trim())
} }
} else null } else null
} 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(' ')) { private fun wrapTag(namespace: String, tag: String) = if (tag.contains(' ')) {
"$namespace:\"$tag$\"" "$namespace:\"$tag$\""
} else { } else {
@ -46,4 +47,40 @@ class SourceTagsUtil {
} else { } else {
"$namespace:$tag" "$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
}
} }

View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="140dp"
android:layout_gravity="center_vertical"
android:background="@drawable/list_item_selector"
android:paddingEnd="8dp"
tools:layout_editor_absoluteX="0dp"
tools:layout_editor_absoluteY="25dp">
<ImageView
android:id="@+id/thumbnail"
android:layout_width="100dp"
android:layout_height="140dp"
android:layout_gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@mipmap/ic_launcher" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
style="@style/TextAppearance.Regular.SubHeading"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:ellipsize="end"
android:maxLines="2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/thumbnail"
app:layout_constraintTop_toTopOf="parent"
tools:text="Manga title for the life of me I cant think yes totally" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/uploader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:maxLines="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/thumbnail"
app:layout_constraintTop_toBottomOf="@+id/title"
tools:text="Manga title for the life of me I cant think yes totally" />
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
android:background="@drawable/rounded_rectangle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/thumbnail">
<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:maxLines="1"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp" />
</androidx.cardview.widget.CardView>
<me.zhanghai.android.materialratingbar.MaterialRatingBar
android:id="@+id/rating_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
android:isIndicator="true"
android:maxHeight="20dp"
android:minHeight="20dp"
android:numStars="5"
app:layout_constraintBottom_toTopOf="@+id/cardView"
app:layout_constraintStart_toEndOf="@+id/thumbnail" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/date_posted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:maxLines="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/language"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:maxLines="1"
app:layout_constraintBottom_toTopOf="@+id/date_posted"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -10,6 +10,7 @@
app:showAsAction="collapseActionView|ifRoom" /> app:showAsAction="collapseActionView|ifRoom" />
<item <item
android:id="@+id/action_display_mode"
android:icon="@drawable/ic_view_module_24dp" android:icon="@drawable/ic_view_module_24dp"
android:title="@string/action_display_mode" android:title="@string/action_display_mode"
app:iconTint="?attr/colorOnPrimary" app:iconTint="?attr/colorOnPrimary"

View File

@ -71,6 +71,8 @@
<string name="eh_image_quality_1280">1280x</string> <string name="eh_image_quality_1280">1280x</string>
<string name="eh_image_quality_980">980x</string> <string name="eh_image_quality_980">980x</string>
<string name="eh_image_quality_780">780x</string> <string name="eh_image_quality_780">780x</string>
<string name="pref_enhanced_e_hentai_view">Enhanced E/ExHentai browse</string>
<string name="pref_enhanced_e_hentai_view_summary">Enable/Disable the enhanced browse menu made for E/ExHentai</string>
<string name="favorites_sync">Favorites sync</string> <string name="favorites_sync">Favorites sync</string>
<string name="disable_favorites_uploading">Disable favorites uploading</string> <string name="disable_favorites_uploading">Disable favorites uploading</string>
<string name="disable_favorites_uploading_summary">Favorites are only downloaded from ExHentai. Any changes to favorites in the app will not be uploaded. Prevents accidental loss of favorites on ExHentai. Note that removals will still be downloaded (if you remove a favorites on ExHentai, it will be removed in the app as well).</string> <string name="disable_favorites_uploading_summary">Favorites are only downloaded from ExHentai. Any changes to favorites in the app will not be uploaded. Prevents accidental loss of favorites on ExHentai. Note that removals will still be downloaded (if you remove a favorites on ExHentai, it will be removed in the app as well).</string>
@ -414,11 +416,21 @@
<string name="parodies">Parodies</string> <string name="parodies">Parodies</string>
<!-- Extra gallery info --> <!-- Extra gallery info -->
<string name="num_pages">%1$d pages</string> <plurals name="num_pages">
<item quantity="one">%1$d page</item>
<item quantity="other">%1$d pages</item>
</plurals>
<string name="tags">Tags</string> <string name="tags">Tags</string>
<string name="is_visible">Visible: %1$s</string> <string name="is_visible">Visible: %1$s</string>
<string name="rating_view">%1$s (%2$s, %3$d)</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="rating_view_no_count">%1$s (%2$s)</string>
<string name="language_translated">%1$s TR</string> <string name="language_translated">%1$s TR</string>
<!-- Enhanced E/ExHentai Browse View -->
<plurals name="browse_language_and_pages">
<item quantity="one">%2$s, %1$d page</item>
<item quantity="other">%2$s, %1$d pages</item>
</plurals>
</resources> </resources>