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