MaterialMenuSheet now uses a recyclerview

Also moved whats new sheet to mainactivity as a function
This commit is contained in:
Jays2Kings 2021-04-26 04:43:52 -04:00
parent d0af0ddffe
commit 8753d188d1
5 changed files with 153 additions and 124 deletions

View File

@ -2,30 +2,30 @@ package eu.kanade.tachiyomi.ui.base
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.os.Build import android.os.Build
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.material.bottomsheet.BottomSheetBehavior import androidx.recyclerview.widget.LinearLayoutManager
import eu.kanade.tachiyomi.R import androidx.recyclerview.widget.RecyclerView
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.adapters.ItemAdapter
import eu.kanade.tachiyomi.databinding.BottomMenuSheetBinding import eu.kanade.tachiyomi.databinding.BottomMenuSheetBinding
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.hasSideNavBar import eu.kanade.tachiyomi.util.system.hasSideNavBar
import eu.kanade.tachiyomi.util.system.isInNightMode import eu.kanade.tachiyomi.util.system.isInNightMode
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.view.checkHeightThen
import eu.kanade.tachiyomi.util.view.expand import eu.kanade.tachiyomi.util.view.expand
import eu.kanade.tachiyomi.util.view.setBottomEdge
import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.widget.EdgeToEdgeBottomSheetDialog import eu.kanade.tachiyomi.widget.EdgeToEdgeBottomSheetDialog
import eu.kanade.tachiyomi.widget.MenuSheetItemView import kotlin.math.min
@SuppressLint("InflateParams") class MaterialMenuSheet(
open class MaterialMenuSheet(
activity: Activity, activity: Activity,
items: List<MenuSheetItem>, items: List<MenuSheetItem>,
title: String? = null, title: String? = null,
@ -36,73 +36,61 @@ open class MaterialMenuSheet(
) : EdgeToEdgeBottomSheetDialog<BottomMenuSheetBinding>(activity) { ) : EdgeToEdgeBottomSheetDialog<BottomMenuSheetBinding>(activity) {
override fun createBinding(inflater: LayoutInflater) = BottomMenuSheetBinding.inflate(inflater) override fun createBinding(inflater: LayoutInflater) = BottomMenuSheetBinding.inflate(inflater)
private val fastAdapter: FastAdapter<MaterialMenuSheetItem>
private val itemAdapter = ItemAdapter<MaterialMenuSheetItem>()
override var recyclerView: RecyclerView? = binding.menuSheetRecycler
init { init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !context.isInNightMode() && !activity.window.decorView.rootWindowInsets.hasSideNavBar()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !context.isInNightMode() && !activity.window.decorView.rootWindowInsets.hasSideNavBar()) {
window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
} }
if (maxHeight != null) { binding.menuSheetLayout.checkHeightThen {
binding.menuScrollView.maxHeight = maxHeight + activity.window.decorView.rootWindowInsets.systemWindowInsetBottom binding.menuSheetRecycler.updateLayoutParams<ConstraintLayout.LayoutParams> {
binding.menuScrollView.requestLayout() val fullHeight = activity.window.decorView.height
} else { val insets = activity.window.decorView.rootWindowInsets
binding.titleLayout.viewTreeObserver.addOnGlobalLayoutListener { matchConstraintMaxHeight =
binding.menuScrollView.updateLayoutParams<ConstraintLayout.LayoutParams> { min(
val fullHeight = activity.window.decorView.height (maxHeight ?: fullHeight) + (insets?.systemWindowInsetBottom ?: 0),
val insets = activity.window.decorView.rootWindowInsets
matchConstraintMaxHeight =
fullHeight - (insets?.systemWindowInsetTop ?: 0) - fullHeight - (insets?.systemWindowInsetTop ?: 0) -
binding.titleLayout.height - 26.dpToPx binding.titleLayout.height - 26.dpToPx
} )
} }
} }
binding.divider.isVisible = showDivider binding.divider.isVisible = showDivider
var currentIndex: Int? = null
items.forEachIndexed { index, item -> fastAdapter = FastAdapter.with(itemAdapter)
val view = fastAdapter.setHasStableIds(true)
activity.layoutInflater.inflate(R.layout.menu_sheet_item, null) as MenuSheetItemView itemAdapter.set(items.map(::MaterialMenuSheetItem))
if (index == 0 && title == null) {
view.setBackgroundResource(R.drawable.rounded_item_background) binding.menuSheetRecycler.layoutManager = LinearLayoutManager(context)
} binding.menuSheetRecycler.adapter = fastAdapter
with(view) {
id = item.id fastAdapter.onClickListener = { _, _, item, _ ->
binding.menuLayout.addView(this) val shouldDismiss = onMenuItemClicked(this@MaterialMenuSheet, item.sheetItem.id)
if (shouldDismiss) {
if (item.text != null) { dismiss()
text = item.text
} else {
setText(item.textRes)
}
setIcon(item.drawable)
if (item.drawable == 0) {
textSize = 14f
}
if (item.id == selectedId) {
currentIndex = index
isSelected = true
}
setOnClickListener {
val shouldDismiss = onMenuItemClicked(this@MaterialMenuSheet, id)
if (shouldDismiss) {
dismiss()
}
}
} }
false
} }
BottomSheetBehavior.from(binding.root.parent as ViewGroup).expand() sheetBehavior.expand()
BottomSheetBehavior.from(binding.root.parent as ViewGroup).skipCollapsed = true sheetBehavior.skipCollapsed = true
setBottomEdge(binding.menuLayout, activity)
binding.menuSheetRecycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
binding.titleLayout.isVisible = title != null binding.titleLayout.isVisible = title != null
binding.toolbarTitle.text = title binding.toolbarTitle.text = title
currentIndex?.let { if (selectedId != null) {
itemAdapter.getAdapterItem(selectedId).isSelected = true
binding.root.post { binding.root.post {
binding.menuScrollView.scrollTo(0, it * 48.dpToPx - binding.menuScrollView.height / 2) binding.root.post {
binding.menuSheetRecycler.scrollBy(
0,
selectedId * 48.dpToPx - binding.menuSheetRecycler.height / 2
)
}
} }
} }
@ -117,38 +105,41 @@ open class MaterialMenuSheet(
binding.titleLayout, binding.titleLayout,
"elevation", "elevation",
binding.titleLayout.elevation, binding.titleLayout.elevation,
if (elevate) 10f else 0f if (elevate) 5f else 0f
) )
elevationAnimator?.start() elevationAnimator?.start()
} }
elevate(binding.menuScrollView.canScrollVertically(-1)) elevate(binding.menuSheetRecycler.canScrollVertically(-1))
if (binding.titleLayout.isVisible) { if (binding.titleLayout.isVisible) {
binding.menuScrollView.setOnScrollChangeListener { _: View?, _: Int, _: Int, _: Int, _: Int -> binding.menuSheetRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
val notAtTop = binding.menuScrollView.canScrollVertically(-1) override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (notAtTop != isElevated) { val notAtTop = binding.menuSheetRecycler.canScrollVertically(-1)
elevate(notAtTop) if (notAtTop != isElevated) {
elevate(notAtTop)
}
} }
} })
} }
} }
private fun clearEndDrawables() { private fun clearEndDrawables() {
(0 until binding.menuLayout.childCount).forEach { itemAdapter.adapterItems.forEach { it.isSelected = false }
(binding.menuLayout.getChildAt(it) as? MenuSheetItemView)?.isSelected = false
}
} }
fun setDrawable(id: Int, @DrawableRes drawableRes: Int, clearAll: Boolean = true) { fun setDrawable(id: Int, @DrawableRes drawableRes: Int, clearAll: Boolean = true) {
if (clearAll) { if (clearAll) {
clearEndDrawables() clearEndDrawables()
} }
binding.menuLayout.findViewById<MenuSheetItemView>(id)?.selectWithEndIcon(drawableRes) itemAdapter.getAdapterItem(id).sheetItem.endDrawableRes = drawableRes
itemAdapter.getAdapterItem(id).isSelected = true
fastAdapter.notifyAdapterDataSetChanged()
} }
data class MenuSheetItem( data class MenuSheetItem(
val id: Int, val id: Int,
@DrawableRes val drawable: Int = 0, @DrawableRes val drawable: Int = 0,
@StringRes val textRes: Int = 0, @StringRes val textRes: Int = 0,
val text: String? = null val text: String? = null,
var endDrawableRes: Int = 0
) )
} }

