music: simplify playlist decision events

Simplify the 4 stateflows controlling when playlist decision dialogs
must be opened to just one enum.

This is like the detail view, and makes the amount of observers I have
to spin up much smaller.

Eventually, most of even these observer calls will be collapsed into
the menu itself.
This commit is contained in:
Alexander Capehart 2023-06-27 19:43:15 -06:00
parent 07e9ca8ef6
commit 9b0e39919b
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
9 changed files with 229 additions and 80 deletions

View file

@ -62,8 +62,6 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* high-level navigation features. * high-level navigation features.
* *
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*
* TODO: Break up the god navigation setup going on here
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class MainFragment : class MainFragment :

View file

@ -46,6 +46,7 @@ import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.PlaylistDecision
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.info.Disc import org.oxycblt.auxio.music.info.Disc
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
@ -124,6 +125,7 @@ class AlbumDetailFragment :
collectImmediately(detailModel.albumList, ::updateList) collectImmediately(detailModel.albumList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)
collect(musicModel.playlistDecision.flow, ::handleDecision)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist) collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist)
@ -298,6 +300,36 @@ class AlbumDetailFragment :
} }
} }
private fun updateSelection(selected: List<Music>) {
albumListAdapter.setSelected(selected.toSet())
val binding = requireBinding()
if (selected.isNotEmpty()) {
binding.detailSelectionToolbar.title = getString(R.string.fmt_selected, selected.size)
binding.detailToolbar.setVisible(R.id.detail_selection_toolbar)
} else {
binding.detailToolbar.setVisible(R.id.detail_normal_toolbar)
}
}
private fun handleDecision(decision: PlaylistDecision?) {
when (decision) {
is PlaylistDecision.Add ->{
logD("Adding ${decision.songs.size} songs to a playlist")
findNavController().navigateSafe(
AlbumDetailFragmentDirections.addToPlaylist(
decision.songs.map { it.uid }.toTypedArray())
)
musicModel.playlistDecision.consume()
}
is PlaylistDecision.New, is PlaylistDecision.Rename, is PlaylistDecision.Delete ->
error("Unexpected decision $decision")
null -> {}
}
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
albumListAdapter.setPlaying( albumListAdapter.setPlaying(
song.takeIf { parent == detailModel.currentAlbum.value }, isPlaying) song.takeIf { parent == detailModel.currentAlbum.value }, isPlaying)
@ -352,16 +384,4 @@ class AlbumDetailFragment :
} }
} }
} }
private fun updateSelection(selected: List<Music>) {
albumListAdapter.setSelected(selected.toSet())
val binding = requireBinding()
if (selected.isNotEmpty()) {
binding.detailSelectionToolbar.title = getString(R.string.fmt_selected, selected.size)
binding.detailToolbar.setVisible(R.id.detail_selection_toolbar)
} else {
binding.detailToolbar.setVisible(R.id.detail_normal_toolbar)
}
}
} }

View file

