music: add playlist naming flow
Add a real playlist naming dialog and UX flow. This is a bit rough at the moment since theres a good amount of nuance here. Should improve as the playlist implementation continues to grow more fleshed out.
This commit is contained in:
parent
7ba2b1bb41
commit
e01ea25d0b
18 changed files with 302 additions and 68 deletions
|
@ -11,6 +11,7 @@
|
|||
be parsed as images
|
||||
- Fixed issue where searches would match song file names case-sensitively
|
||||
- Fixed issue where the notification would not respond to changes in the album cover setting
|
||||
- Fixed issue where short names starting with an article would not be correctly sorted (ex. "the 1")
|
||||
|
||||
## 3.0.5
|
||||
|
||||
|
|
|
@ -40,7 +40,9 @@ import kotlin.math.min
|
|||
import org.oxycblt.auxio.databinding.FragmentMainBinding
|
||||
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.PendingName
|
||||
import org.oxycblt.auxio.navigation.MainNavigationAction
|
||||
import org.oxycblt.auxio.navigation.NavigationViewModel
|
||||
import org.oxycblt.auxio.playback.PlaybackBottomSheetBehavior
|
||||
|
@ -60,8 +62,9 @@ class MainFragment :
|
|||
ViewBindingFragment<FragmentMainBinding>(),
|
||||
ViewTreeObserver.OnPreDrawListener,
|
||||
NavController.OnDestinationChangedListener {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val navModel: NavigationViewModel by activityViewModels()
|
||||
private val musicModel: MusicViewModel by activityViewModels()
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val selectionModel: SelectionViewModel by activityViewModels()
|
||||
private val callback = DynamicBackPressedCallback()
|
||||
private var lastInsets: WindowInsets? = null
|
||||
|
@ -132,6 +135,7 @@ class MainFragment :
|
|||
collect(navModel.mainNavigationAction.flow, ::handleMainNavigation)
|
||||
collect(navModel.exploreNavigationItem.flow, ::handleExploreNavigation)
|
||||
collect(navModel.exploreArtistNavigationItem.flow, ::handleArtistNavigationPicker)
|
||||
collect(musicModel.pendingPlaylistNaming.flow, ::handlePlaylistNaming)
|
||||
collectImmediately(playbackModel.song, ::updateSong)
|
||||
collect(playbackModel.artistPickerSong.flow, ::handlePlaybackArtistPicker)
|
||||
collect(playbackModel.genrePickerSong.flow, ::handlePlaybackGenrePicker)
|
||||
|
@ -300,6 +304,13 @@ class MainFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun handlePlaylistNaming(args: PendingName.Args?) {
|
||||
if (args != null) {
|
||||
findNavController().navigateSafe(MainFragmentDirections.actionNamePlaylist(args))
|
||||
musicModel.pendingPlaylistNaming.consume()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePlaybackArtistPicker(song: Song?) {
|
||||
if (song != null) {
|
||||
navModel.mainNavigateTo(
|
||||
|
|
|
@ -78,11 +78,15 @@ constructor(
|
|||
// Avoid doing a flip if the given config is already being applied.
|
||||
if (tag == iconRes) return
|
||||
tag = iconRes
|
||||
flipping = true
|
||||
pendingConfig = PendingConfig(iconRes, contentDescriptionRes, clickListener)
|
||||
|
||||
// Already hiding for whatever reason, apply the configuration when the FAB is shown again.
|
||||
if (!isOrWillBeHidden) {
|
||||
flipping = true
|
||||
// We will re-show the FAB later, assuming that there was not a prior flip operation.
|
||||
super.hide(FlipVisibilityListener())
|
||||
}
|
||||
}
|
||||
|
||||
private data class PendingConfig(
|
||||
@DrawableRes val iconRes: Int,
|
||||
|
|
|
@ -321,7 +321,7 @@ class HomeFragment :
|
|||
}
|
||||
} else {
|
||||
binding.homeFab.flipTo(R.drawable.ic_add_24, R.string.desc_new_playlist) {
|
||||
musicModel.createPlaylist()
|
||||
musicModel.createPlaylist("New playlist")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.oxycblt.auxio.music.dialog.PendingName
|
||||
import org.oxycblt.auxio.util.Event
|
||||
import org.oxycblt.auxio.util.MutableEvent
|
||||
|
||||
/**
|
||||
* A [ViewModel] providing data specific to the music loading process.
|
||||
|
@ -42,6 +45,9 @@ class MusicViewModel @Inject constructor(private val musicRepository: MusicRepos
|
|||
val statistics: StateFlow<Statistics?>
|
||||
get() = _statistics
|
||||
|
||||
private val _pendingPlaylistNaming = MutableEvent<PendingName.Args?>()
|
||||
val pendingPlaylistNaming: Event<PendingName.Args?> = _pendingPlaylistNaming
|
||||
|
||||
init {
|
||||
musicRepository.addUpdateListener(this)
|
||||
musicRepository.addIndexingListener(this)
|
||||
|
@ -79,12 +85,15 @@ class MusicViewModel @Inject constructor(private val musicRepository: MusicRepos
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a new generic playlist.
|
||||
* Create a new generic playlist. This will prompt the user to edit the name before the creation
|
||||
* finishes.
|
||||
*
|
||||
* @param name The name of the new playlist. If null, the user will be prompted for a name.
|
||||
* @param name The preferred name of the new playlist.
|
||||
*/
|
||||
fun createPlaylist(name: String? = null) {
|
||||
musicRepository.createPlaylist(name ?: "New playlist", listOf())
|
||||
fun createPlaylist(name: String, songs: List<Song> = listOf()) {
|
||||
// TODO: Default to something like "Playlist 1", "Playlist 2", etc.
|
||||
// TODO: Attempt to unify playlist creation flow with dialog model
|
||||
_pendingPlaylistNaming.put(PendingName.Args(name, songs.map { it.uid }))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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 {
|
||||
private val _currentPendingName = MutableStateFlow<PendingName?>(null)
|
||||
val currentPendingName: StateFlow<PendingName?> = _currentPendingName
|
||||
|
||||
init {
|
||||
musicRepository.addUpdateListener(this)
|
||||
}
|
||||
|
||||
override fun onMusicChanges(changes: MusicRepository.Changes) {
|
||||
if (!changes.deviceLibrary) return
|
||||
val deviceLibrary = musicRepository.deviceLibrary ?: return
|
||||
// Update the pending name to reflect new information in the music library.
|
||||
_currentPendingName.value =
|
||||
_currentPendingName.value?.let { pendingName ->
|
||||
PendingName(
|
||||
pendingName.name,
|
||||
pendingName.songs.mapNotNull { deviceLibrary.findSong(it.uid) })
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
musicRepository.removeUpdateListener(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current [PendingName] based on the given [PendingName.Args].
|
||||
* @param args The [PendingName.Args] to update with.
|
||||
*/
|
||||
fun setPendingName(args: PendingName.Args) {
|
||||
val deviceLibrary = musicRepository.deviceLibrary ?: return
|
||||
val name =
|
||||
PendingName(args.preferredName, args.songUids.mapNotNull(deviceLibrary::findSong))
|
||||
_currentPendingName.value = name
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current [PendingName] based on new user input.
|
||||
* @param name The new user-inputted name, directly from the UI.
|
||||
*/
|
||||
fun updatePendingName(name: String?) {
|
||||
// Remove any additional whitespace from the string to be consistent with all other
|
||||
// music items.
|
||||
val normalized = (name ?: return).trim()
|
||||
_currentPendingName.value =
|
||||
_currentPendingName.value?.run { PendingName(normalized, songs) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the current [PendingName] operation and write it to the database.
|
||||
*/
|
||||
fun confirmPendingName() {
|
||||
val pendingName = _currentPendingName.value ?: return
|
||||
musicRepository.createPlaylist(pendingName.name, pendingName.songs)
|
||||
_currentPendingName.value = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a name operation
|
||||
*/
|
||||
data class PendingName(val name: String, val songs: List<Song>) {
|
||||
/**
|
||||
* A [Parcelable] version of [PendingName], to be used as a dialog argument.
|
||||
* @param preferredName The name to be used initially by the dialog.
|
||||
* @param songUids The [Music.UID] of any pending [Song]s that will be put in the playlist.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Args(val preferredName: String, val songUids: List<Music.UID>) : Parcelable
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* PlaylistNamingDialog.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.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.activityViewModels
|
||||
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.DialogPlaylistNamingBinding
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.collectImmediately
|
||||
|
||||
/**
|
||||
* A dialog allowing the name of a new/existing playlist to be edited.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class PlaylistNamingDialog : ViewBindingDialogFragment<DialogPlaylistNamingBinding>() {
|
||||
// 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()
|
||||
// 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: PlaylistNamingDialogArgs by navArgs()
|
||||
private var initializedInput = false
|
||||
|
||||
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
||||
builder
|
||||
.setTitle(R.string.lbl_new_playlist)
|
||||
.setPositiveButton(R.string.lbl_ok) { _, _ -> dialogModel.confirmPendingName() }
|
||||
.setNegativeButton(R.string.lbl_cancel, null)
|
||||
}
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||
DialogPlaylistNamingBinding.inflate(inflater)
|
||||
|
||||
override fun onBindingCreated(
|
||||
binding: DialogPlaylistNamingBinding,
|
||||
savedInstanceState: Bundle?
|
||||
) {
|
||||
super.onBindingCreated(binding, savedInstanceState)
|
||||
|
||||
binding.playlistName.addTextChangedListener {
|
||||
dialogModel.updatePendingName(it?.toString())
|
||||
}
|
||||
|
||||
dialogModel.setPendingName(args.pendingName)
|
||||
collectImmediately(dialogModel.currentPendingName, ::updatePendingName)
|
||||
}
|
||||
|
||||
private fun updatePendingName(pendingName: PendingName?) {
|
||||
if (pendingName == null) {
|
||||
findNavController().navigateUp()
|
||||
return
|
||||
}
|
||||
// Make sure we initialize the TextView with the preferred name if we haven't already.
|
||||
if (!initializedInput) {
|
||||
requireBinding().playlistName.setText(pendingName.name)
|
||||
initializedInput = true
|
||||
}
|
||||
// Disable the OK button if the name is invalid (empty or whitespace)
|
||||
(dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled =
|
||||
pendingName.name.isNotBlank()
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.metadata
|
||||
package org.oxycblt.auxio.music.dialog
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
|
@ -99,6 +99,19 @@ class SeparatorsDialog : ViewBindingDialogFragment<DialogSeparatorsBinding>() {
|
|||
return separators
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the allowed separator characters that can be used to delimit multi-value tags.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
private object Separators {
|
||||
const val COMMA = ','
|
||||
const val SEMICOLON = ';'
|
||||
const val SLASH = '/'
|
||||
const val PLUS = '+'
|
||||
const val AND = '&'
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val KEY_PENDING_SEPARATORS = BuildConfig.APPLICATION_ID + ".key.PENDING_SEPARATORS"
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* Separators.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.metadata
|
||||
|
||||
/**
|
||||
* Defines the allowed separator characters that can be used to delimit multi-value tags.
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
object Separators {
|
||||
const val COMMA = ','
|
||||
const val SEMICOLON = ';'
|
||||
const val SLASH = '/'
|
||||
const val PLUS = '+'
|
||||
const val AND = '&'
|
||||
}
|
|
@ -169,7 +169,9 @@ private class TagWorkerImpl(
|
|||
(textFrames["TCMP"]
|
||||
?: textFrames["TXXX:compilation"] ?: textFrames["TXXX:itunescompilation"])
|
||||
?.let {
|
||||
// Ignore invalid instances of this tag
|
||||
if (it.size != 1 || it[0] != "1") return@let
|
||||
// Change the metadata to be a compilation album made by "Various Artists"
|
||||
rawSong.albumArtistNames =
|
||||
rawSong.albumArtistNames.ifEmpty { COMPILATION_ALBUM_ARTISTS }
|
||||
rawSong.releaseTypes = rawSong.releaseTypes.ifEmpty { COMPILATION_RELEASE_TYPES }
|
||||
|
@ -262,7 +264,9 @@ private class TagWorkerImpl(
|
|||
|
||||
// Compilation Flag
|
||||
(comments["compilation"] ?: comments["itunescompilation"])?.let {
|
||||
// Ignore invalid instances of this tag
|
||||
if (it.size != 1 || it[0] != "1") return@let
|
||||
// Change the metadata to be a compilation album made by "Various Artists"
|
||||
rawSong.albumArtistNames =
|
||||
rawSong.albumArtistNames.ifEmpty { COMPILATION_ALBUM_ARTISTS }
|
||||
rawSong.releaseTypes = rawSong.releaseTypes.ifEmpty { COMPILATION_RELEASE_TYPES }
|
||||
|
|
|
@ -48,7 +48,7 @@ import org.oxycblt.auxio.util.collectImmediately
|
|||
class ArtistNavigationPickerDialog :
|
||||
ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener<Artist> {
|
||||
private val navigationModel: NavigationViewModel by activityViewModels()
|
||||
private val pickerModel: NavigationPickerViewModel by viewModels()
|
||||
private val pickerModel: NavigationDialogViewModel 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: ArtistNavigationPickerDialogArgs by navArgs()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* NavigationPickerViewModel.kt is part of Auxio.
|
||||
* NavigationDialogViewModel.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
|
||||
|
@ -26,12 +26,12 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import org.oxycblt.auxio.music.*
|
||||
|
||||
/**
|
||||
* A [ViewModel] that stores the current information required for [ArtistNavigationPickerDialog].
|
||||
* A [ViewModel] that stores the current information required for navigation dialogs
|
||||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@HiltViewModel
|
||||
class NavigationPickerViewModel @Inject constructor(private val musicRepository: MusicRepository) :
|
||||
class NavigationDialogViewModel @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. */
|
|
@ -49,7 +49,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
class ArtistPlaybackPickerDialog :
|
||||
ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener<Artist> {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val pickerModel: PlaybackPickerViewModel by viewModels()
|
||||
private val pickerModel: PlaybackDialogViewModel 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: ArtistPlaybackPickerDialogArgs by navArgs()
|
||||
|
|
|
@ -49,7 +49,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
class GenrePlaybackPickerDialog :
|
||||
ViewBindingDialogFragment<DialogMusicPickerBinding>(), ClickableListListener<Genre> {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val pickerModel: PlaybackPickerViewModel by viewModels()
|
||||
private val pickerModel: PlaybackDialogViewModel 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: GenrePlaybackPickerDialogArgs by navArgs()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Auxio Project
|
||||
* PlaybackPickerViewModel.kt is part of Auxio.
|
||||
* PlaybackDialogViewModel.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
|
||||
|
@ -31,7 +31,7 @@ import org.oxycblt.auxio.music.*
|
|||
* @author OxygenCobalt (Alexander Capehart)
|
||||
*/
|
||||
@HiltViewModel
|
||||
class PlaybackPickerViewModel @Inject constructor(private val musicRepository: MusicRepository) :
|
||||
class PlaybackDialogViewModel @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. */
|
17
app/src/main/res/layout/dialog_playlist_naming.xml
Normal file
17
app/src/main/res/layout/dialog_playlist_naming.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/spacing_medium"
|
||||
android:paddingEnd="@dimen/spacing_mid_large"
|
||||
android:paddingStart="@dimen/spacing_mid_large"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
app:hintEnabled="false">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/playlist_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
|
@ -18,26 +18,39 @@
|
|||
android:id="@+id/action_show_details"
|
||||
app:destination="@id/song_detail_dialog" />
|
||||
<action
|
||||
android:id="@+id/action_pick_playback_artist"
|
||||
app:destination="@id/artist_playback_picker_dialog" />
|
||||
android:id="@+id/action_name_playlist"
|
||||
app:destination="@id/playlist_naming_dialog" />
|
||||
<action
|
||||
android:id="@+id/action_pick_navigation_artist"
|
||||
app:destination="@id/artist_navigation_picker_dialog" />
|
||||
<action
|
||||
android:id="@+id/action_pick_playback_artist"
|
||||
app:destination="@id/artist_playback_picker_dialog" />
|
||||
<action
|
||||
android:id="@+id/action_pick_playback_genre"
|
||||
app:destination="@id/genre_playback_picker_dialog" />
|
||||
</fragment>
|
||||
|
||||
<dialog
|
||||
android:id="@+id/artist_playback_picker_dialog"
|
||||
android:name="org.oxycblt.auxio.playback.dialog.ArtistPlaybackPickerDialog"
|
||||
android:label="artist_playback_picker_dialog"
|
||||
tools:layout="@layout/dialog_music_picker">
|
||||
android:id="@+id/song_detail_dialog"
|
||||
android:name="org.oxycblt.auxio.detail.SongDetailDialog"
|
||||
android:label="song_detail_dialog"
|
||||
tools:layout="@layout/dialog_song_detail">
|
||||
<argument
|
||||
android:name="artistUid"
|
||||
android:name="songUid"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
</dialog>
|
||||
|
||||
<dialog
|
||||
android:id="@+id/playlist_naming_dialog"
|
||||
android:name="org.oxycblt.auxio.music.dialog.PlaylistNamingDialog"
|
||||
android:label="playlist_naming_dialog"
|
||||
tools:layout="@layout/dialog_song_detail">
|
||||
<argument
|
||||
android:name="pendingName"
|
||||
app:argType="org.oxycblt.auxio.music.dialog.PendingName$Args" />
|
||||
</dialog>
|
||||
|
||||
<dialog
|
||||
android:id="@+id/artist_navigation_picker_dialog"
|
||||
android:name="org.oxycblt.auxio.navigation.dialog.ArtistNavigationPickerDialog"
|
||||
|
@ -48,6 +61,16 @@
|
|||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
</dialog>
|
||||
|
||||
<dialog
|
||||
android:id="@+id/artist_playback_picker_dialog"
|
||||
android:name="org.oxycblt.auxio.playback.dialog.ArtistPlaybackPickerDialog"
|
||||
android:label="artist_playback_picker_dialog"
|
||||
tools:layout="@layout/dialog_music_picker">
|
||||
<argument
|
||||
android:name="artistUid"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
</dialog>
|
||||
|
||||
<dialog
|
||||
android:id="@+id/genre_playback_picker_dialog"
|
||||
android:name="org.oxycblt.auxio.playback.dialog.GenrePlaybackPickerDialog"
|
||||
|
@ -58,16 +81,6 @@
|
|||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
</dialog>
|
||||
|
||||
<dialog
|
||||
android:id="@+id/song_detail_dialog"
|
||||
android:name="org.oxycblt.auxio.detail.SongDetailDialog"
|
||||
android:label="song_detail_dialog"
|
||||
tools:layout="@layout/dialog_song_detail">
|
||||
<argument
|
||||
android:name="songUid"
|
||||
app:argType="org.oxycblt.auxio.music.Music$UID" />
|
||||
</dialog>
|
||||
|
||||
|
||||
<fragment
|
||||
android:id="@+id/root_preferences_fragment"
|
||||
|
@ -148,7 +161,7 @@
|
|||
tools:layout="@layout/dialog_music_dirs" />
|
||||
<dialog
|
||||
android:id="@+id/separators_dialog"
|
||||
android:name="org.oxycblt.auxio.music.metadata.SeparatorsDialog"
|
||||
android:name="org.oxycblt.auxio.music.dialog.SeparatorsDialog"
|
||||
android:label="music_dirs_dialog"
|
||||
tools:layout="@layout/dialog_separators" />
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@
|
|||
|
||||
<string name="lbl_playlist">Playlist</string>
|
||||
<string name="lbl_playlists">Playlists</string>
|
||||
<string name="lbl_new_playlist">New playlist</string>
|
||||
|
||||
<!-- Search for music -->
|
||||
<string name="lbl_search">Search</string>
|
||||
|
|
Loading…
Reference in a new issue