mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-20 06:39:20 +01:00
Animated insets for the fabs when keyboard shows
The reason I'm on android 11's sdk in the first place Also gonna need a lot of checks because android just deprecated all the inset stuff for pre 11 Some of these changes actually optimzed the tab bar animations a lot
This commit is contained in:
parent
47c2f5f97f
commit
0fa3eef40e
@ -8,6 +8,7 @@ import android.app.Activity
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
@ -20,12 +21,16 @@ import android.view.MotionEvent
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewPropertyAnimator
|
import android.view.ViewPropertyAnimator
|
||||||
|
import android.view.WindowInsets
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.view.GestureDetectorCompat
|
import androidx.core.view.GestureDetectorCompat
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsAnimationCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
@ -80,6 +85,7 @@ import eu.kanade.tachiyomi.util.moveCategories
|
|||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import eu.kanade.tachiyomi.util.system.getBottomGestureInsets
|
import eu.kanade.tachiyomi.util.system.getBottomGestureInsets
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
|
import eu.kanade.tachiyomi.util.system.isImeVisible
|
||||||
import eu.kanade.tachiyomi.util.system.launchUI
|
import eu.kanade.tachiyomi.util.system.launchUI
|
||||||
import eu.kanade.tachiyomi.util.view.activityBinding
|
import eu.kanade.tachiyomi.util.view.activityBinding
|
||||||
import eu.kanade.tachiyomi.util.view.collapse
|
import eu.kanade.tachiyomi.util.view.collapse
|
||||||
@ -218,6 +224,28 @@ class LibraryController(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val cb = object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
|
||||||
|
override fun onStart(
|
||||||
|
animation: WindowInsetsAnimationCompat,
|
||||||
|
bounds: WindowInsetsAnimationCompat.BoundsCompat
|
||||||
|
): WindowInsetsAnimationCompat.BoundsCompat {
|
||||||
|
updateHopperY()
|
||||||
|
return bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProgress(
|
||||||
|
insets: WindowInsetsCompat,
|
||||||
|
runningAnimations: List<WindowInsetsAnimationCompat>
|
||||||
|
): WindowInsetsCompat {
|
||||||
|
updateHopperY(insets.toWindowInsets())
|
||||||
|
return insets
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEnd(animation: WindowInsetsAnimationCompat) {
|
||||||
|
updateHopperY()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var scrollListener = object : RecyclerView.OnScrollListener() {
|
private var scrollListener = object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
@ -536,6 +564,8 @@ class LibraryController(
|
|||||||
}
|
}
|
||||||
setSwipeRefresh()
|
setSwipeRefresh()
|
||||||
|
|
||||||
|
ViewCompat.setWindowInsetsAnimationCallback(view, cb)
|
||||||
|
|
||||||
if (selectedMangas.isNotEmpty()) {
|
if (selectedMangas.isNotEmpty()) {
|
||||||
createActionModeIfNeeded()
|
createActionModeIfNeeded()
|
||||||
}
|
}
|
||||||
@ -691,16 +721,21 @@ class LibraryController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateHopperY() {
|
fun updateHopperY(windowInsets: WindowInsets? = null) {
|
||||||
val view = view ?: return
|
val view = view ?: return
|
||||||
|
val insets = windowInsets ?: view.rootWindowInsets
|
||||||
val listOfYs = mutableListOf(
|
val listOfYs = mutableListOf(
|
||||||
binding.filterBottomSheet.filterBottomSheet.y,
|
binding.filterBottomSheet.filterBottomSheet.y,
|
||||||
activityBinding?.bottomNav?.y ?: binding.filterBottomSheet.filterBottomSheet.y
|
activityBinding?.bottomNav?.y ?: binding.filterBottomSheet.filterBottomSheet.y
|
||||||
)
|
)
|
||||||
val insetBottom = view.rootWindowInsets?.systemWindowInsetBottom ?: 0
|
val insetBottom = insets?.systemWindowInsetBottom ?: 0
|
||||||
if (!preferences.autohideHopper().get() || activityBinding?.bottomNav == null) {
|
if (!preferences.autohideHopper().get() || activityBinding?.bottomNav == null) {
|
||||||
listOfYs.add(view.height - (insetBottom).toFloat())
|
listOfYs.add(view.height - (insetBottom).toFloat())
|
||||||
}
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && insets?.isImeVisible() == true) {
|
||||||
|
val insetKey = insets.getInsets(WindowInsets.Type.ime() or WindowInsets.Type.systemBars()).bottom
|
||||||
|
listOfYs.add(view.height - (insetKey).toFloat())
|
||||||
|
}
|
||||||
binding.categoryHopperFrame.y = -binding.categoryHopperFrame.height +
|
binding.categoryHopperFrame.y = -binding.categoryHopperFrame.height +
|
||||||
(listOfYs.minOrNull() ?: binding.filterBottomSheet.filterBottomSheet.y) +
|
(listOfYs.minOrNull() ?: binding.filterBottomSheet.filterBottomSheet.y) +
|
||||||
hopperOffset +
|
hopperOffset +
|
||||||
|
@ -767,11 +767,15 @@ open class MainActivity : BaseActivity<MainActivityBinding>(), DownloadServiceLi
|
|||||||
setFloatingToolbar(canShowFloatingToolbar(to))
|
setFloatingToolbar(canShowFloatingToolbar(to))
|
||||||
val onRoot = router.backstackSize == 1
|
val onRoot = router.backstackSize == 1
|
||||||
if (onRoot) {
|
if (onRoot) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R && !isPush) {
|
||||||
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
|
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
|
||||||
|
}
|
||||||
binding.toolbar.navigationIcon = searchDrawable
|
binding.toolbar.navigationIcon = searchDrawable
|
||||||
binding.cardToolbar.navigationIcon = searchDrawable
|
binding.cardToolbar.navigationIcon = searchDrawable
|
||||||
} else {
|
} else {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||||
|
}
|
||||||
binding.toolbar.navigationIcon = drawerArrow
|
binding.toolbar.navigationIcon = drawerArrow
|
||||||
binding.cardToolbar.navigationIcon = drawerArrow
|
binding.cardToolbar.navigationIcon = drawerArrow
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.source.browse
|
package eu.kanade.tachiyomi.ui.source.browse
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
@ -35,6 +36,7 @@ import eu.kanade.tachiyomi.util.addOrRemoveToFavorites
|
|||||||
import eu.kanade.tachiyomi.util.system.connectivityManager
|
import eu.kanade.tachiyomi.util.system.connectivityManager
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
|
import eu.kanade.tachiyomi.util.view.applyBottomAnimatedInsets
|
||||||
import eu.kanade.tachiyomi.util.view.inflate
|
import eu.kanade.tachiyomi.util.view.inflate
|
||||||
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
||||||
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
|
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
|
||||||
@ -177,11 +179,14 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
recycler,
|
recycler,
|
||||||
true,
|
true,
|
||||||
afterInsets = { insets ->
|
afterInsets = { insets ->
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
binding.fab.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.fab.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
bottomMargin = insets.systemWindowInsetBottom + 16.dpToPx
|
bottomMargin = insets.systemWindowInsetBottom + 16.dpToPx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
binding.fab.applyBottomAnimatedInsets(16.dpToPx)
|
||||||
|
|
||||||
recycler.addOnScrollListener(
|
recycler.addOnScrollListener(
|
||||||
object : RecyclerView.OnScrollListener() {
|
object : RecyclerView.OnScrollListener() {
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.util.system
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
|
||||||
fun WindowInsets.getBottomGestureInsets(): Int {
|
fun WindowInsets.getBottomGestureInsets(): Int {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) mandatorySystemGestureInsets.bottom
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) mandatorySystemGestureInsets.bottom
|
||||||
@ -20,3 +21,6 @@ fun WindowInsets.hasSideInsets() = systemWindowInsetLeft > 0 || systemWindowInse
|
|||||||
fun WindowInsets.hasSideNavBar() =
|
fun WindowInsets.hasSideNavBar() =
|
||||||
(systemWindowInsetLeft > 0 || systemWindowInsetRight > 0) && !isBottomTappable() &&
|
(systemWindowInsetLeft > 0 || systemWindowInsetRight > 0) && !isBottomTappable() &&
|
||||||
systemWindowInsetBottom == 0
|
systemWindowInsetBottom == 0
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
|
fun WindowInsets.isImeVisible() = isVisible(WindowInsets.Type.ime())
|
||||||
|
@ -113,7 +113,11 @@ fun Controller.liftAppbarWith(recycler: RecyclerView, padView: Boolean = false)
|
|||||||
val headerHeight = insets.systemWindowInsetTop + appBarHeight
|
val headerHeight = insets.systemWindowInsetTop + appBarHeight
|
||||||
view.updatePaddingRelative(
|
view.updatePaddingRelative(
|
||||||
top = headerHeight,
|
top = headerHeight,
|
||||||
bottom = insets.systemWindowInsetBottom
|
bottom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
insets.getInsets(WindowInsets.Type.ime() or WindowInsets.Type.systemBars()).bottom
|
||||||
|
} else {
|
||||||
|
insets.systemWindowInsetBottom
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -25,6 +25,8 @@ import androidx.appcompat.widget.PopupMenu
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsAnimationCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||||
@ -121,6 +123,57 @@ object RecyclerWindowInsetsListener : View.OnApplyWindowInsetsListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun View.applyBottomAnimatedInsets(bottomMargin: Int = 0) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return
|
||||||
|
val setInsets: ((WindowInsets) -> Unit) = { insets ->
|
||||||
|
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
val bottom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
insets.getInsets(WindowInsets.Type.systemBars() or WindowInsets.Type.ime()).bottom
|
||||||
|
} else {
|
||||||
|
insets.systemWindowInsetBottom
|
||||||
|
}
|
||||||
|
this.bottomMargin = bottom + bottomMargin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var handleInsets = true
|
||||||
|
doOnApplyWindowInsets { _, insets, _ ->
|
||||||
|
if (handleInsets) {
|
||||||
|
setInsets(insets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewCompat.setWindowInsetsAnimationCallback(
|
||||||
|
this,
|
||||||
|
object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
|
||||||
|
override fun onPrepare(animation: WindowInsetsAnimationCompat) {
|
||||||
|
handleInsets = false
|
||||||
|
super.onPrepare(animation)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart(
|
||||||
|
animation: WindowInsetsAnimationCompat,
|
||||||
|
bounds: WindowInsetsAnimationCompat.BoundsCompat
|
||||||
|
): WindowInsetsAnimationCompat.BoundsCompat {
|
||||||
|
handleInsets = false
|
||||||
|
rootWindowInsets?.let { insets -> setInsets(insets) }
|
||||||
|
return super.onStart(animation, bounds)
|
||||||
|
}
|
||||||
|
override fun onProgress(
|
||||||
|
insets: WindowInsetsCompat,
|
||||||
|
runningAnimations: List<WindowInsetsAnimationCompat>
|
||||||
|
): WindowInsetsCompat {
|
||||||
|
insets.toWindowInsets()?.let { setInsets(it) }
|
||||||
|
return insets
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEnd(animation: WindowInsetsAnimationCompat) {
|
||||||
|
handleInsets = true
|
||||||
|
rootWindowInsets?.let { insets -> setInsets(insets) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
object ControllerViewWindowInsetsListener : View.OnApplyWindowInsetsListener {
|
object ControllerViewWindowInsetsListener : View.OnApplyWindowInsetsListener {
|
||||||
override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
|
override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
|
||||||
v.updateLayoutParams<FrameLayout.LayoutParams> {
|
v.updateLayoutParams<FrameLayout.LayoutParams> {
|
||||||
@ -155,6 +208,16 @@ fun View.doOnApplyWindowInsets(f: (View, WindowInsets, ViewPaddingState) -> Unit
|
|||||||
requestApplyInsetsWhenAttached()
|
requestApplyInsetsWhenAttached()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun View.doOnApplyWindowInsetsCompat(f: (View, WindowInsetsCompat, ViewPaddingState) -> Unit) {
|
||||||
|
// Create a snapshot of the view's padding state
|
||||||
|
val paddingState = createStateForView(this)
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->
|
||||||
|
f(v, insets, paddingState)
|
||||||
|
insets
|
||||||
|
}
|
||||||
|
requestApplyInsetsWhenAttached()
|
||||||
|
}
|
||||||
|
|
||||||
fun View.applyWindowInsetsForController() {
|
fun View.applyWindowInsetsForController() {
|
||||||
setOnApplyWindowInsetsListener(ControllerViewWindowInsetsListener)
|
setOnApplyWindowInsetsListener(ControllerViewWindowInsetsListener)
|
||||||
requestApplyInsetsWhenAttached()
|
requestApplyInsetsWhenAttached()
|
||||||
|
Loading…
Reference in New Issue
Block a user