Skip to content

Commit

Permalink
music: allow renaming playlist before import
Browse files Browse the repository at this point in the history
When you import a playlist, Auxio will now always display the
"New Playlist" dialog so you can change whatever name Auxio has picked
for the imported playlist.

This also prevents the creation of two playlists with the same names.
  • Loading branch information
OxygenCobalt committed Jan 1, 2024
1 parent 68584ba commit 9ad11ec
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 13 deletions.
4 changes: 3 additions & 1 deletion app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,9 @@ class HomeFragment :
is PlaylistDecision.New -> {
logD("Creating new playlist")
HomeFragmentDirections.newPlaylist(
decision.songs.map { it.uid }.toTypedArray(), decision.reason)
decision.songs.map { it.uid }.toTypedArray(),
decision.template,
decision.reason)
}
is PlaylistDecision.Import -> {
logD("Importing playlist")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
Expand Down
41 changes: 31 additions & 10 deletions app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,18 @@ constructor(
) : ViewModel(), MusicRepository.UpdateListener, MusicRepository.IndexingListener {

private val _indexingState = MutableStateFlow<IndexingState?>(null)

/** The current music loading state, or null if no loading is going on. */
val indexingState: StateFlow<IndexingState?> = _indexingState

private val _statistics = MutableStateFlow<Statistics?>(null)

/** [Statistics] about the last completed music load. */
val statistics: StateFlow<Statistics?>
get() = _statistics

private val _playlistDecision = MutableEvent<PlaylistDecision>()

/**
* A [PlaylistDecision] command that is awaiting a view capable of responding to it. Null if
* none currently.
Expand Down Expand Up @@ -137,7 +140,7 @@ constructor(
}
} else {
logD("Launching creation dialog for ${songs.size} songs")
_playlistDecision.put(PlaylistDecision.New(songs, reason))
_playlistDecision.put(PlaylistDecision.New(songs, null, reason))
}
}

Expand Down Expand Up @@ -168,14 +171,14 @@ constructor(
_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, PlaylistDecision.New.Reason.IMPORT)
_playlistDecision.put(
PlaylistDecision.New(
songs, importedPlaylist.name, PlaylistDecision.New.Reason.IMPORT))
}
}
} else {
Expand Down Expand Up @@ -211,17 +214,27 @@ constructor(
*
* @param playlist The [Playlist] to rename,
* @param name The new name of the [Playlist]. If null, the user will be prompted for a name.
* @param reason The reason why the playlist is being renamed. For all intensive purposes, you
*/
fun renamePlaylist(playlist: Playlist, name: String? = null) {
fun renamePlaylist(
playlist: Playlist,
name: String? = null,
reason: PlaylistDecision.Rename.Reason = PlaylistDecision.Rename.Reason.ACTION
) {
if (name != null) {
logD("Renaming $playlist to $name")
viewModelScope.launch(Dispatchers.IO) {
musicRepository.renamePlaylist(playlist, name)
_playlistMessage.put(PlaylistMessage.RenameSuccess)
val message =
when (reason) {
PlaylistDecision.Rename.Reason.ACTION -> PlaylistMessage.RenameSuccess
PlaylistDecision.Rename.Reason.IMPORT -> PlaylistMessage.ImportSuccess
}
_playlistMessage.put(message)
}
} else {
logD("Launching rename dialog for $playlist")
_playlistDecision.put(PlaylistDecision.Rename(playlist))
_playlistDecision.put(PlaylistDecision.Rename(playlist, reason))
}
}

Expand Down Expand Up @@ -336,9 +349,12 @@ sealed interface PlaylistDecision {
* Navigate to a dialog that allows a user to pick a name for a new [Playlist].
*
* @param songs The [Song]s to contain in the new [Playlist].
* @param template An existing playlist name that should be editable in the opened dialog. If
* null, a placeholder should be created and shown as a hint instead.
* @param context The context in which this decision is being fulfilled.
*/
data class New(val songs: List<Song>, val reason: Reason) : PlaylistDecision {
data class New(val songs: List<Song>, val template: String?, val reason: Reason) :
PlaylistDecision {
enum class Reason {
NEW,
ADD,
Expand All @@ -359,7 +375,12 @@ sealed interface PlaylistDecision {
*
* @param playlist The playlist to act on.
*/
data class Rename(val playlist: Playlist) : PlaylistDecision
data class Rename(val playlist: Playlist, val reason: Reason) : PlaylistDecision {
enum class Reason {
ACTION,
IMPORT
}
}

/**
* Navigate to a dialog that allows the user to export a [Playlist].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class AddToPlaylistDialog :
findNavController()
.navigateSafe(
AddToPlaylistDialogDirections.newPlaylist(
songs.map { it.uid }.toTypedArray(), PlaylistDecision.New.Reason.ADD))
songs.map { it.uid }.toTypedArray(), null, PlaylistDecision.New.Reason.ADD))
}

private fun updatePendingSongs(songs: List<Song>?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.oxycblt.auxio.music.decision

import android.os.Bundle
import android.text.Editable
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.addTextChangedListener
Expand Down Expand Up @@ -47,6 +48,7 @@ class NewPlaylistDialog : ViewBindingMaterialDialogFragment<DialogPlaylistNameBi
// Information about what playlist to name for is initially within the navigation arguments
// as UIDs, as that is the only safe way to parcel playlist information.
private val args: NewPlaylistDialogArgs by navArgs()
private var initializedField = false

override fun onConfigDialog(builder: AlertDialog.Builder) {
builder
Expand Down Expand Up @@ -83,6 +85,14 @@ class NewPlaylistDialog : ViewBindingMaterialDialogFragment<DialogPlaylistNameBi
// --- VIEWMODEL SETUP ---
musicModel.playlistDecision.consume()
pickerModel.setPendingPlaylist(requireContext(), args.songUids, args.reason)
if (!initializedField) {
initializedField = true
// Need to convert args.existingName to an Editable
if (args.template != null) {
binding.playlistName.text = EDITABLE_FACTORY.newEditable(args.template)
}
}

collectImmediately(pickerModel.currentPendingPlaylist, ::updatePendingPlaylist)
collectImmediately(pickerModel.chosenName, ::updateChosenName)
}
Expand All @@ -101,4 +111,8 @@ class NewPlaylistDialog : ViewBindingMaterialDialogFragment<DialogPlaylistNameBi
(dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled =
chosenName is ChosenName.Valid || chosenName is ChosenName.Empty
}

private companion object {
val EDITABLE_FACTORY: Editable.Factory = Editable.Factory.getInstance()
}
}
4 changes: 4 additions & 0 deletions app/src/main/res/navigation/inner.xml
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,10 @@
<argument
android:name="songUids"
app:argType="org.oxycblt.auxio.music.Music$UID[]" />
<argument
android:name="template"
app:argType="string"
app:nullable="true" />
<argument
android:name="reason"
app:argType="org.oxycblt.auxio.music.PlaylistDecision$New$Reason" />
Expand Down

0 comments on commit 9ad11ec

Please sign in to comment.