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.
This commit is contained in:
parent
21970349cc
commit
c5a3f72b99
13 changed files with 128 additions and 54 deletions
|
@ -44,6 +44,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.PlaylistDecision
|
import org.oxycblt.auxio.music.PlaylistDecision
|
||||||
|
import org.oxycblt.auxio.music.PlaylistMessage
|
||||||
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.PlaybackDecision
|
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.navigateSafe
|
||||||
import org.oxycblt.auxio.util.overrideOnOverflowMenuClick
|
import org.oxycblt.auxio.util.overrideOnOverflowMenuClick
|
||||||
import org.oxycblt.auxio.util.setFullWidthLookup
|
import org.oxycblt.auxio.util.setFullWidthLookup
|
||||||
|
import org.oxycblt.auxio.util.showToast
|
||||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,6 +128,7 @@ class AlbumDetailFragment :
|
||||||
collect(listModel.menu.flow, ::handleMenu)
|
collect(listModel.menu.flow, ::handleMenu)
|
||||||
collectImmediately(listModel.selected, ::updateSelection)
|
collectImmediately(listModel.selected, ::updateSelection)
|
||||||
collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision)
|
collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision)
|
||||||
|
collect(musicModel.playlistMessage.flow, ::handlePlaylistMessage)
|
||||||
collectImmediately(
|
collectImmediately(
|
||||||
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
||||||
collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision)
|
collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision)
|
||||||
|
@ -281,6 +284,12 @@ class AlbumDetailFragment :
|
||||||
findNavController().navigateSafe(directions)
|
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) {
|
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)
|
||||||
|
|
|
@ -45,6 +45,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.PlaylistDecision
|
import org.oxycblt.auxio.music.PlaylistDecision
|
||||||
|
import org.oxycblt.auxio.music.PlaylistMessage
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.PlaybackDecision
|
import org.oxycblt.auxio.playback.PlaybackDecision
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
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.navigateSafe
|
||||||
import org.oxycblt.auxio.util.overrideOnOverflowMenuClick
|
import org.oxycblt.auxio.util.overrideOnOverflowMenuClick
|
||||||
import org.oxycblt.auxio.util.setFullWidthLookup
|
import org.oxycblt.auxio.util.setFullWidthLookup
|
||||||
|
import org.oxycblt.auxio.util.showToast
|
||||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -128,6 +130,7 @@ class ArtistDetailFragment :
|
||||||
collect(listModel.menu.flow, ::handleMenu)
|
collect(listModel.menu.flow, ::handleMenu)
|
||||||
collectImmediately(listModel.selected, ::updateSelection)
|
collectImmediately(listModel.selected, ::updateSelection)
|
||||||
collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision)
|
collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision)
|
||||||
|
collect(musicModel.playlistMessage.flow, ::handlePlaylistMessage)
|
||||||
collectImmediately(
|
collectImmediately(
|
||||||
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
||||||
collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision)
|
collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision)
|
||||||
|
@ -284,6 +287,12 @@ class ArtistDetailFragment :
|
||||||
findNavController().navigateSafe(directions)
|
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) {
|
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 =
|
||||||
|
|
|
@ -45,6 +45,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.PlaylistDecision
|
import org.oxycblt.auxio.music.PlaylistDecision
|
||||||
|
import org.oxycblt.auxio.music.PlaylistMessage
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.PlaybackDecision
|
import org.oxycblt.auxio.playback.PlaybackDecision
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
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.navigateSafe
|
||||||
import org.oxycblt.auxio.util.overrideOnOverflowMenuClick
|
import org.oxycblt.auxio.util.overrideOnOverflowMenuClick
|
||||||
import org.oxycblt.auxio.util.setFullWidthLookup
|
import org.oxycblt.auxio.util.setFullWidthLookup
|
||||||
|
import org.oxycblt.auxio.util.showToast
|
||||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -125,7 +127,8 @@ class GenreDetailFragment :
|
||||||
collect(detailModel.toShow.flow, ::handleShow)
|
collect(detailModel.toShow.flow, ::handleShow)
|
||||||
collect(listModel.menu.flow, ::handleMenu)
|
collect(listModel.menu.flow, ::handleMenu)
|
||||||
collectImmediately(listModel.selected, ::updateSelection)
|
collectImmediately(listModel.selected, ::updateSelection)
|
||||||
collect(musicModel.playlistDecision.flow, ::handleDecision)
|
collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision)
|
||||||
|
collect(musicModel.playlistMessage.flow, ::handlePlaylistMessage)
|
||||||
collectImmediately(
|
collectImmediately(
|
||||||
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
||||||
collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision)
|
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
|
if (decision == null) return
|
||||||
val directions =
|
val directions =
|
||||||
when (decision) {
|
when (decision) {
|
||||||
|
@ -277,6 +280,12 @@ class GenreDetailFragment :
|
||||||
findNavController().navigateSafe(directions)
|
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) {
|
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 =
|
||||||
|
|
|
@ -49,6 +49,7 @@ 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.PlaylistDecision
|
||||||
|
import org.oxycblt.auxio.music.PlaylistMessage
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.external.M3U
|
import org.oxycblt.auxio.music.external.M3U
|
||||||
import org.oxycblt.auxio.playback.PlaybackDecision
|
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.navigateSafe
|
||||||
import org.oxycblt.auxio.util.overrideOnOverflowMenuClick
|
import org.oxycblt.auxio.util.overrideOnOverflowMenuClick
|
||||||
import org.oxycblt.auxio.util.setFullWidthLookup
|
import org.oxycblt.auxio.util.setFullWidthLookup
|
||||||
|
import org.oxycblt.auxio.util.showToast
|
||||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -159,7 +161,8 @@ class PlaylistDetailFragment :
|
||||||
collect(detailModel.toShow.flow, ::handleShow)
|
collect(detailModel.toShow.flow, ::handleShow)
|
||||||
collect(listModel.menu.flow, ::handleMenu)
|
collect(listModel.menu.flow, ::handleMenu)
|
||||||
collectImmediately(listModel.selected, ::updateSelection)
|
collectImmediately(listModel.selected, ::updateSelection)
|
||||||
collect(musicModel.playlistDecision.flow, ::handleDecision)
|
collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision)
|
||||||
|
collect(musicModel.playlistMessage.flow, ::handlePlaylistMessage)
|
||||||
collectImmediately(
|
collectImmediately(
|
||||||
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
||||||
collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision)
|
collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision)
|
||||||
|
@ -333,7 +336,7 @@ class PlaylistDetailFragment :
|
||||||
updateMultiToolbar()
|
updateMultiToolbar()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDecision(decision: PlaylistDecision?) {
|
private fun handlePlaylistDecision(decision: PlaylistDecision?) {
|
||||||
if (decision == null) return
|
if (decision == null) return
|
||||||
val directions =
|
val directions =
|
||||||
when (decision) {
|
when (decision) {
|
||||||
|
@ -369,6 +372,12 @@ class PlaylistDetailFragment :
|
||||||
findNavController().navigateSafe(directions)
|
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) {
|
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(
|
||||||
|
|
|
@ -70,7 +70,7 @@ 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.Playlist
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.PlaylistDecision
|
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.Song
|
||||||
import org.oxycblt.auxio.music.external.M3U
|
import org.oxycblt.auxio.music.external.M3U
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
|
@ -211,7 +211,7 @@ class HomeFragment :
|
||||||
collectImmediately(listModel.selected, ::updateSelection)
|
collectImmediately(listModel.selected, ::updateSelection)
|
||||||
collectImmediately(musicModel.indexingState, ::updateIndexerState)
|
collectImmediately(musicModel.indexingState, ::updateIndexerState)
|
||||||
collect(musicModel.playlistDecision.flow, ::handleDecision)
|
collect(musicModel.playlistDecision.flow, ::handleDecision)
|
||||||
collectImmediately(musicModel.playlistError.flow, ::handlePlaylistError)
|
collectImmediately(musicModel.playlistMessage.flow, ::handlePlaylistMessage)
|
||||||
collect(detailModel.toShow.flow, ::handleShow)
|
collect(detailModel.toShow.flow, ::handleShow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,19 +503,10 @@ class HomeFragment :
|
||||||
findNavController().navigateSafe(directions)
|
findNavController().navigateSafe(directions)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePlaylistError(error: PlaylistError?) {
|
private fun handlePlaylistMessage(message: PlaylistMessage?) {
|
||||||
when (error) {
|
if (message == null) return
|
||||||
is PlaylistError.ImportFailed -> {
|
requireContext().showToast(message.stringRes)
|
||||||
requireContext().showToast(R.string.err_import_failed)
|
musicModel.playlistMessage.consume()
|
||||||
musicModel.importError.consume()
|
|
||||||
}
|
|
||||||
is PlaylistError.ExportFailed -> {
|
|
||||||
requireContext().showToast(R.string.err_export_failed)
|
|
||||||
musicModel.importError.consume()
|
|
||||||
}
|
|
||||||
null -> {}
|
|
||||||
}
|
|
||||||
musicModel.playlistError.consume()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFab(songs: List<Song>, isFastScrolling: Boolean) {
|
private fun updateFab(songs: List<Song>, isFastScrolling: Boolean) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.list.ListSettings
|
import org.oxycblt.auxio.list.ListSettings
|
||||||
import org.oxycblt.auxio.music.external.ExportConfig
|
import org.oxycblt.auxio.music.external.ExportConfig
|
||||||
import org.oxycblt.auxio.music.external.ExternalPlaylistManager
|
import org.oxycblt.auxio.music.external.ExternalPlaylistManager
|
||||||
|
@ -65,19 +66,9 @@ constructor(
|
||||||
val playlistDecision: Event<PlaylistDecision>
|
val playlistDecision: Event<PlaylistDecision>
|
||||||
get() = _playlistDecision
|
get() = _playlistDecision
|
||||||
|
|
||||||
private val _playlistError = MutableEvent<PlaylistError>()
|
private val _playlistMessage = MutableEvent<PlaylistMessage>()
|
||||||
val playlistError: Event<PlaylistError>
|
val playlistMessage: Event<PlaylistMessage>
|
||||||
get() = _playlistError
|
get() = _playlistMessage
|
||||||
|
|
||||||
private val _importError = MutableEvent<Unit>()
|
|
||||||
/** Flag for when playlist importing failed. Consume this and show an error if active. */
|
|
||||||
val importError: Event<Unit>
|
|
||||||
get() = _importError
|
|
||||||
|
|
||||||
private val _exportError = MutableEvent<Unit>()
|
|
||||||
/** Flag for when playlist exporting failed. Consume this and show an error if active. */
|
|
||||||
val exportError: Event<Unit>
|
|
||||||
get() = _exportError
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
musicRepository.addUpdateListener(this)
|
musicRepository.addUpdateListener(this)
|
||||||
|
@ -127,7 +118,10 @@ constructor(
|
||||||
fun createPlaylist(name: String? = null, songs: List<Song> = listOf()) {
|
fun createPlaylist(name: String? = null, songs: List<Song> = listOf()) {
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
logD("Creating $name with ${songs.size} songs]")
|
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 {
|
} else {
|
||||||
logD("Launching creation dialog for ${songs.size} songs")
|
logD("Launching creation dialog for ${songs.size} songs")
|
||||||
_playlistDecision.put(PlaylistDecision.New(songs))
|
_playlistDecision.put(PlaylistDecision.New(songs))
|
||||||
|
@ -148,7 +142,7 @@ constructor(
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val importedPlaylist = externalPlaylistManager.import(uri)
|
val importedPlaylist = externalPlaylistManager.import(uri)
|
||||||
if (importedPlaylist == null) {
|
if (importedPlaylist == null) {
|
||||||
_playlistError.put(PlaylistError.ImportFailed)
|
_playlistMessage.put(PlaylistMessage.ImportFailed)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,14 +150,16 @@ constructor(
|
||||||
val songs = importedPlaylist.paths.mapNotNull(deviceLibrary::findSongByPath)
|
val songs = importedPlaylist.paths.mapNotNull(deviceLibrary::findSongByPath)
|
||||||
|
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
_playlistError.put(PlaylistError.ImportFailed)
|
_playlistMessage.put(PlaylistMessage.ImportFailed)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
// TODO Require the user to name it something else if the name is a duplicate of
|
// TODO Require the user to name it something else if the name is a duplicate of
|
||||||
// a prior playlist
|
// a prior playlist
|
||||||
if (target !== null) {
|
if (target !== null) {
|
||||||
musicRepository.rewritePlaylist(target, songs)
|
musicRepository.rewritePlaylist(target, songs)
|
||||||
|
_playlistMessage.put(PlaylistMessage.ImportSuccess)
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: Have to properly propagate the "Playlist Created" message
|
||||||
createPlaylist(importedPlaylist.name, songs)
|
createPlaylist(importedPlaylist.name, songs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,8 +179,10 @@ constructor(
|
||||||
if (uri != null && config != null) {
|
if (uri != null && config != null) {
|
||||||
logD("Exporting playlist to $uri")
|
logD("Exporting playlist to $uri")
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
if (!externalPlaylistManager.export(playlist, uri, config)) {
|
if (externalPlaylistManager.export(playlist, uri, config)) {
|
||||||
_playlistError.put(PlaylistError.ExportFailed)
|
_playlistMessage.put(PlaylistMessage.ExportSuccess)
|
||||||
|
} else {
|
||||||
|
_playlistMessage.put(PlaylistMessage.ExportFailed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -202,7 +200,10 @@ constructor(
|
||||||
fun renamePlaylist(playlist: Playlist, name: String? = null) {
|
fun renamePlaylist(playlist: Playlist, name: String? = null) {
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
logD("Renaming $playlist to $name")
|
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 {
|
} else {
|
||||||
logD("Launching rename dialog for $playlist")
|
logD("Launching rename dialog for $playlist")
|
||||||
_playlistDecision.put(PlaylistDecision.Rename(playlist))
|
_playlistDecision.put(PlaylistDecision.Rename(playlist))
|
||||||
|
@ -219,7 +220,10 @@ constructor(
|
||||||
fun deletePlaylist(playlist: Playlist, rude: Boolean = false) {
|
fun deletePlaylist(playlist: Playlist, rude: Boolean = false) {
|
||||||
if (rude) {
|
if (rude) {
|
||||||
logD("Deleting $playlist")
|
logD("Deleting $playlist")
|
||||||
viewModelScope.launch(Dispatchers.IO) { musicRepository.deletePlaylist(playlist) }
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
musicRepository.deletePlaylist(playlist)
|
||||||
|
_playlistMessage.put(PlaylistMessage.DeleteSuccess)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logD("Launching deletion dialog for $playlist")
|
logD("Launching deletion dialog for $playlist")
|
||||||
_playlistDecision.put(PlaylistDecision.Delete(playlist))
|
_playlistDecision.put(PlaylistDecision.Delete(playlist))
|
||||||
|
@ -279,7 +283,10 @@ constructor(
|
||||||
fun addToPlaylist(songs: List<Song>, playlist: Playlist? = null) {
|
fun addToPlaylist(songs: List<Song>, playlist: Playlist? = null) {
|
||||||
if (playlist != null) {
|
if (playlist != null) {
|
||||||
logD("Adding ${songs.size} songs to $playlist")
|
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 {
|
} else {
|
||||||
logD("Launching addition dialog for songs=${songs.size}")
|
logD("Launching addition dialog for songs=${songs.size}")
|
||||||
_playlistDecision.put(PlaylistDecision.Add(songs))
|
_playlistDecision.put(PlaylistDecision.Add(songs))
|
||||||
|
@ -354,8 +361,46 @@ sealed interface PlaylistDecision {
|
||||||
data class Add(val songs: List<Song>) : PlaylistDecision
|
data class Add(val songs: List<Song>) : PlaylistDecision
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface PlaylistError {
|
sealed interface PlaylistMessage {
|
||||||
data object ImportFailed : PlaylistError
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.navigateSafe
|
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.
|
* 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) {
|
override fun onClick(item: PlaylistChoice, viewHolder: RecyclerView.ViewHolder) {
|
||||||
musicModel.addToPlaylist(pickerModel.currentSongsToAdd.value ?: return, item.playlist)
|
musicModel.addToPlaylist(pickerModel.currentSongsToAdd.value ?: return, item.playlist)
|
||||||
requireContext().showToast(R.string.lng_playlist_added)
|
|
||||||
findNavController().navigateUp()
|
findNavController().navigateUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,6 @@ import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
|
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.showToast
|
|
||||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,7 +55,6 @@ class DeletePlaylistDialog : ViewBindingMaterialDialogFragment<DialogDeletePlayl
|
||||||
// Now we can delete the playlist for-real this time.
|
// Now we can delete the playlist for-real this time.
|
||||||
musicModel.deletePlaylist(
|
musicModel.deletePlaylist(
|
||||||
unlikelyToBeNull(pickerModel.currentPlaylistToDelete.value), rude = true)
|
unlikelyToBeNull(pickerModel.currentPlaylistToDelete.value), rude = true)
|
||||||
requireContext().showToast(R.string.lng_playlist_deleted)
|
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.lbl_cancel, null)
|
.setNegativeButton(R.string.lbl_cancel, null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,6 @@ class ExportPlaylistDialog : ViewBindingMaterialDialogFragment<DialogPlaylistExp
|
||||||
|
|
||||||
binding.exportWindowsPaths.setOnClickListener { _ ->
|
binding.exportWindowsPaths.setOnClickListener { _ ->
|
||||||
val current = pickerModel.currentExportConfig.value
|
val current = pickerModel.currentExportConfig.value
|
||||||
logD("change")
|
|
||||||
pickerModel.setExportConfig(current.copy(windowsPaths = !current.windowsPaths))
|
pickerModel.setExportConfig(current.copy(windowsPaths = !current.windowsPaths))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,6 @@ import org.oxycblt.auxio.music.MusicViewModel
|
||||||
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
|
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.showToast
|
|
||||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,7 +61,6 @@ class NewPlaylistDialog : ViewBindingMaterialDialogFragment<DialogPlaylistNameBi
|
||||||
}
|
}
|
||||||
// TODO: Navigate to playlist if there are songs in it
|
// TODO: Navigate to playlist if there are songs in it
|
||||||
musicModel.createPlaylist(name, pendingPlaylist.songs)
|
musicModel.createPlaylist(name, pendingPlaylist.songs)
|
||||||
requireContext().showToast(R.string.lng_playlist_created)
|
|
||||||
findNavController().apply {
|
findNavController().apply {
|
||||||
navigateUp()
|
navigateUp()
|
||||||
// Do an additional navigation away from the playlist addition dialog, if
|
// Do an additional navigation away from the playlist addition dialog, if
|
||||||
|
|
|
@ -34,7 +34,6 @@ import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
|
import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment
|
||||||
import org.oxycblt.auxio.util.collectImmediately
|
import org.oxycblt.auxio.util.collectImmediately
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.showToast
|
|
||||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,7 +57,6 @@ class RenamePlaylistDialog : ViewBindingMaterialDialogFragment<DialogPlaylistNam
|
||||||
val playlist = unlikelyToBeNull(pickerModel.currentPlaylistToRename.value)
|
val playlist = unlikelyToBeNull(pickerModel.currentPlaylistToRename.value)
|
||||||
val chosenName = pickerModel.chosenName.value as ChosenName.Valid
|
val chosenName = pickerModel.chosenName.value as ChosenName.Valid
|
||||||
musicModel.renamePlaylist(playlist, chosenName.value)
|
musicModel.renamePlaylist(playlist, chosenName.value)
|
||||||
requireContext().showToast(R.string.lng_playlist_renamed)
|
|
||||||
findNavController().navigateUp()
|
findNavController().navigateUp()
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.lbl_cancel, null)
|
.setNegativeButton(R.string.lbl_cancel, null)
|
||||||
|
|
|
@ -52,6 +52,7 @@ 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.PlaylistDecision
|
||||||
|
import org.oxycblt.auxio.music.PlaylistMessage
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.external.M3U
|
import org.oxycblt.auxio.music.external.M3U
|
||||||
import org.oxycblt.auxio.playback.PlaybackDecision
|
import org.oxycblt.auxio.playback.PlaybackDecision
|
||||||
|
@ -64,6 +65,7 @@ import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
import org.oxycblt.auxio.util.navigateSafe
|
import org.oxycblt.auxio.util.navigateSafe
|
||||||
import org.oxycblt.auxio.util.setFullWidthLookup
|
import org.oxycblt.auxio.util.setFullWidthLookup
|
||||||
|
import org.oxycblt.auxio.util.showToast
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [ListFragment] providing search functionality for the music library.
|
* The [ListFragment] providing search functionality for the music library.
|
||||||
|
@ -160,7 +162,8 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
||||||
collectImmediately(searchModel.searchResults, ::updateSearchResults)
|
collectImmediately(searchModel.searchResults, ::updateSearchResults)
|
||||||
collectImmediately(listModel.selected, ::updateSelection)
|
collectImmediately(listModel.selected, ::updateSelection)
|
||||||
collect(listModel.menu.flow, ::handleMenu)
|
collect(listModel.menu.flow, ::handleMenu)
|
||||||
collect(musicModel.playlistDecision.flow, ::handleDecision)
|
collect(musicModel.playlistDecision.flow, ::handlePlaylistDecision)
|
||||||
|
collect(musicModel.playlistMessage.flow, ::handlePlaylistMessage)
|
||||||
collectImmediately(
|
collectImmediately(
|
||||||
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
||||||
collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision)
|
collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision)
|
||||||
|
@ -302,7 +305,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDecision(decision: PlaylistDecision?) {
|
private fun handlePlaylistDecision(decision: PlaylistDecision?) {
|
||||||
if (decision == null) return
|
if (decision == null) return
|
||||||
val directions =
|
val directions =
|
||||||
when (decision) {
|
when (decision) {
|
||||||
|
@ -340,6 +343,12 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
|
||||||
findNavController().navigateSafe(directions)
|
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) {
|
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
|
||||||
searchAdapter.setPlaying(parent ?: song, isPlaying)
|
searchAdapter.setPlaying(parent ?: song, isPlaying)
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,7 +196,9 @@
|
||||||
<string name="lng_observing">Monitoring your music library for changes…</string>
|
<string name="lng_observing">Monitoring your music library for changes…</string>
|
||||||
<string name="lng_queue_added">Added to queue</string>
|
<string name="lng_queue_added">Added to queue</string>
|
||||||
<string name="lng_playlist_created">Playlist created</string>
|
<string name="lng_playlist_created">Playlist created</string>
|
||||||
|
<string name="lng_playlist_imported">Playlist imported</string>
|
||||||
<string name="lng_playlist_renamed">Playlist renamed</string>
|
<string name="lng_playlist_renamed">Playlist renamed</string>
|
||||||
|
<string name="lng_playlist_exported">Playlist exported</string>
|
||||||
<string name="lng_playlist_deleted">Playlist deleted</string>
|
<string name="lng_playlist_deleted">Playlist deleted</string>
|
||||||
<string name="lng_playlist_added">Added to playlist</string>
|
<string name="lng_playlist_added">Added to playlist</string>
|
||||||
<string name="lng_author">Developed by Alexander Capehart</string>
|
<string name="lng_author">Developed by Alexander Capehart</string>
|
||||||
|
|
Loading…
Reference in a new issue