@ -46,6 +46,7 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.PlaylistDecision
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.util.collect import org.oxycblt.auxio.util.collect
@ -124,11 +125,12 @@ class ArtistDetailFragment :
collectImmediately(detailModel.currentArtist, ::updateArtist) collectImmediately(detailModel.currentArtist, ::updateArtist)
collectImmediately(detailModel.artistList, ::updateList) collectImmediately(detailModel.artistList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(selectionModel.selected, ::updateSelection)
collect(musicModel.playlistDecision.flow, ::handleDecision)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist) collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist)
collect(playbackModel.genrePickerSong.flow, ::handlePlayFromGenre) collect(playbackModel.genrePickerSong.flow, ::handlePlayFromGenre)
collectImmediately(selectionModel.selected, ::updateSelection)
} }
override fun onDestroyBinding(binding: FragmentDetailBinding) { override fun onDestroyBinding(binding: FragmentDetailBinding) {
@ -308,6 +310,36 @@ class ArtistDetailFragment :
} }
} }
private fun updateSelection(selected: List<Music>) {
artistListAdapter.setSelected(selected.toSet())
val binding = requireBinding()
if (selected.isNotEmpty()) {
binding.detailSelectionToolbar.title = getString(R.string.fmt_selected, selected.size)
binding.detailToolbar.setVisible(R.id.detail_selection_toolbar)
} else {
binding.detailToolbar.setVisible(R.id.detail_normal_toolbar)
}
}
private fun handleDecision(decision: PlaylistDecision?) {
when (decision) {
is PlaylistDecision.Add ->{
logD("Adding ${decision.songs.size} songs to a playlist")
findNavController().navigateSafe(
ArtistDetailFragmentDirections.addToPlaylist(
decision.songs.map { it.uid }.toTypedArray())
)
musicModel.playlistDecision.consume()
}
is PlaylistDecision.New, is PlaylistDecision.Rename, is PlaylistDecision.Delete ->
error("Unexpected decision $decision")
null -> {}
}
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
val currentArtist = unlikelyToBeNull(detailModel.currentArtist.value) val currentArtist = unlikelyToBeNull(detailModel.currentArtist.value)
val playingItem = val playingItem =
@ -334,16 +366,4 @@ class ArtistDetailFragment :
logD("Launching play from genre dialog for $song") logD("Launching play from genre dialog for $song")
findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromGenre(song.uid)) findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromGenre(song.uid))
} }
private fun updateSelection(selected: List<Music>) {
artistListAdapter.setSelected(selected.toSet())
val binding = requireBinding()
if (selected.isNotEmpty()) {
binding.detailSelectionToolbar.title = getString(R.string.fmt_selected, selected.size)
binding.detailToolbar.setVisible(R.id.detail_selection_toolbar)
} else {
binding.detailToolbar.setVisible(R.id.detail_normal_toolbar)
}
}
} }

View file

@ -46,6 +46,7 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.PlaylistDecision
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.util.collect import org.oxycblt.auxio.util.collect
@ -122,11 +123,12 @@ class GenreDetailFragment :
collectImmediately(detailModel.currentGenre, ::updatePlaylist) collectImmediately(detailModel.currentGenre, ::updatePlaylist)
collectImmediately(detailModel.genreList, ::updateList) collectImmediately(detailModel.genreList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(selectionModel.selected, ::updateSelection)
collect(musicModel.playlistDecision.flow, ::handleDecision)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist) collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist)
collect(playbackModel.genrePickerSong.flow, ::handlePlayFromGenre) collect(playbackModel.genrePickerSong.flow, ::handlePlayFromGenre)
collectImmediately(selectionModel.selected, ::updateSelection)
} }
override fun onDestroyBinding(binding: FragmentDetailBinding) { override fun onDestroyBinding(binding: FragmentDetailBinding) {
@ -295,6 +297,36 @@ class GenreDetailFragment :
} }
} }
private fun updateSelection(selected: List<Music>) {
genreListAdapter.setSelected(selected.toSet())
val binding = requireBinding()
if (selected.isNotEmpty()) {
binding.detailSelectionToolbar.title = getString(R.string.fmt_selected, selected.size)
binding.detailToolbar.setVisible(R.id.detail_selection_toolbar)
} else {
binding.detailToolbar.setVisible(R.id.detail_normal_toolbar)
}
}
private fun handleDecision(decision: PlaylistDecision?) {
when (decision) {
is PlaylistDecision.Add ->{
logD("Adding ${decision.songs.size} songs to a playlist")
findNavController().navigateSafe(
GenreDetailFragmentDirections.addToPlaylist(
decision.songs.map { it.uid }.toTypedArray())
)
musicModel.playlistDecision.consume()
}
is PlaylistDecision.New, is PlaylistDecision.Rename, is PlaylistDecision.Delete ->
error("Unexpected decision $decision")
null -> {}
}
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
val currentGenre = unlikelyToBeNull(detailModel.currentGenre.value) val currentGenre = unlikelyToBeNull(detailModel.currentGenre.value)
val playingItem = val playingItem =
@ -321,16 +353,4 @@ class GenreDetailFragment :
logD("Launching play from genre dialog for $song") logD("Launching play from genre dialog for $song")
findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromGenre(song.uid)) findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromGenre(song.uid))
} }
private fun updateSelection(selected: List<Music>) {
genreListAdapter.setSelected(selected.toSet())
val binding = requireBinding()
if (selected.isNotEmpty()) {
binding.detailSelectionToolbar.title = getString(R.string.fmt_selected, selected.size)
binding.detailToolbar.setVisible(R.id.detail_selection_toolbar)
} else {
binding.detailToolbar.setVisible(R.id.detail_normal_toolbar)
}
}
} }

View file

