MangaController overhaul (#7244)

(cherry picked from commit 33a778873a)

# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSettingsSheet.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
#	app/src/main/java/eu/kanade/tachiyomi/widget/MangaSummaryView.kt
#	app/src/main/res/layout-sw720dp/manga_info_header.xml
#	app/src/main/res/layout/manga_controller.xml
#	app/src/main/res/layout/manga_info_header.xml
#	app/src/main/res/layout/manga_summary.xml
#	app/src/main/res/menu/manga.xml
This commit is contained in:
Ivan Iskandar
2022-06-25 22:03:48 +07:00
committed by Jobobby04
parent 1e53ad97db
commit 2b7aca710e
106 changed files with 5016 additions and 4705 deletions
@@ -1,5 +1,6 @@
package exh.ui.base
import android.os.Bundle
import androidx.annotation.CallSuper
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
@@ -7,26 +8,36 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.isActive
import nucleus.presenter.Presenter
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@Suppress("DEPRECATION", "unused")
open class CoroutinePresenter<V>(
scope: CoroutineScope = MainScope(),
) : Presenter<V>(), CoroutineScope by scope {
private val scope: () -> CoroutineScope = ::MainScope,
) : Presenter<V>() {
var presenterScope: CoroutineScope = scope()
@CallSuper
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
if (!presenterScope.isActive) {
presenterScope = scope()
}
}
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use launchInView, Flow.inView, Flow.mapView")
override fun getView(): V? {
return super.getView()
}
fun launchInView(block: (CoroutineScope, V) -> Unit) = launchUI {
fun launchInView(block: (CoroutineScope, V) -> Unit) = presenterScope.launchUI {
view?.let { block.invoke(this, it) }
}
@@ -44,14 +55,14 @@ open class CoroutinePresenter<V>(
}
}
fun Flow<*>.launchUnderContext(context: CoroutineContext = EmptyCoroutineContext) =
launch(context) { this@launchUnderContext.collect() }
fun Flow<*>.launchUnderContext(context: CoroutineContext = EmptyCoroutineContext) = flowOn(context)
.launch()
fun Flow<*>.launch() = launchIn(this@CoroutinePresenter)
fun Flow<*>.launch() = launchIn(presenterScope)
@CallSuper
override fun destroy() {
super.destroy()
cancel()
override fun onDestroy() {
super.onDestroy()
presenterScope.cancel()
}
}
@@ -6,31 +6,30 @@ import android.view.View
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.databinding.MetadataViewControllerBinding
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.metadata.base.RaisedSearchMetadata
import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MetadataViewController : NucleusController<MetadataViewControllerBinding, MetadataViewPresenter> {
constructor(manga: Manga?) : super(
constructor(manga: Manga) : super(
bundleOf(
MangaController.MANGA_EXTRA to (manga?.id ?: 0),
MangaController.MANGA_EXTRA to manga.id,
),
) {
this.manga = manga
if (manga != null) {
source = Injekt.get<SourceManager>().getOrStub(manga.source)
}
source = Injekt.get<SourceManager>().getOrStub(manga.source)
}
constructor(mangaId: Long) : this(
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking(),
runBlocking { Injekt.get<GetMangaById>().await(mangaId)!! },
)
@Suppress("unused")
@@ -1,17 +1,16 @@
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.data.DatabaseHandler
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.util.lang.launchIO
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.metadata.metadata.base.awaitFlatMetadataForManga
import exh.source.getMainSource
import exh.ui.base.CoroutinePresenter
import exh.util.executeOnIO
import kotlinx.coroutines.flow.MutableStateFlow
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -20,7 +19,7 @@ class MetadataViewPresenter(
val manga: Manga,
val source: Source,
val preferences: PreferencesHelper = Injekt.get(),
private val db: DatabaseHelper = Injekt.get(),
private val db: DatabaseHandler = Injekt.get(),
) : CoroutinePresenter<MetadataViewController>() {
val meta = MutableStateFlow<RaisedSearchMetadata?>(null)
@@ -29,7 +28,7 @@ class MetadataViewPresenter(
super.onCreate(savedState)
launchIO {
val flatMetadata = db.getFlatMetadataForManga(manga.id!!).executeOnIO() ?: return@launchIO
val flatMetadata = db.awaitFlatMetadataForManga(manga.id) ?: return@launchIO
val mainSource = source.getMainSource<MetadataSource<*, *>>()
if (mainSource != null) {
meta.value = flatMetadata.raise(mainSource.metaClass)
@@ -2,132 +2,29 @@ package exh.ui.metadata.adapters
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterEhBinding
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil
import exh.metadata.bindDrawable
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.ui.metadata.MetadataViewController
class EHentaiDescriptionAdapter(
private val controller: MangaController,
) :
RecyclerView.Adapter<EHentaiDescriptionAdapter.EHentaiDescriptionViewHolder>() {
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.value
if (meta == null || meta !is EHentaiSearchMetadata) return
binding.genre.text =
meta.genre?.let { MetadataUtil.getGenreAndColour(itemView.context, it) }
?.let {
binding.genre.setBackgroundColor(it.first)
it.second
}
?: meta.genre
?: itemView.context.getString(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()
binding.favorites.bindDrawable(itemView.context, R.drawable.ic_book_24dp)
binding.uploader.text = meta.uploader ?: itemView.context.getString(R.string.unknown)
binding.size.text = MetadataUtil.humanReadableByteCount(meta.size ?: 0, true)
binding.size.bindDrawable(itemView.context, R.drawable.ic_outline_sd_card_24)
val length = meta.length ?: 0
binding.pages.text = itemView.resources.getQuantityString(R.plurals.num_pages, length, length)
binding.pages.bindDrawable(itemView.context, R.drawable.ic_baseline_menu_book_24)
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()
binding.ratingBar.rating = ratingFloat ?: 0F
@SuppressLint("SetTextI18n")
binding.rating.text = (ratingFloat ?: 0F).toString() + " - " + MetadataUtil.getRatingString(itemView.context, ratingFloat?.times(2))
binding.moreInfo.bindDrawable(itemView.context, R.drawable.ic_info_24dp)
listOf(
binding.favorites,
binding.genre,
binding.language,
binding.pages,
binding.rating,
binding.uploader,
binding.visible,
).forEach { textView ->
textView.setOnLongClickListener {
itemView.context.copyToClipboard(
textView.text.toString(),
textView.text.toString(),
)
true
}
}
binding.uploader.setOnClickListener {
meta.uploader?.let { controller.performSearch("uploader:\"$it\"") }
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
}
}
}
}
@Composable
fun EHentaiDescription(controller: MangaController) {
val meta by controller.presenter.meta.collectAsState()
EHentaiDescription(controller = controller, meta = meta)
}
@Composable
private fun EHentaiDescription(controller: MangaController, meta: RaisedSearchMetadata?) {
fun EHentaiDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
val context = LocalContext.current
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { factoryContext ->
DescriptionAdapterEhBinding.inflate(LayoutInflater.from(factoryContext)).root
},
update = {
val meta = state.meta
if (meta == null || meta !is EHentaiSearchMetadata) return@AndroidView
val binding = DescriptionAdapterEhBinding.bind(it)
@@ -187,15 +84,11 @@ private fun EHentaiDescription(controller: MangaController, meta: RaisedSearchMe
}
binding.uploader.setOnClickListener {
meta.uploader?.let { controller.performSearch("uploader:\"$it\"") }
meta.uploader?.let { search("uploader:\"$it\"") }
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
openMetadataViewer()
}
},
)
@@ -1,84 +1,28 @@
package exh.ui.metadata.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapter8mBinding
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.bindDrawable
import exh.metadata.metadata.EightMusesSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.ui.metadata.MetadataViewController
class EightMusesDescriptionAdapter(
private val controller: MangaController,
) :
RecyclerView.Adapter<EightMusesDescriptionAdapter.EightMusesDescriptionViewHolder>() {
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.value
if (meta == null || meta !is EightMusesSearchMetadata) return
binding.title.text = meta.title ?: itemView.context.getString(R.string.unknown)
binding.moreInfo.bindDrawable(itemView.context, R.drawable.ic_info_24dp)
binding.title.setOnLongClickListener {
itemView.context.copyToClipboard(
binding.title.text.toString(),
binding.title.text.toString(),
)
true
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
}
}
}
}
@Composable
fun EightMusesDescription(controller: MangaController) {
val meta by controller.presenter.meta.collectAsState()
EightMusesDescription(controller = controller, meta = meta)
}
@Composable
private fun EightMusesDescription(controller: MangaController, meta: RaisedSearchMetadata?) {
fun EightMusesDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { factoryContext ->
DescriptionAdapter8mBinding.inflate(LayoutInflater.from(factoryContext)).root
},
update = {
val meta = state.meta
if (meta == null || meta !is EightMusesSearchMetadata) return@AndroidView
val binding = DescriptionAdapter8mBinding.bind(it)
@@ -95,11 +39,7 @@ private fun EightMusesDescription(controller: MangaController, meta: RaisedSearc
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
openMetadataViewer()
}
},
)
@@ -1,85 +1,28 @@
package exh.ui.metadata.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterHbBinding
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.bindDrawable
import exh.metadata.metadata.HBrowseSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.ui.metadata.MetadataViewController
class HBrowseDescriptionAdapter(
private val controller: MangaController,
) :
RecyclerView.Adapter<HBrowseDescriptionAdapter.HBrowseDescriptionViewHolder>() {
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.value
if (meta == null || meta !is HBrowseSearchMetadata) return
binding.pages.text = itemView.resources.getQuantityString(R.plurals.num_pages, meta.length ?: 0, meta.length ?: 0)
binding.pages.bindDrawable(itemView.context, R.drawable.ic_baseline_menu_book_24)
binding.moreInfo.bindDrawable(itemView.context, R.drawable.ic_info_24dp)
binding.pages.setOnLongClickListener {
itemView.context.copyToClipboard(
binding.pages.text.toString(),
binding.pages.text.toString(),
)
true
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
}
}
}
}
@Composable
fun HBrowseDescription(controller: MangaController) {
val meta by controller.presenter.meta.collectAsState()
HBrowseDescription(controller = controller, meta = meta)
}
@Composable
private fun HBrowseDescription(controller: MangaController, meta: RaisedSearchMetadata?) {
fun HBrowseDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { factoryContext ->
DescriptionAdapterHbBinding.inflate(LayoutInflater.from(factoryContext)).root
},
update = {
val meta = state.meta
if (meta == null || meta !is HBrowseSearchMetadata) return@AndroidView
val binding = DescriptionAdapterHbBinding.bind(it)
@@ -97,11 +40,7 @@ private fun HBrowseDescription(controller: MangaController, meta: RaisedSearchMe
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
openMetadataViewer()
}
},
)
@@ -1,98 +1,30 @@
package exh.ui.metadata.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterHiBinding
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil
import exh.metadata.bindDrawable
import exh.metadata.metadata.HitomiSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.ui.metadata.MetadataViewController
import java.util.Date
class HitomiDescriptionAdapter(
private val controller: MangaController,
) :
RecyclerView.Adapter<HitomiDescriptionAdapter.HitomiDescriptionViewHolder>() {
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.value
if (meta == null || meta !is HitomiSearchMetadata) return
binding.genre.text = meta.genre?.let { MetadataUtil.getGenreAndColour(itemView.context, it) }?.let {
binding.genre.setBackgroundColor(it.first)
it.second
} ?: meta.genre ?: itemView.context.getString(R.string.unknown)
binding.whenPosted.text = MetadataUtil.EX_DATE_FORMAT.format(Date(meta.uploadDate ?: 0))
binding.language.text = meta.language ?: itemView.context.getString(R.string.unknown)
binding.moreInfo.bindDrawable(itemView.context, R.drawable.ic_info_24dp)
listOf(
binding.genre,
binding.language,
binding.whenPosted,
).forEach { textView ->
textView.setOnLongClickListener {
itemView.context.copyToClipboard(
textView.text.toString(),
textView.text.toString(),
)
true
}
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
}
}
}
}
@Composable
fun HitomiDescription(controller: MangaController) {
val meta by controller.presenter.meta.collectAsState()
HitomiDescription(controller = controller, meta = meta)
}
@Composable
private fun HitomiDescription(controller: MangaController, meta: RaisedSearchMetadata?) {
fun HitomiDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { factoryContext ->
DescriptionAdapterHiBinding.inflate(LayoutInflater.from(factoryContext)).root
},
update = {
val meta = state.meta
if (meta == null || meta !is HitomiSearchMetadata) return@AndroidView
val binding = DescriptionAdapterHiBinding.bind(it)
@@ -121,11 +53,7 @@ private fun HitomiDescription(controller: MangaController, meta: RaisedSearchMet
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
openMetadataViewer()
}
},
)
@@ -2,94 +2,31 @@ package exh.ui.metadata.adapters
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterMdBinding
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil.getRatingString
import exh.metadata.bindDrawable
import exh.metadata.metadata.MangaDexSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.ui.metadata.MetadataViewController
import kotlin.math.round
class MangaDexDescriptionAdapter(
private val controller: MangaController,
) :
RecyclerView.Adapter<MangaDexDescriptionAdapter.MangaDexDescriptionViewHolder>() {
private lateinit var binding: DescriptionAdapterMdBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MangaDexDescriptionViewHolder {
binding = DescriptionAdapterMdBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MangaDexDescriptionViewHolder(binding.root)
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: MangaDexDescriptionViewHolder, position: Int) {
holder.bind()
}
inner class MangaDexDescriptionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
val meta = controller.presenter.meta.value
if (meta == null || meta !is MangaDexSearchMetadata) return
// todo
val ratingFloat = meta.rating
binding.ratingBar.rating = ratingFloat?.div(2F) ?: 0F
@SuppressLint("SetTextI18n")
binding.rating.text = (round((ratingFloat ?: 0F) * 100.0) / 100.0).toString() + " - " + getRatingString(itemView.context, ratingFloat)
binding.rating.isVisible = ratingFloat != null
binding.ratingBar.isVisible = ratingFloat != null
binding.moreInfo.bindDrawable(itemView.context, R.drawable.ic_info_24dp)
binding.rating.setOnLongClickListener {
itemView.context.copyToClipboard(
binding.rating.text.toString(),
binding.rating.text.toString(),
)
true
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
}
}
}
}
@Composable
fun MangaDexDescription(controller: MangaController) {
val meta by controller.presenter.meta.collectAsState()
MangaDexDescription(controller = controller, meta = meta)
}
@Composable
private fun MangaDexDescription(controller: MangaController, meta: RaisedSearchMetadata?) {
fun MangaDexDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { factoryContext ->
DescriptionAdapterMdBinding.inflate(LayoutInflater.from(factoryContext)).root
},
update = {
val meta = state.meta
if (meta == null || meta !is MangaDexSearchMetadata) return@AndroidView
val binding = DescriptionAdapterMdBinding.bind(it)
@@ -112,11 +49,7 @@ private fun MangaDexDescription(controller: MangaController, meta: RaisedSearchM
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
openMetadataViewer()
}
},
)
@@ -2,116 +2,30 @@ package exh.ui.metadata.adapters
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterNhBinding
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil
import exh.metadata.bindDrawable
import exh.metadata.metadata.NHentaiSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.ui.metadata.MetadataViewController
import java.util.Date
class NHentaiDescriptionAdapter(
private val controller: MangaController,
) :
RecyclerView.Adapter<NHentaiDescriptionAdapter.NHentaiDescriptionViewHolder>() {
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.value
if (meta == null || meta !is NHentaiSearchMetadata) return
binding.genre.text = meta.tags.filter { it.namespace == NHentaiSearchMetadata.NHENTAI_CATEGORIES_NAMESPACE }.let { tags ->
if (tags.isNotEmpty()) tags.joinToString(transform = { it.name }) else null
}.let { categoriesString ->
categoriesString?.let { MetadataUtil.getGenreAndColour(itemView.context, it) }?.let {
binding.genre.setBackgroundColor(it.first)
it.second
} ?: categoriesString ?: itemView.context.getString(R.string.unknown)
}
meta.favoritesCount?.let {
if (it == 0L) return@let
binding.favorites.text = it.toString()
binding.favorites.bindDrawable(itemView.context, R.drawable.ic_book_24dp)
}
binding.whenPosted.text = MetadataUtil.EX_DATE_FORMAT.format(Date((meta.uploadDate ?: 0) * 1000))
binding.pages.text = itemView.resources.getQuantityString(R.plurals.num_pages, meta.pageImageTypes.size, meta.pageImageTypes.size)
binding.pages.bindDrawable(itemView.context, R.drawable.ic_baseline_menu_book_24)
@SuppressLint("SetTextI18n")
binding.id.text = "#" + (meta.nhId ?: 0)
binding.moreInfo.bindDrawable(itemView.context, R.drawable.ic_info_24dp)
listOf(
binding.favorites,
binding.genre,
binding.id,
binding.pages,
binding.whenPosted,
).forEach { textView ->
textView.setOnLongClickListener {
itemView.context.copyToClipboard(
textView.text.toString(),
textView.text.toString(),
)
true
}
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
}
}
}
}
@Composable
fun NHentaiDescription(controller: MangaController) {
val meta by controller.presenter.meta.collectAsState()
NHentaiDescription(controller = controller, meta = meta)
}
@Composable
private fun NHentaiDescription(controller: MangaController, meta: RaisedSearchMetadata?) {
fun NHentaiDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { factoryContext ->
DescriptionAdapterNhBinding.inflate(LayoutInflater.from(factoryContext)).root
},
update = {
val meta = state.meta
if (meta == null || meta !is NHentaiSearchMetadata) return@AndroidView
val binding = DescriptionAdapterNhBinding.bind(it)
@@ -157,11 +71,7 @@ private fun NHentaiDescription(controller: MangaController, meta: RaisedSearchMe
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
openMetadataViewer()
}
},
)
@@ -2,106 +2,31 @@ package exh.ui.metadata.adapters
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterPeBinding
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil
import exh.metadata.bindDrawable
import exh.metadata.metadata.PervEdenSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.ui.metadata.MetadataViewController
import java.util.Locale
import kotlin.math.round
class PervEdenDescriptionAdapter(
private val controller: MangaController,
) :
RecyclerView.Adapter<PervEdenDescriptionAdapter.PervEdenDescriptionViewHolder>() {
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.value
if (meta == null || meta !is PervEdenSearchMetadata) return
binding.genre.text = meta.genre?.let { MetadataUtil.getGenreAndColour(itemView.context, it) }?.let {
binding.genre.setBackgroundColor(it.first)
it.second
} ?: meta.genre ?: itemView.context.getString(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)
binding.ratingBar.rating = meta.rating ?: 0F
@SuppressLint("SetTextI18n")
binding.rating.text = (round((meta.rating ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUtil.getRatingString(itemView.context, meta.rating?.times(2))
binding.moreInfo.bindDrawable(itemView.context, R.drawable.ic_info_24dp)
listOf(
binding.genre,
binding.language,
binding.rating,
).forEach { textView ->
textView.setOnLongClickListener {
itemView.context.copyToClipboard(
textView.text.toString(),
textView.text.toString(),
)
true
}
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
}
}
}
}
@Composable
fun PervEdenDescription(controller: MangaController) {
val meta by controller.presenter.meta.collectAsState()
PervEdenDescription(controller = controller, meta = meta)
}
@Composable
private fun PervEdenDescription(controller: MangaController, meta: RaisedSearchMetadata?) {
fun PervEdenDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { factoryContext ->
DescriptionAdapterPeBinding.inflate(LayoutInflater.from(factoryContext)).root
},
update = {
val meta = state.meta
if (meta == null || meta !is PervEdenSearchMetadata) return@AndroidView
val binding = DescriptionAdapterPeBinding.bind(it)
@@ -137,11 +62,7 @@ private fun PervEdenDescription(controller: MangaController, meta: RaisedSearchM
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
openMetadataViewer()
}
},
)
@@ -2,112 +2,30 @@ package exh.ui.metadata.adapters
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterPuBinding
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil
import exh.metadata.bindDrawable
import exh.metadata.metadata.PururinSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.ui.metadata.MetadataViewController
import kotlin.math.round
class PururinDescriptionAdapter(
private val controller: MangaController,
) :
RecyclerView.Adapter<PururinDescriptionAdapter.PururinDescriptionViewHolder>() {
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.value
if (meta == null || meta !is PururinSearchMetadata) return
binding.genre.text = meta.tags.find { it.namespace == PururinSearchMetadata.TAG_NAMESPACE_CATEGORY }.let { genre ->
genre?.let { MetadataUtil.getGenreAndColour(itemView.context, it.name) }?.let {
binding.genre.setBackgroundColor(it.first)
it.second
} ?: genre?.name ?: itemView.context.getString(R.string.unknown)
}
binding.uploader.text = meta.uploaderDisp ?: meta.uploader.orEmpty()
binding.size.text = meta.fileSize ?: itemView.context.getString(R.string.unknown)
binding.size.bindDrawable(itemView.context, R.drawable.ic_outline_sd_card_24)
binding.pages.text = itemView.resources.getQuantityString(R.plurals.num_pages, meta.pages ?: 0, meta.pages ?: 0)
binding.pages.bindDrawable(itemView.context, R.drawable.ic_baseline_menu_book_24)
val ratingFloat = meta.averageRating?.toFloat()
binding.ratingBar.rating = ratingFloat ?: 0F
@SuppressLint("SetTextI18n")
binding.rating.text = (round((ratingFloat ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUtil.getRatingString(itemView.context, ratingFloat?.times(2))
binding.moreInfo.bindDrawable(itemView.context, R.drawable.ic_info_24dp)
listOf(
binding.genre,
binding.pages,
binding.rating,
binding.size,
binding.uploader,
).forEach { textView ->
textView.setOnLongClickListener {
itemView.context.copyToClipboard(
textView.text.toString(),
textView.text.toString(),
)
true
}
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
}
}
}
}
@Composable
fun PururinDescription(controller: MangaController) {
val meta by controller.presenter.meta.collectAsState()
PururinDescription(controller = controller, meta = meta)
}
@Composable
private fun PururinDescription(controller: MangaController, meta: RaisedSearchMetadata?) {
fun PururinDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { factoryContext ->
DescriptionAdapterPuBinding.inflate(LayoutInflater.from(factoryContext)).root
},
update = {
val meta = state.meta
if (meta == null || meta !is PururinSearchMetadata) return@AndroidView
val binding = DescriptionAdapterPuBinding.bind(it)
@@ -150,11 +68,7 @@ private fun PururinDescription(controller: MangaController, meta: RaisedSearchMe
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
openMetadataViewer()
}
},
)
@@ -2,113 +2,31 @@ package exh.ui.metadata.adapters
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterTsBinding
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil
import exh.metadata.bindDrawable
import exh.metadata.metadata.TsuminoSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.ui.metadata.MetadataViewController
import java.util.Date
import kotlin.math.round
class TsuminoDescriptionAdapter(
private val controller: MangaController,
) :
RecyclerView.Adapter<TsuminoDescriptionAdapter.TsuminoDescriptionViewHolder>() {
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.value
if (meta == null || meta !is TsuminoSearchMetadata) return
binding.genre.text = meta.category?.let { MetadataUtil.getGenreAndColour(itemView.context, it) }?.let {
binding.genre.setBackgroundColor(it.first)
it.second
} ?: meta.category ?: itemView.context.getString(R.string.unknown)
binding.favorites.text = (meta.favorites ?: 0).toString()
binding.favorites.bindDrawable(itemView.context, R.drawable.ic_book_24dp)
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.resources.getQuantityString(R.plurals.num_pages, meta.length ?: 0, meta.length ?: 0)
binding.pages.bindDrawable(itemView.context, R.drawable.ic_baseline_menu_book_24)
binding.ratingBar.rating = meta.averageRating ?: 0F
@SuppressLint("SetTextI18n")
binding.rating.text = (round((meta.averageRating ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUtil.getRatingString(itemView.context, meta.averageRating?.times(2))
binding.moreInfo.bindDrawable(itemView.context, R.drawable.ic_info_24dp)
listOf(
binding.favorites,
binding.genre,
binding.pages,
binding.rating,
binding.uploader,
binding.whenPosted,
).forEach { textView ->
textView.setOnLongClickListener {
itemView.context.copyToClipboard(
textView.text.toString(),
textView.text.toString(),
)
true
}
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
}
}
}
}
@Composable
fun TsuminoDescription(controller: MangaController) {
val meta by controller.presenter.meta.collectAsState()
TsuminoDescription(controller = controller, meta = meta)
}
@Composable
private fun TsuminoDescription(controller: MangaController, meta: RaisedSearchMetadata?) {
fun TsuminoDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { factoryContext ->
DescriptionAdapterTsBinding.inflate(LayoutInflater.from(factoryContext)).root
},
update = {
val meta = state.meta
if (meta == null || meta !is TsuminoSearchMetadata) return@AndroidView
val binding = DescriptionAdapterTsBinding.bind(it)
@@ -151,11 +69,7 @@ private fun TsuminoDescription(controller: MangaController, meta: RaisedSearchMe
}
binding.moreInfo.setOnClickListener {
controller.router?.pushController(
MetadataViewController(
controller.manga,
),
)
openMetadataViewer()
}
},
)
@@ -22,7 +22,7 @@ class SmartSearchPresenter(private val source: CatalogueSource, private val conf
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
launchIO {
presenterScope.launchIO {
val result = try {
val resultManga = smartSearchEngine.smartSearch(source, config.origTitle)
if (resultManga != null) {