diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/LewdSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/LewdSource.kt index 6d5e16837..b3510543d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/LewdSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/LewdSource.kt @@ -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 : 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 + } + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt index 275024f90..c1469684b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt @@ -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" diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Hitomi.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Hitomi.kt index 7ca297174..22d4a3c34 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Hitomi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Hitomi.kt @@ -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 { 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) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt index ca6817860..5642b5493 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt @@ -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) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index a42a77e32..9a1fbf849 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -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?> = 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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index 4dcf17a0c..e2dad5d71 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt @@ -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 <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoButtonsAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoButtonsAdapter.kt new file mode 100644 index 000000000..3d0ec4899 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoButtonsAdapter.kt @@ -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() { + + 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 <-- + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt index 43a7f3fb8..bb34528bf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt @@ -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() { 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> = 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 <-- } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoItemAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoItemAdapter.kt new file mode 100644 index 000000000..35f323416 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoItemAdapter.kt @@ -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() { + + 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>? = 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 = 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 <-- + } + } +} diff --git a/app/src/main/java/exh/EHSourceHelpers.kt b/app/src/main/java/exh/EHSourceHelpers.kt index a8f0ef3ae..8d5e8b622 100755 --- a/app/src/main/java/exh/EHSourceHelpers.kt +++ b/app/src/main/java/exh/EHSourceHelpers.kt @@ -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 diff --git a/app/src/main/java/exh/metadata/metadata/EHentaiSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/EHentaiSearchMetadata.kt index 558fe711b..b71e85bf8 100644 --- a/app/src/main/java/exh/metadata/metadata/EHentaiSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/EHentaiSearchMetadata.kt @@ -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> { + val pairs = mutableListOf>() + 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" diff --git a/app/src/main/java/exh/metadata/metadata/EightMusesSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/EightMusesSearchMetadata.kt index 1102f9451..f5595ae0b 100644 --- a/app/src/main/java/exh/metadata/metadata/EightMusesSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/EightMusesSearchMetadata.kt @@ -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 = 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> { + val pairs = mutableListOf>() + 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 { diff --git a/app/src/main/java/exh/metadata/metadata/HBrowseSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/HBrowseSearchMetadata.kt index d1ec0677d..1ac752b20 100644 --- a/app/src/main/java/exh/metadata/metadata/HBrowseSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/HBrowseSearchMetadata.kt @@ -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> { + val pairs = mutableListOf>() + 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 { diff --git a/app/src/main/java/exh/metadata/metadata/HentaiCafeSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/HentaiCafeSearchMetadata.kt index 598b057a0..d7c32c9ba 100644 --- a/app/src/main/java/exh/metadata/metadata/HentaiCafeSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/HentaiCafeSearchMetadata.kt @@ -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> { + val pairs = mutableListOf>() + 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 { diff --git a/app/src/main/java/exh/metadata/metadata/HitomiSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/HitomiSearchMetadata.kt index ab4d24e2a..098ca68de 100644 --- a/app/src/main/java/exh/metadata/metadata/HitomiSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/HitomiSearchMetadata.kt @@ -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> { + val pairs = mutableListOf>() + 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 { diff --git a/app/src/main/java/exh/metadata/metadata/NHentaiSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/NHentaiSearchMetadata.kt index cc717fdb2..164e0b786 100644 --- a/app/src/main/java/exh/metadata/metadata/NHentaiSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/NHentaiSearchMetadata.kt @@ -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> { + val pairs = mutableListOf>() + 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) { diff --git a/app/src/main/java/exh/metadata/metadata/PervEdenSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/PervEdenSearchMetadata.kt index bbeb8b07b..7dc11d3be 100644 --- a/app/src/main/java/exh/metadata/metadata/PervEdenSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/PervEdenSearchMetadata.kt @@ -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> { + val pairs = mutableListOf>() + 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 { diff --git a/app/src/main/java/exh/metadata/metadata/PururinSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/PururinSearchMetadata.kt index 2f011c6c9..80bbaa226 100644 --- a/app/src/main/java/exh/metadata/metadata/PururinSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/PururinSearchMetadata.kt @@ -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> { + val pairs = mutableListOf>() + 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" } diff --git a/app/src/main/java/exh/metadata/metadata/TsuminoSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/TsuminoSearchMetadata.kt index 4d4a3af86..203d4e405 100644 --- a/app/src/main/java/exh/metadata/metadata/TsuminoSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/TsuminoSearchMetadata.kt @@ -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> { + val pairs = mutableListOf>() + 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 diff --git a/app/src/main/java/exh/metadata/metadata/base/RaisedSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/base/RaisedSearchMetadata.kt index ba8134d77..ce6728309 100644 --- a/app/src/main/java/exh/metadata/metadata/base/RaisedSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/base/RaisedSearchMetadata.kt @@ -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> + companion object { // Virtual tags allow searching of otherwise unindexed fields const val TAG_TYPE_VIRTUAL = -2 diff --git a/app/src/main/java/exh/ui/metadata/MetadataViewAdapter.kt b/app/src/main/java/exh/ui/metadata/MetadataViewAdapter.kt new file mode 100644 index 000000000..82895d63a --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/MetadataViewAdapter.kt @@ -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>) : + RecyclerView.Adapter() { + + 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>) { + 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 + } + } +} diff --git a/app/src/main/java/exh/ui/metadata/MetadataViewController.kt b/app/src/main/java/exh/ui/metadata/MetadataViewController.kt new file mode 100644 index 000000000..b47cdeade --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/MetadataViewController.kt @@ -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 { + constructor(manga: Manga?) : super( + Bundle().apply { + putLong(MangaController.MANGA_EXTRA, manga?.id ?: 0) + } + ) { + this.manga = manga + if (manga != null) { + source = Injekt.get().getOrStub(manga.source) + } + } + + constructor(mangaId: Long) : this( + Injekt.get().getManga(mangaId).executeAsBlocking() + ) + + @Suppress("unused") + constructor(bundle: Bundle) : this(bundle.getLong(MangaController.MANGA_EXTRA)) + + var data = emptyList>() + + 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) + } +} diff --git a/app/src/main/java/exh/ui/metadata/MetadataViewPresenter.kt b/app/src/main/java/exh/ui/metadata/MetadataViewPresenter.kt new file mode 100644 index 000000000..18fc4be6e --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/MetadataViewPresenter.kt @@ -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() { + + 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 { + return db.getManga(manga.url, manga.source).asRxObservable() + } + + private fun getMangaMetaObservable(): Observable { + val mangaId = manga.id + return if (mangaId != null) { + db.getFlatMetadataForManga(mangaId).asRxObservable() + .observeOn(AndroidSchedulers.mainThread()) + } else Observable.just(null) + } +} diff --git a/app/src/main/java/exh/ui/metadata/adapters/EHentaiDescriptionAdapter.kt b/app/src/main/java/exh/ui/metadata/adapters/EHentaiDescriptionAdapter.kt new file mode 100644 index 000000000..7f44576e8 --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/adapters/EHentaiDescriptionAdapter.kt @@ -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() { + + 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) + } + } +} diff --git a/app/src/main/java/exh/ui/metadata/adapters/EightMusesDescriptionAdapter.kt b/app/src/main/java/exh/ui/metadata/adapters/EightMusesDescriptionAdapter.kt new file mode 100644 index 000000000..7e900acaa --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/adapters/EightMusesDescriptionAdapter.kt @@ -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() { + + 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) + } + } +} diff --git a/app/src/main/java/exh/ui/metadata/adapters/HBrowseDescriptionAdapter.kt b/app/src/main/java/exh/ui/metadata/adapters/HBrowseDescriptionAdapter.kt new file mode 100644 index 000000000..95e99381d --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/adapters/HBrowseDescriptionAdapter.kt @@ -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() { + + 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) + } + } +} diff --git a/app/src/main/java/exh/ui/metadata/adapters/HentaiCafeDescriptionAdapter.kt b/app/src/main/java/exh/ui/metadata/adapters/HentaiCafeDescriptionAdapter.kt new file mode 100644 index 000000000..bb36cb2c9 --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/adapters/HentaiCafeDescriptionAdapter.kt @@ -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() { + + 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) + } + } +} diff --git a/app/src/main/java/exh/ui/metadata/adapters/HitomiDescriptionAdapter.kt b/app/src/main/java/exh/ui/metadata/adapters/HitomiDescriptionAdapter.kt new file mode 100644 index 000000000..7147cc53c --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/adapters/HitomiDescriptionAdapter.kt @@ -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() { + + 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) + } + } +} diff --git a/app/src/main/java/exh/ui/metadata/adapters/NHentaiDescriptionAdapter.kt b/app/src/main/java/exh/ui/metadata/adapters/NHentaiDescriptionAdapter.kt new file mode 100644 index 000000000..83beec647 --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/adapters/NHentaiDescriptionAdapter.kt @@ -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() { + + 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) + } + } +} diff --git a/app/src/main/java/exh/ui/metadata/adapters/PervEdenDescriptionAdapter.kt b/app/src/main/java/exh/ui/metadata/adapters/PervEdenDescriptionAdapter.kt new file mode 100644 index 000000000..0d43c1bec --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/adapters/PervEdenDescriptionAdapter.kt @@ -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() { + + 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) + } + } +} diff --git a/app/src/main/java/exh/ui/metadata/adapters/PururinDescriptionAdapter.kt b/app/src/main/java/exh/ui/metadata/adapters/PururinDescriptionAdapter.kt new file mode 100644 index 000000000..43262dc14 --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/adapters/PururinDescriptionAdapter.kt @@ -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() { + + 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) + } + } +} diff --git a/app/src/main/java/exh/ui/metadata/adapters/TsuminoDescriptionAdapter.kt b/app/src/main/java/exh/ui/metadata/adapters/TsuminoDescriptionAdapter.kt new file mode 100644 index 000000000..384b38686 --- /dev/null +++ b/app/src/main/java/exh/ui/metadata/adapters/TsuminoDescriptionAdapter.kt @@ -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() { + + 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) + } + } +} diff --git a/app/src/main/java/exh/util/ViewExtensions.kt b/app/src/main/java/exh/util/ViewExtensions.kt index 4fbf93a63..fba5be301 100644 --- a/app/src/main/java/exh/util/ViewExtensions.kt +++ b/app/src/main/java/exh/util/ViewExtensions.kt @@ -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?, 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 + } + } } } diff --git a/app/src/main/res/drawable/border.xml b/app/src/main/res/drawable/border.xml new file mode 100644 index 000000000..bb8469ac9 --- /dev/null +++ b/app/src/main/res/drawable/border.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/description_adapter_8m.xml b/app/src/main/res/layout/description_adapter_8m.xml new file mode 100644 index 000000000..7e0fee9a5 --- /dev/null +++ b/app/src/main/res/layout/description_adapter_8m.xml @@ -0,0 +1,35 @@ + + + + + + + + +