Skip to content

Commit

Permalink
music: add more playlist messages
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
OxygenCobalt committed Dec 24, 2023
1 parent 2197034 commit c5a3f72
Show file tree
Hide file tree
Showing 13 changed files with 128 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

/**
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

/**
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 =
Expand Down
13 changes: 11 additions & 2 deletions app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

/**
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -259,7 +262,7 @@ class GenreDetailFragment :
}
}

private fun handleDecision(decision: PlaylistDecision?) {
private fun handlePlaylistDecision(decision: PlaylistDecision?) {
if (decision == null) return
val directions =
when (decision) {
Expand All @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

/**
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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(
Expand Down
21 changes: 6 additions & 15 deletions app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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<Song>, isFastScrolling: Boolean) {
Expand Down
93 changes: 69 additions & 24 deletions app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -65,19 +66,9 @@ constructor(
val playlistDecision: Event<PlaylistDecision>
get() = _playlistDecision

private val _playlistError = MutableEvent<PlaylistError>()
val playlistError: Event<PlaylistError>
get() = _playlistError

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
private val _playlistMessage = MutableEvent<PlaylistMessage>()
val playlistMessage: Event<PlaylistMessage>
get() = _playlistMessage

init {
musicRepository.addUpdateListener(this)
Expand Down Expand Up @@ -127,7 +118,10 @@ constructor(
fun createPlaylist(name: String? = null, songs: List<Song> = 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))
Expand All @@ -148,22 +142,24 @@ constructor(
viewModelScope.launch(Dispatchers.IO) {
val importedPlaylist = externalPlaylistManager.import(uri)
if (importedPlaylist == null) {
_playlistError.put(PlaylistError.ImportFailed)
_playlistMessage.put(PlaylistMessage.ImportFailed)
return@launch
}

val deviceLibrary = musicRepository.deviceLibrary ?: return@launch
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)
}
}
Expand All @@ -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 {
Expand All @@ -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))
Expand All @@ -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))
Expand Down Expand Up @@ -279,7 +283,10 @@ constructor(
fun addToPlaylist(songs: List<Song>, 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))
Expand Down Expand Up @@ -354,8 +361,46 @@ sealed interface PlaylistDecision {
data class Add(val songs: List<Song>) : PlaylistDecision
}

sealed interface PlaylistError {
data object ImportFailed : PlaylistError
sealed interface PlaylistMessage {
val stringRes: Int

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 ExportFailed : PlaylistError
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()
}

Expand Down
Loading

0 comments on commit c5a3f72

Please sign in to comment.