diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 1e69affcf..395ded94f 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -129,12 +129,12 @@ class MainFragment : } // --- VIEWMODEL SETUP --- - collect(navModel.mainNavigationAction, ::handleMainNavigation) - collect(navModel.exploreNavigationItem, ::handleExploreNavigation) - collect(navModel.exploreArtistNavigationItem, ::handleArtistNavigationPicker) + collect(navModel.mainNavigationAction.flow, ::handleMainNavigation) + collect(navModel.exploreNavigationItem.flow, ::handleExploreNavigation) + collect(navModel.exploreArtistNavigationItem.flow, ::handleArtistNavigationPicker) collectImmediately(playbackModel.song, ::updateSong) - collect(playbackModel.artistPickerSong, ::handlePlaybackArtistPicker) - collect(playbackModel.genrePickerSong, ::handlePlaybackGenrePicker) + collect(playbackModel.artistPickerSong.flow, ::handlePlaybackArtistPicker) + collect(playbackModel.genrePickerSong.flow, ::handlePlaybackGenrePicker) } override fun onStart() { @@ -273,7 +273,7 @@ class MainFragment : is MainNavigationAction.Directions -> findNavController().navigate(action.directions) } - navModel.finishMainNavigation() + navModel.mainNavigationAction.consume() } private fun handleExploreNavigation(item: Music?) { @@ -287,7 +287,7 @@ class MainFragment : navModel.mainNavigateTo( MainNavigationAction.Directions( MainFragmentDirections.actionPickNavigationArtist(item.uid))) - navModel.finishExploreNavigation() + navModel.exploreArtistNavigationItem.consume() } } @@ -304,7 +304,7 @@ class MainFragment : navModel.mainNavigateTo( MainNavigationAction.Directions( MainFragmentDirections.actionPickPlaybackArtist(song.uid))) - playbackModel.finishPlaybackArtistPicker() + playbackModel.artistPickerSong.consume() } } @@ -313,7 +313,7 @@ class MainFragment : navModel.mainNavigateTo( MainNavigationAction.Directions( MainFragmentDirections.actionPickPlaybackGenre(song.uid))) - playbackModel.finishPlaybackGenrePicker() + playbackModel.genrePickerSong.consume() } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index cedd8bd11..9267184a1 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -96,7 +96,7 @@ class AlbumDetailFragment : collectImmediately(detailModel.albumList, ::updateList) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) - collect(navModel.exploreNavigationItem, ::handleNavigation) + collect(navModel.exploreNavigationItem.flow, ::handleNavigation) collectImmediately(selectionModel.selected, ::updateSelection) } @@ -205,7 +205,7 @@ class AlbumDetailFragment : if (unlikelyToBeNull(detailModel.currentAlbum.value) == item.album) { logD("Navigating to a song in this album") scrollToAlbumSong(item) - navModel.finishExploreNavigation() + navModel.exploreNavigationItem.consume() } else { logD("Navigating to another album") findNavController() @@ -219,7 +219,7 @@ class AlbumDetailFragment : if (unlikelyToBeNull(detailModel.currentAlbum.value) == item) { logD("Navigating to the top of this album") binding.detailRecycler.scrollToPosition(0) - navModel.finishExploreNavigation() + navModel.exploreNavigationItem.consume() } else { logD("Navigating to another album") findNavController() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index ac5c6671f..ea012162c 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -99,7 +99,7 @@ class ArtistDetailFragment : collectImmediately(detailModel.artistList, ::updateList) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) - collect(navModel.exploreNavigationItem, ::handleNavigation) + collect(navModel.exploreNavigationItem.flow, ::handleNavigation) collectImmediately(selectionModel.selected, ::updateSelection) } @@ -240,7 +240,7 @@ class ArtistDetailFragment : if (item.uid == detailModel.currentArtist.value?.uid) { logD("Navigating to the top of this artist") binding.detailRecycler.scrollToPosition(0) - navModel.finishExploreNavigation() + navModel.exploreNavigationItem.consume() } else { logD("Navigating to another artist") findNavController() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index 66e549d5c..a51387a28 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -98,7 +98,7 @@ class GenreDetailFragment : collectImmediately(detailModel.genreList, ::updateList) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) - collect(navModel.exploreNavigationItem, ::handleNavigation) + collect(navModel.exploreNavigationItem.flow, ::handleNavigation) collectImmediately(selectionModel.selected, ::updateSelection) } @@ -229,7 +229,7 @@ class GenreDetailFragment : .navigate(GenreDetailFragmentDirections.actionShowArtist(item.uid)) } is Genre -> { - navModel.finishExploreNavigation() + navModel.exploreNavigationItem.consume() } null -> {} } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 5cee75104..c021febe2 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -155,11 +155,11 @@ class HomeFragment : binding.homeFab.setOnClickListener { playbackModel.shuffleAll() } // --- VIEWMODEL SETUP --- - collect(homeModel.shouldRecreate.flow, ::handleRecreate) + collect(homeModel.recreateTabs.flow, ::handleRecreate) collectImmediately(homeModel.currentTabMode, ::updateCurrentTab) collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab) collectImmediately(musicModel.indexerState, ::updateIndexerState) - collect(navModel.exploreNavigationItem, ::handleNavigation) + collect(navModel.exploreNavigationItem.flow, ::handleNavigation) collectImmediately(selectionModel.selected, ::updateSelection) } @@ -337,7 +337,7 @@ class HomeFragment : binding.homePager.currentItem = 0 // Make sure tabs are set up to also follow the new ViewPager configuration. setupPager(binding) - homeModel.shouldRecreate.consume() + homeModel.recreateTabs.consume() } private fun updateIndexerState(state: Indexer.State?) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index aa06905c9..aa3058f31 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -109,7 +109,7 @@ constructor( * flag is true, all tabs (and their respective ViewPager2 fragments) will be re-created from * scratch. */ - val shouldRecreate: Event + val recreateTabs: Event get() = _shouldRecreate private val _isFastScrolling = MutableStateFlow(false) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 2ac0f05d8..9301bb341 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -31,6 +31,8 @@ import org.oxycblt.auxio.music.* import org.oxycblt.auxio.playback.persist.PersistenceRepository import org.oxycblt.auxio.playback.queue.Queue import org.oxycblt.auxio.playback.state.* +import org.oxycblt.auxio.util.Event +import org.oxycblt.auxio.util.MutableEvent /** * An [ViewModel] that provides a safe UI frontend for the current playback state. @@ -74,22 +76,22 @@ constructor( val isShuffled: StateFlow get() = _isShuffled - private val _artistPlaybackPickerSong = MutableStateFlow(null) + private val _artistPlaybackPickerSong = MutableEvent() /** * Flag signaling to open a picker dialog in order to resolve an ambiguous choice when playing a * [Song] from one of it's [Artist]s. * * @see playFromArtist */ - val artistPickerSong: StateFlow + val artistPickerSong: Event get() = _artistPlaybackPickerSong - private val _genrePlaybackPickerSong = MutableStateFlow(null) + private val _genrePlaybackPickerSong = MutableEvent() /** * Flag signaling to open a picker dialog in order to resolve an ambiguous choice when playing a * [Song] from one of it's [Genre]s. */ - val genrePickerSong: StateFlow + val genrePickerSong: Event get() = _genrePlaybackPickerSong /** The current action to show on the playback bar. */ @@ -192,20 +194,10 @@ constructor( } else if (song.artists.size == 1) { playImpl(song, song.artists[0]) } else { - _artistPlaybackPickerSong.value = song + _artistPlaybackPickerSong.put(song) } } - /** - * Mark the [Artist] playback choice process as complete. This should occur when the [Artist] - * choice dialog is opened after this flag is detected. - * - * @see playFromArtist - */ - fun finishPlaybackArtistPicker() { - _artistPlaybackPickerSong.value = null - } - /** * PLay a [Song] from one of it's [Genre]s. * @@ -219,20 +211,10 @@ constructor( } else if (song.genres.size == 1) { playImpl(song, song.genres[0]) } else { - _genrePlaybackPickerSong.value = song + _genrePlaybackPickerSong.put(song) } } - /** - * Mark the [Genre] playback choice process as complete. This should occur when the [Genre] - * choice dialog is opened after this flag is detected. - * - * @see playFromGenre - */ - fun finishPlaybackGenrePicker() { - _genrePlaybackPickerSong.value = null - } - /** * Play an [Album]. * diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index e5b563698..c724e2f39 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -115,7 +115,7 @@ class SearchFragment : ListFragment() { collectImmediately(searchModel.searchResults, ::updateSearchResults) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) - collect(navModel.exploreNavigationItem, ::handleNavigation) + collect(navModel.exploreNavigationItem.flow, ::handleNavigation) collectImmediately(selectionModel.selected, ::updateSelection) } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt b/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt index c7210573e..116f57013 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/NavigationViewModel.kt @@ -20,38 +20,38 @@ package org.oxycblt.auxio.ui import androidx.lifecycle.ViewModel import androidx.navigation.NavDirections -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.util.Event +import org.oxycblt.auxio.util.MutableEvent import org.oxycblt.auxio.util.logD /** A [ViewModel] that handles complicated navigation functionality. */ class NavigationViewModel : ViewModel() { - private val _mainNavigationAction = MutableStateFlow(null) + private val _mainNavigationAction = MutableEvent() /** * Flag for navigation within the main navigation graph. Only intended for use by MainFragment. */ - val mainNavigationAction: StateFlow + val mainNavigationAction: Event get() = _mainNavigationAction - private val _exploreNavigationItem = MutableStateFlow(null) + private val _exploreNavigationItem = MutableEvent() /** * Flag for navigation within the explore navigation graph. Observe this to coordinate * navigation to a specific [Music] item. */ - val exploreNavigationItem: StateFlow + val exploreNavigationItem: Event get() = _exploreNavigationItem - private val _exploreArtistNavigationItem = MutableStateFlow(null) + private val _exploreArtistNavigationItem = MutableEvent() /** * Variation of [exploreNavigationItem] for situations where the choice of parent [Artist] to * navigate to is ambiguous. Only intended for use by MainFragment, as the resolved choice will * eventually be assigned to [exploreNavigationItem]. */ - val exploreArtistNavigationItem: StateFlow + val exploreArtistNavigationItem: Event get() = _exploreArtistNavigationItem /** @@ -62,21 +62,12 @@ class NavigationViewModel : ViewModel() { * @param action The [MainNavigationAction] to perform. */ fun mainNavigateTo(action: MainNavigationAction) { - if (_mainNavigationAction.value != null) { + if (_mainNavigationAction.flow.value != null) { logD("Already navigating, not doing main action") return } logD("Navigating with action $action") - _mainNavigationAction.value = action - } - - /** - * Mark that the navigation process within the main navigation graph (initiated by - * [mainNavigateTo]) was completed. - */ - fun finishMainNavigation() { - logD("Finishing main navigation process") - _mainNavigationAction.value = null + _mainNavigationAction.put(action) } /** @@ -85,12 +76,12 @@ class NavigationViewModel : ViewModel() { * @param music The [Music] to navigate to. */ fun exploreNavigateTo(music: Music) { - if (_exploreNavigationItem.value != null) { + if (_exploreNavigationItem.flow.value != null) { logD("Already navigating, not doing explore action") return } logD("Navigating to ${music.rawName}") - _exploreNavigationItem.value = music + _exploreNavigationItem.put(music) } /** @@ -114,7 +105,7 @@ class NavigationViewModel : ViewModel() { } private fun exploreNavigateToParentArtistImpl(item: Music, artists: List) { - if (_exploreArtistNavigationItem.value != null) { + if (_exploreArtistNavigationItem.flow.value != null) { logD("Already navigating, not doing explore action") return } @@ -123,19 +114,9 @@ class NavigationViewModel : ViewModel() { exploreNavigateTo(artists[0]) } else { logD("Navigating to a choice of ${artists.map { it.rawName }}") - _exploreArtistNavigationItem.value = item + _exploreArtistNavigationItem.put(item) } } - - /** - * Mark that the navigation process within the explore navigation graph (initiated by - * [exploreNavigateTo]) was completed. - */ - fun finishExploreNavigation() { - logD("Finishing explore navigation process") - _exploreNavigationItem.value = null - _exploreArtistNavigationItem.value = null - } } /** diff --git a/app/src/main/java/org/oxycblt/auxio/util/StateUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/StateUtil.kt index 29a6513d5..c1e1a4a92 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/StateUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/StateUtil.kt @@ -28,17 +28,40 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch +/** + * A wrapper around [StateFlow] exposing a one-time consumable event. + * + * @author Alexander Capehart (OxygenCobalt) + */ interface Event { + /** The inner [StateFlow] contained by the [Event]. */ val flow: StateFlow + /** + * Consume whatever value is currently contained by this instance. + * + * @return A value placed into this instance prior, or null if there isn't any. + */ fun consume(): T? } +/** + * A wrapper around [StateFlow] exposing a one-time consumable event that can be modified by it's + * owner. + * + * @author Alexander Capehart (OxygenCobalt) + */ class MutableEvent : Event { override val flow = MutableStateFlow(null) + override fun consume() = flow.value?.also { flow.value = null } + + /** + * Place a new value into this instance, replacing any prior value. + * + * @param v The value to update with. + */ fun put(v: T) { flow.value = v } - override fun consume() = flow.value?.also { flow.value = null } } /**