From 0fa3eef40ee0cfe76429c92125eba32ef8803e70 Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Wed, 26 May 2021 23:25:28 -0400 Subject: [PATCH] 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 --- .../tachiyomi/ui/library/LibraryController.kt | 39 +++++++++++- .../kanade/tachiyomi/ui/main/MainActivity.kt | 8 ++- .../source/browse/BrowseSourceController.kt | 9 ++- .../util/system/WindowInsetsExtensions.kt | 4 ++ .../util/view/ControllerExtensions.kt | 6 +- .../tachiyomi/util/view/ViewExtensions.kt | 63 +++++++++++++++++++ 6 files changed, 122 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index e98c5f1919..5e36a0207d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -8,6 +8,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.graphics.Color +import android.os.Build import android.os.Bundle import android.os.Handler import android.util.TypedValue @@ -20,12 +21,16 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewPropertyAnimator +import android.view.WindowInsets 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.core.view.ViewCompat +import androidx.core.view.WindowInsetsAnimationCompat +import androidx.core.view.WindowInsetsCompat import androidx.core.view.isInvisible import androidx.core.view.isVisible 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.getBottomGestureInsets 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.view.activityBinding 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 + ): WindowInsetsCompat { + updateHopperY(insets.toWindowInsets()) + return insets + } + + override fun onEnd(animation: WindowInsetsAnimationCompat) { + updateHopperY() + } + } + private var scrollListener = object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) @@ -536,6 +564,8 @@ class LibraryController( } setSwipeRefresh() + ViewCompat.setWindowInsetsAnimationCallback(view, cb) + if (selectedMangas.isNotEmpty()) { createActionModeIfNeeded() } @@ -691,16 +721,21 @@ class LibraryController( } } - fun updateHopperY() { + fun updateHopperY(windowInsets: WindowInsets? = null) { val view = view ?: return + val insets = windowInsets ?: view.rootWindowInsets val listOfYs = mutableListOf( 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) { 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 + (listOfYs.minOrNull() ?: binding.filterBottomSheet.filterBottomSheet.y) + hopperOffset + diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index bb9d20feb0..5d394166df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -767,11 +767,15 @@ open class MainActivity : BaseActivity(), DownloadServiceLi setFloatingToolbar(canShowFloatingToolbar(to)) val onRoot = router.backstackSize == 1 if (onRoot) { - window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R && !isPush) { + window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN) + } binding.toolbar.navigationIcon = searchDrawable binding.cardToolbar.navigationIcon = searchDrawable } else { - window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + } binding.toolbar.navigationIcon = drawerArrow binding.cardToolbar.navigationIcon = drawerArrow } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt index bd85227e50..0a9746d40b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.source.browse +import android.os.Build import android.os.Bundle import android.view.LayoutInflater 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.dpToPx 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.scrollViewWith import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener @@ -177,11 +179,14 @@ open class BrowseSourceController(bundle: Bundle) : recycler, true, afterInsets = { insets -> - binding.fab.updateLayoutParams { - bottomMargin = insets.systemWindowInsetBottom + 16.dpToPx + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + binding.fab.updateLayoutParams { + bottomMargin = insets.systemWindowInsetBottom + 16.dpToPx + } } } ) + binding.fab.applyBottomAnimatedInsets(16.dpToPx) recycler.addOnScrollListener( object : RecyclerView.OnScrollListener() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/WindowInsetsExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/WindowInsetsExtensions.kt index 9c24f920bb..155fe06649 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/WindowInsetsExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/WindowInsetsExtensions.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.util.system import android.os.Build import android.view.WindowInsets +import androidx.annotation.RequiresApi fun WindowInsets.getBottomGestureInsets(): Int { 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() = (systemWindowInsetLeft > 0 || systemWindowInsetRight > 0) && !isBottomTappable() && systemWindowInsetBottom == 0 + +@RequiresApi(Build.VERSION_CODES.R) +fun WindowInsets.isImeVisible() = isVisible(WindowInsets.Type.ime()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt index 2bec988fa5..8afaa5283a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt @@ -113,7 +113,11 @@ fun Controller.liftAppbarWith(recycler: RecyclerView, padView: Boolean = false) val headerHeight = insets.systemWindowInsetTop + appBarHeight view.updatePaddingRelative( 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 { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 1143f2cc04..3e42580767 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -25,6 +25,8 @@ import androidx.appcompat.widget.PopupMenu import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsAnimationCompat +import androidx.core.view.WindowInsetsCompat import androidx.core.view.forEach import androidx.recyclerview.widget.LinearLayoutManager 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 { + 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 + ): WindowInsetsCompat { + insets.toWindowInsets()?.let { setInsets(it) } + return insets + } + + override fun onEnd(animation: WindowInsetsAnimationCompat) { + handleInsets = true + rootWindowInsets?.let { insets -> setInsets(insets) } + } + } + ) +} + object ControllerViewWindowInsetsListener : View.OnApplyWindowInsetsListener { override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { v.updateLayoutParams { @@ -155,6 +208,16 @@ fun View.doOnApplyWindowInsets(f: (View, WindowInsets, ViewPaddingState) -> Unit 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() { setOnApplyWindowInsetsListener(ControllerViewWindowInsetsListener) requestApplyInsetsWhenAttached()