ui: avoid crashes with simultanious navigation

If navigation occurs with more than one destination at once, just drop
the slower one rather than crashing.

Resolves #374.
This commit is contained in:
Alexander Capehart 2023-03-17 15:33:59 -06:00
parent 1df15a32cd
commit 5e0059fdba
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
12 changed files with 43 additions and 32 deletions

View file

@ -270,7 +270,7 @@ class MainFragment :
when (action) { when (action) {
is MainNavigationAction.Expand -> tryExpandSheets() is MainNavigationAction.Expand -> tryExpandSheets()
is MainNavigationAction.Collapse -> tryCollapseSheets() is MainNavigationAction.Collapse -> tryCollapseSheets()
is MainNavigationAction.Directions -> findNavController().navigate(action.directions) is MainNavigationAction.Directions -> findNavController().navigateSafe(action.directions)
} }
navModel.mainNavigationAction.consume() navModel.mainNavigationAction.consume()

View file

@ -209,7 +209,7 @@ class AlbumDetailFragment :
} else { } else {
logD("Navigating to another album") logD("Navigating to another album")
findNavController() findNavController()
.navigate(AlbumDetailFragmentDirections.actionShowAlbum(item.album.uid)) .navigateSafe(AlbumDetailFragmentDirections.actionShowAlbum(item.album.uid))
} }
} }
@ -223,7 +223,7 @@ class AlbumDetailFragment :
} else { } else {
logD("Navigating to another album") logD("Navigating to another album")
findNavController() findNavController()
.navigate(AlbumDetailFragmentDirections.actionShowAlbum(item.uid)) .navigateSafe(AlbumDetailFragmentDirections.actionShowAlbum(item.uid))
} }
} }
@ -231,7 +231,7 @@ class AlbumDetailFragment :
is Artist -> { is Artist -> {
logD("Navigating to another artist") logD("Navigating to another artist")
findNavController() findNavController()
.navigate(AlbumDetailFragmentDirections.actionShowArtist(item.uid)) .navigateSafe(AlbumDetailFragmentDirections.actionShowArtist(item.uid))
} }
null -> {} null -> {}
else -> error("Unexpected datatype: ${item::class.java}") else -> error("Unexpected datatype: ${item::class.java}")

View file

@ -42,11 +42,7 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.*
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* A [ListFragment] that shows information about an [Artist]. * A [ListFragment] that shows information about an [Artist].
@ -225,14 +221,14 @@ class ArtistDetailFragment :
is Song -> { is Song -> {
logD("Navigating to another album") logD("Navigating to another album")
findNavController() findNavController()
.navigate(ArtistDetailFragmentDirections.actionShowAlbum(item.album.uid)) .navigateSafe(ArtistDetailFragmentDirections.actionShowAlbum(item.album.uid))
} }
// Launch a new detail view for an album, even if it is part of // Launch a new detail view for an album, even if it is part of
// this artist. // this artist.
is Album -> { is Album -> {
logD("Navigating to another album") logD("Navigating to another album")
findNavController() findNavController()
.navigate(ArtistDetailFragmentDirections.actionShowAlbum(item.uid)) .navigateSafe(ArtistDetailFragmentDirections.actionShowAlbum(item.uid))
} }
// If the artist that should be navigated to is this artist, then // If the artist that should be navigated to is this artist, then
// scroll back to the top. Otherwise launch a new detail view. // scroll back to the top. Otherwise launch a new detail view.
@ -244,7 +240,7 @@ class ArtistDetailFragment :
} else { } else {
logD("Navigating to another artist") logD("Navigating to another artist")
findNavController() findNavController()
.navigate(ArtistDetailFragmentDirections.actionShowArtist(item.uid)) .navigateSafe(ArtistDetailFragmentDirections.actionShowArtist(item.uid))
} }
} }
null -> {} null -> {}

View file

