Manga description adjustments (#6011)

* Manga description adjustments

- Animated state changes
- Adjust scrim position to fully show 2 lines when shrunk
- Set minLines to avoid scrim hiding oneliner

* Change icon and adjust animation

* Revert fancy scrim animation

(cherry picked from commit f32f1eeaa547dbf3a7a6d0069ee6332d8a440fe7)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
#	app/src/main/res/layout-sw720dp/manga_info_header.xml
#	app/src/main/res/layout/manga_info_header.xml
This commit is contained in:
Ivan Iskandar 2021-10-09 22:02:45 +07:00 committed by Jobobby04
parent 1d80725ea9
commit 6bb8ae0d1e
10 changed files with 650 additions and 489 deletions

View File

@ -8,7 +8,6 @@ import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -21,17 +20,14 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.ui.base.controller.getMainAppBarHeight
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.loadAnyAutoPause
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
@ -58,17 +54,17 @@ class MangaInfoHeaderAdapter(
// SY <--
private var trackCount: Int = 0
private var metaInfoAdapter: RecyclerView.Adapter<*>? = null
private var mangaTagsInfoAdapter: NamespaceTagsAdapter? = NamespaceTagsAdapter(controller, source)
private lateinit var binding: MangaInfoHeaderBinding
private var initialLoad: Boolean = true
private val maxLines = 3
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
binding = MangaInfoHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
updateCoverPosition()
// Expand manga info if navigated from source listing or explicitly set to
// (e.g. on tablets)
binding.mangaSummarySection.expanded = fromSource || isTablet
// SY -->
metaInfoAdapter = source.getMainSource<MetadataSource<*, *>>()?.getDescriptionAdapter(controller)
binding.metadataView.isVisible = if (metaInfoAdapter != null) {
@ -78,9 +74,6 @@ class MangaInfoHeaderAdapter(
} else {
false
}
binding.genreGroups.layoutManager = LinearLayoutManager(binding.root.context)
binding.genreGroups.adapter = mangaTagsInfoAdapter
// SY <--
return HeaderViewHolder(binding.root)
@ -132,15 +125,6 @@ class MangaInfoHeaderAdapter(
// For rounded corners
binding.mangaCover.clipToOutline = true
// SY -->
mangaTagsInfoAdapter?.mItemClickListener = FlexibleAdapter.OnItemClickListener { _, _ ->
controller.viewScope.launchUI {
toggleMangaInfo()
}
false
}
// SY <--
binding.btnFavorite.clicks()
.onEach { controller.onFavoriteClick() }
.launchIn(controller.viewScope)
@ -252,15 +236,6 @@ 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.clicks()
.onEach {
controller.showFullCoverDialog()
@ -273,7 +248,7 @@ class MangaInfoHeaderAdapter(
}
.launchIn(controller.viewScope)
setMangaInfo(manga, source, meta)
setMangaInfo()
}
private fun showCoverOptionsDialog() {
@ -303,7 +278,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?, meta: RaisedSearchMetadata?) {
private fun setMangaInfo() {
// Update full title TextView.
binding.mangaFullTitle.text = if (manga.title.isBlank()) {
view.context.getString(R.string.unknown)
@ -326,40 +301,36 @@ class MangaInfoHeaderAdapter(
}
// If manga source is known update source TextView.
val mangaSource = source?.toString()
val mangaSource = source.toString()
with(binding.mangaSource) {
if (mangaSource != null) {
val enabledLanguages = preferences.enabledLanguages().get()
.filterNot { it in listOf("all", "other") }
val enabledLanguages = preferences.enabledLanguages().get()
.filterNot { it in listOf("all", "other") }
// SY -->
val isMergedSource = source?.id == MERGED_SOURCE_ID
// SY <--
val hasOneActiveLanguages = enabledLanguages.size == 1
val isInEnabledLanguages = source.lang in enabledLanguages
text = when {
// SY -->
val isMergedSource = source.id == MERGED_SOURCE_ID
isMergedSource && hasOneActiveLanguages -> getMergedSourcesString(
enabledLanguages,
true
)
isMergedSource -> getMergedSourcesString(
enabledLanguages,
false
)
// SY <--
val hasOneActiveLanguages = enabledLanguages.size == 1
val isInEnabledLanguages = source.lang in enabledLanguages
text = when {
// SY -->
isMergedSource && hasOneActiveLanguages -> getMergedSourcesString(
enabledLanguages,
true
)
isMergedSource -> getMergedSourcesString(
enabledLanguages,
false
)
// SY <--
// For edge cases where user disables a source they got manga of in their library.
hasOneActiveLanguages && !isInEnabledLanguages -> mangaSource
// Hide the language tag when only one language is used.
hasOneActiveLanguages && isInEnabledLanguages -> source.name
else -> mangaSource
}
// For edge cases where user disables a source they got manga of in their library.
hasOneActiveLanguages && !isInEnabledLanguages -> mangaSource
// Hide the language tag when only one language is used.
hasOneActiveLanguages && isInEnabledLanguages -> source.name
else -> mangaSource
}
setOnClickListener {
controller.performSearch(sourceManager.getOrStub(source.id).name)
}
} else {
text = view.context.getString(R.string.unknown)
setOnClickListener {
controller.performSearch(sourceManager.getOrStub(source.id).name)
}
}
@ -386,102 +357,35 @@ class MangaInfoHeaderAdapter(
binding.mangaCover.loadAnyAutoPause(manga)
// Manga info section
val hasInfoContent = !manga.description.isNullOrBlank() || !manga.genre.isNullOrBlank()
showMangaInfo(hasInfoContent)
if (hasInfoContent) {
// Update description TextView.
binding.mangaSummaryText.text = updateDescription(manga.description, (fromSource || isTablet).not())
// Update genres list
if (!manga.genre.isNullOrBlank()) {
binding.mangaGenresTagsCompactChips.setChips(
manga.getGenres(),
controller::performGenreSearch
)
// SY -->
// if (source?.getMainSource<NamespaceSource>() != null) {
setChipsWithNamespace(
manga.getGenres(),
meta
)
// binding.mangaGenresTagsFullChips.isVisible = false
/*} else {
binding.mangaGenresTagsFullChips.setChips(
manga.getGenres(),
controller::performGenreSearch
)
binding.genreGroups.isVisible = false
}*/
// SY <--
} else {
binding.mangaGenresTagsCompact.isVisible = false
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)
if (initialLoad) {
binding.mangaGenresTagsCompact.requestLayout()
}
// Expand manga info if navigated from source listing or explicitly set to
// (e.g. on tablets)
if (initialLoad && (fromSource || isTablet)) {
toggleMangaInfo()
initialLoad = false
}
}
}
private fun showMangaInfo(visible: Boolean) {
binding.mangaSummarySection.isVisible = visible
}
private fun toggleMangaInfo() {
val isCurrentlyExpanded = binding.mangaSummaryText.maxLines != maxLines
binding.mangaInfoToggleMore.isVisible = isCurrentlyExpanded
binding.mangaInfoScrim.isVisible = isCurrentlyExpanded
binding.mangaInfoToggleMoreScrim.isVisible = isCurrentlyExpanded
binding.mangaGenresTagsCompact.isVisible = isCurrentlyExpanded
binding.mangaGenresTagsCompactChips.isVisible = isCurrentlyExpanded
binding.mangaInfoToggleLess.isVisible = !isCurrentlyExpanded
// SY --> binding.mangaGenresTagsFullChips.isVisible = !isCurrentlyExpanded
binding.genreGroups.isVisible = !isCurrentlyExpanded
binding.mangaSummarySection.isVisible = !manga.description.isNullOrBlank() || !manga.genre.isNullOrBlank()
binding.mangaSummarySection.description = manga.description
// SY -->
binding.mangaSummarySection.setTags(
manga.getGenres(),
meta,
controller::performGenreSearch,
controller::performGlobalSearch,
source
)
// SY <--
binding.mangaSummaryText.text = updateDescription(manga.description, isCurrentlyExpanded)
binding.mangaSummaryText.maxLines = when {
isCurrentlyExpanded -> maxLines
else -> Int.MAX_VALUE
}
}
private fun updateDescription(description: String?, isCurrentlyExpanded: Boolean): CharSequence {
return when {
description.isNullOrBlank() -> view.context.getString(R.string.unknown)
// SY -->
description == "meta" -> ""
// SY <--
isCurrentlyExpanded ->
description
.replace(Regex(" +\$", setOf(RegexOption.MULTILINE)), "")
.replace(Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE)), "\n")
else -> description
/**
* Update favorite button with correct drawable and text.
*
* @param isFavorite determines if manga is favorite or not.
*/
private fun setFavoriteButtonState(isFavorite: Boolean) {
// Set the Favorite drawable to the correct one.
// Border drawable if false, filled drawable if true.
val (iconResource, stringResource) = when (isFavorite) {
true -> R.drawable.ic_favorite_24dp to R.string.in_library
false -> R.drawable.ic_favorite_border_24dp to R.string.add_to_library
}
binding.btnFavorite.apply {
setIconResource(iconResource)
text = context.getString(stringResource)
isActivated = isFavorite
}
}
@ -502,66 +406,6 @@ class MangaInfoHeaderAdapter(
}.distinct().joinToString()
}
}
private fun setChipsWithNamespace(genre: List<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 -> {
if (genre.all { it.contains(':') }) {
genre
.map { tag ->
val index = tag.indexOf(':')
tag.substring(0, index).trim() to tag.substring(index + 1).trim()
}
.groupBy {
it.first
}
.mapValues { group ->
group.value.map { it.second to 0 }
}
.map { (namespace, tags) ->
NamespaceTagsItem(namespace, tags)
}
} else {
listOf(NamespaceTagsItem(null, genre.map { it to null }))
}
}
else -> emptyList()
}
mangaTagsInfoAdapter?.updateDataSet(namespaceTags)
}
// SY <--
/**
* Update favorite button with correct drawable and text.
*
* @param isFavorite determines if manga is favorite or not.
*/
private fun setFavoriteButtonState(isFavorite: Boolean) {
// Set the Favorite drawable to the correct one.
// Border drawable if false, filled drawable if true.
val (iconResource, stringResource) = when (isFavorite) {
true -> R.drawable.ic_favorite_24dp to R.string.in_library
false -> R.drawable.ic_favorite_border_24dp to R.string.add_to_library
}
binding.btnFavorite.apply {
setIconResource(iconResource)
text = context.getString(stringResource)
isActivated = isFavorite
}
}
}
}

View File

@ -1,8 +0,0 @@
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)

View File

@ -5,6 +5,7 @@ import android.widget.LinearLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import com.google.android.material.chip.Chip
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.databinding.MangaInfoGenreGroupingBinding
import eu.kanade.tachiyomi.util.system.dpToPx
@ -12,7 +13,7 @@ import exh.util.makeSearchChip
class NamespaceTagsHolder(
view: View,
val adapter: NamespaceTagsAdapter
adapter: FlexibleAdapter<*>
) : FlexibleViewHolder(view, adapter) {
val binding = MangaInfoGenreGroupingBinding.bind(view)
@ -40,9 +41,9 @@ class NamespaceTagsHolder(
item.tags.map { (tag, type) ->
binding.root.context.makeSearchChip(
tag,
adapter.controller::performSearch,
adapter.controller::performGlobalSearch,
adapter.source.id,
item.onClick,
item.onLongClick,
item.source.id,
namespace,
type
)

View File

@ -6,8 +6,15 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import tachiyomi.source.Source
class NamespaceTagsItem(val namespace: String?, val tags: List<Pair<String, Int?>>) :
class NamespaceTagsItem(
val namespace: String?,
val tags: List<Pair<String, Int?>>,
val onClick: (item: String) -> Unit,
val onLongClick: (item: String) -> Unit,
val source: Source
) :
AbstractFlexibleItem<NamespaceTagsHolder>() {
override fun getLayoutRes(): Int {
@ -15,7 +22,7 @@ class NamespaceTagsItem(val namespace: String?, val tags: List<Pair<String, Int?
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): NamespaceTagsHolder {
return NamespaceTagsHolder(view, adapter as NamespaceTagsAdapter)
return NamespaceTagsHolder(view, adapter)
}
override fun bindViewHolder(

View File

@ -0,0 +1,297 @@
package eu.kanade.tachiyomi.widget
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.drawable.Animatable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.annotation.AttrRes
import androidx.annotation.StyleRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.view.doOnNextLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.MangaSummaryBinding
import eu.kanade.tachiyomi.ui.manga.info.NamespaceTagsItem
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.util.setChipsExtended
import tachiyomi.source.Source
import kotlin.math.roundToInt
import kotlin.math.roundToLong
class MangaSummaryView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = 0,
@StyleRes defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
private val binding = MangaSummaryBinding.inflate(LayoutInflater.from(context), this, true)
private var animatorSet: AnimatorSet? = null
private var recalculateHeights = false
private var descExpandedHeight = -1
private var descShrunkHeight = -1
// SY -->
private var mangaTagsInfoAdapter = FlexibleAdapter<NamespaceTagsItem>(emptyList())
// SY <--
var expanded = false
set(value) {
if (field != value) {
field = value
updateExpandState()
}
}
var description: CharSequence? = null
set(value) {
if (field != value) {
field = if (value.isNullOrBlank()) {
context.getString(R.string.unknown)
// SY -->
} else if (value == "meta") {
""
// SY <--
} else {
value
}
binding.descriptionText.text = field
recalculateHeights = true
doOnNextLayout {
updateExpandState()
}
requestLayout()
}
}
// SY -->
fun setTags(
items: List<String>?,
meta: RaisedSearchMetadata?,
onClick: (item: String) -> Unit,
onLongClick: (item: String) -> Unit,
source: Source
) {
binding.tagChipsShrunk.setChipsExtended(
items,
onClick,
onLongClick,
source.id
)
// binding.tagChipsExpanded.setChips(items, onClick)
setChipsWithNamespace(
items,
meta,
onClick,
onLongClick,
source
)
}
// SY <--
private fun updateExpandState() = binding.apply {
val initialSetup = descriptionText.maxHeight < 0
val maxHeightTarget = if (expanded) descExpandedHeight else descShrunkHeight
val maxHeightStart = if (initialSetup) maxHeightTarget else descriptionText.maxHeight
val descMaxHeightAnimator = ValueAnimator().apply {
setIntValues(maxHeightStart, maxHeightTarget)
addUpdateListener {
descriptionText.maxHeight = it.animatedValue as Int
}
}
val toggleDrawable = ContextCompat.getDrawable(
context,
if (expanded) R.drawable.anim_caret_up else R.drawable.anim_caret_down
)
toggleMore.setImageDrawable(toggleDrawable)
var pastHalf = false
val toggleTarget = if (expanded) 1F else 0F
val toggleStart = if (initialSetup) {
toggleTarget
} else {
toggleMore.translationY / toggleMore.height
}
val toggleAnimator = ValueAnimator().apply {
setFloatValues(toggleStart, toggleTarget)
addUpdateListener {
val value = it.animatedValue as Float
toggleMore.translationY = toggleMore.height * value
descriptionScrim.translationY = toggleMore.translationY
toggleMoreScrim.translationY = toggleMore.translationY
tagChipsShrunkContainer.updateLayoutParams<ConstraintLayout.LayoutParams> {
topMargin = toggleMore.translationY.roundToInt()
}
tagChipsExpanded.updateLayoutParams<ConstraintLayout.LayoutParams> {
topMargin = toggleMore.translationY.roundToInt()
}
// Update non-animatable objects mid-animation makes it feel less abrupt
if (it.animatedFraction >= 0.5F && !pastHalf) {
pastHalf = true
descriptionText.text = trimWhenNeeded(description)
tagChipsShrunkContainer.scrollX = 0
tagChipsShrunkContainer.isVisible = !expanded
tagChipsExpanded.isVisible = expanded
}
}
}
animatorSet?.cancel()
animatorSet = AnimatorSet().apply {
interpolator = FastOutSlowInInterpolator()
duration = (TOGGLE_ANIM_DURATION * context.animatorDurationScale).roundToLong()
playTogether(toggleAnimator, descMaxHeightAnimator)
start()
}
(toggleDrawable as? Animatable)?.start()
}
private fun trimWhenNeeded(text: CharSequence?): CharSequence? {
return if (!expanded) {
text
?.replace(Regex(" +\$", setOf(RegexOption.MULTILINE)), "")
?.replace(Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE)), "\n")
} else {
text
}
}
// SY -->
private fun setChipsWithNamespace(
genre: List<String>?,
meta: RaisedSearchMetadata?,
onClick: (item: String) -> Unit,
onLongClick: (item: String) -> Unit,
source: Source
) {
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
},
onClick,
onLongClick,
source
)
}
}
genre != null -> {
if (genre.all { it.contains(':') }) {
genre
.map { tag ->
val index = tag.indexOf(':')
tag.substring(0, index).trim() to tag.substring(index + 1).trim()
}
.groupBy {
it.first
}
.mapValues { group ->
group.value.map { it.second to 0 }
}
.map { (namespace, tags) ->
NamespaceTagsItem(
namespace,
tags,
onClick,
onLongClick,
source
)
}
} else {
listOf(
NamespaceTagsItem(
null,
genre.map { it to null },
onClick,
onLongClick,
source
)
)
}
}
else -> emptyList()
}
mangaTagsInfoAdapter.updateDataSet(namespaceTags)
}
// SY <--
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (!recalculateHeights) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
return
}
recalculateHeights = false
// Measure with expanded lines
binding.descriptionText.maxLines = Int.MAX_VALUE
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
descExpandedHeight = binding.descriptionText.measuredHeight
// Measure with shrunk lines
binding.descriptionText.maxLines = SHRUNK_DESC_MAX_LINES
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
descShrunkHeight = binding.descriptionText.measuredHeight
}
init {
binding.descriptionText.apply {
// So that 1 line of text won't be hidden by scrim
minLines = DESC_MIN_LINES
setOnLongClickListener {
context.copyToClipboard(
context.getString(R.string.description),
text.toString()
)
true
}
}
// SY -->
binding.tagChipsExpanded.layoutManager = LinearLayoutManager(binding.root.context)
binding.tagChipsExpanded.adapter = mangaTagsInfoAdapter
mangaTagsInfoAdapter.mItemClickListener = FlexibleAdapter.OnItemClickListener { _, _ ->
expanded = !expanded
false
}
// SY <--
arrayOf(
binding.descriptionText,
binding.descriptionScrim,
binding.toggleMoreScrim,
binding.toggleMore
).forEach {
it.setOnClickListener { expanded = !expanded }
}
}
}
private const val TOGGLE_ANIM_DURATION = 300L
private const val DESC_MIN_LINES = 2
private const val SHRUNK_DESC_MAX_LINES = 3

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="caret_up"
android:width="24.0dip"
android:height="24.0dip"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<group
android:name="caret01"
android:rotation="90.0"
android:translateX="12.0"
android:translateY="15.0">
<group
android:name="caret_l"
android:rotation="45.0">
<group
android:name="caret_l_pivot"
android:translateY="4.0">
<group
android:name="caret_l_rect_position"
android:translateY="-1.0">
<path
android:name="caret_l_rect"
android:fillColor="@android:color/black"
android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
</group>
</group>
</group>
<group
android:name="caret_r"
android:rotation="-45.0">
<group
android:name="caret_r_pivot"
android:translateY="-4.0">
<group
android:name="caret_r_rect_position"
android:translateY="1.0">
<path
android:name="caret_r_rect"
android:fillColor="@android:color/black"
android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
</group>
</group>
</group>
</group>
</vector>
</aapt:attr>
<target android:name="caret01">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:pathData="M 12.0,9.0 c 0.0,0.66667 0.0,5.0 0.0,6.0"
android:propertyXName="translateX"
android:propertyYName="translateY" />
</aapt:attr>
</target>
<target android:name="caret_l">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="rotation"
android:valueFrom="-45.0"
android:valueTo="45.0"
android:valueType="floatType" />
</aapt:attr>
</target>
<target
android:name="caret_r">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="rotation"
android:valueFrom="45.0"
android:valueTo="-45.0"
android:valueType="floatType" />
</aapt:attr>
</target>
</animated-vector>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="caret_up"
android:height="24.0dip"
android:width="24.0dip"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<group
android:name="caret02"
android:rotation="90.0"
android:translateX="12.0"
android:translateY="9.0">
<group
android:name="caret02_l"
android:rotation="-45.0">
<group
android:name="caret02_l_pivot"
android:translateY="4.0">
<group
android:name="caret02_l_rect_position"
android:translateY="-1.0">
<path
android:name="caret02_l_rect"
android:fillColor="@android:color/black"
android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
</group>
</group>
</group>
<group
android:name="caret02_r"
android:rotation="45.0">
<group
android:name="caret02_r_pivot"
android:translateY="-4.0">
<group
android:name="caret02_r_rect_position"
android:translateY="1.0">
<path
android:name="caret02_r_rect"
android:fillColor="@android:color/black"
android:pathData="M -1.0,-4.0 l 2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -2.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-8.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
</group>
</group>
</group>
</group>
</vector>
</aapt:attr>
<target android:name="caret02">
<aapt:attr name="android:animation">
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="300"
android:pathData="M 12.0,15.0 c 0.0,-1.0 0.0,-5.33333 0.0,-6.0"
android:propertyXName="translateX"
android:propertyYName="translateY" />
</aapt:attr>
</target>
<target android:name="caret02_l">
<aapt:attr name="android:animation">
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="300"
android:valueFrom="45.0"
android:valueTo="-45.0"
android:valueType="floatType"
android:propertyName="rotation" />
</aapt:attr>
</target>
<target android:name="caret02_r">
<aapt:attr name="android:animation">
<objectAnimator
android:interpolator="@android:interpolator/fast_out_slow_in"
android:duration="300"
android:valueFrom="-45.0"
android:valueTo="45.0"
android:valueType="floatType"
android:propertyName="rotation" />
</aapt:attr>
</target>
</animated-vector>