View File

@ -0,0 +1,50 @@
package eu.kanade.tachiyomi.ui.base
import android.view.View
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.items.AbstractItem
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.MenuSheetItemBinding
class MaterialMenuSheetItem(val sheetItem: MaterialMenuSheet.MenuSheetItem) : AbstractItem<MaterialMenuSheetItem.ViewHolder>() {
/** defines the type defining this item. must be unique. preferably an id */
override val type: Int = R.id.item_text_view
/**
* Returns the layout resource for this item.
*/
override val layoutRes: Int = R.layout.menu_sheet_item
override var identifier = sheetItem.id.hashCode().toLong()
override fun getViewHolder(v: View): ViewHolder {
return ViewHolder(v)
}
class ViewHolder(view: View) : FastAdapter.ViewHolder<MaterialMenuSheetItem>(view) {
private val binding = MenuSheetItemBinding.bind(view)
override fun bindView(item: MaterialMenuSheetItem, payloads: List<Any>) {
val sheetItem = item.sheetItem
with(binding.root) {
if (sheetItem.text != null) {
text = sheetItem.text
} else {
setText(sheetItem.textRes)
}
setIcon(sheetItem.drawable)
if (sheetItem.drawable == 0) {
textSize = 14f
}
isSelected = this.isSelected
if (isSelected) {
selectWithEndIcon(sheetItem.endDrawableRes)
}
}
}
override fun unbindView(item: MaterialMenuSheetItem) {
}
}
}

