From c5a3f72b9935097b902419633a57d1ae4120c271 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Sat, 23 Dec 2023 22:00:38 -0700 Subject: [PATCH] music: add more playlist messages Add more types of playlist messages corresponding to other actions, so they can be indicated in the UI only when the process is complete. This is somewhat incomplete. It does not include indicating errors for other playlist operations (Which I want to do), and neither does it handle situations in which some playlist operations and up reducing to others (i.e import -> create). I need to do that later. --- .../auxio/detail/AlbumDetailFragment.kt | 9 ++ .../auxio/detail/ArtistDetailFragment.kt | 9 ++ .../auxio/detail/GenreDetailFragment.kt | 13 ++- .../auxio/detail/PlaylistDetailFragment.kt | 13 ++- .../org/oxycblt/auxio/home/HomeFragment.kt | 21 ++--- .../org/oxycblt/auxio/music/MusicViewModel.kt | 93 ++++++++++++++----- .../music/decision/AddToPlaylistDialog.kt | 2 - .../music/decision/DeletePlaylistDialog.kt | 2 - .../music/decision/ExportPlaylistDialog.kt | 1 - .../auxio/music/decision/NewPlaylistDialog.kt | 2 - .../music/decision/RenamePlaylistDialog.kt | 2 - .../oxycblt/auxio/search/SearchFragment.kt | 13 ++- app/src/main/res/values/strings.xml | 2 + 13 files changed, 128 insertions(+), 54 deletions(-) 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 b2bef79d1..e041909cf 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -44,6 +44,7 @@ import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.PlaylistDecision +import org.oxycblt.auxio.music.PlaylistMessage import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.info.Disc import org.oxycblt.auxio.playback.PlaybackDecision @@ -55,6 +56,7 @@ import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.navigateSafe import org.oxycblt.auxio.util.overrideOnOverflowMenuClick import org.oxycblt.auxio.util.setFullWidthLookup +import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -126,6 +128,7 @@ class AlbumDetailFragment : collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision) + collect(musicModel.playlistMessage.flow, ::handlePlaylistMessage) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision) @@ -281,6 +284,12 @@ class AlbumDetailFragment : findNavController().navigateSafe(directions) } + private fun handlePlaylistMessage(message: PlaylistMessage?) { + if (message == null) return + requireContext().showToast(message.stringRes) + musicModel.playlistMessage.consume() + } + private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { albumListAdapter.setPlaying( song.takeIf { parent == detailModel.currentAlbum.value }, isPlaying) 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 f45fa5d34..bdd5b04af 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -45,6 +45,7 @@ import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.PlaylistDecision +import org.oxycblt.auxio.music.PlaylistMessage import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackDecision import org.oxycblt.auxio.playback.PlaybackViewModel @@ -54,6 +55,7 @@ import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.navigateSafe import org.oxycblt.auxio.util.overrideOnOverflowMenuClick import org.oxycblt.auxio.util.setFullWidthLookup +import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -128,6 +130,7 @@ class ArtistDetailFragment : collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision) + collect(musicModel.playlistMessage.flow, ::handlePlaylistMessage) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision) @@ -284,6 +287,12 @@ class ArtistDetailFragment : findNavController().navigateSafe(directions) } + private fun handlePlaylistMessage(message: PlaylistMessage?) { + if (message == null) return + requireContext().showToast(message.stringRes) + musicModel.playlistMessage.consume() + } + private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { val currentArtist = unlikelyToBeNull(detailModel.currentArtist.value) val playingItem = 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 b5f5550fd..79ad38f23 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -45,6 +45,7 @@ import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.PlaylistDecision +import org.oxycblt.auxio.music.PlaylistMessage import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackDecision import org.oxycblt.auxio.playback.PlaybackViewModel @@ -54,6 +55,7 @@ import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.navigateSafe import org.oxycblt.auxio.util.overrideOnOverflowMenuClick import org.oxycblt.auxio.util.setFullWidthLookup +import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -125,7 +127,8 @@ class GenreDetailFragment : collect(detailModel.toShow.flow, ::handleShow) collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) - collect(musicModel.playlistDecision.flow, ::handleDecision) + collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision) + collect(musicModel.playlistMessage.flow, ::handlePlaylistMessage) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision) @@ -259,7 +262,7 @@ class GenreDetailFragment : } } - private fun handleDecision(decision: PlaylistDecision?) { + private fun handlePlaylistDecision(decision: PlaylistDecision?) { if (decision == null) return val directions = when (decision) { @@ -277,6 +280,12 @@ class GenreDetailFragment : findNavController().navigateSafe(directions) } + private fun handlePlaylistMessage(message: PlaylistMessage?) { + if (message == null) return + requireContext().showToast(message.stringRes) + musicModel.playlistMessage.consume() + } + private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { val currentGenre = unlikelyToBeNull(detailModel.currentGenre.value) val playingItem = diff --git a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt index aa9039881..40a03c6e8 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt @@ -49,6 +49,7 @@ import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.PlaylistDecision +import org.oxycblt.auxio.music.PlaylistMessage import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.external.M3U import org.oxycblt.auxio.playback.PlaybackDecision @@ -61,6 +62,7 @@ import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.navigateSafe import org.oxycblt.auxio.util.overrideOnOverflowMenuClick import org.oxycblt.auxio.util.setFullWidthLookup +import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -159,7 +161,8 @@ class PlaylistDetailFragment : collect(detailModel.toShow.flow, ::handleShow) collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) - collect(musicModel.playlistDecision.flow, ::handleDecision) + collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision) + collect(musicModel.playlistMessage.flow, ::handlePlaylistMessage) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision) @@ -333,7 +336,7 @@ class PlaylistDetailFragment : updateMultiToolbar() } - private fun handleDecision(decision: PlaylistDecision?) { + private fun handlePlaylistDecision(decision: PlaylistDecision?) { if (decision == null) return val directions = when (decision) { @@ -369,6 +372,12 @@ class PlaylistDetailFragment : findNavController().navigateSafe(directions) } + private fun handlePlaylistMessage(message: PlaylistMessage?) { + if (message == null) return + requireContext().showToast(message.stringRes) + musicModel.playlistMessage.consume() + } + private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { // Prefer songs that are playing from this playlist. playlistListAdapter.setPlaying( 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 cb88c6241..56047aa1d 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -70,7 +70,7 @@ import org.oxycblt.auxio.music.NoMusicException import org.oxycblt.auxio.music.PERMISSION_READ_AUDIO import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.PlaylistDecision -import org.oxycblt.auxio.music.PlaylistError +import org.oxycblt.auxio.music.PlaylistMessage import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.external.M3U import org.oxycblt.auxio.playback.PlaybackViewModel @@ -211,7 +211,7 @@ class HomeFragment : collectImmediately(listModel.selected, ::updateSelection) collectImmediately(musicModel.indexingState, ::updateIndexerState) collect(musicModel.playlistDecision.flow, ::handleDecision) - collectImmediately(musicModel.playlistError.flow, ::handlePlaylistError) + collectImmediately(musicModel.playlistMessage.flow, ::handlePlaylistMessage) collect(detailModel.toShow.flow, ::handleShow) } @@ -503,19 +503,10 @@ class HomeFragment : findNavController().navigateSafe(directions) } - private fun handlePlaylistError(error: PlaylistError?) { - when (error) { - is PlaylistError.ImportFailed -> { - requireContext().showToast(R.string.err_import_failed) - musicModel.importError.consume() - } - is PlaylistError.ExportFailed -> { - requireContext().showToast(R.string.err_export_failed) - musicModel.importError.consume() - } - null -> {} - } - musicModel.playlistError.consume() + private fun handlePlaylistMessage(message: PlaylistMessage?) { + if (message == null) return + requireContext().showToast(message.stringRes) + musicModel.playlistMessage.consume() } private fun updateFab(songs: List, isFastScrolling: Boolean) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt index c440d6e9c..175959225 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch +import org.oxycblt.auxio.R import org.oxycblt.auxio.list.ListSettings import org.oxycblt.auxio.music.external.ExportConfig import org.oxycblt.auxio.music.external.ExternalPlaylistManager @@ -65,19 +66,9 @@ constructor( val playlistDecision: Event get() = _playlistDecision - private val _playlistError = MutableEvent() - val playlistError: Event - get() = _playlistError - - private val _importError = MutableEvent() - /** Flag for when playlist importing failed. Consume this and show an error if active. */ - val importError: Event - get() = _importError - - private val _exportError = MutableEvent() - /** Flag for when playlist exporting failed. Consume this and show an error if active. */ - val exportError: Event - get() = _exportError + private val _playlistMessage = MutableEvent() + val playlistMessage: Event + get() = _playlistMessage init { musicRepository.addUpdateListener(this) @@ -127,7 +118,10 @@ constructor( fun createPlaylist(name: String? = null, songs: List = listOf()) { if (name != null) { logD("Creating $name with ${songs.size} songs]") - viewModelScope.launch(Dispatchers.IO) { musicRepository.createPlaylist(name, songs) } + viewModelScope.launch(Dispatchers.IO) { + musicRepository.createPlaylist(name, songs) + _playlistMessage.put(PlaylistMessage.NewPlaylistSuccess) + } } else { logD("Launching creation dialog for ${songs.size} songs") _playlistDecision.put(PlaylistDecision.New(songs)) @@ -148,7 +142,7 @@ constructor( viewModelScope.launch(Dispatchers.IO) { val importedPlaylist = externalPlaylistManager.import(uri) if (importedPlaylist == null) { - _playlistError.put(PlaylistError.ImportFailed) + _playlistMessage.put(PlaylistMessage.ImportFailed) return@launch } @@ -156,14 +150,16 @@ constructor( val songs = importedPlaylist.paths.mapNotNull(deviceLibrary::findSongByPath) if (songs.isEmpty()) { - _playlistError.put(PlaylistError.ImportFailed) + _playlistMessage.put(PlaylistMessage.ImportFailed) return@launch } // TODO Require the user to name it something else if the name is a duplicate of // a prior playlist if (target !== null) { musicRepository.rewritePlaylist(target, songs) + _playlistMessage.put(PlaylistMessage.ImportSuccess) } else { + // TODO: Have to properly propagate the "Playlist Created" message createPlaylist(importedPlaylist.name, songs) } } @@ -183,8 +179,10 @@ constructor( if (uri != null && config != null) { logD("Exporting playlist to $uri") viewModelScope.launch(Dispatchers.IO) { - if (!externalPlaylistManager.export(playlist, uri, config)) { - _playlistError.put(PlaylistError.ExportFailed) + if (externalPlaylistManager.export(playlist, uri, config)) { + _playlistMessage.put(PlaylistMessage.ExportSuccess) + } else { + _playlistMessage.put(PlaylistMessage.ExportFailed) } } } else { @@ -202,7 +200,10 @@ constructor( fun renamePlaylist(playlist: Playlist, name: String? = null) { if (name != null) { logD("Renaming $playlist to $name") - viewModelScope.launch(Dispatchers.IO) { musicRepository.renamePlaylist(playlist, name) } + viewModelScope.launch(Dispatchers.IO) { + musicRepository.renamePlaylist(playlist, name) + _playlistMessage.put(PlaylistMessage.RenameSuccess) + } } else { logD("Launching rename dialog for $playlist") _playlistDecision.put(PlaylistDecision.Rename(playlist)) @@ -219,7 +220,10 @@ constructor( fun deletePlaylist(playlist: Playlist, rude: Boolean = false) { if (rude) { logD("Deleting $playlist") - viewModelScope.launch(Dispatchers.IO) { musicRepository.deletePlaylist(playlist) } + viewModelScope.launch(Dispatchers.IO) { + musicRepository.deletePlaylist(playlist) + _playlistMessage.put(PlaylistMessage.DeleteSuccess) + } } else { logD("Launching deletion dialog for $playlist") _playlistDecision.put(PlaylistDecision.Delete(playlist)) @@ -279,7 +283,10 @@ constructor( fun addToPlaylist(songs: List, playlist: Playlist? = null) { if (playlist != null) { logD("Adding ${songs.size} songs to $playlist") - viewModelScope.launch(Dispatchers.IO) { musicRepository.addToPlaylist(songs, playlist) } + viewModelScope.launch(Dispatchers.IO) { + musicRepository.addToPlaylist(songs, playlist) + _playlistMessage.put(PlaylistMessage.AddSuccess) + } } else { logD("Launching addition dialog for songs=${songs.size}") _playlistDecision.put(PlaylistDecision.Add(songs)) @@ -354,8 +361,46 @@ sealed interface PlaylistDecision { data class Add(val songs: List) : PlaylistDecision } -sealed interface PlaylistError { - data object ImportFailed : PlaylistError +sealed interface PlaylistMessage { + val stringRes: Int - data object ExportFailed : PlaylistError + data object NewPlaylistSuccess : PlaylistMessage { + override val stringRes: Int + get() = R.string.lng_playlist_created + } + + data object ImportSuccess : PlaylistMessage { + override val stringRes: Int + get() = R.string.lng_playlist_imported + } + + data object ImportFailed : PlaylistMessage { + override val stringRes: Int + get() = R.string.err_import_failed + } + + data object RenameSuccess : PlaylistMessage { + override val stringRes: Int + get() = R.string.lng_playlist_renamed + } + + data object DeleteSuccess : PlaylistMessage { + override val stringRes: Int + get() = R.string.lng_playlist_deleted + } + + data object AddSuccess : PlaylistMessage { + override val stringRes: Int + get() = R.string.lng_playlist_added + } + + data object ExportSuccess : PlaylistMessage { + override val stringRes: Int + get() = R.string.lng_playlist_exported + } + + data object ExportFailed : PlaylistMessage { + override val stringRes: Int + get() = R.string.err_export_failed + } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/decision/AddToPlaylistDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/AddToPlaylistDialog.kt index 51dcfcf6c..e84deb420 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/decision/AddToPlaylistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/decision/AddToPlaylistDialog.kt @@ -37,7 +37,6 @@ import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.navigateSafe -import org.oxycblt.auxio.util.showToast /** * A dialog that allows the user to pick a specific playlist to add song(s) to. @@ -86,7 +85,6 @@ class AddToPlaylistDialog : override fun onClick(item: PlaylistChoice, viewHolder: RecyclerView.ViewHolder) { musicModel.addToPlaylist(pickerModel.currentSongsToAdd.value ?: return, item.playlist) - requireContext().showToast(R.string.lng_playlist_added) findNavController().navigateUp() } diff --git a/app/src/main/java/org/oxycblt/auxio/music/decision/DeletePlaylistDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/DeletePlaylistDialog.kt index a1683c6b0..0e97dea4b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/decision/DeletePlaylistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/decision/DeletePlaylistDialog.kt @@ -33,7 +33,6 @@ import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.logD -import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -56,7 +55,6 @@ class DeletePlaylistDialog : ViewBindingMaterialDialogFragment val current = pickerModel.currentExportConfig.value - logD("change") pickerModel.setExportConfig(current.copy(windowsPaths = !current.windowsPaths)) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/decision/NewPlaylistDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/decision/NewPlaylistDialog.kt index 46a2b3d0a..6cefe9730 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/decision/NewPlaylistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/decision/NewPlaylistDialog.kt @@ -33,7 +33,6 @@ import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.logD -import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.unlikelyToBeNull /** @@ -62,7 +61,6 @@ class NewPlaylistDialog : ViewBindingMaterialDialogFragment() { collectImmediately(searchModel.searchResults, ::updateSearchResults) collectImmediately(listModel.selected, ::updateSelection) collect(listModel.menu.flow, ::handleMenu) - collect(musicModel.playlistDecision.flow, ::handleDecision) + collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision) + collect(musicModel.playlistMessage.flow, ::handlePlaylistMessage) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision) @@ -302,7 +305,7 @@ class SearchFragment : ListFragment() { } } - private fun handleDecision(decision: PlaylistDecision?) { + private fun handlePlaylistDecision(decision: PlaylistDecision?) { if (decision == null) return val directions = when (decision) { @@ -340,6 +343,12 @@ class SearchFragment : ListFragment() { findNavController().navigateSafe(directions) } + private fun handlePlaylistMessage(message: PlaylistMessage?) { + if (message == null) return + requireContext().showToast(message.stringRes) + musicModel.playlistMessage.consume() + } + private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) { searchAdapter.setPlaying(parent ?: song, isPlaying) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e77064f41..2474e88de 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -196,7 +196,9 @@ Monitoring your music library for changes… Added to queue Playlist created + Playlist imported Playlist renamed + Playlist exported Playlist deleted Added to playlist Developed by Alexander Capehart