@ -48,6 +48,7 @@ import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.PlaylistDecision
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.util.collect import org.oxycblt.auxio.util.collect
@ -137,11 +138,12 @@ class PlaylistDetailFragment :
collectImmediately(detailModel.playlistList, ::updateList) collectImmediately(detailModel.playlistList, ::updateList)
collectImmediately(detailModel.editedPlaylist, ::updateEditedList) collectImmediately(detailModel.editedPlaylist, ::updateEditedList)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(selectionModel.selected, ::updateSelection)
collect(musicModel.playlistDecision.flow, ::handleDecision)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist) collect(playbackModel.artistPickerSong.flow, ::handlePlayFromArtist)
collect(playbackModel.genrePickerSong.flow, ::handlePlayFromGenre) collect(playbackModel.genrePickerSong.flow, ::handlePlayFromGenre)
collectImmediately(selectionModel.selected, ::updateSelection)
} }
override fun onStart() { override fun onStart() {
@ -342,6 +344,38 @@ class PlaylistDetailFragment :
} }
} }
private fun updateSelection(selected: List<Music>) {
playlistListAdapter.setSelected(selected.toSet())
val binding = requireBinding()
if (selected.isNotEmpty()) {
binding.detailSelectionToolbar.title = getString(R.string.fmt_selected, selected.size)
}
updateMultiToolbar()
}
private fun handleDecision(decision: PlaylistDecision?) {
if (decision == null) return
when (decision) {
is PlaylistDecision.Rename -> {
logD("Renaming ${decision.playlist}")
findNavController().navigateSafe(
PlaylistDetailFragmentDirections.renamePlaylist(decision.playlist.uid)
)
}
is PlaylistDecision.Delete -> {
logD("Deleting ${decision.playlist}")
findNavController().navigateSafe(
PlaylistDetailFragmentDirections.deletePlaylist(decision.playlist.uid)
)
}
is PlaylistDecision.Add, is PlaylistDecision.New -> error("Unexpected decision $decision")
}
musicModel.playlistDecision.consume()
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
// Prefer songs that are playing from this playlist. // Prefer songs that are playing from this playlist.
playlistListAdapter.setPlaying( playlistListAdapter.setPlaying(
@ -359,17 +393,6 @@ class PlaylistDetailFragment :
logD("Launching play from genre dialog for $song") logD("Launching play from genre dialog for $song")
findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromGenre(song.uid)) findNavController().navigateSafe(AlbumDetailFragmentDirections.playFromGenre(song.uid))
} }
private fun updateSelection(selected: List<Music>) {
playlistListAdapter.setSelected(selected.toSet())
val binding = requireBinding()
if (selected.isNotEmpty()) {
binding.detailSelectionToolbar.title = getString(R.string.fmt_selected, selected.size)
}
updateMultiToolbar()
}
private fun updateMultiToolbar() { private fun updateMultiToolbar() {
val id = val id =
when { when {

View file

@ -130,6 +130,7 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
if (show == null) return if (show == null) return
if (show is Show.SongDetails) { if (show is Show.SongDetails) {
logD("Navigated to this song") logD("Navigated to this song")
detailModel.toShow.consume()
} else { } else {
error("Unexpected show command $show") error("Unexpected show command $show")
} }

View file

@ -65,6 +65,7 @@ import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.NoAudioPermissionException import org.oxycblt.auxio.music.NoAudioPermissionException
import org.oxycblt.auxio.music.NoMusicException import org.oxycblt.auxio.music.NoMusicException
import org.oxycblt.auxio.music.PERMISSION_READ_AUDIO import org.oxycblt.auxio.music.PERMISSION_READ_AUDIO
import org.oxycblt.auxio.music.PlaylistDecision
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.util.collect import org.oxycblt.auxio.util.collect
@ -85,9 +86,9 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
@AndroidEntryPoint @AndroidEntryPoint
class HomeFragment : class HomeFragment :
SelectionFragment<FragmentHomeBinding>(), AppBarLayout.OnOffsetChangedListener { SelectionFragment<FragmentHomeBinding>(), AppBarLayout.OnOffsetChangedListener {
override val playbackModel: PlaybackViewModel by activityViewModels()
override val selectionModel: SelectionViewModel by activityViewModels() override val selectionModel: SelectionViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels()
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private var storagePermissionLauncher: ActivityResultLauncher<String>? = null private var storagePermissionLauncher: ActivityResultLauncher<String>? = null
@ -171,9 +172,10 @@ class HomeFragment :
collect(homeModel.recreateTabs.flow, ::handleRecreate) collect(homeModel.recreateTabs.flow, ::handleRecreate)
collectImmediately(homeModel.currentTabMode, ::updateCurrentTab) collectImmediately(homeModel.currentTabMode, ::updateCurrentTab)
collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab) collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab)
collectImmediately(musicModel.indexingState, ::updateIndexerState)
collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(selectionModel.selected, ::updateSelection) collectImmediately(selectionModel.selected, ::updateSelection)
collectImmediately(musicModel.indexingState, ::updateIndexerState)
collect(musicModel.playlistDecision.flow, ::handleDecision)
collect(detailModel.toShow.flow, ::handleShow)
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@ -479,6 +481,39 @@ class HomeFragment :
} }
} }
private fun handleDecision(decision: PlaylistDecision?) {
if (decision == null) return
when (decision) {
is PlaylistDecision.New -> {
logD("Creating new playlist")
findNavController().navigateSafe(
HomeFragmentDirections.newPlaylist(decision.songs.map { it.uid }.toTypedArray()))
}
is PlaylistDecision.Rename -> {
logD("Renaming ${decision.playlist}")
findNavController().navigateSafe(
HomeFragmentDirections.renamePlaylist(decision.playlist.uid)
)
}
is PlaylistDecision.Delete -> {
logD("Deleting ${decision.playlist}")
findNavController().navigateSafe(
HomeFragmentDirections.deletePlaylist(decision.playlist.uid)
)
}
is PlaylistDecision.Add -> {
logD("Adding ${decision.songs.size} to a playlist")
findNavController().navigateSafe(
HomeFragmentDirections.addToPlaylist(decision.songs.map { it.uid }.toTypedArray())
)
}
}
musicModel.playlistDecision.consume()
}
private fun updateFab(songs: List<Song>, isFastScrolling: Boolean) { private fun updateFab(songs: List<Song>, isFastScrolling: Boolean) {
val binding = requireBinding() val binding = requireBinding()
// If there are no songs, it's likely that the library has not been loaded, so // If there are no songs, it's likely that the library has not been loaded, so

View file

@ -52,23 +52,8 @@ constructor(
val statistics: StateFlow<Statistics?> val statistics: StateFlow<Statistics?>
get() = _statistics get() = _statistics
private val _newPlaylistSongs = MutableEvent<List<Song>>() private val _playlistDecision = MutableEvent<PlaylistDecision>()
/** Flag for opening a dialog to create a playlist of the given [Song]s. */ val playlistDecision: Event<PlaylistDecision> get() = _playlistDecision
val newPlaylistSongs: Event<List<Song>> = _newPlaylistSongs
private val _playlistToRename = MutableEvent<Playlist?>()
/** Flag for opening a dialog to rename the given [Playlist]. */
val playlistToRename: Event<Playlist?>
get() = _playlistToRename
private val _playlistToDelete = MutableEvent<Playlist>()
/** Flag for opening a dialog to confirm deletion of the given [Playlist]. */
val playlistToDelete: Event<Playlist>
get() = _playlistToDelete
private val _songsToAdd = MutableEvent<List<Song>>()
/** Flag for opening a dialog to add the given [Song]s to a playlist. */
val songsToAdd: Event<List<Song>> = _songsToAdd
init { init {
musicRepository.addUpdateListener(this) musicRepository.addUpdateListener(this)
@ -121,7 +106,7 @@ constructor(
viewModelScope.launch(Dispatchers.IO) { musicRepository.createPlaylist(name, songs) } viewModelScope.launch(Dispatchers.IO) { musicRepository.createPlaylist(name, songs) }
} else { } else {
logD("Launching creation dialog for ${songs.size} songs") logD("Launching creation dialog for ${songs.size} songs")
_newPlaylistSongs.put(songs) _playlistDecision.put(PlaylistDecision.New(songs))
} }
} }
@ -137,7 +122,7 @@ constructor(
viewModelScope.launch(Dispatchers.IO) { musicRepository.renamePlaylist(playlist, name) } viewModelScope.launch(Dispatchers.IO) { musicRepository.renamePlaylist(playlist, name) }
} else { } else {
logD("Launching rename dialog for $playlist") logD("Launching rename dialog for $playlist")
_playlistToRename.put(playlist) _playlistDecision.put(PlaylistDecision.Rename(playlist))
} }
} }
@ -154,7 +139,7 @@ constructor(
viewModelScope.launch(Dispatchers.IO) { musicRepository.deletePlaylist(playlist) } viewModelScope.launch(Dispatchers.IO) { musicRepository.deletePlaylist(playlist) }
} else { } else {
logD("Launching deletion dialog for $playlist") logD("Launching deletion dialog for $playlist")
_playlistToDelete.put(playlist) _playlistDecision.put(PlaylistDecision.Delete(playlist))
} }
} }
@ -214,7 +199,7 @@ constructor(
viewModelScope.launch(Dispatchers.IO) { musicRepository.addToPlaylist(songs, playlist) } viewModelScope.launch(Dispatchers.IO) { musicRepository.addToPlaylist(songs, playlist) }
} else { } else {
logD("Launching addition dialog for songs=${songs.size}") logD("Launching addition dialog for songs=${songs.size}")
_songsToAdd.put(songs) _playlistDecision.put(PlaylistDecision.Add(songs))
} }
} }
@ -235,3 +220,10 @@ constructor(
val durationMs: Long val durationMs: Long
) )
} }
sealed interface PlaylistDecision {
data class New(val songs: List<Song>) : PlaylistDecision
data class Rename(val playlist: Playlist) : PlaylistDecision
data class Delete(val playlist: Playlist) : PlaylistDecision
data class Add(val songs: List<Song>) : PlaylistDecision
}

