Added category hopper to bottom of library

settings for it coming soon
This commit is contained in:
Jay 2020-05-06 03:07:58 -04:00
parent 79ebb03cfd
commit 4c5d33b1fe
7 changed files with 273 additions and 14 deletions

View File

@ -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)

View File

@ -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,8 +213,8 @@ 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.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
@ -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

View File

@ -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
}
}

View File

@ -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"

View File

@ -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" />

View File

@ -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" />

View 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>