View File

@ -25,6 +25,7 @@ import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.net.toUri
import androidx.core.view.GestureDetectorCompat import androidx.core.view.GestureDetectorCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -50,6 +51,7 @@ import eu.kanade.tachiyomi.data.updater.UpdateChecker
import eu.kanade.tachiyomi.data.updater.UpdateResult import eu.kanade.tachiyomi.data.updater.UpdateResult
import eu.kanade.tachiyomi.databinding.MainActivityBinding import eu.kanade.tachiyomi.databinding.MainActivityBinding
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.ui.base.MaterialMenuSheet
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.base.controller.BaseController import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -69,6 +71,7 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.hasSideNavBar import eu.kanade.tachiyomi.util.system.hasSideNavBar
import eu.kanade.tachiyomi.util.system.isBottomTappable import eu.kanade.tachiyomi.util.system.isBottomTappable
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.view.getItemView import eu.kanade.tachiyomi.util.view.getItemView
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
@ -326,7 +329,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
if (Migrations.upgrade(preferences)) { if (Migrations.upgrade(preferences)) {
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
content.post { content.post {
WhatsNewSheet(this).show() whatsNewSheet().show()
} }
} }
} }
@ -800,6 +803,39 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
} }
} }
private fun whatsNewSheet() = MaterialMenuSheet(
this,
listOf(
MaterialMenuSheet.MenuSheetItem(
0,
textRes = R.string.whats_new_this_release,
drawable = R.drawable.ic_new_releases_24dp
),
MaterialMenuSheet.MenuSheetItem(
1,
textRes = R.string.close,
drawable = R.drawable.ic_close_24dp
)
),
title = getString(R.string.updated_to_, BuildConfig.VERSION_NAME),
showDivider = true,
selectedId = 0,
onMenuItemClicked = { _, item ->
if (item == 0) {
try {
val intent = Intent(
Intent.ACTION_VIEW,
"https://github.com/jays2kings/tachiyomiJ2K/releases/tag/v${BuildConfig.VERSION_NAME}".toUri()
)
startActivity(intent)
} catch (e: Throwable) {
toast(e.message)
}
}
true
}
)
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() { private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent): Boolean { override fun onDown(e: MotionEvent): Boolean {
return true return true

View File

@ -1,42 +0,0 @@
package eu.kanade.tachiyomi.ui.main
import android.app.Activity
import android.content.Intent
import androidx.core.net.toUri
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.MaterialMenuSheet
import eu.kanade.tachiyomi.util.system.toast
class WhatsNewSheet(activity: Activity) : MaterialMenuSheet(
activity,
listOf(
MenuSheetItem(
0,
textRes = R.string.whats_new_this_release,
drawable = R.drawable.ic_new_releases_24dp
),
MenuSheetItem(
1,
textRes = R.string.close,
drawable = R.drawable.ic_close_24dp
)
),
title = activity.getString(R.string.updated_to_, BuildConfig.VERSION_NAME),
showDivider = true,
selectedId = 0,
onMenuItemClicked = { _, item ->
if (item == 0) {
try {
val intent = Intent(
Intent.ACTION_VIEW,
"https://github.com/jays2kings/tachiyomiJ2K/releases/tag/v${BuildConfig.VERSION_NAME}".toUri()
)
activity.startActivity(intent)
} catch (e: Throwable) {
activity.toast(e.message)
}
}
true
}
)

View File

@ -2,30 +2,24 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/source_filter_sheet" android:id="@+id/menu_sheet_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/bottom_sheet_rounded_background" android:background="@drawable/bottom_sheet_rounded_background"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
android:backgroundTint="?android:attr/colorBackground"> android:backgroundTint="?android:attr/colorBackground">
<eu.kanade.tachiyomi.ui.base.MaxHeightScrollView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/menu_scroll_view" android:id="@+id/menu_sheet_recycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:clipToPadding="false"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constrainedHeight="true"
app:layout_constraintTop_toBottomOf="@id/title_layout"> app:layout_constraintTop_toBottomOf="@id/title_layout">
<LinearLayout </androidx.recyclerview.widget.RecyclerView>
android:id="@+id/menu_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</eu.kanade.tachiyomi.ui.base.MaxHeightScrollView>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/title_layout" android:id="@+id/title_layout"
@ -38,7 +32,7 @@
android:elevation="0dp" android:elevation="0dp"
android:focusable="true" android:focusable="true"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintBottom_toTopOf="@id/menu_scroll_view" app:layout_constraintBottom_toTopOf="@id/menu_sheet_recycler"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">