View file

@ -34,8 +34,10 @@ import com.google.android.material.transition.MaterialSharedAxis
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentSearchBinding import org.oxycblt.auxio.databinding.FragmentSearchBinding
import org.oxycblt.auxio.detail.ArtistDetailFragmentDirections
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.detail.Show import org.oxycblt.auxio.detail.Show
import org.oxycblt.auxio.home.HomeFragmentDirections
import org.oxycblt.auxio.list.Divider import org.oxycblt.auxio.list.Divider
import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.Item
@ -48,6 +50,7 @@ import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.PlaylistDecision
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.util.collect import org.oxycblt.auxio.util.collect
@ -136,10 +139,11 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
collectImmediately(searchModel.searchResults, ::updateSearchResults) collectImmediately(searchModel.searchResults, ::updateSearchResults)
collectImmediately(selectionModel.selected, ::updateSelection)
collect(musicModel.playlistDecision.flow, ::handleDecision)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collectImmediately(selectionModel.selected, ::updateSelection)
} }
override fun onDestroyBinding(binding: FragmentSearchBinding) { override fun onDestroyBinding(binding: FragmentSearchBinding) {
@ -200,9 +204,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
} }
} }
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
searchAdapter.setPlaying(parent ?: song, isPlaying)
}
private fun handleShow(show: Show?) { private fun handleShow(show: Show?) {
when (show) { when (show) {
@ -257,6 +259,44 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
hideKeyboard() hideKeyboard()
} }
private fun handleDecision(decision: PlaylistDecision?) {
if (decision == null) return
when (decision) {
is PlaylistDecision.New -> {
logD("Creating new playlist")
findNavController().navigateSafe(
HomeFragmentDirections.newPlaylist(decision.songs.map { it.uid }.toTypedArray()))
}
is PlaylistDecision.Rename -> {
logD("Renaming ${decision.playlist}")
findNavController().navigateSafe(
HomeFragmentDirections.renamePlaylist(decision.playlist.uid)
)
}
is PlaylistDecision.Delete -> {
logD("Deleting ${decision.playlist}")
findNavController().navigateSafe(
SearchFragmentDirections.deletePlaylist(decision.playlist.uid)
)
}
is PlaylistDecision.Add -> {
logD("Adding ${decision.songs.size} to a playlist")
findNavController().navigateSafe(
HomeFragmentDirections.addToPlaylist(decision.songs.map { it.uid }.toTypedArray())
)
}
}
musicModel.playlistDecision.consume()
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
searchAdapter.setPlaying(parent ?: song, isPlaying)
}
private fun updateSelection(selected: List<Music>) { private fun updateSelection(selected: List<Music>) {
searchAdapter.setSelected(selected.toSet()) searchAdapter.setSelected(selected.toSet())
val binding = requireBinding() val binding = requireBinding()