mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 16:01:48 +01:00
Using material fast scroll on manga details
This concludes the use of reddit's fastscroll Also updated the fast scroll's scroll listener so that the scroll handle respects its margins (thus making it reach the bottom of the manga details
This commit is contained in:
parent
60c3f24515
commit
4dae06803c
@ -206,7 +206,6 @@ dependencies {
|
||||
implementation("me.zhanghai.android.systemuihelper:library:1.0.0")
|
||||
implementation("com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0")
|
||||
implementation("com.github.mthli:Slice:v1.2")
|
||||
implementation("com.reddit:indicator-fast-scroll:1.2.1")
|
||||
|
||||
implementation("com.github.kizitonwose:AndroidTagGroup:1.6.0")
|
||||
implementation("com.github.chrisbanes:PhotoView:2.3.0")
|
||||
|
@ -3,9 +3,12 @@ package eu.kanade.tachiyomi.ui.library
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.fastscroller.FastScroller
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.dpToPxEnd
|
||||
import eu.kanade.tachiyomi.util.view.marginTop
|
||||
import kotlin.math.abs
|
||||
|
||||
class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
FastScroller(context, attrs) {
|
||||
@ -16,6 +19,7 @@ class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: Attr
|
||||
)
|
||||
autoHideEnabled = true
|
||||
ignoreTouchesOutsideHandle = true
|
||||
updateScrollListener()
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
@ -30,4 +34,22 @@ class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: Attr
|
||||
bubble.translationX = (-45f).dpToPxEnd
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateScrollListener() {
|
||||
onScrollListener = object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
if (!isEnabled || bubble == null || handle.isSelected) return
|
||||
val verticalScrollOffset = recyclerView.computeVerticalScrollOffset()
|
||||
val verticalScrollRange = recyclerView.computeVerticalScrollRange() - marginTop
|
||||
val proportion =
|
||||
verticalScrollOffset.toFloat() / (verticalScrollRange - height).toFloat()
|
||||
setBubbleAndHandlePosition(height * proportion)
|
||||
// If scroll amount is small, don't show it
|
||||
if (minimumScrollThreshold == 0 || dy == 0 || abs(dy) > minimumScrollThreshold || scrollbarAnimator.isAnimating) {
|
||||
showScrollbar()
|
||||
if (autoHideEnabled) hideScrollbar()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,54 +59,33 @@ class MangaDetailsAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
fun getSectionText(position: Int): String? {
|
||||
val chapter = getItem(position) as? ChapterItem ?: return null
|
||||
if (position == itemCount - 1) return "-"
|
||||
return when (presenter.scrollType) {
|
||||
MangaDetailsPresenter.MULTIPLE_VOLUMES, MangaDetailsPresenter.MULTIPLE_SEASONS ->
|
||||
presenter.getGroupNumber(chapter)?.toString() ?: "*"
|
||||
MangaDetailsPresenter.HUNDREDS_OF_CHAPTERS ->
|
||||
if (chapter.chapter_number < 0) "*"
|
||||
else (chapter.chapter_number / 100).toInt().toString()
|
||||
MangaDetailsPresenter.TENS_OF_CHAPTERS ->
|
||||
if (chapter.chapter_number < 0) "*"
|
||||
else (chapter.chapter_number / 10).toInt().toString()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun getFullText(position: Int): String {
|
||||
override fun onCreateBubbleText(position: Int): String {
|
||||
val chapter =
|
||||
getItem(position) as? ChapterItem ?: return recyclerView.context.getString(R.string.top)
|
||||
if (position == itemCount - 1) return recyclerView.context.getString(R.string.bottom)
|
||||
return when (val scrollType = presenter.scrollType) {
|
||||
MangaDetailsPresenter.MULTIPLE_VOLUMES, MangaDetailsPresenter.MULTIPLE_SEASONS -> {
|
||||
val volume = presenter.getGroupNumber(chapter)
|
||||
if (volume != null) recyclerView.context.getString(
|
||||
if (scrollType == MangaDetailsPresenter.MULTIPLE_SEASONS) R.string.season_
|
||||
else R.string.volume_, volume)
|
||||
else recyclerView.context.getString(R.string.unknown)
|
||||
if (volume != null) {
|
||||
recyclerView.context.getString(
|
||||
if (scrollType == MangaDetailsPresenter.MULTIPLE_SEASONS) R.string.season_
|
||||
else R.string.volume_, volume
|
||||
)
|
||||
} else {
|
||||
recyclerView.context.getString(R.string.unknown)
|
||||
}
|
||||
}
|
||||
MangaDetailsPresenter.HUNDREDS_OF_CHAPTERS -> recyclerView.context.getString(
|
||||
R.string.chapters_, get100sRange(
|
||||
chapter.chapter_number
|
||||
)
|
||||
)
|
||||
MangaDetailsPresenter.TENS_OF_CHAPTERS -> recyclerView.context.getString(
|
||||
R.string.chapters_, get10sRange(
|
||||
chapter.chapter_number
|
||||
)
|
||||
)
|
||||
else -> recyclerView.context.getString(R.string.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
private fun get100sRange(value: Float): String {
|
||||
val number = value.toInt()
|
||||
return if (number < 100) "0-99"
|
||||
else {
|
||||
val hundred = number / 100
|
||||
"${hundred}00-${hundred}99"
|
||||
else -> if (chapter.chapter_number > 0) {
|
||||
recyclerView.context.getString(
|
||||
R.string.chapter_, decimalFormat.format(chapter.chapter_number)
|
||||
)
|
||||
} else {
|
||||
chapter.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,8 +53,6 @@ import com.bumptech.glide.request.transition.Transition
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.reddit.indicatorfastscroll.FastScrollItemIndicator
|
||||
import com.reddit.indicatorfastscroll.FastScrollerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.SelectableAdapter
|
||||
import eu.kanade.tachiyomi.R
|
||||
@ -101,14 +99,10 @@ import eu.kanade.tachiyomi.util.system.isOnline
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.getText
|
||||
import eu.kanade.tachiyomi.util.view.hide
|
||||
import eu.kanade.tachiyomi.util.view.requestPermissionsSafe
|
||||
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
||||
import eu.kanade.tachiyomi.util.view.setBackground
|
||||
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
|
||||
import eu.kanade.tachiyomi.util.view.setStartTranslationX
|
||||
import eu.kanade.tachiyomi.util.view.setStyle
|
||||
import eu.kanade.tachiyomi.util.view.show
|
||||
import eu.kanade.tachiyomi.util.view.snack
|
||||
import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
||||
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
|
||||
@ -205,7 +199,7 @@ class MangaDetailsController : BaseController,
|
||||
|
||||
setRecycler(view)
|
||||
setPaletteColor()
|
||||
setFastScroller()
|
||||
adapter?.fastScroller = fast_scroller2
|
||||
|
||||
presenter.onCreate()
|
||||
swipe_refresh.isRefreshing = presenter.isLoading
|
||||
@ -259,17 +253,6 @@ class MangaDetailsController : BaseController,
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
val atTop = !recycler.canScrollVertically(-1)
|
||||
if (atTop) getHeader()?.backdrop?.translationY = 0f
|
||||
when (newState) {
|
||||
RecyclerView.SCROLL_STATE_DRAGGING -> {
|
||||
scrollAnim?.cancel()
|
||||
if (fast_scroller.translationX != 0f) {
|
||||
showFastScroller()
|
||||
}
|
||||
}
|
||||
RecyclerView.SCROLL_STATE_IDLE -> {
|
||||
scrollAnim = fast_scroller.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -281,51 +264,12 @@ class MangaDetailsController : BaseController,
|
||||
swipe_refresh.setProgressViewOffset(false, (-40).dpToPx, headerHeight + offset)
|
||||
// 1dp extra to line up chapter header and manga header
|
||||
getHeader()?.setTopHeight(headerHeight)
|
||||
fast_scroll_layout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
fast_scroller2.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = headerHeight
|
||||
bottomMargin = insets.systemWindowInsetBottom
|
||||
}
|
||||
}
|
||||
|
||||
private fun setFastScroller() {
|
||||
fast_scroller.setStartTranslationX(true)
|
||||
fast_scroller.setBackground(true)
|
||||
fast_scroller.setupWithRecyclerView(recycler, { position ->
|
||||
val letter = adapter?.getSectionText(position)
|
||||
when {
|
||||
presenter.scrollType == 0 -> null
|
||||
letter != null -> FastScrollItemIndicator.Text(letter)
|
||||
else -> FastScrollItemIndicator.Icon(R.drawable.ic_star_24dp)
|
||||
}
|
||||
})
|
||||
fast_scroller.useDefaultScroller = false
|
||||
fast_scroller.itemIndicatorSelectedCallbacks += object :
|
||||
FastScrollerView.ItemIndicatorSelectedCallback {
|
||||
override fun onItemIndicatorSelected(
|
||||
indicator: FastScrollItemIndicator,
|
||||
indicatorCenterY: Int,
|
||||
itemPosition: Int
|
||||
) {
|
||||
scrollAnim?.cancel()
|
||||
scrollAnim = fast_scroller.hide(2000)
|
||||
|
||||
textAnim?.cancel()
|
||||
textAnim = text_view_m.animate().alpha(0f).setDuration(250L).setStartDelay(1000)
|
||||
textAnim?.start()
|
||||
|
||||
text_view_m.translationY = indicatorCenterY.toFloat() - text_view_m.height / 2
|
||||
text_view_m.alpha = 1f
|
||||
text_view_m.text = adapter?.getFullText(itemPosition)
|
||||
val appbar = activity?.appbar
|
||||
appbar?.y = 0f
|
||||
(recycler.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(
|
||||
itemPosition, headerHeight
|
||||
)
|
||||
colorToolbar(itemPosition > 0, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Set the toolbar to fully transparent or colored and translucent */
|
||||
fun colorToolbar(isColor: Boolean, animate: Boolean = true) {
|
||||
if (isColor == toolbarIsColored) return
|
||||
@ -594,12 +538,6 @@ class MangaDetailsController : BaseController,
|
||||
activity?.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
private fun showFastScroller(animate: Boolean = true) {
|
||||
if (presenter.scrollType != 0) {
|
||||
fast_scroller.show(animate)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addMangaHeader() {
|
||||
if (adapter?.scrollableHeaders?.isEmpty() == true) {
|
||||
adapter?.removeAllScrollableHeaders()
|
||||
|
@ -251,7 +251,6 @@ class MangaDetailsPresenter(
|
||||
scrollType = when {
|
||||
hasMultipleVolumes(chapters) -> MULTIPLE_VOLUMES
|
||||
hasMultipleSeasons(chapters) -> MULTIPLE_SEASONS
|
||||
hasHundredsOfChapters(chapters) -> HUNDREDS_OF_CHAPTERS
|
||||
hasTensOfChapters(chapters) -> TENS_OF_CHAPTERS
|
||||
else -> 0
|
||||
}
|
||||
@ -283,7 +282,7 @@ class MangaDetailsPresenter(
|
||||
val volNum = getVolumeNumber(it)
|
||||
if (volNum != null) {
|
||||
volumeSet.add(volNum)
|
||||
if (volumeSet.size >= 3) return true
|
||||
if (volumeSet.size >= 2) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
@ -295,18 +294,14 @@ class MangaDetailsPresenter(
|
||||
val volNum = getSeasonNumber(it)
|
||||
if (volNum != null) {
|
||||
volumeSet.add(volNum)
|
||||
if (volumeSet.size >= 3) return true
|
||||
if (volumeSet.size >= 2) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun hasHundredsOfChapters(chapters: List<ChapterItem>): Boolean {
|
||||
return chapters.size > 300
|
||||
}
|
||||
|
||||
private fun hasTensOfChapters(chapters: List<ChapterItem>): Boolean {
|
||||
return chapters.size in 21..300
|
||||
return chapters.size > 20
|
||||
}
|
||||
|
||||
/**
|
||||
@ -878,7 +873,6 @@ class MangaDetailsPresenter(
|
||||
companion object {
|
||||
const val MULTIPLE_VOLUMES = 1
|
||||
const val TENS_OF_CHAPTERS = 2
|
||||
const val HUNDREDS_OF_CHAPTERS = 3
|
||||
const val MULTIPLE_SEASONS = 4
|
||||
const val MULTIPLE_SEASONS = 3
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
package eu.kanade.tachiyomi.util.view
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.view.ViewPropertyAnimator
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.reddit.indicatorfastscroll.FastScrollerView
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.dpToPxEnd
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
|
||||
fun FastScrollerView.setBackground(hasBackground: Boolean) {
|
||||
background = if (hasBackground) ContextCompat.getDrawable(
|
||||
context, R.drawable.fast_scroll_background
|
||||
) else null
|
||||
textColor = ColorStateList.valueOf(
|
||||
if (hasBackground) Color.WHITE
|
||||
else context.getResourceColor(android.R.attr.textColorPrimary)
|
||||
)
|
||||
iconColor = textColor
|
||||
}
|
||||
|
||||
fun FastScrollerView.setStartTranslationX(shouldHide: Boolean) {
|
||||
translationX = if (shouldHide) 25f.dpToPxEnd else 0f
|
||||
}
|
||||
|
||||
fun FastScrollerView.hide(duration: Long = 1000): ViewPropertyAnimator {
|
||||
val scrollAnim = animate().setStartDelay(duration).setDuration(250)
|
||||
.translationX(25f.dpToPxEnd)
|
||||
scrollAnim.start()
|
||||
return scrollAnim
|
||||
}
|
||||
|
||||
fun FastScrollerView.show(animate: Boolean = true) {
|
||||
if (animate) animate().setStartDelay(0).setDuration(100).translationX(0f).start()
|
||||
else translationX = 0f
|
||||
}
|
@ -14,55 +14,25 @@
|
||||
android:background="?android:colorBackground">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
<FrameLayout
|
||||
android:id="@+id/linear_recycler_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0.25"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/chapters_item" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/fast_scroll_layout"
|
||||
<eu.kanade.tachiyomi.ui.library.MaterialFastScroll
|
||||
android:id="@+id/fast_scroller2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.reddit.indicatorfastscroll.FastScrollerView
|
||||
android:id="@+id/fast_scroller"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:elevation="10dp"
|
||||
android:paddingStart="1dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
app:iconColor="?android:attr/textColorPrimary"
|
||||
app:layout_constrainedHeight="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_view_m"
|
||||
style="@style/FloatingTextView"
|
||||
android:layout_marginEnd="50dp"
|
||||
android:alpha="0"
|
||||
app:layout_constraintEnd_toStartOf="@id/fast_scroller"
|
||||
app:layout_constraintTop_toTopOf="@id/fast_scroller"
|
||||
tools:text="Volume 12"
|
||||
tools:alpha="1" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
android:layout_height="match_parent"
|
||||
app:fastScrollerBubbleEnabled="true" />
|
||||
|
||||
<View
|
||||
android:id="@+id/full_backdrop"
|
||||
|
Loading…
Reference in New Issue
Block a user