music: streamline new playlist implementation
Make the implementation of the playlist creation dialog signifigantly simpler by removing some aspects that don't really need implementation yet.
This commit is contained in:
parent
13709e3e8e
commit
4fe91c25e3
17 changed files with 236 additions and 184 deletions
|
@ -42,7 +42,6 @@ import org.oxycblt.auxio.list.selection.SelectionViewModel
|
|||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicViewModel
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.dialog.PendingPlaylist
|
||||
import org.oxycblt.auxio.navigation.MainNavigationAction
|
||||
import org.oxycblt.auxio.navigation.NavigationViewModel
|
||||
import org.oxycblt.auxio.playback.PlaybackBottomSheetBehavior
|
||||
|
@ -135,7 +134,7 @@ class MainFragment :
|
|||
collect(navModel.mainNavigationAction.flow, ::handleMainNavigation)
|
||||
collect(navModel.exploreNavigationItem.flow, ::handleExploreNavigation)
|
||||
collect(navModel.exploreArtistNavigationItem.flow, ::handleArtistNavigationPicker)
|
||||
collect(musicModel.pendingNewPlaylist.flow, ::handlePlaylistNaming)
|
||||
collect(musicModel.newPlaylistSongs.flow, ::handleNewPlaylist)
|
||||
collectImmediately(playbackModel.song, ::updateSong)
|
||||
collect(playbackModel.artistPickerSong.flow, ::handlePlaybackArtistPicker)
|
||||
collect(playbackModel.genrePickerSong.flow, ::handlePlaybackGenrePicker)
|
||||
|
@ -304,11 +303,12 @@ class MainFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun handlePlaylistNaming(pendingPlaylist: PendingPlaylist?) {
|
||||
if (pendingPlaylist != null) {
|
||||
private fun handleNewPlaylist(songs: List<Song>?) {
|
||||
if (songs != null) {
|
||||
findNavController()
|
||||
.navigateSafe(MainFragmentDirections.actionNewPlaylist(pendingPlaylist))
|
||||
musicModel.pendingNewPlaylist.consume()
|
||||
.navigateSafe(
|
||||
MainFragmentDirections.actionNewPlaylist(songs.map { it.uid }.toTypedArray()))
|
||||
musicModel.newPlaylistSongs.consume()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -321,7 +321,7 @@ class HomeFragment :
|
|||
}
|
||||
} else {
|
||||
binding.homeFab.flipTo(R.drawable.ic_add_24, R.string.desc_new_playlist) {
|
||||
musicModel.createPlaylist(requireContext())
|
||||
musicModel.createPlaylist()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,14 +18,11 @@
|
|||
|
||||
package org.oxycblt.auxio.music
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.music.dialog.PendingPlaylist
|
||||
import org.oxycblt.auxio.util.Event
|
||||
import org.oxycblt.auxio.util.MutableEvent
|
||||
|
||||
|
@ -47,8 +44,9 @@ class MusicViewModel @Inject constructor(private val musicRepository: MusicRepos
|
|||
val statistics: StateFlow<Statistics?>
|
||||
get() = _statistics
|
||||
|
||||
private val _pendingNewPlaylist = MutableEvent<PendingPlaylist?>()
|
||||
val pendingNewPlaylist: Event<PendingPlaylist?> = _pendingNewPlaylist
|
||||
private val _newPlaylistSongs = MutableEvent<List<Song>?>()
|
||||
/** Flag for opening a dialog to create a playlist of the given [Song]s. */
|
||||
val newPlaylistSongs: Event<List<Song>?> = _newPlaylistSongs
|
||||
|
||||
init {
|
||||
musicRepository.addUpdateListener(this)
|
||||
|
@ -87,35 +85,23 @@ class MusicViewModel @Inject constructor(private val musicRepository: MusicRepos
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a new generic playlist. This will automatically generate a playlist name and then
|
||||
* prompt the user to edit the name before the creation finished.
|
||||
* Create a new generic playlist. This will first open a dialog for the user to make a naming
|
||||
* choice before committing the playlist to the database.
|
||||
*
|
||||
* @param context The [Context] required to generate the playlist name.
|
||||
* @param songs The [Song]s to be contained in the new playlist.
|
||||
*/
|
||||
fun createPlaylist(context: Context, songs: List<Song> = listOf()) {
|
||||
val userLibrary = musicRepository.userLibrary ?: return
|
||||
var i = 1
|
||||
while (true) {
|
||||
val possibleName = context.getString(R.string.fmt_def_playlist, i)
|
||||
if (userLibrary.playlists.none { it.name.resolve(context) == possibleName }) {
|
||||
createPlaylist(possibleName, songs)
|
||||
return
|
||||
}
|
||||
++i
|
||||
}
|
||||
fun createPlaylist(songs: List<Song> = listOf()) {
|
||||
_newPlaylistSongs.put(songs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new generic playlist. This will prompt the user to edit the name before the creation
|
||||
* finishes.
|
||||
* Create a new generic playlist. This will immediately commit the playlist to the database.
|
||||
*
|
||||
* @param name The preferred name of the new playlist.
|
||||
* @param name The name of the new playlist.
|
||||
* @param songs The [Song]s to be contained in the new playlist.
|
||||
*/
|
||||
fun createPlaylist(name: String, songs: List<Song> = listOf()) {
|
||||
// TODO: Attempt to unify playlist creation flow with dialog model
|
||||
_pendingNewPlaylist.put(PendingPlaylist(name, songs.map { it.uid }))
|
||||
musicRepository.createPlaylist(name, songs)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,13 +16,14 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.fs
|
||||
package org.oxycblt.auxio.music.config
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.oxycblt.auxio.databinding.ItemMusicDirBinding
|
||||
import org.oxycblt.auxio.list.recycler.DialogRecyclerView
|
||||
import org.oxycblt.auxio.music.fs.Directory
|
||||
import org.oxycblt.auxio.util.context
|
||||
import org.oxycblt.auxio.util.inflater
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.fs
|
||||
package org.oxycblt.auxio.music.config
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.net.Uri
|
||||
|
@ -35,6 +35,8 @@ import org.oxycblt.auxio.BuildConfig
|
|||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
|
||||
import org.oxycblt.auxio.music.MusicSettings
|
||||
import org.oxycblt.auxio.music.fs.Directory
|
||||
import org.oxycblt.auxio.music.fs.MusicDirectories
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||
import org.oxycblt.auxio.util.logD
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.dialog
|
||||
package org.oxycblt.auxio.music.config
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* PlaylistDialogViewModel.kt is part of Auxio.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.dialog
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicRepository
|
||||
import org.oxycblt.auxio.music.Song
|
||||
|
||||
/**
|
||||
* A [ViewModel] managing the state of the playlist editing dialogs.
|
||||
*
|
||||
* @author Alexander Capehart
|
||||
*/
|
||||
@HiltViewModel
|
||||
class PlaylistDialogViewModel @Inject constructor(private val musicRepository: MusicRepository) :
|
||||
ViewModel(), MusicRepository.UpdateListener {
|
||||
var pendingPlaylist: PendingPlaylist? = null
|
||||
private set
|
||||
|
||||
private val _pendingPlaylistValid = MutableStateFlow(false)
|
||||
val pendingPlaylistValid: StateFlow<Boolean> = _pendingPlaylistValid
|
||||
|
||||
init {
|
||||
musicRepository.addUpdateListener(this)
|
||||
}
|
||||
|
||||
override fun onMusicChanges(changes: MusicRepository.Changes) {
|
||||
pendingPlaylist?.let(::validateName)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
musicRepository.removeUpdateListener(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current [PendingPlaylist]. Will do nothing if already equal.
|
||||
*
|
||||
* @param pendingPlaylist The [PendingPlaylist] to update with.
|
||||
*/
|
||||
fun setPendingName(pendingPlaylist: PendingPlaylist) {
|
||||
if (this.pendingPlaylist == pendingPlaylist) return
|
||||
this.pendingPlaylist = pendingPlaylist
|
||||
validateName(pendingPlaylist)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current [PendingPlaylist] based on new user input.
|
||||
*
|
||||
* @param name The new user-inputted name.
|
||||
*/
|
||||
fun updatePendingName(name: String) {
|
||||
val current = pendingPlaylist ?: return
|
||||
// Remove any additional whitespace from the string to be consistent with all other
|
||||
// music items.
|
||||
val new = PendingPlaylist(name.trim(), current.songUids)
|
||||
pendingPlaylist = new
|
||||
validateName(new)
|
||||
}
|
||||
|
||||
/** Confirm the current [PendingPlaylist] operation and write it to the database. */
|
||||
fun confirmPendingName() {
|
||||
val playlist = pendingPlaylist ?: return
|
||||
val deviceLibrary = musicRepository.deviceLibrary ?: return
|
||||
musicRepository.createPlaylist(
|
||||
playlist.name, playlist.songUids.mapNotNull(deviceLibrary::findSong))
|
||||
}
|
||||
|
||||
private fun validateName(pendingPlaylist: PendingPlaylist) {
|
||||
val userLibrary = musicRepository.userLibrary
|
||||
_pendingPlaylistValid.value =
|
||||
pendingPlaylist.name.isNotBlank() &&
|
||||
userLibrary != null &&
|
||||
userLibrary.findPlaylist(pendingPlaylist.name) == null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a playlist that is currently being named before actually being completed.
|
||||
*
|
||||
* @param name The name of the playlist.
|
||||
* @param songUids The [Music.UID]s of the [Song]s to be contained by the playlist.
|
||||
*/
|
||||
@Parcelize data class PendingPlaylist(val name: String, val songUids: List<Music.UID>) : Parcelable
|
|
@ -16,17 +16,20 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.dialog
|
||||
package org.oxycblt.auxio.music.picker
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.DialogPlaylistNameBinding
|
||||
import org.oxycblt.auxio.music.MusicViewModel
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.collectImmediately
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
@ -38,9 +41,8 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
*/
|
||||
@AndroidEntryPoint
|
||||
class NewPlaylistDialog : ViewBindingDialogFragment<DialogPlaylistNameBinding>() {
|
||||
// activityViewModels is intentional here as the ViewModel will do work that we
|
||||
// do not want to cancel after this dialog closes.
|
||||
private val dialogModel: PlaylistDialogViewModel by activityViewModels()
|
||||
private val musicModel: MusicViewModel by activityViewModels()
|
||||
private val pickerModel: PlaylistPickerViewModel by viewModels()
|
||||
// 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()
|
||||
|
@ -48,7 +50,16 @@ class NewPlaylistDialog : ViewBindingDialogFragment<DialogPlaylistNameBinding>()
|
|||
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
||||
builder
|
||||
.setTitle(R.string.lbl_new_playlist)
|
||||
.setPositiveButton(R.string.lbl_ok) { _, _ -> dialogModel.confirmPendingName() }
|
||||
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
||||
val pendingPlaylist = unlikelyToBeNull(pickerModel.currentPendingPlaylist.value)
|
||||
val name =
|
||||
when (val chosenName = pickerModel.chosenName.value) {
|
||||
is ChosenName.Valid -> chosenName.value
|
||||
is ChosenName.Empty -> pendingPlaylist.preferredName
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
musicModel.createPlaylist(name, pendingPlaylist.songs)
|
||||
}
|
||||
.setNegativeButton(R.string.lbl_cancel, null)
|
||||
}
|
||||
|
||||
|
@ -58,20 +69,24 @@ class NewPlaylistDialog : ViewBindingDialogFragment<DialogPlaylistNameBinding>()
|
|||
override fun onBindingCreated(binding: DialogPlaylistNameBinding, savedInstanceState: Bundle?) {
|
||||
super.onBindingCreated(binding, savedInstanceState)
|
||||
|
||||
binding.playlistName.apply {
|
||||
hint = args.pendingPlaylist.name
|
||||
addTextChangedListener {
|
||||
dialogModel.updatePendingName(
|
||||
(if (it.isNullOrEmpty()) unlikelyToBeNull(hint) else it).toString())
|
||||
}
|
||||
binding.playlistName.addTextChangedListener { pickerModel.updateChosenName(it?.toString()) }
|
||||
|
||||
pickerModel.setPendingPlaylist(requireContext(), args.songUids)
|
||||
collectImmediately(pickerModel.currentPendingPlaylist, ::updatePendingPlaylist)
|
||||
collectImmediately(pickerModel.chosenName, ::handleChosenName)
|
||||
}
|
||||
|
||||
private fun updatePendingPlaylist(pendingPlaylist: PendingPlaylist?) {
|
||||
if (pendingPlaylist == null) {
|
||||
findNavController().navigateUp()
|
||||
return
|
||||
}
|
||||
|
||||
dialogModel.setPendingName(args.pendingPlaylist)
|
||||
collectImmediately(dialogModel.pendingPlaylistValid, ::updateValid)
|
||||
requireBinding().playlistName.hint = pendingPlaylist.preferredName
|
||||
}
|
||||
|
||||
private fun updateValid(valid: Boolean) {
|
||||
// Disable the OK button if the name is invalid (empty or whitespace)
|
||||
(dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = valid
|
||||
private fun handleChosenName(chosenName: ChosenName) {
|
||||
(dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled =
|
||||
chosenName is ChosenName.Valid || chosenName is ChosenName.Empty
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* PlaylistPickerViewModel.kt is part of Auxio.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.picker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicRepository
|
||||
import org.oxycblt.auxio.music.Song
|
||||
|
||||
/**
|
||||
* A [ViewModel] managing the state of the playlist picker dialogs.
|
||||
*
|
||||
* @author Alexander Capehart
|
||||
*/
|
||||
@HiltViewModel
|
||||
class PlaylistPickerViewModel @Inject constructor(private val musicRepository: MusicRepository) :
|
||||
ViewModel(), MusicRepository.UpdateListener {
|
||||
private val _currentPendingPlaylist = MutableStateFlow<PendingPlaylist?>(null)
|
||||
val currentPendingPlaylist: StateFlow<PendingPlaylist?>
|
||||
get() = _currentPendingPlaylist
|
||||
|
||||
private val _chosenName = MutableStateFlow<ChosenName>(ChosenName.Empty)
|
||||
val chosenName: StateFlow<ChosenName>
|
||||
get() = _chosenName
|
||||
|
||||
init {
|
||||
musicRepository.addUpdateListener(this)
|
||||
}
|
||||
|
||||
override fun onMusicChanges(changes: MusicRepository.Changes) {
|
||||
val deviceLibrary = musicRepository.deviceLibrary
|
||||
if (changes.deviceLibrary && deviceLibrary != null) {
|
||||
_currentPendingPlaylist.value =
|
||||
_currentPendingPlaylist.value?.let { pendingPlaylist ->
|
||||
PendingPlaylist(
|
||||
pendingPlaylist.preferredName,
|
||||
pendingPlaylist.songs.mapNotNull { deviceLibrary.findSong(it.uid) })
|
||||
}
|
||||
}
|
||||
|
||||
val chosenName = _chosenName.value
|
||||
if (changes.userLibrary) {
|
||||
when (chosenName) {
|
||||
is ChosenName.Valid -> updateChosenName(chosenName.value)
|
||||
is ChosenName.AlreadyExists -> updateChosenName(chosenName.prior)
|
||||
else -> {
|
||||
// Nothing to do.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
musicRepository.removeUpdateListener(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current [PendingPlaylist]. Will do nothing if already equal.
|
||||
*
|
||||
* @param context [Context] required to generate a playlist name.
|
||||
* @param songUids The list of [Music.UID] representing the songs to be present in the playlist.
|
||||
*/
|
||||
fun setPendingPlaylist(context: Context, songUids: Array<Music.UID>) {
|
||||
if (currentPendingPlaylist.value?.songs?.map { it.uid } == songUids) {
|
||||
// Nothing to do.
|
||||
return
|
||||
}
|
||||
val deviceLibrary = musicRepository.deviceLibrary ?: return
|
||||
val songs = songUids.mapNotNull(deviceLibrary::findSong)
|
||||
val userLibrary = musicRepository.userLibrary ?: return
|
||||
|
||||
var i = 1
|
||||
while (true) {
|
||||
val possibleName = context.getString(R.string.fmt_def_playlist, i)
|
||||
if (userLibrary.playlists.none { it.name.resolve(context) == possibleName }) {
|
||||
_currentPendingPlaylist.value = PendingPlaylist(possibleName, songs)
|
||||
return
|
||||
}
|
||||
++i
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current [ChosenName] based on new user input.
|
||||
*
|
||||
* @param name The new user-inputted name, or null if not present.
|
||||
*/
|
||||
fun updateChosenName(name: String?) {
|
||||
_chosenName.value =
|
||||
when {
|
||||
name.isNullOrEmpty() -> ChosenName.Empty
|
||||
name.isBlank() -> ChosenName.Blank
|
||||
else -> {
|
||||
val trimmed = name.trim()
|
||||
val userLibrary = musicRepository.userLibrary
|
||||
if (userLibrary != null && userLibrary.findPlaylist(trimmed) == null) {
|
||||
ChosenName.Valid(trimmed)
|
||||
} else {
|
||||
ChosenName.AlreadyExists(trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a playlist that will be created as soon as a name is chosen.
|
||||
*
|
||||
* @param preferredName The name to be used by default if no other name is chosen.
|
||||
* @param songs The [Song]s to be contained in the [PendingPlaylist]
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
data class PendingPlaylist(val preferredName: String, val songs: List<Song>)
|
||||
|
||||
/**
|
||||
* Represents the (processed) user input from the playlist naming dialogs.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
sealed interface ChosenName {
|
||||
/** The current name is valid. */
|
||||
data class Valid(val value: String) : ChosenName
|
||||
/** The current name already exists. */
|
||||
data class AlreadyExists(val prior: String) : ChosenName
|
||||
/** The current name is empty. */
|
||||
object Empty : ChosenName
|
||||
/** The current name only consists of whitespace. */
|
||||
object Blank : ChosenName
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.navigation.dialog
|
||||
package org.oxycblt.auxio.navigation.picker
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
|
@ -48,7 +48,7 @@ import org.oxycblt.auxio.util.collectImmediately
|
|||
class NavigateToArtistDialog :
|
||||
ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener<Artist> {
|
||||
private val navigationModel: NavigationViewModel by activityViewModels()
|
||||
private val pickerModel: NavigationDialogViewModel by viewModels()
|
||||
private val pickerModel: NavigationPickerViewModel by viewModels()
|
||||
// Information about what artists to show choices for is initially within the navigation
|
||||
// arguments as UIDs, as that is the only safe way to parcel an artist.
|
||||
private val args: NavigateToArtistDialogArgs by navArgs()
|
||||
|
@ -69,7 +69,7 @@ class NavigateToArtistDialog :
|
|||
adapter = choiceAdapter
|
||||
}
|
||||
|
||||
pickerModel.setArtistChoiceUid(args.artistUid)
|
||||
pickerModel.setArtistChoiceUid(args.itemUid)
|
||||
collectImmediately(pickerModel.currentArtistChoices) {
|
||||
if (it != null) {
|
||||
choiceAdapter.update(it.choices, UpdateInstructions.Replace(0))
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* NavigationDialogViewModel.kt is part of Auxio.
|
||||
* NavigationPickerViewModel.kt is part of Auxio.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.navigation.dialog
|
||||
package org.oxycblt.auxio.navigation.picker
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
@ -26,12 +26,12 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import org.oxycblt.auxio.music.*
|
||||
|
||||
/**
|
||||
* A [ViewModel] that stores the current information required for navigation dialogs
|
||||
* A [ViewModel] that stores the current information required for navigation picker dialogs
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@HiltViewModel
|
||||
class NavigationDialogViewModel @Inject constructor(private val musicRepository: MusicRepository) :
|
||||
class NavigationPickerViewModel @Inject constructor(private val musicRepository: MusicRepository) :
|
||||
ViewModel(), MusicRepository.UpdateListener {
|
||||
private val _currentArtistChoices = MutableStateFlow<ArtistNavigationChoices?>(null)
|
||||
/** The current set of [Artist] choices to show in the picker, or null if to show nothing. */
|
||||
|
@ -68,12 +68,12 @@ class NavigationDialogViewModel @Inject constructor(private val musicRepository:
|
|||
/**
|
||||
* Set the [Music.UID] of the item to show artist choices for.
|
||||
*
|
||||
* @param uid The [Music.UID] of the item to show. Must be a [Song] or [Album].
|
||||
* @param itemUid The [Music.UID] of the item to show. Must be a [Song] or [Album].
|
||||
*/
|
||||
fun setArtistChoiceUid(uid: Music.UID) {
|
||||
fun setArtistChoiceUid(itemUid: Music.UID) {
|
||||
// Support Songs and Albums, which have parent artists.
|
||||
_currentArtistChoices.value =
|
||||
when (val music = musicRepository.find(uid)) {
|
||||
when (val music = musicRepository.find(itemUid)) {
|
||||
is Song -> SongArtistNavigationChoices(music)
|
||||
is Album -> AlbumArtistNavigationChoices(music)
|
||||
else -> null
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.playback.dialog
|
||||
package org.oxycblt.auxio.playback.picker
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
|
@ -49,7 +49,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
class PlayFromArtistDialog :
|
||||
ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener<Artist> {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val pickerModel: PlaybackDialogViewModel by viewModels()
|
||||
private val pickerModel: PlaybackPickerViewModel by viewModels()
|
||||
// Information about what Song to show choices for is initially within the navigation arguments
|
||||
// as UIDs, as that is the only safe way to parcel a Song.
|
||||
private val args: PlayFromArtistDialogArgs by navArgs()
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.playback.dialog
|
||||
package org.oxycblt.auxio.playback.picker
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
|
@ -49,7 +49,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
class PlayFromGenreDialog :
|
||||
ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener<Genre> {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val pickerModel: PlaybackDialogViewModel by viewModels()
|
||||
private val pickerModel: PlaybackPickerViewModel by viewModels()
|
||||
// Information about what Song to show choices for is initially within the navigation arguments
|
||||
// as UIDs, as that is the only safe way to parcel a Song.
|
||||
private val args: PlayFromGenreDialogArgs by navArgs()
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* PlaybackDialogViewModel.kt is part of Auxio.
|
||||
* PlaybackPickerViewModel.kt is part of Auxio.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.playback.dialog
|
||||
package org.oxycblt.auxio.playback.picker
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
@ -31,7 +31,7 @@ import org.oxycblt.auxio.music.*
|
|||
* @author OxygenCobalt (Alexander Capehart)
|
||||
*/
|
||||
@HiltViewModel
|
||||
class PlaybackDialogViewModel @Inject constructor(private val musicRepository: MusicRepository) :
|
||||
class PlaybackPickerViewModel @Inject constructor(private val musicRepository: MusicRepository) :
|
||||
ViewModel(), MusicRepository.UpdateListener {
|
||||
private val _currentPickerSong = MutableStateFlow<Song?>(null)
|
||||
/** The current set of [Artist] choices to show in the picker, or null if to show nothing. */
|
|
@ -33,6 +33,7 @@ import androidx.navigation.NavController
|
|||
import androidx.navigation.NavDirections
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
/**
|
||||
* Get if this [View] contains the given [PointF], with optional leeway.
|
||||
|
@ -124,8 +125,10 @@ fun AppCompatButton.fixDoubleRipple() {
|
|||
fun NavController.navigateSafe(directions: NavDirections) =
|
||||
try {
|
||||
navigate(directions)
|
||||
} catch (e: IllegalStateException) {
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// Nothing to do.
|
||||
logE("Could not navigate from this destination.")
|
||||
logE(e.stackTraceToString())
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/playlist_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/spacing_medium"
|
||||
|
|
|
@ -43,27 +43,27 @@
|
|||
|
||||
<dialog
|
||||
android:id="@+id/new_playlist_dialog"
|
||||
android:name="org.oxycblt.auxio.music.dialog.NewPlaylistDialog"
|
||||
android:name="org.oxycblt.auxio.music.picker.NewPlaylistDialog"
|
||||
android:label="new_playlist_dialog"
|
||||
tools:layout="@layout/dialog_playlist_name">
|
||||
<argument
|
||||
android:name="pendingPlaylist"
|
||||
app:argType="org.oxycblt.auxio.music.dialog.PendingPlaylist" />
|
||||
android:name="songUids"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID[]" />
|
||||
</dialog>
|
||||
|
||||
<dialog
|
||||
android:id="@+id/navigate_to_artist_dialog"
|
||||
android:name="org.oxycblt.auxio.navigation.dialog.NavigateToArtistDialog"
|
||||
android:name="org.oxycblt.auxio.navigation.picker.NavigateToArtistDialog"
|
||||
android:label="navigate_to_artist_dialog"
|
||||
tools:layout="@layout/dialog_music_picker">
|
||||
<argument
|
||||
android:name="artistUid"
|
||||
android:name="itemUid"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
</dialog>
|
||||
|
||||
<dialog
|
||||
android:id="@+id/play_from_artist_dialog"
|
||||
android:name="org.oxycblt.auxio.playback.dialog.PlayFromArtistDialog"
|
||||
android:name="org.oxycblt.auxio.playback.picker.PlayFromArtistDialog"
|
||||
android:label="play_from_artist_dialog"
|
||||
tools:layout="@layout/dialog_music_picker">
|
||||
<argument
|
||||
|
@ -73,7 +73,7 @@
|
|||
|
||||
<dialog
|
||||
android:id="@+id/play_from_genre_dialog"
|
||||
android:name="org.oxycblt.auxio.playback.dialog.PlayFromGenreDialog"
|
||||
android:name="org.oxycblt.auxio.playback.picker.PlayFromGenreDialog"
|
||||
android:label="play_from_genre_dialog"
|
||||
tools:layout="@layout/dialog_music_picker">
|
||||
<argument
|
||||
|
@ -81,7 +81,6 @@
|
|||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
</dialog>
|
||||
|
||||
|
||||
<fragment
|
||||
android:id="@+id/root_preferences_fragment"
|
||||
android:name="org.oxycblt.auxio.settings.RootPreferenceFragment"
|
||||
|
@ -156,12 +155,12 @@
|
|||
tools:layout="@layout/dialog_pre_amp" />
|
||||
<dialog
|
||||
android:id="@+id/music_dirs_dialog"
|
||||
android:name="org.oxycblt.auxio.music.fs.MusicDirsDialog"
|
||||
android:name="org.oxycblt.auxio.music.config.MusicDirsDialog"
|
||||
android:label="music_dirs_dialog"
|
||||
tools:layout="@layout/dialog_music_dirs" />
|
||||
<dialog
|
||||
android:id="@+id/separators_dialog"
|
||||
android:name="org.oxycblt.auxio.music.dialog.SeparatorsDialog"
|
||||
android:name="org.oxycblt.auxio.music.config.SeparatorsDialog"
|
||||
android:label="music_dirs_dialog"
|
||||
tools:layout="@layout/dialog_separators" />
|
||||
|
||||
|
|
Loading…
Reference in a new issue