From 3e9c8dbfd23ed76554ae5153b62e5027ae9be176 Mon Sep 17 00:00:00 2001 From: Jobobby04 Date: Sat, 25 Jul 2020 21:04:13 -0400 Subject: [PATCH] Add a special view to replace descriptions for integrated and delegated sources! As the integrated and delegated websites don't actually have descriptions, just info, I decided to make a special view for them! with all the info you need available to you in front of your face, there is now no need to go searching through the description! This is likely the most work I have put into 1 feature in the whole time I have been developing TachiyomiSY! --- .../tachiyomi/source/online/LewdSource.kt | 22 ++ .../tachiyomi/source/online/all/EHentai.kt | 9 + .../tachiyomi/source/online/all/Hitomi.kt | 6 + .../tachiyomi/source/online/all/NHentai.kt | 9 +- .../tachiyomi/source/online/all/PervEden.kt | 6 + .../source/online/english/EightMuses.kt | 6 + .../source/online/english/HBrowse.kt | 6 + .../source/online/english/HentaiCafe.kt | 6 + .../source/online/english/Pururin.kt | 12 +- .../source/online/english/Tsumino.kt | 15 ++ .../tachiyomi/ui/manga/MangaController.kt | 47 +++- .../tachiyomi/ui/manga/MangaPresenter.kt | 3 +- .../ui/manga/info/MangaInfoButtonsAdapter.kt | 64 ++++++ .../ui/manga/info/MangaInfoHeaderAdapter.kt | 153 +------------ .../ui/manga/info/MangaInfoItemAdapter.kt | 214 ++++++++++++++++++ app/src/main/java/exh/EHSourceHelpers.kt | 2 +- .../metadata/EHentaiSearchMetadata.kt | 37 ++- .../metadata/EightMusesSearchMetadata.kt | 22 +- .../metadata/HBrowseSearchMetadata.kt | 21 +- .../metadata/HentaiCafeSearchMetadata.kt | 26 ++- .../metadata/metadata/HitomiSearchMetadata.kt | 53 ++++- .../metadata/NHentaiSearchMetadata.kt | 30 ++- .../metadata/PervEdenSearchMetadata.kt | 56 +++-- .../metadata/PururinSearchMetadata.kt | 27 ++- .../metadata/TsuminoSearchMetadata.kt | 52 ++++- .../metadata/base/RaisedSearchMetadata.kt | 3 + .../exh/ui/metadata/MetadataViewAdapter.kt | 41 ++++ .../exh/ui/metadata/MetadataViewController.kt | 87 +++++++ .../exh/ui/metadata/MetadataViewPresenter.kt | 48 ++++ .../adapters/EHentaiDescriptionAdapter.kt | 124 ++++++++++ .../adapters/EightMusesDescriptionAdapter.kt | 57 +++++ .../adapters/HBrowseDescriptionAdapter.kt | 57 +++++ .../adapters/HentaiCafeDescriptionAdapter.kt | 57 +++++ .../adapters/HitomiDescriptionAdapter.kt | 84 +++++++ .../adapters/NHentaiDescriptionAdapter.kt | 103 +++++++++ .../adapters/PervEdenDescriptionAdapter.kt | 98 ++++++++ .../adapters/PururinDescriptionAdapter.kt | 102 +++++++++ .../adapters/TsuminoDescriptionAdapter.kt | 107 +++++++++ app/src/main/java/exh/util/ViewExtensions.kt | 15 +- app/src/main/res/drawable/border.xml | 7 + .../res/layout/description_adapter_8m.xml | 35 +++ .../res/layout/description_adapter_eh.xml | 146 ++++++++++++ .../res/layout/description_adapter_hb.xml | 34 +++ .../res/layout/description_adapter_hc.xml | 34 +++ .../res/layout/description_adapter_hi.xml | 80 +++++++ .../res/layout/description_adapter_nh.xml | 90 ++++++++ .../res/layout/description_adapter_pe.xml | 82 +++++++ .../res/layout/description_adapter_pu.xml | 107 +++++++++ .../res/layout/description_adapter_ts.xml | 117 ++++++++++ .../main/res/layout/manga_info_buttons.xml | 35 +++ app/src/main/res/layout/manga_info_header.xml | 128 ----------- app/src/main/res/layout/manga_info_item.xml | 110 +++++++++ .../res/layout/metadata_view_controller.xml | 14 ++ .../main/res/layout/metadata_view_item.xml | 15 ++ app/src/main/res/values/strings_sy.xml | 76 +++++++ 55 files changed, 2635 insertions(+), 362 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoButtonsAdapter.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoItemAdapter.kt create mode 100644 app/src/main/java/exh/ui/metadata/MetadataViewAdapter.kt create mode 100644 app/src/main/java/exh/ui/metadata/MetadataViewController.kt create mode 100644 app/src/main/java/exh/ui/metadata/MetadataViewPresenter.kt create mode 100644 app/src/main/java/exh/ui/metadata/adapters/EHentaiDescriptionAdapter.kt create mode 100644 app/src/main/java/exh/ui/metadata/adapters/EightMusesDescriptionAdapter.kt create mode 100644 app/src/main/java/exh/ui/metadata/adapters/HBrowseDescriptionAdapter.kt create mode 100644 app/src/main/java/exh/ui/metadata/adapters/HentaiCafeDescriptionAdapter.kt create mode 100644 app/src/main/java/exh/ui/metadata/adapters/HitomiDescriptionAdapter.kt create mode 100644 app/src/main/java/exh/ui/metadata/adapters/NHentaiDescriptionAdapter.kt create mode 100644 app/src/main/java/exh/ui/metadata/adapters/PervEdenDescriptionAdapter.kt create mode 100644 app/src/main/java/exh/ui/metadata/adapters/PururinDescriptionAdapter.kt create mode 100644 app/src/main/java/exh/ui/metadata/adapters/TsuminoDescriptionAdapter.kt create mode 100644 app/src/main/res/drawable/border.xml create mode 100644 app/src/main/res/layout/description_adapter_8m.xml create mode 100644 app/src/main/res/layout/description_adapter_eh.xml create mode 100644 app/src/main/res/layout/description_adapter_hb.xml create mode 100644 app/src/main/res/layout/description_adapter_hc.xml create mode 100644 app/src/main/res/layout/description_adapter_hi.xml create mode 100644 app/src/main/res/layout/description_adapter_nh.xml create mode 100644 app/src/main/res/layout/description_adapter_pe.xml create mode 100644 app/src/main/res/layout/description_adapter_pu.xml create mode 100644 app/src/main/res/layout/description_adapter_ts.xml create mode 100644 app/src/main/res/layout/manga_info_buttons.xml create mode 100644 app/src/main/res/layout/manga_info_item.xml create mode 100644 app/src/main/res/layout/metadata_view_controller.xml create mode 100644 app/src/main/res/layout/metadata_view_item.xml 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 @@ + + + + + + + + +