ui: add predictive back gesture
Rework the back pressed callbacks to support a predictive back gesture. This completes the trivial Android 13 reworks.
This commit is contained in:
parent
0b43dd011c
commit
a2f27f303b
6 changed files with 61 additions and 51 deletions
|
@ -5,9 +5,10 @@
|
|||
#### What's New
|
||||
- Added Android 13 support [#129]
|
||||
- Switch to new storage permissions
|
||||
- Add monochrome icon
|
||||
- Add themed icon
|
||||
- Fix issue where widget covers would not load
|
||||
- Use new media notification panel style
|
||||
- Add predictive back navigation
|
||||
|
||||
#### What's Improved
|
||||
- Playback bar now has a marquee effect
|
||||
|
|
|
@ -70,7 +70,7 @@ dependencies {
|
|||
|
||||
// General
|
||||
implementation "androidx.core:core-ktx:1.8.0"
|
||||
implementation "androidx.activity:activity-ktx:1.5.1"
|
||||
implementation "androidx.activity:activity-ktx:1.6.0-rc01"
|
||||
implementation "androidx.fragment:fragment-ktx:1.5.2"
|
||||
|
||||
// UI
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
android:theme="@style/Theme.Auxio.App"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:appCategory="audio"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<activity
|
||||
|
|
|
@ -53,9 +53,12 @@ class MainFragment :
|
|||
ViewBindingFragment<FragmentMainBinding>(), ViewTreeObserver.OnPreDrawListener {
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
private val navModel: NavigationViewModel by activityViewModels()
|
||||
private var callback: DynamicBackPressedCallback? = null
|
||||
private val callback = DynamicBackPressedCallback()
|
||||
private var lastInsets: WindowInsets? = null
|
||||
private var elevationNormal = -1f
|
||||
|
||||
private val elevationNormal: Float by lifecycleObject { binding ->
|
||||
binding.context.getDimen(R.dimen.elevation_normal)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -69,10 +72,7 @@ class MainFragment :
|
|||
// --- UI SETUP ---
|
||||
val context = requireActivity()
|
||||
|
||||
context.onBackPressedDispatcher.addCallback(
|
||||
viewLifecycleOwner, DynamicBackPressedCallback().also { callback = it })
|
||||
|
||||
elevationNormal = requireContext().getDimen(R.dimen.elevation_normal)
|
||||
context.onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
|
||||
|
||||
binding.root.setOnApplyWindowInsetsListener { _, insets ->
|
||||
lastInsets = insets
|
||||
|
@ -128,16 +128,6 @@ class MainFragment :
|
|||
requireBinding().playbackSheet.viewTreeObserver.addOnPreDrawListener(this)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
callback?.isEnabled = true
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
callback?.isEnabled = false
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
requireBinding().playbackSheet.viewTreeObserver.removeOnPreDrawListener(this)
|
||||
|
@ -209,6 +199,10 @@ class MainFragment :
|
|||
tryHideAll()
|
||||
}
|
||||
|
||||
// Since the callback is also reliant on the bottom sheets, we must also update it
|
||||
// every frame.
|
||||
callback.updateEnabledState()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -322,10 +316,8 @@ class MainFragment :
|
|||
private inner class DynamicBackPressedCallback : OnBackPressedCallback(false) {
|
||||
override fun handleOnBackPressed() {
|
||||
val binding = requireBinding()
|
||||
|
||||
val playbackSheetBehavior =
|
||||
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
||||
|
||||
val queueSheetBehavior =
|
||||
binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior?
|
||||
|
||||
|
@ -344,15 +336,29 @@ class MainFragment :
|
|||
return
|
||||
}
|
||||
|
||||
// First navigate upwards in the explore graph, then navigate back in the activity.
|
||||
val navController = binding.exploreNavHost.findNavController()
|
||||
if (navController.currentDestination?.id == navController.graph.startDestinationId) {
|
||||
isEnabled = false
|
||||
requireActivity().onBackPressed()
|
||||
isEnabled = true
|
||||
} else {
|
||||
navController.navigateUp()
|
||||
}
|
||||
binding.exploreNavHost.findNavController().navigateUp()
|
||||
}
|
||||
|
||||
fun updateEnabledState() {
|
||||
val binding = requireBinding()
|
||||
val playbackSheetBehavior =
|
||||
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
|
||||
val queueSheetBehavior =
|
||||
binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior?
|
||||
|
||||
val exploreNavController = binding.exploreNavHost.findNavController()
|
||||
|
||||
logD(
|
||||
playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED ||
|
||||
queueSheetBehavior?.state == NeoBottomSheetBehavior.STATE_EXPANDED ||
|
||||
exploreNavController.currentDestination?.id !=
|
||||
exploreNavController.graph.startDestinationId)
|
||||
|
||||
isEnabled =
|
||||
playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED ||
|
||||
queueSheetBehavior?.state == NeoBottomSheetBehavior.STATE_EXPANDED ||
|
||||
exploreNavController.currentDestination?.id !=
|
||||
exploreNavController.graph.startDestinationId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,10 +84,9 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
|||
// Orientation change will wipe whatever transition we were using prior, which will
|
||||
// result in no transition when the user navigates back. Make sure we re-initialize
|
||||
// our transitions.
|
||||
if (savedInstanceState.getBoolean(KEY_INIT_WITH_SEARCH_TRANSITIONS)) {
|
||||
initSearchTransitions()
|
||||
} else {
|
||||
initDetailTransitions()
|
||||
val axis = savedInstanceState.getInt(KEY_LAST_TRANSITION_AXIS, -1)
|
||||
if (axis > -1) {
|
||||
initAxisTransitions(axis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +151,11 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
|||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putBoolean(KEY_INIT_WITH_SEARCH_TRANSITIONS, enterTransition is MaterialSharedAxis)
|
||||
val enter = enterTransition
|
||||
if (enter is MaterialSharedAxis) {
|
||||
outState.putInt(KEY_LAST_TRANSITION_AXIS, enter.axis)
|
||||
}
|
||||
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
|
@ -165,7 +168,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
|||
when (item.itemId) {
|
||||
R.id.action_search -> {
|
||||
logD("Navigating to search")
|
||||
initSearchTransitions()
|
||||
initAxisTransitions(MaterialSharedAxis.Z)
|
||||
findNavController().navigate(HomeFragmentDirections.actionShowSearch())
|
||||
}
|
||||
R.id.action_settings -> {
|
||||
|
@ -376,23 +379,22 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
|||
else -> return
|
||||
}
|
||||
|
||||
initDetailTransitions()
|
||||
initAxisTransitions(MaterialSharedAxis.X)
|
||||
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
|
||||
private fun initSearchTransitions() {
|
||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
|
||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
|
||||
}
|
||||
private fun initAxisTransitions(axis: Int) {
|
||||
// Sanity check
|
||||
if (axis != MaterialSharedAxis.X && axis != MaterialSharedAxis.Z) {
|
||||
logW("Invalid axis provided")
|
||||
return
|
||||
}
|
||||
|
||||
private fun initDetailTransitions() {
|
||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
enterTransition = MaterialSharedAxis(axis, true)
|
||||
returnTransition = MaterialSharedAxis(axis, false)
|
||||
exitTransition = MaterialSharedAxis(axis, true)
|
||||
reenterTransition = MaterialSharedAxis(axis, false)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -433,7 +435,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
|||
lazyReflectedField(ViewPager2::class, "mRecyclerView")
|
||||
private val VIEW_PAGER_TOUCH_SLOP_FIELD: Field by
|
||||
lazyReflectedField(RecyclerView::class, "mTouchSlop")
|
||||
private const val KEY_INIT_WITH_SEARCH_TRANSITIONS =
|
||||
BuildConfig.APPLICATION_ID + ".key.INIT_WITH_SEARCH_TRANSITIONS"
|
||||
private const val KEY_LAST_TRANSITION_AXIS =
|
||||
BuildConfig.APPLICATION_ID + ".key.LAST_TRANSITION_AXIS"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ import android.util.AttributeSet
|
|||
import com.google.android.material.button.MaterialButton
|
||||
|
||||
/**
|
||||
* A [MaterialButton] that automatically morphs from a circle to a squircle shape appearance when
|
||||
* it is activated.
|
||||
* A [MaterialButton] that automatically morphs from a circle to a squircle shape appearance when it
|
||||
* is activated.
|
||||
*/
|
||||
class AnimatedMaterialButton
|
||||
@JvmOverloads
|
||||
|
|
Loading…
Reference in a new issue