ui: use event everywhere
Replace all prior uses of the StateFlow + finish method pattern with the new event class.
This commit is contained in:
parent
f0d62e8176
commit
1df15a32cd
10 changed files with 67 additions and 81 deletions
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 -> {}
|
||||
}
|
||||
|
|
|
@ -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?) {
|
||||
|
|
|
@ -109,7 +109,7 @@ constructor(
|
|||
* flag is true, all tabs (and their respective ViewPager2 fragments) will be re-created from
|
||||
* scratch.
|
||||
*/
|
||||
val shouldRecreate: Event<Unit>
|
||||
val recreateTabs: Event<Unit>
|
||||
get() = _shouldRecreate
|
||||
|
||||
private val _isFastScrolling = MutableStateFlow(false)
|
||||
|
|
|
@ -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<Boolean>
|
||||
get() = _isShuffled
|
||||
|
||||
private val _artistPlaybackPickerSong = MutableStateFlow<Song?>(null)
|
||||
private val _artistPlaybackPickerSong = MutableEvent<Song>()
|
||||
/**
|
||||
* 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<Song?>
|
||||
val artistPickerSong: Event<Song>
|
||||
get() = _artistPlaybackPickerSong
|
||||
|
||||
private val _genrePlaybackPickerSong = MutableStateFlow<Song?>(null)
|
||||
private val _genrePlaybackPickerSong = MutableEvent<Song>()
|
||||
/**
|
||||
* 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<Song?>
|
||||
val genrePickerSong: Event<Song>
|
||||
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].
|
||||
*
|
||||
|
|
|
@ -115,7 +115,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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<MainNavigationAction?>(null)
|
||||
private val _mainNavigationAction = MutableEvent<MainNavigationAction>()
|
||||
/**
|
||||
* Flag for navigation within the main navigation graph. Only intended for use by MainFragment.
|
||||
*/
|
||||
val mainNavigationAction: StateFlow<MainNavigationAction?>
|
||||
val mainNavigationAction: Event<MainNavigationAction>
|
||||
get() = _mainNavigationAction
|
||||
|
||||
private val _exploreNavigationItem = MutableStateFlow<Music?>(null)
|
||||
private val _exploreNavigationItem = MutableEvent<Music>()
|
||||
/**
|
||||
* Flag for navigation within the explore navigation graph. Observe this to coordinate
|
||||
* navigation to a specific [Music] item.
|
||||
*/
|
||||
val exploreNavigationItem: StateFlow<Music?>
|
||||
val exploreNavigationItem: Event<Music>
|
||||
get() = _exploreNavigationItem
|
||||
|
||||
private val _exploreArtistNavigationItem = MutableStateFlow<Music?>(null)
|
||||
private val _exploreArtistNavigationItem = MutableEvent<Music>()
|
||||
/**
|
||||
* 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<Music?>
|
||||
val exploreArtistNavigationItem: Event<Music>
|
||||
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<Artist>) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<T> {
|
||||
/** The inner [StateFlow] contained by the [Event]. */
|
||||
val flow: StateFlow<T?>
|
||||
/**
|
||||
* 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<T> : Event<T> {
|
||||
override val flow = MutableStateFlow<T?>(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 }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue