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:
Alexander Capehart 2023-05-13 11:39:51 -06:00
parent 13709e3e8e
commit 4fe91c25e3
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
17 changed files with 236 additions and 184 deletions

View file

@ -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()
}
}

View file

@ -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()
}
}
}

View file

@ -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)
}
/**

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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))

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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. */

View file

@ -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())
}
/**

View file

@ -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"

View file

@ -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" />