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:
parent
07e9ca8ef6
commit
9b0e39919b
9 changed files with 229 additions and 80 deletions
|
@ -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 :
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue