diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index 783f16d64..0cb082671 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -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?) { + if (songs != null) { findNavController() - .navigateSafe(MainFragmentDirections.actionNewPlaylist(pendingPlaylist)) - musicModel.pendingNewPlaylist.consume() + .navigateSafe( + MainFragmentDirections.actionNewPlaylist(songs.map { it.uid }.toTypedArray())) + musicModel.newPlaylistSongs.consume() } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index c7d6f9cac..e042e59b0 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -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() } } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt index b0cf77587..1972209b2 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt @@ -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 get() = _statistics - private val _pendingNewPlaylist = MutableEvent() - val pendingNewPlaylist: Event = _pendingNewPlaylist + private val _newPlaylistSongs = MutableEvent?>() + /** Flag for opening a dialog to create a playlist of the given [Song]s. */ + val newPlaylistSongs: Event?> = _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 = 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 = 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 = listOf()) { - // TODO: Attempt to unify playlist creation flow with dialog model - _pendingNewPlaylist.put(PendingPlaylist(name, songs.map { it.uid })) + musicRepository.createPlaylist(name, songs) } /** diff --git a/app/src/main/java/org/oxycblt/auxio/music/fs/DirectoryAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/config/DirectoryAdapter.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/music/fs/DirectoryAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/music/config/DirectoryAdapter.kt index 5913c2b8c..f9e7b4229 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/fs/DirectoryAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/config/DirectoryAdapter.kt @@ -16,13 +16,14 @@ * along with this program. If not, see . */ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/music/fs/MusicDirsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/config/MusicDirsDialog.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/music/fs/MusicDirsDialog.kt rename to app/src/main/java/org/oxycblt/auxio/music/config/MusicDirsDialog.kt index 4ecea1336..28a4960ba 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/fs/MusicDirsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/config/MusicDirsDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/music/dialog/SeparatorsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/config/SeparatorsDialog.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/dialog/SeparatorsDialog.kt rename to app/src/main/java/org/oxycblt/auxio/music/config/SeparatorsDialog.kt index 86e1ebbf4..2fc2c5c58 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/dialog/SeparatorsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/config/SeparatorsDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.dialog +package org.oxycblt.auxio.music.config import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/org/oxycblt/auxio/music/dialog/PlaylistDialogViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/dialog/PlaylistDialogViewModel.kt deleted file mode 100644 index 6f4813759..000000000 --- a/app/src/main/java/org/oxycblt/auxio/music/dialog/PlaylistDialogViewModel.kt +++ /dev/null @@ -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 . - */ - -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 = _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) : Parcelable diff --git a/app/src/main/java/org/oxycblt/auxio/music/dialog/NewPlaylistDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/NewPlaylistDialog.kt similarity index 58% rename from app/src/main/java/org/oxycblt/auxio/music/dialog/NewPlaylistDialog.kt rename to app/src/main/java/org/oxycblt/auxio/music/picker/NewPlaylistDialog.kt index 8bfcf68e1..f5ce94c87 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/dialog/NewPlaylistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/NewPlaylistDialog.kt @@ -16,17 +16,20 @@ * along with this program. If not, see . */ -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() { - // 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() 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() 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 } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/PlaylistPickerViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/PlaylistPickerViewModel.kt new file mode 100644 index 000000000..33b9fc28d --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/PlaylistPickerViewModel.kt @@ -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 . + */ + +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(null) + val currentPendingPlaylist: StateFlow + get() = _currentPendingPlaylist + + private val _chosenName = MutableStateFlow(ChosenName.Empty) + val chosenName: StateFlow + 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) { + 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) + +/** + * 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 +} diff --git a/app/src/main/java/org/oxycblt/auxio/navigation/dialog/NavigateToArtistDialog.kt b/app/src/main/java/org/oxycblt/auxio/navigation/picker/NavigateToArtistDialog.kt similarity index 96% rename from app/src/main/java/org/oxycblt/auxio/navigation/dialog/NavigateToArtistDialog.kt rename to app/src/main/java/org/oxycblt/auxio/navigation/picker/NavigateToArtistDialog.kt index d90cc1d53..b2be5f806 100644 --- a/app/src/main/java/org/oxycblt/auxio/navigation/dialog/NavigateToArtistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/navigation/picker/NavigateToArtistDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -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(), ClickableListListener { 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)) diff --git a/app/src/main/java/org/oxycblt/auxio/navigation/dialog/NavigationDialogViewModel.kt b/app/src/main/java/org/oxycblt/auxio/navigation/picker/NavigationPickerViewModel.kt similarity index 90% rename from app/src/main/java/org/oxycblt/auxio/navigation/dialog/NavigationDialogViewModel.kt rename to app/src/main/java/org/oxycblt/auxio/navigation/picker/NavigationPickerViewModel.kt index 9e626b53e..932322c82 100644 --- a/app/src/main/java/org/oxycblt/auxio/navigation/dialog/NavigationDialogViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/navigation/picker/NavigationPickerViewModel.kt @@ -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 . */ -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(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 diff --git a/app/src/main/java/org/oxycblt/auxio/playback/dialog/PlayFromArtistDialog.kt b/app/src/main/java/org/oxycblt/auxio/playback/picker/PlayFromArtistDialog.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/playback/dialog/PlayFromArtistDialog.kt rename to app/src/main/java/org/oxycblt/auxio/playback/picker/PlayFromArtistDialog.kt index 0b76b75ea..d0907c39f 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/dialog/PlayFromArtistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/picker/PlayFromArtistDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -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(), ClickableListListener { 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() diff --git a/app/src/main/java/org/oxycblt/auxio/playback/dialog/PlayFromGenreDialog.kt b/app/src/main/java/org/oxycblt/auxio/playback/picker/PlayFromGenreDialog.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/playback/dialog/PlayFromGenreDialog.kt rename to app/src/main/java/org/oxycblt/auxio/playback/picker/PlayFromGenreDialog.kt index 008fe1ee1..21c81aa25 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/dialog/PlayFromGenreDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/picker/PlayFromGenreDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -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(), ClickableListListener { 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() diff --git a/app/src/main/java/org/oxycblt/auxio/playback/dialog/PlaybackDialogViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/picker/PlaybackPickerViewModel.kt similarity index 93% rename from app/src/main/java/org/oxycblt/auxio/playback/dialog/PlaybackDialogViewModel.kt rename to app/src/main/java/org/oxycblt/auxio/playback/picker/PlaybackPickerViewModel.kt index eb97b88f8..577b93c50 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/dialog/PlaybackDialogViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/picker/PlaybackPickerViewModel.kt @@ -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 . */ -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(null) /** The current set of [Artist] choices to show in the picker, or null if to show nothing. */ diff --git a/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt index 9ec0caa75..14cd2a20e 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt @@ -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()) } /** diff --git a/app/src/main/res/layout/dialog_playlist_name.xml b/app/src/main/res/layout/dialog_playlist_name.xml index 390292f34..e441d1ed4 100644 --- a/app/src/main/res/layout/dialog_playlist_name.xml +++ b/app/src/main/res/layout/dialog_playlist_name.xml @@ -1,5 +1,6 @@ + android:name="songUids" + app:argType="org.oxycblt.auxio.music.Music$UID[]" /> -