detail: add playlist resorting

Add the ability to resort a playlist when in edit mode.

Resolves #446.
This commit is contained in:
Alexander Capehart 2023-07-25 19:26:06 -06:00
parent b2a3416289
commit 6ef2f74694
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 127 additions and 17 deletions

View file

@ -3,7 +3,8 @@
## dev ## dev
#### What's New #### What's New
- Menus have been refreshed with a cleaner look - Item and sort menus have been refreshed with a cleaner look
- Added ability to sort playlists
- Added option to play song by itself in library/item details - Added option to play song by itself in library/item details
#### What's Improved #### What's Improved

View file

@ -480,6 +480,17 @@ constructor(
return true return true
} }
/**
* Apply a [Sort] to the edited playlist. Does nothing if not in an editing session.
*
* @param sort The [Sort] to apply.
*/
fun applyPlaylistSongSort(sort: Sort) {
val playlist = _currentPlaylist.value ?: return
_editedPlaylist.value = sort.songs(_editedPlaylist.value ?: return)
refreshPlaylistList(playlist, UpdateInstructions.Replace(2))
}
/** /**
* (Visually) move a song in the current playlist. Does nothing if not in an editing session. * (Visually) move a song in the current playlist. Does nothing if not in an editing session.
* *
@ -488,7 +499,6 @@ constructor(
* @return true if the song was moved, false otherwise. * @return true if the song was moved, false otherwise.
*/ */
fun movePlaylistSongs(from: Int, to: Int): Boolean { fun movePlaylistSongs(from: Int, to: Int): Boolean {
// TODO: Song re-sorting
val playlist = _currentPlaylist.value ?: return false val playlist = _currentPlaylist.value ?: return false
val editedPlaylist = (_editedPlaylist.value ?: return false).toMutableList() val editedPlaylist = (_editedPlaylist.value ?: return false).toMutableList()
val realFrom = from - 2 val realFrom = from - 2

View file

@ -182,9 +182,12 @@ class PlaylistDetailFragment :
initialNavDestinationChange = true initialNavDestinationChange = true
return return
} }
// Drop any pending playlist edits when navigating away. This could actually happen if (destination.id != R.id.playlist_detail_fragment &&
// if the user is quick enough. destination.id != R.id.playlist_song_sort_dialog) {
detailModel.dropPlaylistEdit() // Drop any pending playlist edits when navigating away. This could actually happen
// if the user is quick enough.
detailModel.dropPlaylistEdit()
}
} }
override fun onMenuItemClick(item: MenuItem): Boolean { override fun onMenuItemClick(item: MenuItem): Boolean {
@ -251,7 +254,9 @@ class PlaylistDetailFragment :
detailModel.startPlaylistEdit() detailModel.startPlaylistEdit()
} }
override fun onOpenSortMenu() {} override fun onOpenSortMenu() {
findNavController().navigateSafe(PlaylistDetailFragmentDirections.sort())
}
private fun updatePlaylist(playlist: Playlist?) { private fun updatePlaylist(playlist: Playlist?) {
if (playlist == null) { if (playlist == null) {

View file

@ -170,10 +170,25 @@ private class EditHeaderViewHolder private constructor(private val binding: Item
TooltipCompat.setTooltipText(this, contentDescription) TooltipCompat.setTooltipText(this, contentDescription)
setOnClickListener { listener.onStartEdit() } setOnClickListener { listener.onStartEdit() }
} }
binding.headerSort.apply {
TooltipCompat.setTooltipText(this, contentDescription)
setOnClickListener { listener.onOpenSortMenu() }
}
} }
override fun updateEditing(editing: Boolean) { override fun updateEditing(editing: Boolean) {
binding.headerEdit.isEnabled = !editing binding.headerEdit.apply {
isVisible = !editing
isClickable = !editing
isFocusable = !editing
jumpDrawablesToCurrentState()
}
binding.headerSort.apply {
isVisible = editing
isClickable = editing
isFocusable = editing
jumpDrawablesToCurrentState()
}
} }
companion object { companion object {

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2023 Auxio Project
* PlaylistSongSortDialog.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.detail.sort
import android.os.Bundle
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.databinding.DialogSortBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.sort.SortDialog
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.logD
/**
* A [SortDialog] that controls the [Sort] of [DetailViewModel.genreSongSort].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@AndroidEntryPoint
class PlaylistSongSortDialog : SortDialog() {
private val detailModel: DetailViewModel by activityViewModels()
override fun onBindingCreated(binding: DialogSortBinding, savedInstanceState: Bundle?) {
super.onBindingCreated(binding, savedInstanceState)
// --- VIEWMODEL SETUP ---
collectImmediately(detailModel.currentPlaylist, ::updatePlaylist)
}
override fun getInitialSort() = null
override fun applyChosenSort(sort: Sort) {
detailModel.applyPlaylistSongSort(sort)
}
override fun getModeChoices() =
listOf(
Sort.Mode.ByName,
Sort.Mode.ByArtist,
Sort.Mode.ByAlbum,
Sort.Mode.ByDate,
Sort.Mode.ByDuration)
private fun updatePlaylist(genre: Playlist?) {
if (genre == null) {
logD("No genre to sort, navigating away")
findNavController().navigateUp()
}
}
}

View file

@ -62,9 +62,10 @@ abstract class SortDialog :
} }
// --- STATE SETUP --- // --- STATE SETUP ---
modeAdapter.update(getModeChoices(), UpdateInstructions.Diff)
val initial = getInitialSort() val initial = getInitialSort()
if (initial != null) { if (initial != null) {
modeAdapter.update(getModeChoices(), UpdateInstructions.Diff)
modeAdapter.setSelected(initial.mode) modeAdapter.setSelected(initial.mode)
val directionId = val directionId =
when (initial.direction) { when (initial.direction) {

View file

@ -28,14 +28,14 @@
app:icon="@drawable/ic_edit_24" app:icon="@drawable/ic_edit_24"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
<!-- <org.oxycblt.auxio.ui.RippleFixMaterialButton--> <org.oxycblt.auxio.ui.RippleFixMaterialButton
<!-- android:id="@+id/header_sort"--> android:id="@+id/header_sort"
<!-- style="@style/Widget.Auxio.Button.Icon.Small"--> style="@style/Widget.Auxio.Button.Icon.Small"
<!-- android:layout_width="wrap_content"--> android:layout_width="wrap_content"
<!-- android:layout_height="wrap_content"--> android:layout_height="wrap_content"
<!-- android:layout_marginEnd="@dimen/spacing_mid_medium"--> android:layout_marginEnd="@dimen/spacing_mid_medium"
<!-- android:contentDescription="@string/lbl_cancel"--> android:contentDescription="@string/lbl_edit"
<!-- app:icon="@drawable/ic_sort_24"--> app:icon="@drawable/ic_sort_24"
<!-- app:layout_constraintEnd_toEndOf="parent" />--> app:layout_constraintEnd_toEndOf="parent" />
</LinearLayout> </LinearLayout>

View file

@ -309,6 +309,9 @@
<argument <argument
android:name="playlistUid" android:name="playlistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" /> app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/sort"
app:destination="@+id/playlist_song_sort_dialog" />
<action <action
android:id="@+id/show_song" android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" /> app:destination="@id/song_detail_dialog" />
@ -338,6 +341,12 @@
app:destination="@id/play_from_genre_dialog" /> app:destination="@id/play_from_genre_dialog" />
</fragment> </fragment>
<dialog
android:id="@+id/playlist_song_sort_dialog"
android:name="org.oxycblt.auxio.detail.sort.PlaylistSongSortDialog"
android:label="PlaylistSongSortDialog"
tools:layout="@layout/dialog_sort" />
<dialog <dialog
android:id="@+id/new_playlist_dialog" android:id="@+id/new_playlist_dialog"
android:name="org.oxycblt.auxio.music.decision.NewPlaylistDialog" android:name="org.oxycblt.auxio.music.decision.NewPlaylistDialog"