ui: fix more bottom sheet issues

Fix more bottom sheet issues regarding the queue sheet.

Guess I brought it upon myself for nesting bottom sheets like this in a
way that I doubt the behavior expects.
This commit is contained in:
OxygenCobalt 2022-07-29 15:38:16 -06:00
parent 5536dd48df
commit ed2f836280
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47

View file

@ -19,7 +19,7 @@ package org.oxycblt.auxio
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.ViewTreeObserver
import android.view.WindowInsets import android.view.WindowInsets
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
@ -27,7 +27,6 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior
import com.google.android.material.transition.MaterialFadeThrough import com.google.android.material.transition.MaterialFadeThrough
import java.util.* import java.util.*
import kotlin.math.max import kotlin.math.max
@ -48,7 +47,8 @@ import org.oxycblt.auxio.util.*
* high-level navigation features. * high-level navigation features.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class MainFragment : ViewBindingFragment<FragmentMainBinding>() { class MainFragment :
ViewBindingFragment<FragmentMainBinding>(), ViewTreeObserver.OnPreDrawListener {
private val playbackModel: PlaybackViewModel by androidActivityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val navModel: NavigationViewModel by activityViewModels() private val navModel: NavigationViewModel by activityViewModels()
private var callback: DynamicBackPressedCallback? = null private var callback: DynamicBackPressedCallback? = null
@ -75,26 +75,14 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
val playbackSheetBehavior = val playbackSheetBehavior =
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
val queueSheetBehavior = binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior val queueSheetBehavior = binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior
queueSheetBehavior.addBottomSheetCallback( binding.handleWrapper.setOnClickListener {
object : NeoBottomSheetBehavior.BottomSheetCallback() { if (playbackSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED &&
override fun onSlide(bottomSheet: View, slideOffset: Float) {} queueSheetBehavior.state == BottomSheetBehavior.STATE_COLLAPSED) {
queueSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
override fun onStateChanged(bottomSheet: View, newState: Int) { }
playbackSheetBehavior.isDraggable =
!playbackSheetBehavior.isHideable &&
newState == BottomSheetBehavior.STATE_COLLAPSED
}
})
// We would use the onSlide callback, but doing that would require us to initialize
// when the view first starts up, and that may not always work due to the insanity of
// the CoordinatorLayout lifecycle. Instead, do the less efficient alternative of updating
// the transition on every redraw.
binding.playbackSheet.viewTreeObserver.addOnPreDrawListener {
handleSheetTransitions()
true
} }
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
@ -104,6 +92,14 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
collectImmediately(playbackModel.song, ::updateSong) collectImmediately(playbackModel.song, ::updateSong)
} }
override fun onStart() {
super.onStart()
// Callback could still reasonably fire even if we clear the binding, attach/detach
// our pre-draw listener our listener in onStart/onStop respectively.
requireBinding().playbackSheet.viewTreeObserver.addOnPreDrawListener(this)
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
callback?.isEnabled = true callback?.isEnabled = true
@ -114,6 +110,18 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
callback?.isEnabled = false callback?.isEnabled = false
} }
override fun onStop() {
super.onStop()
requireBinding().playbackSheet.viewTreeObserver.removeOnPreDrawListener(this)
}
override fun onPreDraw(): Boolean {
// CoordinatorLayout is insane and thus makes bottom sheet callbacks insane. Do our
// checks before every draw.
handleSheetTransitions()
return true
}
private fun handleSheetTransitions() { private fun handleSheetTransitions() {
val binding = requireBinding() val binding = requireBinding()
val playbackSheetBehavior = val playbackSheetBehavior =
@ -150,11 +158,15 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
} }
binding.queueFragment.alpha = queueRatio binding.queueFragment.alpha = queueRatio
playbackSheetBehavior.isDraggable =
!playbackSheetBehavior.isHideable &&
queueSheetBehavior.state == BottomSheetBehavior.STATE_COLLAPSED
} }
private fun handleMainNavigation(action: MainNavigationAction?) { private fun handleMainNavigation(action: MainNavigationAction?) {
if (action == null) return if (action == null) return
when (action) { when (action) {
is MainNavigationAction.Expand -> tryExpandAll() is MainNavigationAction.Expand -> tryExpandAll()
is MainNavigationAction.Collapse -> tryCollapseAll() is MainNavigationAction.Collapse -> tryCollapseAll()
@ -176,41 +188,6 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
} }
} }
private fun updateSong(song: Song?) {
val binding = requireBinding()
val playbackSheetBehavior =
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
if (song != null) {
playbackSheetBehavior.unsideSafe()
} else {
playbackSheetBehavior.hideSafe()
}
}
/**
* A back press callback that handles how to respond to backwards navigation in the detail
* fragments and the playback panel.
*
* TODO: Migrate to new predictive API
*/
inner class DynamicBackPressedCallback : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
val binding = requireBinding()
if (!tryCollapseAll()) {
val navController = binding.exploreNavHost.findNavController()
if (navController.currentDestination?.id ==
navController.graph.startDestinationId) {
isEnabled = false
requireActivity().onBackPressed()
isEnabled = true
} else {
navController.navigateUp()
}
}
}
}
private fun tryExpandAll(): Boolean { private fun tryExpandAll(): Boolean {
val binding = requireBinding() val binding = requireBinding()
val playbackSheetBehavior = val playbackSheetBehavior =
@ -244,4 +221,54 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
return false return false
} }
private fun updateSong(song: Song?) {
val binding = requireBinding()
val playbackSheetBehavior =
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
if (song != null) {
playbackSheetBehavior.unsideSafe()
} else {
playbackSheetBehavior.hideSafe()
}
}
/**
* A back press callback that handles how to respond to backwards navigation in the detail
* fragments and the playback panel.
*
* TODO: Migrate to new predictive API
*/
inner class DynamicBackPressedCallback : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
val binding = requireBinding()
val queueSheetBehavior =
binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior
if (queueSheetBehavior.state != BottomSheetBehavior.STATE_COLLAPSED) {
queueSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
return
}
val playbackSheetBehavior =
binding.playbackSheet.coordinatorLayoutBehavior as PlaybackSheetBehavior
if (playbackSheetBehavior.state != BottomSheetBehavior.STATE_COLLAPSED &&
playbackSheetBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
playbackSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
return
}
val navController = binding.exploreNavHost.findNavController()
if (navController.currentDestination?.id == navController.graph.startDestinationId) {
isEnabled = false
requireActivity().onBackPressed()
isEnabled = true
} else {
navController.navigateUp()
}
}
}
} }