Fuck motion layout
This commit is contained in:
@@ -2,15 +2,10 @@ package eu.kanade.tachiyomi.ui.manga
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
@@ -18,8 +13,6 @@ import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@@ -31,7 +24,6 @@ import androidx.recyclerview.widget.ConcatAdapter
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.imageLoader
|
||||
import coil.loadAny
|
||||
import coil.request.ImageRequest
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
@@ -85,7 +77,6 @@ import eu.kanade.tachiyomi.ui.manga.chapter.MangaChaptersHeaderAdapter
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChaptersAdapter
|
||||
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.merged.EditMergedSettingsDialog
|
||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
||||
import eu.kanade.tachiyomi.ui.manga.track.TrackSearchDialog
|
||||
@@ -107,6 +98,7 @@ import eu.kanade.tachiyomi.util.view.snack
|
||||
import exh.log.xLogD
|
||||
import exh.md.similar.MangaDexSimilarController
|
||||
import exh.metadata.metadata.base.FlatMetadata
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import exh.recs.RecommendsController
|
||||
import exh.source.MERGED_SOURCE_ID
|
||||
import exh.source.getMainSource
|
||||
@@ -117,7 +109,6 @@ import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.withContext
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
import reactivecircus.flowbinding.recyclerview.scrollEvents
|
||||
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
|
||||
import timber.log.Timber
|
||||
@@ -192,15 +183,13 @@ class MangaController :
|
||||
|
||||
private var mangaInfoAdapter: MangaInfoHeaderAdapter? = null
|
||||
|
||||
// SY >--
|
||||
private var mangaInfoItemAdapter: MangaInfoItemAdapter? = null
|
||||
private var mangaInfoButtonsAdapter: MangaInfoButtonsAdapter? = null
|
||||
private var mangaMetaInfoAdapter: RecyclerView.Adapter<*>? = null
|
||||
|
||||
// SY <--
|
||||
private var chaptersHeaderAdapter: MangaChaptersHeaderAdapter? = null
|
||||
private var chaptersAdapter: ChaptersAdapter? = null
|
||||
|
||||
// SY -->
|
||||
private var mangaInfoButtonsAdapter: MangaInfoButtonsAdapter? = null
|
||||
// SY <--
|
||||
|
||||
// Sheet containing filter/sort/display items.
|
||||
private var settingsSheet: ChaptersSettingsSheet? = null
|
||||
|
||||
@@ -237,8 +226,6 @@ class MangaController :
|
||||
private var editMangaDialog: EditMangaDialog? = null
|
||||
|
||||
private var editMergedSettingsDialog: EditMergedSettingsDialog? = null
|
||||
|
||||
private var currentAnimator: Animator? = null
|
||||
// EXH <--
|
||||
|
||||
init {
|
||||
@@ -298,18 +285,13 @@ class MangaController :
|
||||
if (manga == null || source == null) return
|
||||
|
||||
// Init RecyclerView and adapter
|
||||
mangaInfoAdapter = MangaInfoHeaderAdapter(this, binding.infoRecycler != null)
|
||||
mangaInfoAdapter = MangaInfoHeaderAdapter(this, fromSource, binding.infoRecycler != null)
|
||||
chaptersHeaderAdapter = MangaChaptersHeaderAdapter(this)
|
||||
chaptersAdapter = ChaptersAdapter(this, view.context)
|
||||
// SY -->
|
||||
val mainSource = presenter.source.getMainSource<MetadataSource<*, *>>()
|
||||
if (mainSource != null) {
|
||||
mangaMetaInfoAdapter = mainSource.getDescriptionAdapter(this)
|
||||
}
|
||||
if (!preferences.recommendsInOverflow().get() || smartSearchConfig != null) {
|
||||
mangaInfoButtonsAdapter = MangaInfoButtonsAdapter(this)
|
||||
}
|
||||
mangaInfoItemAdapter = MangaInfoItemAdapter(this, fromSource, binding.infoRecycler != null)
|
||||
// SY <--
|
||||
|
||||
// Phone layout
|
||||
@@ -317,8 +299,6 @@ class MangaController :
|
||||
it.adapter = ConcatAdapter(
|
||||
listOfNotNull(
|
||||
mangaInfoAdapter,
|
||||
mangaMetaInfoAdapter,
|
||||
mangaInfoItemAdapter,
|
||||
mangaInfoButtonsAdapter,
|
||||
chaptersHeaderAdapter,
|
||||
chaptersAdapter
|
||||
@@ -346,9 +326,7 @@ class MangaController :
|
||||
it.adapter = ConcatAdapter(
|
||||
listOfNotNull(
|
||||
mangaInfoAdapter,
|
||||
mangaInfoButtonsAdapter,
|
||||
mangaMetaInfoAdapter,
|
||||
mangaInfoItemAdapter
|
||||
mangaInfoButtonsAdapter
|
||||
)
|
||||
)
|
||||
|
||||
@@ -459,11 +437,6 @@ class MangaController :
|
||||
chaptersHeaderAdapter = null
|
||||
chaptersAdapter = null
|
||||
settingsSheet = null
|
||||
// SY -->
|
||||
mangaInfoButtonsAdapter = null
|
||||
mangaInfoItemAdapter = null
|
||||
mangaMetaInfoAdapter = null
|
||||
// SY <--
|
||||
addSnackbar?.dismiss()
|
||||
updateToolbarTitleAlpha(1F)
|
||||
toolbarTextView = null
|
||||
@@ -559,7 +532,7 @@ class MangaController :
|
||||
val mainSource = presenter.source.getMainSource<MetadataSource<*, *>>()
|
||||
if (mainSource != null) {
|
||||
presenter.meta = flatMetadata.raise(mainSource.metaClass)
|
||||
mangaMetaInfoAdapter?.notifyDataSetChanged()
|
||||
mangaInfoAdapter?.notifyMetaAdapter()
|
||||
updateFilterIconState()
|
||||
}
|
||||
}
|
||||
@@ -573,13 +546,10 @@ class MangaController :
|
||||
* @param manga manga object containing information about manga.
|
||||
* @param source the source of the manga.
|
||||
*/
|
||||
fun onNextMangaInfo(manga: Manga, source: Source) {
|
||||
fun onNextMangaInfo(manga: Manga, source: Source, metadata: RaisedSearchMetadata?) {
|
||||
if (manga.initialized) {
|
||||
// Update view.
|
||||
mangaInfoAdapter?.update(manga, source)
|
||||
mangaInfoItemAdapter?.update(manga, source, presenter.meta)
|
||||
|
||||
binding.expandedImage.loadAny(manga)
|
||||
mangaInfoAdapter?.update(manga, source, metadata, presenter.mergedMangaReferences)
|
||||
} else {
|
||||
// Initialize manga.
|
||||
fetchMangaInfoFromSource()
|
||||
@@ -843,110 +813,6 @@ class MangaController :
|
||||
presenter.moveMangaToCategories(manga, categories)
|
||||
}
|
||||
|
||||
// SY -->
|
||||
fun onThumbnailClick(thumbView: ImageView) {
|
||||
if (!presenter.manga.initialized || presenter.manga.thumbnail_url == null) return
|
||||
currentAnimator?.cancel()
|
||||
|
||||
val startBoundsInt = Rect()
|
||||
val finalBoundsInt = Rect()
|
||||
val globalOffset = Point()
|
||||
|
||||
thumbView.getGlobalVisibleRect(startBoundsInt)
|
||||
binding.root.getGlobalVisibleRect(finalBoundsInt, globalOffset)
|
||||
startBoundsInt.offset(-globalOffset.x, -globalOffset.y)
|
||||
finalBoundsInt.offset(-globalOffset.x, -globalOffset.y)
|
||||
|
||||
val startBounds = RectF(startBoundsInt)
|
||||
val finalBounds = RectF(finalBoundsInt)
|
||||
|
||||
val startScale: Float
|
||||
if ((finalBounds.width() / finalBounds.height() > startBounds.width() / startBounds.height())) {
|
||||
startScale = startBounds.height() / finalBounds.height()
|
||||
val startWidth: Float = startScale * finalBounds.width()
|
||||
val deltaWidth: Float = (startWidth - startBounds.width()) / 2
|
||||
startBounds.left -= deltaWidth.toInt()
|
||||
startBounds.right += deltaWidth.toInt()
|
||||
} else {
|
||||
startScale = startBounds.width() / finalBounds.width()
|
||||
val startHeight: Float = startScale * finalBounds.height()
|
||||
val deltaHeight: Float = (startHeight - startBounds.height()) / 2f
|
||||
startBounds.top -= deltaHeight.toInt()
|
||||
startBounds.bottom += deltaHeight.toInt()
|
||||
}
|
||||
thumbView.alpha = 0f
|
||||
actionFab?.isVisible = false
|
||||
binding.expandedImage.isVisible = true
|
||||
|
||||
binding.expandedImage.pivotX = 0f
|
||||
binding.expandedImage.pivotY = 0f
|
||||
|
||||
currentAnimator = AnimatorSet().apply {
|
||||
play(
|
||||
ObjectAnimator.ofFloat(
|
||||
binding.expandedImage,
|
||||
View.X,
|
||||
startBounds.left,
|
||||
finalBounds.left
|
||||
)
|
||||
).apply {
|
||||
with(ObjectAnimator.ofFloat(binding.expandedImage, View.Y, startBounds.top, finalBounds.top))
|
||||
with(ObjectAnimator.ofFloat(binding.expandedImage, View.SCALE_X, startScale, 1f))
|
||||
with(ObjectAnimator.ofFloat(binding.expandedImage, View.SCALE_Y, startScale, 1f))
|
||||
}
|
||||
duration = resources?.getInteger(android.R.integer.config_shortAnimTime)?.toLong() ?: 150L
|
||||
interpolator = DecelerateInterpolator()
|
||||
addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
currentAnimator = null
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator) {
|
||||
currentAnimator = null
|
||||
}
|
||||
}
|
||||
)
|
||||
start()
|
||||
}
|
||||
|
||||
binding.expandedImage.clicks()
|
||||
.onEach {
|
||||
currentAnimator?.cancel()
|
||||
|
||||
currentAnimator = AnimatorSet().apply {
|
||||
play(ObjectAnimator.ofFloat(binding.expandedImage, View.X, startBounds.left)).apply {
|
||||
with(ObjectAnimator.ofFloat(binding.expandedImage, View.Y, startBounds.top))
|
||||
with(ObjectAnimator.ofFloat(binding.expandedImage, View.SCALE_X, startScale))
|
||||
with(ObjectAnimator.ofFloat(binding.expandedImage, View.SCALE_Y, startScale))
|
||||
}
|
||||
duration = resources?.getInteger(android.R.integer.config_shortAnimTime)?.toLong() ?: 150L
|
||||
interpolator = DecelerateInterpolator()
|
||||
addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
thumbView.alpha = 1f
|
||||
binding.expandedImage.isVisible = false
|
||||
actionFab?.isVisible = presenter.filteredAndSortedChapters.any { !it.read }
|
||||
currentAnimator = null
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator) {
|
||||
thumbView.alpha = 1f
|
||||
binding.expandedImage.isVisible = false
|
||||
actionFab?.isVisible = presenter.filteredAndSortedChapters.any { !it.read }
|
||||
currentAnimator = null
|
||||
}
|
||||
}
|
||||
)
|
||||
start()
|
||||
}
|
||||
}
|
||||
.launchIn(viewScope)
|
||||
}
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* Perform a global search using the provided query.
|
||||
*
|
||||
|
||||
@@ -154,6 +154,8 @@ class MangaPresenter(
|
||||
|
||||
var mergedManga = emptyMap<Long, Manga>()
|
||||
private set
|
||||
var mergedMangaReferences = emptyList<MergedMangaReference>()
|
||||
private set
|
||||
|
||||
var dedupe: Boolean = true
|
||||
|
||||
@@ -166,6 +168,7 @@ class MangaPresenter(
|
||||
// SY -->
|
||||
if (source is MergedSource) {
|
||||
launchIO { mergedManga = db.getMergedMangas(manga.id!!).executeAsBlocking().associateBy { it.id!! } }
|
||||
launchIO { mergedMangaReferences = db.getMergedMangaReferences(manga.id!!).executeAsBlocking() }
|
||||
}
|
||||
// SY <--
|
||||
|
||||
@@ -188,11 +191,13 @@ class MangaPresenter(
|
||||
}
|
||||
}
|
||||
.subscribeLatestCache({ view, (manga, flatMetadata) ->
|
||||
flatMetadata?.let { metadata ->
|
||||
view.onNextMetaInfo(metadata)
|
||||
}
|
||||
val mainSource = source.getMainSource<MetadataSource<*, *>>()
|
||||
val meta = if (mainSource != null) {
|
||||
flatMetadata?.raise(mainSource.metaClass)
|
||||
} else null
|
||||
this.meta = meta
|
||||
// SY <--
|
||||
view.onNextMangaInfo(manga, source)
|
||||
view.onNextMangaInfo(manga, source, meta)
|
||||
})
|
||||
|
||||
getTrackingObservable()
|
||||
@@ -383,7 +388,7 @@ class MangaPresenter(
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeLatestCache(
|
||||
{ view, _ ->
|
||||
view.onNextMangaInfo(manga, source)
|
||||
view.onNextMangaInfo(manga, source, meta)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.loadAny
|
||||
import coil.target.ImageViewTarget
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.databinding.MangaInfoHeaderBinding
|
||||
@@ -16,38 +18,48 @@ import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import eu.kanade.tachiyomi.util.view.setChips
|
||||
import exh.merged.sql.models.MergedMangaReference
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import exh.source.MERGED_SOURCE_ID
|
||||
import exh.source.getMainSource
|
||||
import exh.util.SourceTagsUtil
|
||||
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 isTablet: Boolean
|
||||
private val fromSource: Boolean,
|
||||
private val isTablet: Boolean,
|
||||
) :
|
||||
RecyclerView.Adapter<MangaInfoHeaderAdapter.HeaderViewHolder>() {
|
||||
|
||||
private val trackManager: TrackManager by injectLazy()
|
||||
|
||||
// SY -->
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
|
||||
private var mergedMangaReferences: List<MergedMangaReference> = emptyList()
|
||||
// SY <--
|
||||
|
||||
private var manga: Manga = controller.presenter.manga
|
||||
private var source: Source = controller.presenter.source
|
||||
|
||||
// SY -->
|
||||
private var meta: RaisedSearchMetadata? = controller.presenter.meta
|
||||
private var mergedMangaReferences: List<MergedMangaReference> = controller.presenter.mergedMangaReferences
|
||||
|
||||
// SY <--
|
||||
private var trackCount: Int = 0
|
||||
private var metaInfoAdapter: RecyclerView.Adapter<*>? = null
|
||||
private var mangaTagsInfoAdapter: NamespaceTagsAdapter = NamespaceTagsAdapter(controller, source)
|
||||
|
||||
private lateinit var binding: MangaInfoHeaderBinding
|
||||
|
||||
@@ -55,6 +67,20 @@ class MangaInfoHeaderAdapter(
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
|
||||
binding = MangaInfoHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
// SY -->
|
||||
metaInfoAdapter = source.getMainSource<MetadataSource<*, *>>()?.getDescriptionAdapter(controller)
|
||||
binding.metadataView.isVisible = if (metaInfoAdapter != null) {
|
||||
binding.metadataView.layoutManager = LinearLayoutManager(binding.root.context)
|
||||
binding.metadataView.adapter = metaInfoAdapter
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
binding.genreGroups.layoutManager = LinearLayoutManager(binding.root.context)
|
||||
binding.genreGroups.adapter = mangaTagsInfoAdapter
|
||||
// SY <--
|
||||
|
||||
return HeaderViewHolder(binding.root)
|
||||
}
|
||||
|
||||
@@ -70,16 +96,16 @@ class MangaInfoHeaderAdapter(
|
||||
* @param manga manga object containing information about manga.
|
||||
* @param source the source of the manga.
|
||||
*/
|
||||
fun update(manga: Manga, source: Source) {
|
||||
fun update(manga: Manga, source: Source, meta: RaisedSearchMetadata?, mergedMangaReferences: List<MergedMangaReference>) {
|
||||
this.manga = manga
|
||||
this.source = source
|
||||
// SY -->
|
||||
if (source is MergedSource) {
|
||||
mergedMangaReferences = db.getMergedMangaReferences(manga.id!!).executeAsBlocking()
|
||||
}
|
||||
this.meta = meta
|
||||
this.mergedMangaReferences = mergedMangaReferences
|
||||
// SY <--
|
||||
|
||||
notifyDataSetChanged()
|
||||
notifyMetaAdapter()
|
||||
}
|
||||
|
||||
fun setTrackingCount(trackCount: Int) {
|
||||
@@ -88,14 +114,22 @@ class MangaInfoHeaderAdapter(
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun notifyMetaAdapter() {
|
||||
metaInfoAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
inner class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
|
||||
fun bind() {
|
||||
// For rounded corners
|
||||
binding.mangaCover.clipToOutline = true
|
||||
|
||||
// SY -->
|
||||
binding.mangaCover.clicks()
|
||||
.onEach { controller.onThumbnailClick(binding.mangaCover) }
|
||||
.launchIn(controller.viewScope)
|
||||
mangaTagsInfoAdapter.mItemClickListener = FlexibleAdapter.OnItemClickListener { _, _ ->
|
||||
controller.viewScope.launchUI {
|
||||
toggleMangaInfo()
|
||||
}
|
||||
false
|
||||
}
|
||||
// SY <--
|
||||
|
||||
binding.btnFavorite.clicks()
|
||||
@@ -209,13 +243,22 @@ class MangaInfoHeaderAdapter(
|
||||
}
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
binding.mangaSummaryText.longClicks()
|
||||
.onEach {
|
||||
controller.activity?.copyToClipboard(
|
||||
view.context.getString(R.string.description),
|
||||
binding.mangaSummaryText.text.toString()
|
||||
)
|
||||
}
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
binding.mangaCover.longClicks()
|
||||
.onEach {
|
||||
showCoverOptionsDialog()
|
||||
}
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
setMangaInfo(manga, source)
|
||||
setMangaInfo(manga, source, meta)
|
||||
}
|
||||
|
||||
private fun showCoverOptionsDialog() {
|
||||
@@ -245,7 +288,7 @@ class MangaInfoHeaderAdapter(
|
||||
* @param manga manga object containing information about manga.
|
||||
* @param source the source of the manga.
|
||||
*/
|
||||
private fun setMangaInfo(manga: Manga, source: Source?) {
|
||||
private fun setMangaInfo(manga: Manga, source: Source?, meta: RaisedSearchMetadata?) {
|
||||
// Update full title TextView.
|
||||
binding.mangaFullTitle.text = if (manga.title.isBlank()) {
|
||||
view.context.getString(R.string.unknown)
|
||||
@@ -278,7 +321,6 @@ class MangaInfoHeaderAdapter(
|
||||
} else /* SY <-- */ if (mangaSource != null) {
|
||||
text = mangaSource
|
||||
setOnClickListener {
|
||||
val sourceManager = Injekt.get<SourceManager>()
|
||||
controller.performSearch(sourceManager.getOrStub(source.id).name)
|
||||
}
|
||||
} else {
|
||||
@@ -305,16 +347,141 @@ class MangaInfoHeaderAdapter(
|
||||
setFavoriteButtonState(manga.favorite)
|
||||
|
||||
// Set cover if changed.
|
||||
listOf(binding.mangaCover, binding.backdrop).forEach {
|
||||
it.loadAny(manga)
|
||||
binding.backdrop.loadAny(manga)
|
||||
binding.mangaCover.loadAny(manga) {
|
||||
listener(
|
||||
onSuccess = { request, _ ->
|
||||
(request.target as? ImageViewTarget)?.drawable?.let { drawable ->
|
||||
val ratio = drawable.minimumWidth / drawable.minimumHeight.toFloat()
|
||||
binding.root.getConstraintSet(R.id.end)
|
||||
?.setDimensionRatio(R.id.manga_cover, ratio.toString())
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
if (initialLoad && isTablet) {
|
||||
initialLoad = false
|
||||
// wrap_content and autoFixTextSize can cause unwanted behaviour this tries to solve it
|
||||
binding.mangaFullTitle.requestLayout()
|
||||
|
||||
// Manga info section
|
||||
val hasInfoContent = !manga.description.isNullOrBlank() || !manga.genre.isNullOrBlank()
|
||||
showMangaInfo(hasInfoContent)
|
||||
if (hasInfoContent) {
|
||||
// Update description TextView.
|
||||
binding.mangaSummaryText.text = if (manga.description.isNullOrBlank()) {
|
||||
view.context.getString(R.string.unknown)
|
||||
} else {
|
||||
manga.description
|
||||
}
|
||||
|
||||
// SY -->
|
||||
if (manga.description == "meta") {
|
||||
binding.mangaSummaryText.text = ""
|
||||
/*binding.mangaInfoToggleLess.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
topToBottom = -1
|
||||
bottomToBottom = binding.mangaSummaryText.id
|
||||
}*/
|
||||
}
|
||||
// SY <--
|
||||
|
||||
// Update genres list
|
||||
if (!manga.genre.isNullOrBlank()) {
|
||||
binding.mangaGenresTagsCompactChips.setChips(
|
||||
manga.getGenres(),
|
||||
controller::performGenreSearch
|
||||
)
|
||||
// SY -->
|
||||
// if (source?.getMainSource<NamespaceSource>() != null) {
|
||||
setChipsWithNamespace(
|
||||
manga.genre,
|
||||
meta
|
||||
)
|
||||
// binding.mangaGenresTagsFullChips.isVisible = false
|
||||
/*} else {
|
||||
binding.mangaGenresTagsFullChips.setChips(
|
||||
manga.getGenres(),
|
||||
controller::performGenreSearch
|
||||
)
|
||||
binding.genreGroups.isVisible = false
|
||||
}*/
|
||||
// SY <--
|
||||
} else {
|
||||
binding.mangaGenresTagsCompactChips.isVisible = false
|
||||
// binding.mangaGenresTagsFullChips.isVisible = false
|
||||
// SY -->
|
||||
binding.genreGroups.isVisible = false
|
||||
// SY <--
|
||||
}
|
||||
|
||||
// Handle showing more or less info
|
||||
merge(
|
||||
binding.mangaSummaryText.clicks(),
|
||||
binding.mangaInfoToggleMore.clicks(),
|
||||
binding.mangaInfoToggleLess.clicks(),
|
||||
binding.mangaSummarySection.clicks()
|
||||
)
|
||||
.onEach { toggleMangaInfo() }
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
// Expand manga info if navigated from source listing or explicitly set to
|
||||
// (e.g. on tablets)
|
||||
if (initialLoad && (fromSource || isTablet)) {
|
||||
toggleMangaInfo()
|
||||
initialLoad = false
|
||||
// wrap_content and autoFixTextSize can cause unwanted behaviour this tries to solve it
|
||||
binding.mangaFullTitle.requestLayout()
|
||||
}
|
||||
|
||||
// Refreshes will change the state and it needs to be set to correct state to display correctly
|
||||
if (binding.mangaSummaryText.maxLines == 2) {
|
||||
binding.mangaSummarySection.transitionToState(R.id.start)
|
||||
} else {
|
||||
binding.mangaSummarySection.transitionToState(R.id.end)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showMangaInfo(visible: Boolean) {
|
||||
binding.mangaSummarySection.isVisible = visible
|
||||
}
|
||||
|
||||
private fun toggleMangaInfo() {
|
||||
val isCurrentlyExpanded = binding.mangaSummaryText.maxLines != 2
|
||||
|
||||
if (isCurrentlyExpanded) {
|
||||
binding.mangaSummarySection.transitionToStart()
|
||||
} else {
|
||||
binding.mangaSummarySection.transitionToEnd()
|
||||
}
|
||||
|
||||
binding.mangaSummaryText.maxLines = if (isCurrentlyExpanded) {
|
||||
2
|
||||
} else {
|
||||
Int.MAX_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
private fun setChipsWithNamespace(genre: String?, meta: RaisedSearchMetadata?) {
|
||||
val namespaceTags = when {
|
||||
meta != null -> {
|
||||
meta.tags
|
||||
.filterNot { it.type == RaisedSearchMetadata.TAG_TYPE_VIRTUAL }
|
||||
.groupBy { it.namespace }
|
||||
.map { (namespace, tags) ->
|
||||
NamespaceTagsItem(
|
||||
namespace,
|
||||
tags.map {
|
||||
it.name to it.type
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
genre != null -> {
|
||||
listOf(NamespaceTagsItem(null, genre.split(",").map { it.trim() to null }))
|
||||
}
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
mangaTagsInfoAdapter.updateDataSet(namespaceTags)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update favorite button with correct drawable and text.
|
||||
*
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.info
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
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.source.online.NamespaceSource
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
|
||||
import exh.source.getMainSource
|
||||
import exh.source.isEhBasedSource
|
||||
import exh.util.getRaisedTags
|
||||
import exh.util.makeSearchChip
|
||||
import exh.util.setChipsExtended
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
import reactivecircus.flowbinding.android.view.longClicks
|
||||
|
||||
class MangaInfoItemAdapter(
|
||||
private val controller: MangaController,
|
||||
private val fromSource: Boolean,
|
||||
private val isTablet: Boolean
|
||||
) :
|
||||
RecyclerView.Adapter<MangaInfoItemAdapter.HeaderViewHolder>() {
|
||||
|
||||
private var manga: Manga = controller.presenter.manga
|
||||
private var source: Source = controller.presenter.source
|
||||
private var meta: RaisedSearchMetadata? = controller.presenter.meta
|
||||
|
||||
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<IFlexible<*>>? = 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.mangaSummaryText.longClicks()
|
||||
.onEach {
|
||||
controller.activity?.copyToClipboard(
|
||||
view.context.getString(R.string.description),
|
||||
binding.mangaSummaryText.text.toString()
|
||||
)
|
||||
}
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
binding.genreGroups.layoutManager = LinearLayoutManager(itemView.context)
|
||||
binding.genreGroups.adapter = mangaTagsInfoAdapter
|
||||
|
||||
// SY -->
|
||||
mangaTagsInfoAdapter?.mItemClickListener = FlexibleAdapter.OnItemClickListener { _, _ ->
|
||||
controller.viewScope.launch {
|
||||
toggleMangaInfo()
|
||||
}
|
||||
false
|
||||
}
|
||||
// SY <--
|
||||
|
||||
setMangaInfo(manga, source)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the view with manga information.
|
||||
*
|
||||
* @param manga manga object containing information about manga.
|
||||
* @param source the source of the manga.
|
||||
*/
|
||||
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.mangaSummaryText.text = if (manga.description.isNullOrBlank()) {
|
||||
view.context.getString(R.string.unknown)
|
||||
} else {
|
||||
manga.description
|
||||
}
|
||||
|
||||
// SY -->
|
||||
if (binding.mangaSummaryText.text == "meta") {
|
||||
binding.mangaSummaryText.text = ""
|
||||
binding.mangaSummaryText.maxLines = 1
|
||||
binding.mangaInfoToggleLess.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
topToBottom = -1
|
||||
bottomToBottom = binding.mangaSummaryText.id
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
// Update genres list
|
||||
if (!manga.genre.isNullOrBlank()) {
|
||||
// SY -->
|
||||
if (source != null && source.getMainSource() is NamespaceSource) {
|
||||
val metaTags = meta?.tags?.filterNot { it.type == TAG_TYPE_VIRTUAL }?.groupBy { it.namespace }
|
||||
var namespaceTags: List<NamespaceTagsItem> = emptyList()
|
||||
if (source.isEhBasedSource() && metaTags != null && metaTags.all { it.key != null }) {
|
||||
namespaceTags = metaTags
|
||||
.mapValues { values ->
|
||||
values.value.map {
|
||||
itemView.context.makeSearchChip(
|
||||
it.name,
|
||||
controller::performSearch,
|
||||
controller::performGlobalSearch,
|
||||
source.id,
|
||||
it.namespace,
|
||||
it.type
|
||||
)
|
||||
}
|
||||
}
|
||||
.map { NamespaceTagsItem(it.key!!, it.value) }
|
||||
} else {
|
||||
val genre = manga.getRaisedTags()
|
||||
if (!genre.isNullOrEmpty()) {
|
||||
namespaceTags = genre
|
||||
.groupBy { it.namespace }
|
||||
.mapValues { values ->
|
||||
values.value.map {
|
||||
itemView.context.makeSearchChip(
|
||||
it.name,
|
||||
controller::performSearch,
|
||||
controller::performGlobalSearch,
|
||||
source.id,
|
||||
it.namespace
|
||||
)
|
||||
}
|
||||
}
|
||||
.map { NamespaceTagsItem(it.key, it.value) }
|
||||
}
|
||||
}
|
||||
mangaTagsInfoAdapter?.updateDataSet(namespaceTags)
|
||||
}
|
||||
binding.mangaGenresTagsFullChips.setChipsExtended(manga.getGenres(), controller::performGenreSearch, controller::performGlobalSearch, source?.id ?: 0)
|
||||
binding.mangaGenresTagsCompactChips.setChipsExtended(manga.getGenres(), controller::performGenreSearch, controller::performGlobalSearch, source?.id ?: 0)
|
||||
// SY <--
|
||||
} else {
|
||||
binding.mangaGenresTagsCompactChips.isVisible = false
|
||||
binding.mangaGenresTagsFullChips.isVisible = false
|
||||
// SY -->
|
||||
binding.genreGroups.isVisible = false
|
||||
// SY <--
|
||||
}
|
||||
|
||||
// Handle showing more or less info
|
||||
merge(
|
||||
binding.mangaSummarySection.clicks(),
|
||||
binding.mangaSummaryText.clicks(),
|
||||
binding.mangaInfoToggleMore.clicks(),
|
||||
binding.mangaInfoToggleLess.clicks()
|
||||
)
|
||||
.onEach { toggleMangaInfo() }
|
||||
.launchIn(controller.viewScope)
|
||||
|
||||
// Expand manga info if navigated from source listing
|
||||
if (initialLoad && (fromSource || isTablet)) {
|
||||
toggleMangaInfo()
|
||||
initialLoad = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showMangaInfo(visible: Boolean) {
|
||||
binding.mangaSummarySection.isVisible = visible
|
||||
}
|
||||
|
||||
private fun toggleMangaInfo() {
|
||||
val isCurrentlyExpanded = binding.mangaSummaryText.maxLines != 2 /* SY --> */ && binding.mangaSummaryText.maxLines != 1 /* SY <-- */
|
||||
|
||||
binding.mangaInfoToggleMoreScrim.isVisible = isCurrentlyExpanded
|
||||
binding.mangaInfoToggleMore.isVisible = isCurrentlyExpanded
|
||||
binding.mangaInfoToggleLess.isVisible = !isCurrentlyExpanded
|
||||
|
||||
binding.mangaSummaryText.maxLines = if (isCurrentlyExpanded) {
|
||||
/* SY --> */ if (binding.mangaSummaryText.text.isBlank()) 1 else /* SY <-- */ 2
|
||||
} else {
|
||||
Int.MAX_VALUE
|
||||
}
|
||||
|
||||
binding.mangaGenresTagsCompact.isVisible = isCurrentlyExpanded
|
||||
// SY -->
|
||||
if (source.getMainSource() !is NamespaceSource) {
|
||||
binding.mangaGenresTagsFullChips.isVisible = !isCurrentlyExpanded
|
||||
} else {
|
||||
binding.genreGroups.isVisible = !isCurrentlyExpanded
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.info
|
||||
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
|
||||
class NamespaceTagsAdapter(val controller: MangaController, val source: Source) :
|
||||
FlexibleAdapter<NamespaceTagsItem>(null, controller, true)
|
||||
@@ -0,0 +1,40 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.info
|
||||
|
||||
import android.view.View
|
||||
import com.google.android.material.chip.Chip
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.databinding.MangaInfoGenreGroupingBinding
|
||||
import exh.util.makeSearchChip
|
||||
|
||||
class NamespaceTagsHolder(
|
||||
view: View,
|
||||
val adapter: NamespaceTagsAdapter
|
||||
) : FlexibleViewHolder(view, adapter) {
|
||||
val binding = MangaInfoGenreGroupingBinding.bind(view)
|
||||
|
||||
fun bind(item: NamespaceTagsItem) {
|
||||
binding.namespace.removeAllViews()
|
||||
val namespace = item.namespace
|
||||
if (namespace != null) {
|
||||
binding.namespace.addView(
|
||||
Chip(binding.root.context).apply {
|
||||
text = namespace
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
binding.tags.removeAllViews()
|
||||
item.tags.map { (tag, type) ->
|
||||
binding.root.context.makeSearchChip(
|
||||
tag,
|
||||
adapter.controller::performSearch,
|
||||
adapter.controller::performGlobalSearch,
|
||||
adapter.source.id,
|
||||
namespace,
|
||||
type
|
||||
)
|
||||
}.forEach {
|
||||
binding.tags.addView(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,46 +2,43 @@ package eu.kanade.tachiyomi.ui.manga.info
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
open class NamespaceTagsItem(val namespace: String?, val tags: List<Chip>) : AbstractFlexibleItem<NamespaceTagsItem.Holder>() {
|
||||
class NamespaceTagsItem(val namespace: String?, val tags: List<Pair<String, Int?>>) :
|
||||
AbstractFlexibleItem<NamespaceTagsHolder>() {
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.manga_info_genre_grouping
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
||||
return Holder(view, adapter)
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): NamespaceTagsHolder {
|
||||
return NamespaceTagsHolder(view, adapter as NamespaceTagsAdapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||
val namespaceChip = Chip(holder.itemView.context)
|
||||
namespaceChip.text = namespace ?: holder.itemView.context.getString(R.string.unknown)
|
||||
holder.namespaceChipGroup.addView(namespaceChip)
|
||||
|
||||
tags.forEach {
|
||||
holder.tagsChipGroup.addView(it)
|
||||
}
|
||||
override fun bindViewHolder(
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: NamespaceTagsHolder,
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
holder.bind(this)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
return namespace == (other as NamespaceTagsItem).namespace
|
||||
|
||||
other as NamespaceTagsItem
|
||||
|
||||
if (namespace != other.namespace) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return namespace.hashCode()
|
||||
}
|
||||
|
||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||
val namespaceChipGroup: ChipGroup = itemView.findViewById(R.id.namespace)
|
||||
val tagsChipGroup: ChipGroup = itemView.findViewById(R.id.tags)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,5 +122,3 @@ fun Date.toRelativeString(
|
||||
else -> dateFormat.format(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,12 @@ import exh.source.nHentaiSourceIds
|
||||
import java.util.Locale
|
||||
|
||||
object SourceTagsUtil {
|
||||
fun getWrappedTag(sourceId: Long, namespace: String? = null, tag: String? = null, fullTag: String? = null): String? {
|
||||
fun getWrappedTag(
|
||||
sourceId: Long?,
|
||||
namespace: String? = null,
|
||||
tag: String? = null,
|
||||
fullTag: String? = null
|
||||
): String? {
|
||||
return if (sourceId == EXH_SOURCE_ID || sourceId == EH_SOURCE_ID || sourceId in nHentaiSourceIds || sourceId in hitomiSourceIds) {
|
||||
val parsed = when {
|
||||
fullTag != null -> parseTag(fullTag)
|
||||
|
||||
@@ -24,7 +24,14 @@ fun ChipGroup.setChipsExtended(items: List<String>?, onClick: (item: String) ->
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.makeSearchChip(item: String, onClick: (item: String) -> Unit = {}, onLongClick: (item: String) -> Unit = {}, sourceId: Long, namespace: String? = null, type: Int? = null): Chip {
|
||||
fun Context.makeSearchChip(
|
||||
item: String,
|
||||
onClick: (item: String) -> Unit = {},
|
||||
onLongClick: (item: String) -> Unit = {},
|
||||
sourceId: Long,
|
||||
namespace: String? = null,
|
||||
type: Int? = null
|
||||
): Chip {
|
||||
return Chip(this).apply {
|
||||
text = item
|
||||
val search = if (namespace != null) {
|
||||
|
||||
Reference in New Issue
Block a user