mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-24 13:31:51 +01:00
Added category hopper to bottom of library
settings for it coming soon
This commit is contained in:
parent
79ebb03cfd
commit
4c5d33b1fe
@ -265,6 +265,8 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun showAllCategories() = flowPrefs.getBoolean("show_all_categories", true)
|
||||
|
||||
fun hopperGravity() = flowPrefs.getInt("hopper_gravity", 1)
|
||||
|
||||
// Tutorial preferences
|
||||
fun shownFilterTutorial() = flowPrefs.getBoolean("shown_filter_tutorial", false)
|
||||
|
||||
|
@ -7,6 +7,7 @@ import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
@ -18,6 +19,8 @@ import android.view.inputmethod.InputMethodManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.view.GestureDetectorCompat
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
@ -82,6 +85,7 @@ import kotlinx.android.synthetic.main.filter_bottom_sheet.*
|
||||
import kotlinx.android.synthetic.main.library_grid_recycler.*
|
||||
import kotlinx.android.synthetic.main.library_list_controller.*
|
||||
import kotlinx.android.synthetic.main.main_activity.*
|
||||
import kotlinx.android.synthetic.main.rounded_category_hopper.*
|
||||
import kotlinx.coroutines.delay
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
@ -90,7 +94,7 @@ import kotlin.math.roundToInt
|
||||
|
||||
class LibraryController(
|
||||
bundle: Bundle? = null,
|
||||
private val preferences: PreferencesHelper = Injekt.get()
|
||||
val preferences: PreferencesHelper = Injekt.get()
|
||||
) : BaseController(bundle),
|
||||
ActionMode.Callback,
|
||||
ChangeMangaCategoriesDialog.Listener,
|
||||
@ -108,6 +112,9 @@ class LibraryController(
|
||||
* Position of the active category.
|
||||
*/
|
||||
private var activeCategory: Int = preferences.lastUsedCategory().getOrDefault()
|
||||
set(value) {
|
||||
field = value
|
||||
}
|
||||
|
||||
private var justStarted = true
|
||||
|
||||
@ -164,6 +171,21 @@ class LibraryController(
|
||||
val notAtTop = recycler.canScrollVertically(-1)
|
||||
if (notAtTop != elevate) elevateFunc(notAtTop)
|
||||
val order = getCategoryOrder()
|
||||
if (!recycler_cover.isClickable) {
|
||||
category_hopper_frame.translationY += dy
|
||||
category_hopper_frame.translationY =
|
||||
category_hopper_frame.translationY.coerceIn(0f, 60f.dpToPx)
|
||||
up_category.alpha = if (!recycler.canScrollVertically(-1)) {
|
||||
0.25f
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
down_category.alpha = if (!recycler.canScrollVertically(1)) {
|
||||
0.25f
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
}
|
||||
if (!filter_bottom_sheet.sheetBehavior.isHidden()) {
|
||||
scrollDistance += abs(dy)
|
||||
if (scrollDistance > scrollDistanceTilHidden) {
|
||||
@ -180,8 +202,8 @@ class LibraryController(
|
||||
val view = fast_scroller ?: return
|
||||
|
||||
val height = if (view.childCount > 0) {
|
||||
view.height - (view.getChildAt(0)?.paddingTop ?: 0) -
|
||||
(view.getChildAt(view.childCount - 1)?.paddingBottom ?: 0)
|
||||
view.height - (view.getChildAt(0)?.paddingTop
|
||||
?: 0) - (view.getChildAt(view.childCount - 1)?.paddingBottom ?: 0)
|
||||
} else {
|
||||
view.height
|
||||
}
|
||||
@ -191,9 +213,9 @@ class LibraryController(
|
||||
textAnim?.start()
|
||||
|
||||
// fastScroll height * indicator position - center text - fastScroll padding
|
||||
text_view_m.translationY = height *
|
||||
(index.toFloat() / (adapter.headerItems.size + 1))
|
||||
- text_view_m.height / 2 + 16.dpToPx
|
||||
text_view_m.translationY =
|
||||
height * (index.toFloat() / (adapter.headerItems.size + 1))
|
||||
-text_view_m.height / 2 + 16.dpToPx
|
||||
text_view_m.translationX = 45f.dpToPxEnd
|
||||
text_view_m.alpha = 1f
|
||||
text_view_m.text = headerItem.category.name
|
||||
@ -213,6 +235,15 @@ class LibraryController(
|
||||
}
|
||||
RecyclerView.SCROLL_STATE_IDLE -> {
|
||||
scrollAnim = fast_scroller?.hide()
|
||||
val shortAnimationDuration = resources?.getInteger(
|
||||
android.R.integer.config_shortAnimTime
|
||||
) ?: 0
|
||||
if (!recycler_cover.isClickable) {
|
||||
category_hopper_frame.animate().translationY(
|
||||
if (category_hopper_frame.translationY > 30f.dpToPx) 60f.dpToPx
|
||||
else 0f
|
||||
).setDuration(shortAnimationDuration.toLong()).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -264,9 +295,12 @@ class LibraryController(
|
||||
recycler.adapter = adapter
|
||||
fast_scroller.setupWithRecyclerView(recycler, { position ->
|
||||
val letter = adapter.getSectionText(position)
|
||||
if (!singleCategory && presenter.showAllCategories &&
|
||||
!adapter.isHeader(adapter.getItem(position)) &&
|
||||
position != adapter.itemCount - 1) null
|
||||
if (!singleCategory && presenter.showAllCategories && !adapter.isHeader(
|
||||
adapter.getItem(
|
||||
position
|
||||
)
|
||||
) && position != adapter.itemCount - 1
|
||||
) null
|
||||
else if (letter != null) FastScrollItemIndicator.Text(letter)
|
||||
else FastScrollItemIndicator.Icon(R.drawable.ic_star_24dp)
|
||||
})
|
||||
@ -336,6 +370,7 @@ class LibraryController(
|
||||
showCategories(false)
|
||||
}
|
||||
category_recycler.onCategoryClicked = {
|
||||
recycler.itemAnimator = null
|
||||
scrollToHeader(it)
|
||||
showCategories(show = false, scroll = false)
|
||||
}
|
||||
@ -343,6 +378,60 @@ class LibraryController(
|
||||
preferences.showAllCategories().set(isChecked)
|
||||
presenter.getLibrary()
|
||||
}
|
||||
|
||||
category_hopper_frame.gone()
|
||||
down_category.setOnClickListener {
|
||||
val position = getVisibleHeader() ?: return@setOnClickListener
|
||||
val newOffset = adapter.headerItems.indexOf(position) + 1
|
||||
if (newOffset < presenter.categories.size) {
|
||||
val newOrder = (adapter.headerItems[newOffset] as LibraryHeaderItem).category.order
|
||||
scrollToHeader(newOrder)
|
||||
} else {
|
||||
recycler.scrollToPosition(adapter.itemCount - 1)
|
||||
}
|
||||
}
|
||||
up_category.setOnClickListener {
|
||||
val position = getVisibleHeader() ?: return@setOnClickListener
|
||||
val newOffset = adapter.headerItems.indexOf(position) - 1
|
||||
if (newOffset > -1) {
|
||||
val newOrder = (adapter.headerItems[newOffset] as LibraryHeaderItem).category.order
|
||||
scrollToHeader(newOrder)
|
||||
} else {
|
||||
recycler.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
down_category.setOnLongClickListener {
|
||||
recycler.scrollToPosition(adapter.itemCount - 1)
|
||||
true
|
||||
}
|
||||
up_category.setOnLongClickListener {
|
||||
recycler.scrollToPosition(0)
|
||||
true
|
||||
}
|
||||
category_button.setOnClickListener {
|
||||
showCategories(!recycler_cover.isClickable)
|
||||
}
|
||||
|
||||
category_button.setOnLongClickListener {
|
||||
activity?.toolbar?.menu?.performIdentifierAction(R.id.action_search, 0)
|
||||
true
|
||||
}
|
||||
|
||||
category_hopper_frame.updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
||||
anchorGravity = Gravity.TOP or when (preferences.hopperGravity().get()) {
|
||||
0 -> Gravity.LEFT
|
||||
2 -> Gravity.RIGHT
|
||||
else -> Gravity.CENTER
|
||||
}
|
||||
}
|
||||
|
||||
val gestureDetector = GestureDetectorCompat(activity, LibraryGestureDetector(this))
|
||||
listOf(category_hopper_layout, up_category, down_category, category_button).forEach {
|
||||
it.setOnTouchListener { _, event ->
|
||||
gestureDetector.onTouchEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
scrollViewWith(recycler, swipeRefreshLayout = swipe_refresh, afterInsets = { insets ->
|
||||
category_layout?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = recycler?.paddingTop ?: 0
|
||||
@ -436,6 +525,22 @@ class LibraryController(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getVisibleHeader(): LibraryHeaderItem? {
|
||||
val position =
|
||||
(recycler.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
|
||||
when (val item = adapter.getItem(position)) {
|
||||
is LibraryHeaderItem -> return item
|
||||
is LibraryItem -> return item.header
|
||||
}
|
||||
val fPosition =
|
||||
(recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
||||
when (val item = adapter.getItem(fPosition)) {
|
||||
is LibraryHeaderItem -> return item
|
||||
is LibraryItem -> return item.header
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getCategoryOrder(): Int? {
|
||||
val position =
|
||||
(recycler.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
|
||||
@ -589,6 +694,7 @@ class LibraryController(
|
||||
}
|
||||
}
|
||||
}
|
||||
category_hopper_frame.visibleIf(!singleCategory)
|
||||
adapter.isLongPressDragEnabled = canDrag()
|
||||
category_recycler.setCategories(presenter.categories)
|
||||
setActiveCategory()
|
||||
@ -610,6 +716,7 @@ class LibraryController(
|
||||
val full = category_layout.height.toFloat() + recycler.paddingTop
|
||||
val translateY = if (show) full else 0f
|
||||
recycler.animate().translationY(translateY).start()
|
||||
category_hopper_frame.animate().translationY(translateY).start()
|
||||
if (scroll) {
|
||||
// Smooth scroll the recycler to hide the hidden content blocked by the app bar
|
||||
ValueAnimator.ofInt(recycler.translationY.roundToInt(), translateY.roundToInt()).apply {
|
||||
@ -661,7 +768,6 @@ class LibraryController(
|
||||
|
||||
private fun scrollToHeader(pos: Int) {
|
||||
if (!presenter.showAllCategories) {
|
||||
recycler.itemAnimator = null
|
||||
presenter.switchSection(pos)
|
||||
activeCategory = pos
|
||||
setActiveCategory()
|
||||
@ -1030,6 +1136,10 @@ class LibraryController(
|
||||
override fun sheetIsExpanded(): Boolean = false
|
||||
|
||||
override fun handleSheetBack(): Boolean {
|
||||
if (recycler_cover.isClickable) {
|
||||
showCategories(false)
|
||||
return true
|
||||
}
|
||||
if (filter_bottom_sheet.sheetBehavior.isExpanded()) {
|
||||
filter_bottom_sheet.sheetBehavior?.collapse()
|
||||
return true
|
||||
|
@ -0,0 +1,74 @@
|
||||
package eu.kanade.tachiyomi.ui.library
|
||||
|
||||
import android.view.GestureDetector
|
||||
import android.view.Gravity
|
||||
import android.view.MotionEvent
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.util.view.hide
|
||||
import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
||||
import kotlinx.android.synthetic.main.filter_bottom_sheet.*
|
||||
import kotlinx.android.synthetic.main.library_list_controller.*
|
||||
import kotlin.math.abs
|
||||
|
||||
class LibraryGestureDetector(private val controller: LibraryController) : GestureDetector
|
||||
.SimpleOnGestureListener() {
|
||||
override fun onDown(e: MotionEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onFling(
|
||||
e1: MotionEvent,
|
||||
e2: MotionEvent,
|
||||
velocityX: Float,
|
||||
velocityY: Float
|
||||
): Boolean {
|
||||
var result = false
|
||||
val diffY = e2.y - e1.y
|
||||
val diffX = e2.x - e1.x
|
||||
if (abs(diffX) <= abs(diffY) &&
|
||||
abs(diffY) > MainActivity.SWIPE_THRESHOLD &&
|
||||
abs(velocityY) > MainActivity.SWIPE_VELOCITY_THRESHOLD) {
|
||||
if (diffY <= 0) {
|
||||
controller.showSheet()
|
||||
} else {
|
||||
controller.filter_bottom_sheet.sheetBehavior?.hide()
|
||||
}
|
||||
result = true
|
||||
} else if (abs(diffX) >= abs(diffY) &&
|
||||
abs(diffX) > MainActivity.SWIPE_THRESHOLD &&
|
||||
abs(velocityX) > MainActivity.SWIPE_VELOCITY_THRESHOLD) {
|
||||
// val transition = androidx.transition.AutoTransition()
|
||||
// transition.duration = 150
|
||||
|
||||
androidx.transition.TransitionManager.beginDelayedTransition(
|
||||
controller.library_layout
|
||||
)
|
||||
if (diffX <= 0) {
|
||||
controller.category_hopper_frame.updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
||||
anchorGravity =
|
||||
Gravity.TOP or (if (anchorGravity == Gravity.TOP or Gravity.RIGHT) {
|
||||
controller.preferences.hopperGravity().set(1)
|
||||
Gravity.CENTER
|
||||
} else {
|
||||
controller.preferences.hopperGravity().set(0)
|
||||
Gravity.LEFT
|
||||
})
|
||||
}
|
||||
} else {
|
||||
controller.category_hopper_frame.updateLayoutParams<CoordinatorLayout.LayoutParams> {
|
||||
anchorGravity = Gravity.TOP or
|
||||
Gravity.TOP or (if (anchorGravity == Gravity.TOP or Gravity.LEFT) {
|
||||
controller.preferences.hopperGravity().set(1)
|
||||
Gravity.CENTER
|
||||
} else {
|
||||
controller.preferences.hopperGravity().set(2)
|
||||
Gravity.RIGHT
|
||||
})
|
||||
}
|
||||
}
|
||||
result = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
@ -591,8 +591,8 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val SWIPE_THRESHOLD = 100
|
||||
private const val SWIPE_VELOCITY_THRESHOLD = 100
|
||||
const val SWIPE_THRESHOLD = 100
|
||||
const val SWIPE_VELOCITY_THRESHOLD = 100
|
||||
|
||||
// Shortcut actions
|
||||
const val SHORTCUT_LIBRARY = "eu.kanade.tachiyomi.SHOW_LIBRARY"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/gray_button">
|
||||
android:color="@color/fullRippleColor">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/fullRippleColor" />
|
||||
|
@ -37,7 +37,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
@ -114,6 +114,17 @@
|
||||
app:layout_anchor="@id/filter_bottom_sheet"
|
||||
app:layout_anchorGravity="top" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/category_hopper_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|center"
|
||||
app:layout_anchor="@id/filter_bottom_sheet"
|
||||
app:layout_anchorGravity="top|center">
|
||||
|
||||
<include layout="@layout/rounded_category_hopper" />
|
||||
|
||||
</FrameLayout>
|
||||
<!-- Adding bottom sheet after main content -->
|
||||
<include layout="@layout/filter_bottom_sheet" />
|
||||
|
||||
|
62
app/src/main/res/layout/rounded_category_hopper.xml
Normal file
62
app/src/main/res/layout/rounded_category_hopper.xml
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
app:cardCornerRadius="25dp"
|
||||
app:cardElevation="10dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/category_hopper_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/colorAccent"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/up_category"
|
||||
style="@style/Theme.Widget.CustomImageButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_expand_less_24dp"
|
||||
android:tint="@android:color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/category_button"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/category_button"
|
||||
style="@style/Theme.Widget.CustomImageButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_label_outline_white_24dp"
|
||||
android:tint="@android:color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/down_category"
|
||||
app:layout_constraintStart_toEndOf="@id/up_category"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/down_category"
|
||||
style="@style/Theme.Widget.CustomImageButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_expand_more_24dp"
|
||||
android:tint="@android:color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/category_button"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
Loading…
Reference in New Issue
Block a user