View File

@ -209,141 +209,12 @@
app:layout_constraintTop_toBottomOf="@+id/manga_actions"
tools:visibility="gone" />
<androidx.constraintlayout.widget.ConstraintLayout
<eu.kanade.tachiyomi.widget.MangaSummaryView
android:id="@+id/manga_summary_section"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/metadata_view">
<TextView
android:id="@+id/manga_summary_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:maxLines="3"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
android:textIsSelectable="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content" />
<View
android:id="@+id/manga_info_scrim"
android:layout_width="0dp"
android:layout_height="32sp"
android:background="@drawable/manga_info_gradient"
android:backgroundTint="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="@+id/manga_summary_text"
app:layout_constraintEnd_toEndOf="@+id/manga_summary_text"
app:layout_constraintStart_toStartOf="@+id/manga_summary_text" />
<View
android:id="@+id/manga_info_toggle_more_scrim"
android:layout_width="36sp"
android:layout_height="18sp"
android:background="@drawable/manga_info_more_gradient"
android:backgroundTint="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="@+id/manga_info_toggle_more"
app:layout_constraintEnd_toEndOf="@+id/manga_info_toggle_more"
app:layout_constraintStart_toStartOf="@+id/manga_info_toggle_more"
app:layout_constraintTop_toTopOf="@+id/manga_info_toggle_more" />
<ImageButton
android:id="@+id/manga_info_toggle_more"
style="@style/Widget.Tachiyomi.Button.InlineButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="-4dp"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:background="@android:color/transparent"
android:contentDescription="@string/manga_info_expand"
android:src="@drawable/ic_expand_more_24dp"
app:layout_constraintBottom_toBottomOf="@id/manga_summary_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:tint="?android:attr/textColorPrimary" />
<ImageButton
android:id="@+id/manga_info_toggle_less"
style="@style/Widget.Tachiyomi.Button.InlineButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:background="@android:color/transparent"
android:contentDescription="@string/manga_info_collapse"
android:src="@drawable/ic_expand_less_24dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_summary_text"
app:tint="?android:attr/textColorPrimary"
tools:visibility="visible" />
<HorizontalScrollView
android:id="@+id/manga_genres_tags_compact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal"
android:scrollbars="none"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_more">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_compact_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
app:chipSpacingHorizontal="4dp"
app:singleLine="true" />
</HorizontalScrollView>
<!--<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_full_chips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:visibility="gone"
app:chipSpacingHorizontal="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_less"
tools:visibility="gone" />-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/genre_groups"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:visibility="gone"
android:nestedScrollingEnabled="false"
tools:listitem="@layout/manga_info_genre_grouping"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_less"/>
</androidx.constraintlayout.widget.ConstraintLayout>
app:layout_constraintTop_toBottomOf="@id/metadata_view" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -221,139 +221,12 @@
app:layout_constraintTop_toBottomOf="@+id/manga_actions"
tools:visibility="gone" />
<androidx.constraintlayout.widget.ConstraintLayout
<eu.kanade.tachiyomi.widget.MangaSummaryView
android:id="@+id/manga_summary_section"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/metadata_view">
<TextView
android:id="@+id/manga_summary_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:maxLines="3"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
android:textIsSelectable="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content" />
<View
android:id="@+id/manga_info_scrim"
android:layout_width="0dp"
android:layout_height="32sp"
android:background="@drawable/manga_info_gradient"
android:backgroundTint="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="@+id/manga_summary_text"
app:layout_constraintEnd_toEndOf="@+id/manga_summary_text"
app:layout_constraintStart_toStartOf="@+id/manga_summary_text" />
<View
android:id="@+id/manga_info_toggle_more_scrim"
android:layout_width="36sp"
android:layout_height="18sp"
android:background="@drawable/manga_info_more_gradient"
android:backgroundTint="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="@+id/manga_info_toggle_more"
app:layout_constraintEnd_toEndOf="@+id/manga_info_toggle_more"
app:layout_constraintStart_toStartOf="@+id/manga_info_toggle_more"
app:layout_constraintTop_toTopOf="@+id/manga_info_toggle_more" />
<ImageButton
android:id="@+id/manga_info_toggle_more"
style="@style/Widget.Tachiyomi.Button.InlineButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="-4dp"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:background="@android:color/transparent"
android:contentDescription="@string/manga_info_expand"
android:src="@drawable/ic_expand_more_24dp"
app:layout_constraintBottom_toBottomOf="@id/manga_summary_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:tint="?android:attr/textColorPrimary" />
<ImageButton
android:id="@+id/manga_info_toggle_less"
style="@style/Widget.Tachiyomi.Button.InlineButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:background="@android:color/transparent"
android:contentDescription="@string/manga_info_collapse"
android:src="@drawable/ic_expand_less_24dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_summary_text"
app:tint="?android:attr/textColorPrimary"
tools:visibility="visible" />
<HorizontalScrollView
android:id="@+id/manga_genres_tags_compact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal"
android:scrollbars="none"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_more">
<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_compact_chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
app:chipSpacingHorizontal="4dp"
app:singleLine="true" />
</HorizontalScrollView>
<!--<com.google.android.material.chip.ChipGroup
android:id="@+id/manga_genres_tags_full_chips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:visibility="gone"
app:chipSpacingHorizontal="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_less"
tools:visibility="gone" />-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/genre_groups"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:visibility="gone"
android:nestedScrollingEnabled="false"
tools:listitem="@layout/manga_info_genre_grouping"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manga_info_toggle_less"/>
</androidx.constraintlayout.widget.ConstraintLayout>
app:layout_constraintTop_toBottomOf="@id/metadata_view" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/description_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:ellipsize="end"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
android:textIsSelectable="false"
app:firstBaselineToTopHeight="0dp"
app:lastBaselineToBottomHeight="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content Collapsed summary content" />
<View
android:id="@+id/description_scrim"
android:layout_width="0dp"
android:layout_height="24sp"
android:background="@drawable/manga_info_gradient"
android:backgroundTint="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="@+id/description_text"
app:layout_constraintEnd_toEndOf="@+id/description_text"
app:layout_constraintStart_toStartOf="@+id/description_text" />
<View
android:id="@+id/toggle_more_scrim"
android:layout_width="36sp"
android:layout_height="18sp"
android:background="@drawable/manga_info_more_gradient"
android:backgroundTint="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="@+id/toggle_more"
app:layout_constraintEnd_toEndOf="@+id/toggle_more"
app:layout_constraintStart_toStartOf="@+id/toggle_more"
app:layout_constraintTop_toTopOf="@+id/toggle_more" />
<ImageButton
android:id="@+id/toggle_more"
style="@style/Widget.Tachiyomi.Button.InlineButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="-6dp"
android:background="@android:color/transparent"
android:contentDescription="@string/manga_info_expand"
android:padding="0dp"
android:src="@drawable/anim_caret_down"
app:layout_constraintBottom_toBottomOf="@id/description_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:tint="?android:attr/textColorPrimary" />
<HorizontalScrollView
android:id="@+id/tag_chips_shrunk_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:requiresFadingEdge="horizontal"
android:scrollbars="none"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toggle_more">
<com.google.android.material.chip.ChipGroup
android:id="@+id/tag_chips_shrunk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
app:chipSpacingHorizontal="4dp"
app:singleLine="true" />
</HorizontalScrollView>
<!--<com.google.android.material.chip.ChipGroup
android:id="@+id/tag_chips_expanded"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:visibility="gone"
app:chipSpacingHorizontal="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toggle_more"
tools:visibility="visible" />-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/tag_chips_expanded"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:visibility="gone"
android:nestedScrollingEnabled="false"
tools:listitem="@layout/manga_info_genre_grouping"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toggle_more"/>
</androidx.constraintlayout.widget.ConstraintLayout>