@ -43,11 +43,7 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.*
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* A [ListFragment] that shows information for a particular [Genre]. * A [ListFragment] that shows information for a particular [Genre].
@ -216,17 +212,17 @@ class GenreDetailFragment :
is Song -> { is Song -> {
logD("Navigating to another song") logD("Navigating to another song")
findNavController() findNavController()
.navigate(GenreDetailFragmentDirections.actionShowAlbum(item.album.uid)) .navigateSafe(GenreDetailFragmentDirections.actionShowAlbum(item.album.uid))
} }
is Album -> { is Album -> {
logD("Navigating to another album") logD("Navigating to another album")
findNavController() findNavController()
.navigate(GenreDetailFragmentDirections.actionShowAlbum(item.uid)) .navigateSafe(GenreDetailFragmentDirections.actionShowAlbum(item.uid))
} }
is Artist -> { is Artist -> {
logD("Navigating to another artist") logD("Navigating to another artist")
findNavController() findNavController()
.navigate(GenreDetailFragmentDirections.actionShowArtist(item.uid)) .navigateSafe(GenreDetailFragmentDirections.actionShowArtist(item.uid))
} }
is Genre -> { is Genre -> {
navModel.exploreNavigationItem.consume() navModel.exploreNavigationItem.consume()

View file

@ -201,7 +201,7 @@ class HomeFragment :
R.id.action_search -> { R.id.action_search -> {
logD("Navigating to search") logD("Navigating to search")
setupAxisTransitions(MaterialSharedAxis.Z) setupAxisTransitions(MaterialSharedAxis.Z)
findNavController().navigate(HomeFragmentDirections.actionShowSearch()) findNavController().navigateSafe(HomeFragmentDirections.actionShowSearch())
} }
R.id.action_settings -> { R.id.action_settings -> {
logD("Navigating to settings") logD("Navigating to settings")
@ -454,7 +454,7 @@ class HomeFragment :
} }
setupAxisTransitions(MaterialSharedAxis.X) setupAxisTransitions(MaterialSharedAxis.X)
findNavController().navigate(action) findNavController().navigateSafe(action)
} }
private fun updateSelection(selected: List<Music>) { private fun updateSelection(selected: List<Music>) {

View file

@ -186,7 +186,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
} }
// Keyboard is no longer needed. // Keyboard is no longer needed.
hideKeyboard() hideKeyboard()
findNavController().navigate(action) findNavController().navigateSafe(action)
} }
private fun updateSelection(selected: List<Music>) { private fun updateSelection(selected: List<Music>) {

View file

@ -30,6 +30,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
import org.oxycblt.auxio.util.navigateSafe
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
/** /**
@ -63,19 +64,19 @@ class RootPreferenceFragment : BasePreferenceFragment(R.xml.preferences_root) {
// do one. // do one.
when (preference.key) { when (preference.key) {
getString(R.string.set_key_ui) -> { getString(R.string.set_key_ui) -> {
findNavController().navigate(RootPreferenceFragmentDirections.goToUiPreferences()) findNavController().navigateSafe(RootPreferenceFragmentDirections.goToUiPreferences())
} }
getString(R.string.set_key_personalize) -> { getString(R.string.set_key_personalize) -> {
findNavController() findNavController()
.navigate(RootPreferenceFragmentDirections.goToPersonalizePreferences()) .navigateSafe(RootPreferenceFragmentDirections.goToPersonalizePreferences())
} }
getString(R.string.set_key_music) -> { getString(R.string.set_key_music) -> {
findNavController() findNavController()
.navigate(RootPreferenceFragmentDirections.goToMusicPreferences()) .navigateSafe(RootPreferenceFragmentDirections.goToMusicPreferences())
} }
getString(R.string.set_key_audio) -> { getString(R.string.set_key_audio) -> {
findNavController() findNavController()
.navigate(RootPreferenceFragmentDirections.goToAudioPreferences()) .navigateSafe(RootPreferenceFragmentDirections.goToAudioPreferences())
} }
getString(R.string.set_key_reindex) -> musicModel.refresh() getString(R.string.set_key_reindex) -> musicModel.refresh()
getString(R.string.set_key_rescan) -> musicModel.rescan() getString(R.string.set_key_rescan) -> musicModel.rescan()

View file

@ -22,6 +22,7 @@ import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.settings.BasePreferenceFragment import org.oxycblt.auxio.settings.BasePreferenceFragment
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
import org.oxycblt.auxio.util.navigateSafe
/** /**
* Audio settings interface. * Audio settings interface.
@ -32,7 +33,7 @@ class AudioPreferenceFragment : BasePreferenceFragment(R.xml.preferences_audio)
override fun onOpenDialogPreference(preference: WrappedDialogPreference) { override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_pre_amp)) { if (preference.key == getString(R.string.set_key_pre_amp)) {
findNavController().navigate(AudioPreferenceFragmentDirections.goToPreAmpDialog()) findNavController().navigateSafe(AudioPreferenceFragmentDirections.goToPreAmpDialog())
} }
} }
} }

View file

@ -26,6 +26,7 @@ import javax.inject.Inject
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.settings.BasePreferenceFragment import org.oxycblt.auxio.settings.BasePreferenceFragment
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
import org.oxycblt.auxio.util.navigateSafe
/** /**
* "Content" settings. * "Content" settings.
@ -38,7 +39,7 @@ class MusicPreferenceFragment : BasePreferenceFragment(R.xml.preferences_music)
override fun onOpenDialogPreference(preference: WrappedDialogPreference) { override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_separators)) { if (preference.key == getString(R.string.set_key_separators)) {
findNavController().navigate(MusicPreferenceFragmentDirections.goToSeparatorsDialog()) findNavController().navigateSafe(MusicPreferenceFragmentDirections.goToSeparatorsDialog())
} }
} }

View file

@ -22,6 +22,7 @@ import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.settings.BasePreferenceFragment import org.oxycblt.auxio.settings.BasePreferenceFragment
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
import org.oxycblt.auxio.util.navigateSafe
/** /**
* Personalization settings interface. * Personalization settings interface.
@ -31,7 +32,7 @@ import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
class PersonalizePreferenceFragment : BasePreferenceFragment(R.xml.preferences_personalize) { class PersonalizePreferenceFragment : BasePreferenceFragment(R.xml.preferences_personalize) {
override fun onOpenDialogPreference(preference: WrappedDialogPreference) { override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_home_tabs)) { if (preference.key == getString(R.string.set_key_home_tabs)) {
findNavController().navigate(PersonalizePreferenceFragmentDirections.goToTabDialog()) findNavController().navigateSafe(PersonalizePreferenceFragmentDirections.goToTabDialog())
} }
} }
} }

View file

@ -28,6 +28,7 @@ import org.oxycblt.auxio.settings.BasePreferenceFragment
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
import org.oxycblt.auxio.ui.UISettings import org.oxycblt.auxio.ui.UISettings
import org.oxycblt.auxio.util.isNight import org.oxycblt.auxio.util.isNight
import org.oxycblt.auxio.util.navigateSafe
/** /**
* Display preferences. * Display preferences.
@ -40,7 +41,7 @@ class UIPreferenceFragment : BasePreferenceFragment(R.xml.preferences_ui) {
override fun onOpenDialogPreference(preference: WrappedDialogPreference) { override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
if (preference.key == getString(R.string.set_key_accent)) { if (preference.key == getString(R.string.set_key_accent)) {
findNavController().navigate(UIPreferenceFragmentDirections.goToAccentDialog()) findNavController().navigateSafe(UIPreferenceFragmentDirections.goToAccentDialog())
} }
} }

View file

@ -29,6 +29,9 @@ import androidx.appcompat.widget.AppCompatButton
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import androidx.navigation.NavAction
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
@ -113,6 +116,17 @@ fun AppCompatButton.fixDoubleRipple() {
} }
} }
/**
* Crash-safe wrapped around [NavController.navigate] that will not crash if multiple destinations
* are selected at once.
* @param directions The [NavDirections] to navigate with.
*/
fun NavController.navigateSafe(directions: NavDirections) = try {
navigate(directions)
} catch (e: IllegalStateException) {
// Nothing to do.
}
/** /**
* Get the [CoordinatorLayout.Behavior] of a [View], or null if the [View] is not part of a * Get the [CoordinatorLayout.Behavior] of a [View], or null if the [View] is not part of a
* [CoordinatorLayout] or does not have a [CoordinatorLayout.Behavior]. * [CoordinatorLayout] or does not have a [CoordinatorLayout.Behavior].
@ -149,7 +163,7 @@ val WindowInsets.systemGestureInsetsCompat: Insets
// this should allow this code to fall back to system bar insets easily if the system // this should allow this code to fall back to system bar insets easily if the system
// does not provide system gesture insets. This does require androidx Insets to allow // does not provide system gesture insets. This does require androidx Insets to allow
// us to use the max method on all versions however, so we will want to convert the // us to use the max method on all versions however, so we will want to convert the
// system-provided insets to such.. // system-provided insets to such.
when { when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
// API 30+, use window inset map. // API 30+, use window inset map.