Merge branch 'dev' into weblate-auxio-strings

This commit is contained in:
Alexander Capehart 2023-07-25 17:36:02 -06:00 committed by GitHub
commit 30890d01ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 1076 additions and 654 deletions

View file

@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
@ -75,6 +74,7 @@ class AlbumDetailFragment :
override val listModel: ListViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels()
// Information about what album to display is initially within the navigation arguments
// as a UID, as that is the only safe way to parcel an album.
private val args: AlbumDetailFragmentArgs by navArgs()
@ -110,7 +110,7 @@ class AlbumDetailFragment :
adapter = ConcatAdapter(albumHeaderAdapter, albumListAdapter)
(layoutManager as GridLayoutManager).setFullWidthLookup {
if (it != 0) {
val item = detailModel.albumList.value[it - 1]
val item = detailModel.albumSongList.value[it - 1]
item is Divider || item is Header || item is Disc
} else {
true
@ -122,7 +122,7 @@ class AlbumDetailFragment :
// DetailViewModel handles most initialization from the navigation argument.
detailModel.setAlbum(args.albumUid)
collectImmediately(detailModel.currentAlbum, ::updateAlbum)
collectImmediately(detailModel.albumList, ::updateList)
collectImmediately(detailModel.albumSongList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow)
collect(listModel.menu.flow, ::handleMenu)
collectImmediately(listModel.selected, ::updateSelection)
@ -138,7 +138,7 @@ class AlbumDetailFragment :
binding.detailRecycler.adapter = null
// Avoid possible race conditions that could cause a bad replace instruction to be consumed
// during list initialization and crash the app. Could happen if the user is fast enough.
detailModel.albumInstructions.consume()
detailModel.albumSongInstructions.consume()
}
override fun onMenuItemClick(item: MenuItem): Boolean {
@ -181,7 +181,7 @@ class AlbumDetailFragment :
playbackModel.play(item, detailModel.playInAlbumWith)
}
override fun onOpenMenu(item: Song, anchor: View) {
override fun onOpenMenu(item: Song) {
listModel.openMenu(R.menu.item_album_song, item, detailModel.playInAlbumWith)
}
@ -192,36 +192,9 @@ class AlbumDetailFragment :
override fun onShuffle() {
playbackModel.shuffle(unlikelyToBeNull(detailModel.currentAlbum.value))
}
override fun onOpenSortMenu(anchor: View) {
override fun onOpenSortMenu() {
findNavController().navigateSafe(AlbumDetailFragmentDirections.sort())
// openMenu(anchor, R.menu.sort_album) {
// // Select the corresponding sort mode option
// val sort = detailModel.albumSongSort
// unlikelyToBeNull(menu.findItem(sort.mode.itemId)).isChecked = true
// // Select the corresponding sort direction option
// val directionItemId =
// when (sort.direction) {
// Sort.Direction.ASCENDING -> R.id.option_sort_asc
// Sort.Direction.DESCENDING -> R.id.option_sort_dec
// }
// unlikelyToBeNull(menu.findItem(directionItemId)).isChecked = true
// setOnMenuItemClickListener { item ->
// item.isChecked = !item.isChecked
// detailModel.albumSongSort =
// when (item.itemId) {
// // Sort direction options
// R.id.option_sort_asc ->
// sort.withDirection(Sort.Direction.ASCENDING)
// R.id.option_sort_dec ->
// sort.withDirection(Sort.Direction.DESCENDING)
// // Any other option is a sort mode
// else ->
// sort.withMode(unlikelyToBeNull(Sort.Mode.fromItemId(item.itemId)))
// }
// true
// }
// }
}
override fun onNavigateToParentArtist() {
@ -239,7 +212,7 @@ class AlbumDetailFragment :
}
private fun updateList(list: List<Item>) {
albumListAdapter.update(list, detailModel.albumInstructions.consume())
albumListAdapter.update(list, detailModel.albumSongInstructions.consume())
}
private fun handleShow(show: Show?) {
@ -365,7 +338,7 @@ class AlbumDetailFragment :
private fun scrollToAlbumSong(song: Song) {
// Calculate where the item for the currently played song is
val pos = detailModel.albumList.value.indexOf(song)
val pos = detailModel.albumSongList.value.indexOf(song)
if (pos != -1) {
// Only scroll if the song is within this album.

View file

@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
@ -41,7 +40,6 @@ import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music
@ -111,7 +109,7 @@ class ArtistDetailFragment :
(layoutManager as GridLayoutManager).setFullWidthLookup {
if (it != 0) {
val item =
detailModel.artistList.value.getOrElse(it - 1) {
detailModel.artistSongList.value.getOrElse(it - 1) {
return@setFullWidthLookup false
}
item is Divider || item is Header
@ -125,7 +123,7 @@ class ArtistDetailFragment :
// DetailViewModel handles most initialization from the navigation argument.
detailModel.setArtist(args.artistUid)
collectImmediately(detailModel.currentArtist, ::updateArtist)
collectImmediately(detailModel.artistList, ::updateList)
collectImmediately(detailModel.artistSongList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow)
collect(listModel.menu.flow, ::handleMenu)
collectImmediately(listModel.selected, ::updateSelection)
@ -141,7 +139,7 @@ class ArtistDetailFragment :
binding.detailRecycler.adapter = null
// Avoid possible race conditions that could cause a bad replace instruction to be consumed
// during list initialization and crash the app. Could happen if the user is fast enough.
detailModel.artistInstructions.consume()
detailModel.artistSongInstructions.consume()
}
override fun onMenuItemClick(item: MenuItem): Boolean {
@ -184,7 +182,7 @@ class ArtistDetailFragment :
}
}
override fun onOpenMenu(item: Music, anchor: View) {
override fun onOpenMenu(item: Music) {
when (item) {
is Song ->
listModel.openMenu(R.menu.item_artist_song, item, detailModel.playInArtistWith)
@ -201,33 +199,8 @@ class ArtistDetailFragment :
playbackModel.shuffle(unlikelyToBeNull(detailModel.currentArtist.value))
}
override fun onOpenSortMenu(anchor: View) {
openMenu(anchor, R.menu.sort_artist) {
// Select the corresponding sort mode option
val sort = detailModel.artistSongSort
unlikelyToBeNull(menu.findItem(sort.mode.itemId)).isChecked = true
// Select the corresponding sort direction option
val directionItemId =
when (sort.direction) {
Sort.Direction.ASCENDING -> R.id.option_sort_asc
Sort.Direction.DESCENDING -> R.id.option_sort_dec
}
unlikelyToBeNull(menu.findItem(directionItemId)).isChecked = true
setOnMenuItemClickListener { item ->
item.isChecked = !item.isChecked
detailModel.artistSongSort =
when (item.itemId) {
// Sort direction options
R.id.option_sort_asc -> sort.withDirection(Sort.Direction.ASCENDING)
R.id.option_sort_dec -> sort.withDirection(Sort.Direction.DESCENDING)
// Any other option is a sort mode
else -> sort.withMode(unlikelyToBeNull(Sort.Mode.fromItemId(item.itemId)))
}
true
}
}
override fun onOpenSortMenu() {
findNavController().navigateSafe(ArtistDetailFragmentDirections.sort())
}
private fun updateArtist(artist: Artist?) {
@ -253,7 +226,7 @@ class ArtistDetailFragment :
}
private fun updateList(list: List<Item>) {
artistListAdapter.update(list, detailModel.artistInstructions.consume())
artistListAdapter.update(list, detailModel.artistSongInstructions.consume())
}
private fun handleShow(show: Show?) {

View file

@ -98,24 +98,19 @@ constructor(
val currentAlbum: StateFlow<Album?>
get() = _currentAlbum
private val _albumList = MutableStateFlow(listOf<Item>())
private val _albumSongList = MutableStateFlow(listOf<Item>())
/** The current list data derived from [currentAlbum]. */
val albumList: StateFlow<List<Item>>
get() = _albumList
val albumSongList: StateFlow<List<Item>>
get() = _albumSongList
private val _albumInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for updating [albumList] in the UI. */
val albumInstructions: Event<UpdateInstructions>
get() = _albumInstructions
private val _albumSongInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for updating [albumSongList] in the UI. */
val albumSongInstructions: Event<UpdateInstructions>
get() = _albumSongInstructions
/** The current [Sort] used for [Song]s in [albumList]. */
var albumSongSort: Sort
/** The current [Sort] used for [Song]s in [albumSongList]. */
val albumSongSort: Sort
get() = musicSettings.albumSongSort
set(value) {
musicSettings.albumSongSort = value
// Refresh the album list to reflect the new sort.
currentAlbum.value?.let { refreshAlbumList(it, true) }
}
/** The [PlaySong] instructions to use when playing a [Song] from [Album] details. */
val playInAlbumWith
@ -128,15 +123,16 @@ constructor(
val currentArtist: StateFlow<Artist?>
get() = _currentArtist
private val _artistList = MutableStateFlow(listOf<Item>())
private val _artistSongList = MutableStateFlow(listOf<Item>())
/** The current list derived from [currentArtist]. */
val artistList: StateFlow<List<Item>> = _artistList
private val _artistInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for updating [artistList] in the UI. */
val artistInstructions: Event<UpdateInstructions>
get() = _artistInstructions
val artistSongList: StateFlow<List<Item>> = _artistSongList
/** The current [Sort] used for [Song]s in [artistList]. */
private val _artistSongInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for updating [artistSongList] in the UI. */
val artistSongInstructions: Event<UpdateInstructions>
get() = _artistSongInstructions
/** The current [Sort] used for [Song]s in [artistSongList]. */
var artistSongSort: Sort
get() = musicSettings.artistSongSort
set(value) {
@ -156,15 +152,16 @@ constructor(
val currentGenre: StateFlow<Genre?>
get() = _currentGenre
private val _genreList = MutableStateFlow(listOf<Item>())
private val _genreSongList = MutableStateFlow(listOf<Item>())
/** The current list data derived from [currentGenre]. */
val genreList: StateFlow<List<Item>> = _genreList
private val _genreInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for updating [artistList] in the UI. */
val genreInstructions: Event<UpdateInstructions>
get() = _genreInstructions
val genreSongList: StateFlow<List<Item>> = _genreSongList
/** The current [Sort] used for [Song]s in [genreList]. */
private val _genreSongInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for updating [artistSongList] in the UI. */
val genreSongInstructions: Event<UpdateInstructions>
get() = _genreSongInstructions
/** The current [Sort] used for [Song]s in [genreSongList]. */
var genreSongSort: Sort
get() = musicSettings.genreSongSort
set(value) {
@ -184,13 +181,14 @@ constructor(
val currentPlaylist: StateFlow<Playlist?>
get() = _currentPlaylist
private val _playlistList = MutableStateFlow(listOf<Item>())
private val _playlistSongList = MutableStateFlow(listOf<Item>())
/** The current list data derived from [currentPlaylist] */
val playlistList: StateFlow<List<Item>> = _playlistList
private val _playlistInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for updating [playlistList] in the UI. */
val playlistInstructions: Event<UpdateInstructions>
get() = _playlistInstructions
val playlistSongList: StateFlow<List<Item>> = _playlistSongList
private val _playlistSongInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for updating [playlistSongList] in the UI. */
val playlistSongInstructions: Event<UpdateInstructions>
get() = _playlistSongInstructions
private val _editedPlaylist = MutableStateFlow<List<Song>?>(null)
/**
@ -351,7 +349,7 @@ constructor(
}
/**
* Set a new [currentAlbum] from it's [Music.UID]. [currentAlbum] and [albumList] will be
* Set a new [currentAlbum] from it's [Music.UID]. [currentAlbum] and [albumSongList] will be
* updated to align with the new [Album].
*
* @param uid The [Music.UID] of the [Album] to update [currentAlbum] to. Must be valid.
@ -366,7 +364,17 @@ constructor(
}
/**
* Set a new [currentArtist] from it's [Music.UID]. [currentArtist] and [artistList] will be
* Apply a new [Sort] to [albumSongList].
*
* @param sort The [Sort] to apply.
*/
fun applyAlbumSongSort(sort: Sort) {
musicSettings.albumSongSort = sort
_currentAlbum.value?.let { refreshAlbumList(it, true) }
}
/**
* Set a new [currentArtist] from it's [Music.UID]. [currentArtist] and [artistSongList] will be
* updated to align with the new [Artist].
*
* @param uid The [Music.UID] of the [Artist] to update [currentArtist] to. Must be valid.
@ -381,7 +389,17 @@ constructor(
}
/**
* Set a new [currentGenre] from it's [Music.UID]. [currentGenre] and [genreList] will be
* Apply a new [Sort] to [artistSongList].
*
* @param sort The [Sort] to apply.
*/
fun applyArtistSongSort(sort: Sort) {
musicSettings.artistSongSort = sort
_currentArtist.value?.let { refreshArtistList(it, true) }
}
/**
* Set a new [currentGenre] from it's [Music.UID]. [currentGenre] and [genreSongList] will be
* updated to align with the new album.
*
* @param uid The [Music.UID] of the [Genre] to update [currentGenre] to. Must be valid.
@ -395,6 +413,16 @@ constructor(
}
}
/**
* Apply a new [Sort] to [genreSongList].
*
* @param sort The [Sort] to apply.
*/
fun applyGenreSongSort(sort: Sort) {
musicSettings.genreSongSort = sort
_currentGenre.value?.let { refreshGenreList(it, true) }
}
/**
* Set a new [currentPlaylist] from it's [Music.UID]. If the [Music.UID] differs,
* [currentPlaylist] and [currentPlaylist] will be updated to align with the new album.
@ -544,8 +572,8 @@ constructor(
}
logD("Update album list to ${list.size} items with $instructions")
_albumInstructions.put(instructions)
_albumList.value = list
_albumSongInstructions.put(instructions)
_albumSongList.value = list
}
private fun refreshArtistList(artist: Artist, replace: Boolean = false) {
@ -607,8 +635,8 @@ constructor(
}
logD("Updating artist list to ${list.size} items with $instructions")
_artistInstructions.put(instructions)
_artistList.value = list.toList()
_artistSongInstructions.put(instructions)
_artistSongList.value = list.toList()
}
private fun refreshGenreList(genre: Genre, replace: Boolean = false) {
@ -633,8 +661,8 @@ constructor(
list.addAll(genreSongSort.songs(genre.songs))
logD("Updating genre list to ${list.size} items with $instructions")
_genreInstructions.put(instructions)
_genreList.value = list
_genreSongInstructions.put(instructions)
_genreSongList.value = list
}
private fun refreshPlaylistList(
@ -653,8 +681,8 @@ constructor(
}
logD("Updating playlist list to ${list.size} items with $instructions")
_playlistInstructions.put(instructions)
_playlistList.value = list
_playlistSongInstructions.put(instructions)
_playlistSongList.value = list
}
/**

View file

@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
@ -41,7 +40,6 @@ import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
@ -109,7 +107,7 @@ class GenreDetailFragment :
(layoutManager as GridLayoutManager).setFullWidthLookup {
if (it != 0) {
val item =
detailModel.genreList.value.getOrElse(it - 1) {
detailModel.genreSongList.value.getOrElse(it - 1) {
return@setFullWidthLookup false
}
item is Divider || item is Header
@ -123,7 +121,7 @@ class GenreDetailFragment :
// DetailViewModel handles most initialization from the navigation argument.
detailModel.setGenre(args.genreUid)
collectImmediately(detailModel.currentGenre, ::updatePlaylist)
collectImmediately(detailModel.genreList, ::updateList)
collectImmediately(detailModel.genreSongList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow)
collect(listModel.menu.flow, ::handleMenu)
collectImmediately(listModel.selected, ::updateSelection)
@ -139,7 +137,7 @@ class GenreDetailFragment :
binding.detailRecycler.adapter = null
// Avoid possible race conditions that could cause a bad replace instruction to be consumed
// during list initialization and crash the app. Could happen if the user is fast enough.
detailModel.genreInstructions.consume()
detailModel.genreSongInstructions.consume()
}
override fun onMenuItemClick(item: MenuItem): Boolean {
@ -182,7 +180,7 @@ class GenreDetailFragment :
}
}
override fun onOpenMenu(item: Music, anchor: View) {
override fun onOpenMenu(item: Music) {
when (item) {
is Artist -> listModel.openMenu(R.menu.item_parent, item)
is Song -> listModel.openMenu(R.menu.item_song, item, detailModel.playInGenreWith)
@ -198,31 +196,8 @@ class GenreDetailFragment :
playbackModel.shuffle(unlikelyToBeNull(detailModel.currentGenre.value))
}
override fun onOpenSortMenu(anchor: View) {
openMenu(anchor, R.menu.sort_genre) {
// Select the corresponding sort mode option
val sort = detailModel.genreSongSort
unlikelyToBeNull(menu.findItem(sort.mode.itemId)).isChecked = true
// Select the corresponding sort direction option
val directionItemId =
when (sort.direction) {
Sort.Direction.ASCENDING -> R.id.option_sort_asc
Sort.Direction.DESCENDING -> R.id.option_sort_dec
}
unlikelyToBeNull(menu.findItem(directionItemId)).isChecked = true
setOnMenuItemClickListener { item ->
item.isChecked = !item.isChecked
detailModel.genreSongSort =
when (item.itemId) {
// Sort direction options
R.id.option_sort_asc -> sort.withDirection(Sort.Direction.ASCENDING)
R.id.option_sort_dec -> sort.withDirection(Sort.Direction.DESCENDING)
// Any other option is a sort mode
else -> sort.withMode(unlikelyToBeNull(Sort.Mode.fromItemId(item.itemId)))
}
true
}
}
override fun onOpenSortMenu() {
findNavController().navigateSafe(GenreDetailFragmentDirections.sort())
}
private fun updatePlaylist(genre: Genre?) {
@ -236,7 +211,7 @@ class GenreDetailFragment :
}
private fun updateList(list: List<Item>) {
genreListAdapter.update(list, detailModel.genreInstructions.consume())
genreListAdapter.update(list, detailModel.genreSongInstructions.consume())
}
private fun handleShow(show: Show?) {

View file

@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import androidx.fragment.app.activityViewModels
import androidx.navigation.NavController
import androidx.navigation.NavDestination
@ -123,7 +122,7 @@ class PlaylistDetailFragment :
(layoutManager as GridLayoutManager).setFullWidthLookup {
if (it != 0) {
val item =
detailModel.playlistList.value.getOrElse(it - 1) {
detailModel.playlistSongList.value.getOrElse(it - 1) {
return@setFullWidthLookup false
}
item is Divider || item is Header
@ -137,7 +136,7 @@ class PlaylistDetailFragment :
// DetailViewModel handles most initialization from the navigation argument.
detailModel.setPlaylist(args.playlistUid)
collectImmediately(detailModel.currentPlaylist, ::updatePlaylist)
collectImmediately(detailModel.playlistList, ::updateList)
collectImmediately(detailModel.playlistSongList, ::updateList)
collectImmediately(detailModel.editedPlaylist, ::updateEditedList)
collect(detailModel.toShow.flow, ::handleShow)
collect(listModel.menu.flow, ::handleMenu)
@ -168,7 +167,7 @@ class PlaylistDetailFragment :
binding.detailRecycler.adapter = null
// Avoid possible race conditions that could cause a bad replace instruction to be consumed
// during list initialization and crash the app. Could happen if the user is fast enough.
detailModel.playlistInstructions.consume()
detailModel.playlistSongInstructions.consume()
}
override fun onDestinationChanged(
@ -236,7 +235,7 @@ class PlaylistDetailFragment :
requireNotNull(touchHelper) { "ItemTouchHelper was not available" }.startDrag(viewHolder)
}
override fun onOpenMenu(item: Song, anchor: View) {
override fun onOpenMenu(item: Song) {
listModel.openMenu(R.menu.item_playlist_song, item, detailModel.playInPlaylistWith)
}
@ -252,7 +251,7 @@ class PlaylistDetailFragment :
detailModel.startPlaylistEdit()
}
override fun onOpenSortMenu(anchor: View) {}
override fun onOpenSortMenu() {}
private fun updatePlaylist(playlist: Playlist?) {
if (playlist == null) {
@ -278,7 +277,7 @@ class PlaylistDetailFragment :
}
private fun updateList(list: List<Item>) {
playlistListAdapter.update(list, detailModel.playlistInstructions.consume())
playlistListAdapter.update(list, detailModel.playlistSongInstructions.consume())
}
private fun updateEditedList(editedPlaylist: List<Song>?) {

View file

@ -82,7 +82,7 @@ abstract class DetailListAdapter(
* Called when the button in a [SortHeader] item is pressed, requesting that the sort menu
* should be opened.
*/
fun onOpenSortMenu(anchor: View)
fun onOpenSortMenu()
}
protected companion object {
@ -132,7 +132,7 @@ private class SortHeaderViewHolder(private val binding: ItemSortHeaderBinding) :
// Add a Tooltip based on the content description so that the purpose of this
// button can be clear.
TooltipCompat.setTooltipText(this, contentDescription)
setOnClickListener(listener::onOpenSortMenu)
setOnClickListener { listener.onOpenSortMenu() }
}
}

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2023 Auxio Project
* AlbumSongSortDialog.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.Album
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.logD
/**
* A [SortDialog] that controls the [Sort] of [DetailViewModel.albumSongSort].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@AndroidEntryPoint
class AlbumSongSortDialog : SortDialog() {
private val detailModel: DetailViewModel by activityViewModels()
override fun onBindingCreated(binding: DialogSortBinding, savedInstanceState: Bundle?) {
super.onBindingCreated(binding, savedInstanceState)
// --- VIEWMODEL SETUP ---
collectImmediately(detailModel.currentAlbum, ::updateAlbum)
}
override fun getInitialSort() = detailModel.albumSongSort
override fun applyChosenSort(sort: Sort) {
detailModel.applyAlbumSongSort(sort)
}
override fun getModeChoices() = listOf(Sort.Mode.ByDisc, Sort.Mode.ByTrack)
private fun updateAlbum(album: Album?) {
if (album == null) {
logD("No album to sort, navigating away")
findNavController().navigateUp()
}
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2023 Auxio Project
* ArtistSongSortDialog.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.Artist
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.logD
/**
* A [SortDialog] that controls the [Sort] of [DetailViewModel.artistSongSort].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@AndroidEntryPoint
class ArtistSongSortDialog : SortDialog() {
private val detailModel: DetailViewModel by activityViewModels()
override fun onBindingCreated(binding: DialogSortBinding, savedInstanceState: Bundle?) {
super.onBindingCreated(binding, savedInstanceState)
// --- VIEWMODEL SETUP ---
collectImmediately(detailModel.currentArtist, ::updateArtist)
}
override fun getInitialSort() = detailModel.artistSongSort
override fun applyChosenSort(sort: Sort) {
detailModel.applyArtistSongSort(sort)
}
override fun getModeChoices() =
listOf(Sort.Mode.ByName, Sort.Mode.ByAlbum, Sort.Mode.ByDate, Sort.Mode.ByDuration)
private fun updateArtist(artist: Artist?) {
if (artist == null) {
logD("No artist to sort, navigating away")
findNavController().navigateUp()
}
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2023 Auxio Project
* GenreSongSortDialog.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.Genre
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 GenreSongSortDialog : SortDialog() {
private val detailModel: DetailViewModel by activityViewModels()
override fun onBindingCreated(binding: DialogSortBinding, savedInstanceState: Bundle?) {
super.onBindingCreated(binding, savedInstanceState)
// --- VIEWMODEL SETUP ---
collectImmediately(detailModel.currentGenre, ::updateGenre)
}
override fun getInitialSort() = detailModel.genreSongSort
override fun applyChosenSort(sort: Sort) {
detailModel.applyGenreSongSort(sort)
}
override fun getModeChoices() =
listOf(
Sort.Mode.ByName,
Sort.Mode.ByArtist,
Sort.Mode.ByAlbum,
Sort.Mode.ByDate,
Sort.Mode.ByDuration)
private fun updateGenre(genre: Genre?) {
if (genre == null) {
logD("No genre to sort, navigating away")
findNavController().navigateUp()
}
}
}

View file

@ -26,7 +26,6 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.MenuCompat
import androidx.core.view.isVisible
import androidx.core.view.iterator
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
@ -57,7 +56,6 @@ import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.SelectionFragment
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.music.IndexingProgress
import org.oxycblt.auxio.music.IndexingState
import org.oxycblt.auxio.music.Music
@ -76,7 +74,6 @@ import org.oxycblt.auxio.util.lazyReflectedField
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
import org.oxycblt.auxio.util.navigateSafe
import org.oxycblt.auxio.util.unlikelyToBeNull
/**
* The starting [SelectionFragment] of Auxio. Shows the user's music library and enables navigation
@ -172,7 +169,7 @@ class HomeFragment :
// --- VIEWMODEL SETUP ---
collect(homeModel.recreateTabs.flow, ::handleRecreate)
collectImmediately(homeModel.currentTabType, ::updateCurrentTab)
collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab)
collectImmediately(homeModel.songList, homeModel.isFastScrolling, ::updateFab)
collect(listModel.menu.flow, ::handleMenu)
collectImmediately(listModel.selected, ::updateSelection)
collectImmediately(musicModel.indexingState, ::updateIndexerState)
@ -232,41 +229,22 @@ class HomeFragment :
}
// Handle sort menu
R.id.submenu_sorting -> {
R.id.action_sort -> {
// Junk click event when opening the menu
true
}
R.id.option_sort_asc -> {
logD("Switching to ascending sorting")
item.isChecked = true
homeModel.setSortForCurrentTab(
homeModel
.getSortForTab(homeModel.currentTabType.value)
.withDirection(Sort.Direction.ASCENDING))
true
}
R.id.option_sort_dec -> {
logD("Switching to descending sorting")
item.isChecked = true
homeModel.setSortForCurrentTab(
homeModel
.getSortForTab(homeModel.currentTabType.value)
.withDirection(Sort.Direction.DESCENDING))
val directions =
when (homeModel.currentTabType.value) {
MusicType.SONGS -> HomeFragmentDirections.sortSongs()
MusicType.ALBUMS -> HomeFragmentDirections.sortAlbums()
MusicType.ARTISTS -> HomeFragmentDirections.sortArtists()
MusicType.GENRES -> HomeFragmentDirections.sortGenres()
MusicType.PLAYLISTS -> HomeFragmentDirections.sortPlaylists()
}
findNavController().navigateSafe(directions)
true
}
else -> {
val newMode = Sort.Mode.fromItemId(item.itemId)
if (newMode != null) {
// Sorting option was selected, mark it as selected and update the mode
logD("Updating sort mode")
item.isChecked = true
homeModel.setSortForCurrentTab(
homeModel.getSortForTab(homeModel.currentTabType.value).withMode(newMode))
true
} else {
logW("Unexpected menu item selected")
false
}
logW("Unexpected menu item selected")
false
}
}
}
@ -300,61 +278,6 @@ class HomeFragment :
private fun updateCurrentTab(tabType: MusicType) {
val binding = requireBinding()
// Update the sort options to align with those allowed by the tab
val isVisible: (Int) -> Boolean =
when (tabType) {
// Disallow sorting by count for songs
MusicType.SONGS -> {
logD("Using song-specific menu options")
({ id -> id != R.id.option_sort_count })
}
// Disallow sorting by album for albums
MusicType.ALBUMS -> {
logD("Using album-specific menu options")
({ id -> id != R.id.option_sort_album })
}
// Only allow sorting by name, count, and duration for parents
else -> {
logD("Using parent-specific menu options")
({ id ->
id == R.id.option_sort_asc ||
id == R.id.option_sort_dec ||
id == R.id.option_sort_name ||
id == R.id.option_sort_count ||
id == R.id.option_sort_duration
})
}
}
val sortMenu =
unlikelyToBeNull(binding.homeNormalToolbar.menu.findItem(R.id.submenu_sorting).subMenu)
val toHighlight = homeModel.getSortForTab(tabType)
for (option in sortMenu) {
val isCurrentMode = option.itemId == toHighlight.mode.itemId
val isCurrentlyAscending =
option.itemId == R.id.option_sort_asc &&
toHighlight.direction == Sort.Direction.ASCENDING
val isCurrentlyDescending =
option.itemId == R.id.option_sort_dec &&
toHighlight.direction == Sort.Direction.DESCENDING
// Check the corresponding direction and mode sort options to align with
// the current sort of the tab.
if (isCurrentMode || isCurrentlyAscending || isCurrentlyDescending) {
logD(
"Checking $option option [mode: $isCurrentMode asc: $isCurrentlyAscending dec: $isCurrentlyDescending]")
// Note: We cannot inline this boolean assignment since it unchecks all other radio
// buttons (even when setting it to false), which would result in nothing being
// selected.
option.isChecked = true
}
// Disable options that are not allowed by the isVisible lambda
option.isVisible = isVisible(option.itemId)
if (!option.isVisible) {
logD("Hiding $option option")
}
}
// Update the scrolling view in AppBarLayout to align with the current tab's
// scrolling state. This prevents the lift state from being confused as one

View file

@ -55,63 +55,83 @@ constructor(
private val musicSettings: MusicSettings
) : ViewModel(), MusicRepository.UpdateListener, HomeSettings.Listener {
private val _songsList = MutableStateFlow(listOf<Song>())
private val _songList = MutableStateFlow(listOf<Song>())
/** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */
val songsList: StateFlow<List<Song>>
get() = _songsList
val songList: StateFlow<List<Song>>
get() = _songList
private val _songsInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for how to update [songsList] in the UI. */
val songsInstructions: Event<UpdateInstructions>
get() = _songsInstructions
private val _songInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for how to update [songList] in the UI. */
val songInstructions: Event<UpdateInstructions>
get() = _songInstructions
private val _albumsLists = MutableStateFlow(listOf<Album>())
/** A list of [Album]s, sorted by the preferred [Sort], to be shown in the home view. */
val albumsList: StateFlow<List<Album>>
get() = _albumsLists
private val _albumsInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for how to update [albumsList] in the UI. */
val albumsInstructions: Event<UpdateInstructions>
get() = _albumsInstructions
private val _artistsList = MutableStateFlow(listOf<Artist>())
/**
* A list of [Artist]s, sorted by the preferred [Sort], to be shown in the home view. Note that
* if "Hide collaborators" is on, this list will not include collaborator [Artist]s.
*/
val artistsList: MutableStateFlow<List<Artist>>
get() = _artistsList
private val _artistsInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for how to update [artistsList] in the UI. */
val artistsInstructions: Event<UpdateInstructions>
get() = _artistsInstructions
private val _genresList = MutableStateFlow(listOf<Genre>())
/** A list of [Genre]s, sorted by the preferred [Sort], to be shown in the home view. */
val genresList: StateFlow<List<Genre>>
get() = _genresList
private val _genresInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for how to update [genresList] in the UI. */
val genresInstructions: Event<UpdateInstructions>
get() = _genresInstructions
private val _playlistsList = MutableStateFlow(listOf<Playlist>())
/** A list of [Playlist]s, sorted by the preferred [Sort], to be shown in the home view. */
val playlistsList: StateFlow<List<Playlist>>
get() = _playlistsList
private val _playlistsInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for how to update [genresList] in the UI. */
val playlistsInstructions: Event<UpdateInstructions>
get() = _playlistsInstructions
/** The current [Sort] used for [songList]. */
val songSort: Sort
get() = musicSettings.songSort
/** The [PlaySong] instructions to use when playing a [Song]. */
val playWith
get() = playbackSettings.playInListWith
private val _albumList = MutableStateFlow(listOf<Album>())
/** A list of [Album]s, sorted by the preferred [Sort], to be shown in the home view. */
val albumList: StateFlow<List<Album>>
get() = _albumList
private val _albumInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for how to update [albumList] in the UI. */
val albumInstructions: Event<UpdateInstructions>
get() = _albumInstructions
/** The current [Sort] used for [albumList]. */
val albumSort: Sort
get() = musicSettings.albumSort
private val _artistList = MutableStateFlow(listOf<Artist>())
/**
* A list of [Artist]s, sorted by the preferred [Sort], to be shown in the home view. Note that
* if "Hide collaborators" is on, this list will not include collaborator [Artist]s.
*/
val artistList: MutableStateFlow<List<Artist>>
get() = _artistList
private val _artistInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for how to update [artistList] in the UI. */
val artistInstructions: Event<UpdateInstructions>
get() = _artistInstructions
/** The current [Sort] used for [artistList]. */
val artistSort: Sort
get() = musicSettings.artistSort
private val _genreList = MutableStateFlow(listOf<Genre>())
/** A list of [Genre]s, sorted by the preferred [Sort], to be shown in the home view. */
val genreList: StateFlow<List<Genre>>
get() = _genreList
private val _genreInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for how to update [genreList] in the UI. */
val genreInstructions: Event<UpdateInstructions>
get() = _genreInstructions
/** The current [Sort] used for [genreList]. */
val genreSort: Sort
get() = musicSettings.genreSort
private val _playlistList = MutableStateFlow(listOf<Playlist>())
/** A list of [Playlist]s, sorted by the preferred [Sort], to be shown in the home view. */
val playlistList: StateFlow<List<Playlist>>
get() = _playlistList
private val _playlistInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for how to update [genreList] in the UI. */
val playlistInstructions: Event<UpdateInstructions>
get() = _playlistInstructions
/** The current [Sort] used for [genreList]. */
val playlistSort: Sort
get() = musicSettings.playlistSort
/**
* A list of [MusicType] corresponding to the current [Tab] configuration, excluding invisible
* [Tab]s.
@ -157,12 +177,12 @@ constructor(
logD("Refreshing library")
// Get the each list of items in the library to use as our list data.
// Applying the preferred sorting to them.
_songsInstructions.put(UpdateInstructions.Diff)
_songsList.value = musicSettings.songSort.songs(deviceLibrary.songs)
_albumsInstructions.put(UpdateInstructions.Diff)
_albumsLists.value = musicSettings.albumSort.albums(deviceLibrary.albums)
_artistsInstructions.put(UpdateInstructions.Diff)
_artistsList.value =
_songInstructions.put(UpdateInstructions.Diff)
_songList.value = musicSettings.songSort.songs(deviceLibrary.songs)
_albumInstructions.put(UpdateInstructions.Diff)
_albumList.value = musicSettings.albumSort.albums(deviceLibrary.albums)
_artistInstructions.put(UpdateInstructions.Diff)
_artistList.value =
musicSettings.artistSort.artists(
if (homeSettings.shouldHideCollaborators) {
logD("Filtering collaborator artists")
@ -172,15 +192,15 @@ constructor(
logD("Using all artists")
deviceLibrary.artists
})
_genresInstructions.put(UpdateInstructions.Diff)
_genresList.value = musicSettings.genreSort.genres(deviceLibrary.genres)
_genreInstructions.put(UpdateInstructions.Diff)
_genreList.value = musicSettings.genreSort.genres(deviceLibrary.genres)
}
val userLibrary = musicRepository.userLibrary
if (changes.userLibrary && userLibrary != null) {
logD("Refreshing playlists")
_playlistsInstructions.put(UpdateInstructions.Diff)
_playlistsList.value = musicSettings.playlistSort.playlists(userLibrary.playlists)
_playlistInstructions.put(UpdateInstructions.Diff)
_playlistList.value = musicSettings.playlistSort.playlists(userLibrary.playlists)
}
}
@ -199,59 +219,58 @@ constructor(
}
/**
* Get the preferred [Sort] for a given [Tab].
* Apply a new [Sort] to [songList].
*
* @param tabType The [MusicType] of the [Tab] desired.
* @return The [Sort] preferred for that [Tab]
* @param sort The [Sort] to apply.
*/
fun getSortForTab(tabType: MusicType) =
when (tabType) {
MusicType.SONGS -> musicSettings.songSort
MusicType.ALBUMS -> musicSettings.albumSort
MusicType.ARTISTS -> musicSettings.artistSort
MusicType.GENRES -> musicSettings.genreSort
MusicType.PLAYLISTS -> musicSettings.playlistSort
}
fun applySongSort(sort: Sort) {
musicSettings.songSort = sort
_songInstructions.put(UpdateInstructions.Replace(0))
_songList.value = musicSettings.songSort.songs(_songList.value)
}
/**
* Update the preferred [Sort] for the current [Tab]. Will update corresponding list.
* Apply a new [Sort] to [albumList].
*
* @param sort The new [Sort] to apply. Assumed to be an allowed sort for the current [Tab].
* @param sort The [Sort] to apply.
*/
fun setSortForCurrentTab(sort: Sort) {
// Can simply re-sort the current list of items without having to access the library.
when (val type = _currentTabType.value) {
MusicType.SONGS -> {
logD("Updating song [$type] sort mode to $sort")
musicSettings.songSort = sort
_songsInstructions.put(UpdateInstructions.Replace(0))
_songsList.value = sort.songs(_songsList.value)
}
MusicType.ALBUMS -> {
logD("Updating album [$type] sort mode to $sort")
musicSettings.albumSort = sort
_albumsInstructions.put(UpdateInstructions.Replace(0))
_albumsLists.value = sort.albums(_albumsLists.value)
}
MusicType.ARTISTS -> {
logD("Updating artist [$type] sort mode to $sort")
musicSettings.artistSort = sort
_artistsInstructions.put(UpdateInstructions.Replace(0))
_artistsList.value = sort.artists(_artistsList.value)
}
MusicType.GENRES -> {
logD("Updating genre [$type] sort mode to $sort")
musicSettings.genreSort = sort
_genresInstructions.put(UpdateInstructions.Replace(0))
_genresList.value = sort.genres(_genresList.value)
}
MusicType.PLAYLISTS -> {
logD("Updating playlist [$type] sort mode to $sort")
musicSettings.playlistSort = sort
_playlistsInstructions.put(UpdateInstructions.Replace(0))
_playlistsList.value = sort.playlists(_playlistsList.value)
}
}
fun applyAlbumSort(sort: Sort) {
musicSettings.albumSort = sort
_albumInstructions.put(UpdateInstructions.Replace(0))
_albumList.value = musicSettings.albumSort.albums(_albumList.value)
}
/**
* Apply a new [Sort] to [artistList].
*
* @param sort The [Sort] to apply.
*/
fun applyArtistSort(sort: Sort) {
musicSettings.artistSort = sort
_artistInstructions.put(UpdateInstructions.Replace(0))
_artistList.value = musicSettings.artistSort.artists(_artistList.value)
}
/**
* Apply a new [Sort] to [genreList].
*
* @param sort The [Sort] to apply.
*/
fun applyGenreSort(sort: Sort) {
musicSettings.genreSort = sort
_genreInstructions.put(UpdateInstructions.Replace(0))
_genreList.value = musicSettings.genreSort.genres(_genreList.value)
}
/**
* Apply a new [Sort] to [playlistList].
*
* @param sort The [Sort] to apply.
*/
fun applyPlaylistSort(sort: Sort) {
musicSettings.playlistSort = sort
_playlistInstructions.put(UpdateInstructions.Replace(0))
_playlistList.value = musicSettings.playlistSort.playlists(_playlistList.value)
}
/**

View file

@ -21,7 +21,6 @@ package org.oxycblt.auxio.home.list
import android.os.Bundle
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint
@ -40,13 +39,13 @@ import org.oxycblt.auxio.list.recycler.AlbumViewHolder
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.playback.secsToMs
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.logD
/**
* A [ListFragment] that shows a list of [Album]s.
@ -81,7 +80,7 @@ class AlbumListFragment :
listener = this@AlbumListFragment
}
collectImmediately(homeModel.albumsList, ::updateAlbums)
collectImmediately(homeModel.albumList, ::updateAlbums)
collectImmediately(listModel.selected, ::updateSelection)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -97,9 +96,9 @@ class AlbumListFragment :
}
override fun getPopup(pos: Int): String? {
val album = homeModel.albumsList.value[pos]
val album = homeModel.albumList.value[pos]
// Change how we display the popup depending on the current sort mode.
return when (homeModel.getSortForTab(MusicType.ALBUMS).mode) {
return when (homeModel.albumSort.mode) {
// By Name -> Use Name
is Sort.Mode.ByName -> album.name.thumb
@ -141,12 +140,13 @@ class AlbumListFragment :
detailModel.showAlbum(item)
}
override fun onOpenMenu(item: Album, anchor: View) {
override fun onOpenMenu(item: Album) {
listModel.openMenu(R.menu.item_album, item)
}
private fun updateAlbums(albums: List<Album>) {
albumAdapter.update(albums, homeModel.albumsInstructions.consume())
logD("Absolute fucking retard")
albumAdapter.update(albums, homeModel.albumInstructions.consume())
}
private fun updateSelection(selection: List<Music>) {

View file

@ -20,7 +20,6 @@ package org.oxycblt.auxio.home.list
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint
@ -38,7 +37,6 @@ import org.oxycblt.auxio.list.recycler.ArtistViewHolder
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
@ -76,7 +74,7 @@ class ArtistListFragment :
listener = this@ArtistListFragment
}
collectImmediately(homeModel.artistsList, ::updateArtists)
collectImmediately(homeModel.artistList, ::updateArtists)
collectImmediately(listModel.selected, ::updateSelection)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -92,9 +90,9 @@ class ArtistListFragment :
}
override fun getPopup(pos: Int): String? {
val artist = homeModel.artistsList.value[pos]
val artist = homeModel.artistList.value[pos]
// Change how we display the popup depending on the current sort mode.
return when (homeModel.getSortForTab(MusicType.ARTISTS).mode) {
return when (homeModel.artistSort.mode) {
// By Name -> Use Name
is Sort.Mode.ByName -> artist.name.thumb
@ -117,12 +115,12 @@ class ArtistListFragment :
detailModel.showArtist(item)
}
override fun onOpenMenu(item: Artist, anchor: View) {
override fun onOpenMenu(item: Artist) {
listModel.openMenu(R.menu.item_parent, item)
}
private fun updateArtists(artists: List<Artist>) {
artistAdapter.update(artists, homeModel.artistsInstructions.consume())
artistAdapter.update(artists, homeModel.artistInstructions.consume())
}
private fun updateSelection(selection: List<Music>) {

View file

@ -20,7 +20,6 @@ package org.oxycblt.auxio.home.list
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint
@ -38,7 +37,6 @@ import org.oxycblt.auxio.list.recycler.GenreViewHolder
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
@ -75,7 +73,7 @@ class GenreListFragment :
listener = this@GenreListFragment
}
collectImmediately(homeModel.genresList, ::updateGenres)
collectImmediately(homeModel.genreList, ::updateGenres)
collectImmediately(listModel.selected, ::updateSelection)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -91,9 +89,9 @@ class GenreListFragment :
}
override fun getPopup(pos: Int): String? {
val genre = homeModel.genresList.value[pos]
val genre = homeModel.genreList.value[pos]
// Change how we display the popup depending on the current sort mode.
return when (homeModel.getSortForTab(MusicType.GENRES).mode) {
return when (homeModel.genreSort.mode) {
// By Name -> Use Name
is Sort.Mode.ByName -> genre.name.thumb
@ -116,12 +114,12 @@ class GenreListFragment :
detailModel.showGenre(item)
}
override fun onOpenMenu(item: Genre, anchor: View) {
override fun onOpenMenu(item: Genre) {
listModel.openMenu(R.menu.item_parent, item)
}
private fun updateGenres(genres: List<Genre>) {
genreAdapter.update(genres, homeModel.genresInstructions.consume())
genreAdapter.update(genres, homeModel.genreInstructions.consume())
}
private fun updateSelection(selection: List<Music>) {

View file

@ -20,7 +20,6 @@ package org.oxycblt.auxio.home.list
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import org.oxycblt.auxio.R
@ -36,7 +35,6 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.PlaylistViewHolder
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song
@ -73,7 +71,7 @@ class PlaylistListFragment :
listener = this@PlaylistListFragment
}
collectImmediately(homeModel.playlistsList, ::updatePlaylists)
collectImmediately(homeModel.playlistList, ::updatePlaylists)
collectImmediately(listModel.selected, ::updateSelection)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -89,9 +87,9 @@ class PlaylistListFragment :
}
override fun getPopup(pos: Int): String? {
val playlist = homeModel.playlistsList.value[pos]
val playlist = homeModel.playlistList.value[pos]
// Change how we display the popup depending on the current sort mode.
return when (homeModel.getSortForTab(MusicType.GENRES).mode) {
return when (homeModel.playlistSort.mode) {
// By Name -> Use Name
is Sort.Mode.ByName -> playlist.name.thumb
@ -114,12 +112,12 @@ class PlaylistListFragment :
detailModel.showPlaylist(item)
}
override fun onOpenMenu(item: Playlist, anchor: View) {
override fun onOpenMenu(item: Playlist) {
listModel.openMenu(R.menu.item_playlist, item)
}
private fun updatePlaylists(playlists: List<Playlist>) {
playlistAdapter.update(playlists, homeModel.playlistsInstructions.consume())
playlistAdapter.update(playlists, homeModel.playlistInstructions.consume())
}
private fun updateSelection(selection: List<Music>) {

View file

@ -21,7 +21,6 @@ package org.oxycblt.auxio.home.list
import android.os.Bundle
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint
@ -38,7 +37,6 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SongViewHolder
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
@ -78,7 +76,7 @@ class SongListFragment :
listener = this@SongListFragment
}
collectImmediately(homeModel.songsList, ::updateSongs)
collectImmediately(homeModel.songList, ::updateSongs)
collectImmediately(listModel.selected, ::updateSelection)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -94,11 +92,11 @@ class SongListFragment :
}
override fun getPopup(pos: Int): String? {
val song = homeModel.songsList.value[pos]
val song = homeModel.songList.value[pos]
// Change how we display the popup depending on the current sort mode.
// Note: We don't use the more correct individual artist name here, as sorts are largely
// based off the names of the parent objects and not the child objects.
return when (homeModel.getSortForTab(MusicType.SONGS).mode) {
return when (homeModel.songSort.mode) {
// Name -> Use name
is Sort.Mode.ByName -> song.name.thumb
@ -140,12 +138,12 @@ class SongListFragment :
playbackModel.play(item, homeModel.playWith)
}
override fun onOpenMenu(item: Song, anchor: View) {
override fun onOpenMenu(item: Song) {
listModel.openMenu(R.menu.item_song, item, homeModel.playWith)
}
private fun updateSongs(songs: List<Song>) {
songAdapter.update(songs, homeModel.songsInstructions.consume())
songAdapter.update(songs, homeModel.songInstructions.consume())
}
private fun updateSelection(selection: List<Music>) {

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2023 Auxio Project
* AlbumSortDialog.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.home.sort
import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.sort.SortDialog
/**
* A [SortDialog] that controls the [Sort] of [HomeViewModel.albumList].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@AndroidEntryPoint
class AlbumSortDialog : SortDialog() {
private val homeModel: HomeViewModel by activityViewModels()
override fun getInitialSort() = homeModel.albumSort
override fun applyChosenSort(sort: Sort) {
homeModel.applyAlbumSort(sort)
}
override fun getModeChoices() =
listOf(
Sort.Mode.ByName,
Sort.Mode.ByArtist,
Sort.Mode.ByDate,
Sort.Mode.ByDuration,
Sort.Mode.ByCount,
Sort.Mode.ByDateAdded)
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 Auxio Project
* ArtistSortDialog.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.home.sort
import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.sort.SortDialog
/**
* A [SortDialog] that controls the [Sort] of [HomeViewModel.artistList].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@AndroidEntryPoint
class ArtistSortDialog : SortDialog() {
private val homeModel: HomeViewModel by activityViewModels()
override fun getInitialSort() = homeModel.artistSort
override fun applyChosenSort(sort: Sort) {
homeModel.applyArtistSort(sort)
}
override fun getModeChoices() =
listOf(Sort.Mode.ByName, Sort.Mode.ByDuration, Sort.Mode.ByCount)
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 Auxio Project
* GenreSortDialog.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.home.sort
import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.sort.SortDialog
/**
* A [SortDialog] that controls the [Sort] of [HomeViewModel.genreList].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@AndroidEntryPoint
class GenreSortDialog : SortDialog() {
private val homeModel: HomeViewModel by activityViewModels()
override fun getInitialSort() = homeModel.genreSort
override fun applyChosenSort(sort: Sort) {
homeModel.applyGenreSort(sort)
}
override fun getModeChoices() =
listOf(Sort.Mode.ByName, Sort.Mode.ByDuration, Sort.Mode.ByCount)
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 Auxio Project
* PlaylistSortDialog.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.home.sort
import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.sort.SortDialog
/**
* A [SortDialog] that controls the [Sort] of [HomeViewModel.playlistList].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@AndroidEntryPoint
class PlaylistSortDialog : SortDialog() {
private val homeModel: HomeViewModel by activityViewModels()
override fun getInitialSort() = homeModel.playlistSort
override fun applyChosenSort(sort: Sort) {
homeModel.applyPlaylistSort(sort)
}
override fun getModeChoices() =
listOf(Sort.Mode.ByName, Sort.Mode.ByDuration, Sort.Mode.ByCount)
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2023 Auxio Project
* SongSortDialog.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.home.sort
import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint
import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.sort.SortDialog
/**
* A [SortDialog] that controls the [Sort] of [HomeViewModel.songList].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@AndroidEntryPoint
class SongSortDialog : SortDialog() {
private val homeModel: HomeViewModel by activityViewModels()
override fun getInitialSort() = homeModel.songSort
override fun applyChosenSort(sort: Sort) {
homeModel.applySongSort(sort)
}
override fun getModeChoices() =
listOf(
Sort.Mode.ByName,
Sort.Mode.ByArtist,
Sort.Mode.ByAlbum,
Sort.Mode.ByDate,
Sort.Mode.ByDuration,
Sort.Mode.ByDateAdded)
}

View file

@ -18,14 +18,9 @@
package org.oxycblt.auxio.list
import android.view.View
import androidx.annotation.MenuRes
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.MenuCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.util.logD
/**
* A Fragment containing a selectable list.
@ -34,14 +29,6 @@ import org.oxycblt.auxio.util.logD
*/
abstract class ListFragment<in T : Music, VB : ViewBinding> :
SelectionFragment<VB>(), SelectableListListener<T> {
private var currentMenu: PopupMenu? = null
override fun onDestroyBinding(binding: VB) {
super.onDestroyBinding(binding)
currentMenu?.dismiss()
currentMenu = null
}
/**
* Called when [onClick] is called, but does not result in the item being selected. This more or
* less corresponds to an [onClick] implementation in a non-[ListFragment].
@ -63,30 +50,4 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
final override fun onSelect(item: T) {
listModel.select(item)
}
/**
* Open a menu. This menu will be managed by the Fragment and closed when the view is destroyed.
* If a menu is already opened, this call is ignored.
*
* @param anchor The [View] to anchor the menu to.
* @param menuRes The resource of the menu to load.
* @param block A block that is ran within [PopupMenu] that allows further configuration.
*/
protected fun openMenu(anchor: View, @MenuRes menuRes: Int, block: PopupMenu.() -> Unit) {
if (currentMenu != null) {
logD("Menu already present, not launching")
return
}
logD("Opening popup menu menu")
currentMenu =
PopupMenu(requireContext(), anchor).apply {
inflate(menuRes)
MenuCompat.setGroupDividerEnabled(menu, true)
block()
setOnDismissListener { currentMenu = null }
show()
}
}
}

View file

@ -115,9 +115,8 @@ interface SelectableListListener<in T> : ClickableListListener<T> {
* Called when an item in the list requests that a menu related to it should be opened.
*
* @param item The [T] item to open a menu for.
* @param anchor The [View] to anchor the menu to.
*/
fun onOpenMenu(item: T, anchor: View)
fun onOpenMenu(item: T)
/**
* Called when an item in the list requests that it be selected.
@ -148,6 +147,6 @@ interface SelectableListListener<in T> : ClickableListListener<T> {
true
}
// Map the menu button to the menu opening listener.
menuButton.setOnClickListener { onOpenMenu(item, it) }
menuButton.setOnClickListener { onOpenMenu(item) }
}
}

View file

@ -18,7 +18,6 @@
package org.oxycblt.auxio.list
import androidx.annotation.IdRes
import kotlin.math.max
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
@ -163,8 +162,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
sealed interface Mode {
/** The integer representation of this sort mode. */
val intCode: Int
/** The item ID of this sort mode in menu resources. */
val itemId: Int
/** The string resource of the human-readable name of this sort mode. */
val stringRes: Int
/**
* Get a [Comparator] that sorts [Song]s according to this [Mode].
@ -220,8 +219,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int
get() = IntegerTable.SORT_BY_NAME
override val itemId: Int
get() = R.id.option_sort_name
override val stringRes: Int
get() = R.string.lbl_name
override fun getSongComparator(direction: Direction) =
compareByDynamic(direction, BasicComparator.SONG)
@ -248,8 +247,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int
get() = IntegerTable.SORT_BY_ALBUM
override val itemId: Int
get() = R.id.option_sort_album
override val stringRes: Int
get() = R.string.lbl_album
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
@ -268,8 +267,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int
get() = IntegerTable.SORT_BY_ARTIST
override val itemId: Int
get() = R.id.option_sort_artist
override val stringRes: Int
get() = R.string.lbl_artist
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
@ -297,8 +296,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int
get() = IntegerTable.SORT_BY_YEAR
override val itemId: Int
get() = R.id.option_sort_year
override val stringRes: Int
get() = R.string.lbl_date
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
@ -319,8 +318,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int
get() = IntegerTable.SORT_BY_DURATION
override val itemId: Int
get() = R.id.option_sort_duration
override val stringRes: Int
get() = R.string.lbl_duration
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
@ -354,8 +353,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int
get() = IntegerTable.SORT_BY_COUNT
override val itemId: Int
get() = R.id.option_sort_count
override val stringRes: Int
get() = R.string.lbl_song_count
override fun getAlbumComparator(direction: Direction): Comparator<Album> =
MultiComparator(
@ -385,8 +384,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int
get() = IntegerTable.SORT_BY_DISC
override val itemId: Int
get() = R.id.option_sort_disc
override val stringRes: Int
get() = R.string.lbl_disc
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
@ -404,8 +403,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int
get() = IntegerTable.SORT_BY_TRACK
override val itemId: Int
get() = R.id.option_sort_track
override val stringRes: Int
get() = R.string.lbl_track
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
@ -424,8 +423,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int
get() = IntegerTable.SORT_BY_DATE_ADDED
override val itemId: Int
get() = R.id.option_sort_date_added
override val stringRes: Int
get() = R.string.lbl_date_added
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
@ -458,27 +457,6 @@ data class Sort(val mode: Mode, val direction: Direction) {
ByDateAdded.intCode -> ByDateAdded
else -> null
}
/**
* Convert a menu item ID into a [Mode].
*
* @param itemId The menu resource ID to convert
* @return A [Mode] corresponding to the given ID, or null if the ID is invalid.
* @see itemId
*/
fun fromItemId(@IdRes itemId: Int) =
when (itemId) {
ByName.itemId -> ByName
ByAlbum.itemId -> ByAlbum
ByArtist.itemId -> ByArtist
ByDate.itemId -> ByDate
ByDuration.itemId -> ByDuration
ByCount.itemId -> ByCount
ByDisc.itemId -> ByDisc
ByTrack.itemId -> ByTrack
ByDateAdded.itemId -> ByDateAdded
else -> null
}
}
}

View file

@ -44,7 +44,7 @@ class MenuItemAdapter(private val listener: ClickableListListener<MenuItem>) :
}
/**
* A [DialogRecyclerView.ViewHolder] that displays a list of menu options based on [MenuItem].
* A [DialogRecyclerView.ViewHolder] that displays a [MenuItem].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@ -54,7 +54,7 @@ class MenuItemViewHolder private constructor(private val binding: ItemMenuOption
* Bind new data to this instance.
*
* @param item The new [MenuItem] to bind.
* @param listener An [ClickableListListener] to bind interactions to.
* @param listener A [ClickableListListener] to bind interactions to.
*/
fun bind(item: MenuItem, listener: ClickableListListener<MenuItem>) {
listener.bind(item, this)

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2023 Auxio Project
* SortDialog.kt is part of Auxio.
* MenuDialogFragment.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,29 +16,126 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.list.sort
package org.oxycblt.auxio.list.menu
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import androidx.core.view.updatePadding
import org.oxycblt.auxio.databinding.DialogSortBinding
import org.oxycblt.auxio.list.Sort
import android.view.MenuInflater
import android.view.MenuItem
import androidx.appcompat.view.menu.MenuBuilder
import androidx.core.view.children
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.DialogMenuBinding
import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.adapter.UpdateInstructions
import org.oxycblt.auxio.ui.ViewBindingBottomSheetDialogFragment
import org.oxycblt.auxio.util.systemBarInsetsCompat
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.logD
class SortDialog : ViewBindingBottomSheetDialogFragment<DialogSortBinding>() {
private val sortAdapter = SortAdapter(Sort.Mode.ByName)
/**
* A [ViewBindingBottomSheetDialogFragment] that displays basic music information and a series of
* options.
*
* @author Alexander Capehart (OxygenCobalt)
*
* TODO: Extend the amount of music info shown in the dialog
*/
abstract class MenuDialogFragment<M : Menu> :
ViewBindingBottomSheetDialogFragment<DialogMenuBinding>(), ClickableListListener<MenuItem> {
protected abstract val menuModel: MenuViewModel
protected abstract val listModel: ListViewModel
private val menuAdapter = MenuItemAdapter(@Suppress("LeakingThis") this)
override fun onCreateBinding(inflater: LayoutInflater) = DialogSortBinding.inflate(inflater)
abstract val parcel: Menu.Parcel
override fun onBindingCreated(binding: DialogSortBinding, savedInstanceState: Bundle?) {
/**
* Get the options to disable in the context of the currently shown [M].
*
* @param menu The currently-shown menu [M].
*/
abstract fun getDisabledItemIds(menu: M): Set<Int>
/**
* Update the displayed information about the currently shown [M].
*
* @param binding The [DialogMenuBinding] to bind information to.
* @param menu The currently-shown menu [M].
*/
abstract fun updateMenu(binding: DialogMenuBinding, menu: M)
/**
* Forward the clicked [MenuItem] to it's corresponding handler in another module.
*
* @param item The [MenuItem] that was clicked.
* @param menu The currently-shown menu [M].
*/
abstract fun onClick(item: MenuItem, menu: M)
override fun onCreateBinding(inflater: LayoutInflater) = DialogMenuBinding.inflate(inflater)
override fun onBindingCreated(binding: DialogMenuBinding, savedInstanceState: Bundle?) {
super.onBindingCreated(binding, savedInstanceState)
binding.root.setOnApplyWindowInsetsListener { v, insets ->
v.updatePadding(bottom = insets.systemBarInsetsCompat.bottom)
insets
// --- UI SETUP ---
binding.menuName.isSelected = true
binding.menuInfo.isSelected = true
binding.menuOptionRecycler.apply {
adapter = menuAdapter
itemAnimator = null
}
binding.sortModeRecycler.adapter = sortAdapter
sortAdapter.update(listOf(Sort.Mode.ByName, Sort.Mode.ByDate), UpdateInstructions.Diff)
// --- VIEWMODEL SETUP ---
listModel.menu.consume()
menuModel.setMenu(parcel)
collectImmediately(menuModel.currentMenu, this::updateMenu)
}
}
override fun onDestroyBinding(binding: DialogMenuBinding) {
super.onDestroyBinding(binding)
binding.menuName.isSelected = false
binding.menuInfo.isSelected = false
binding.menuOptionRecycler.adapter = null
}
private fun updateMenu(menu: Menu?) {
if (menu == null) {
logD("No menu to show, navigating away")
findNavController().navigateUp()
return
}
@Suppress("UNCHECKED_CAST") val casted = menu as? M
check(casted != null) { "Unexpected menu instance ${menu::class.simpleName}" }
// We need to inflate the menu on every menu update since it might have changed
// what options are available (ex. if an artist with no songs has had new songs added).
// Since we don't have (and don't want) a dummy view to inflate this menu, just
// depend on the AndroidX Toolbar internal API and hope for the best.
@SuppressLint("RestrictedApi") val builder = MenuBuilder(requireContext())
MenuInflater(requireContext()).inflate(casted.res, builder)
// Disable any menu options as specified by the impl
val disabledIds = getDisabledItemIds(casted)
val visible =
builder.children.mapTo(mutableListOf()) {
it.isEnabled = !disabledIds.contains(it.itemId)
it
}
menuAdapter.update(visible, UpdateInstructions.Diff)
// Delegate to impl how to show music
updateMenu(requireBinding(), casted)
}
final override fun onClick(item: MenuItem, viewHolder: RecyclerView.ViewHolder) {
// All option selections close the dialog currently.
// TODO: This should change if the app is 100% migrated to menu dialogs
findNavController().navigateUp()
// Delegate to impl on how to handle items
@Suppress("UNCHECKED_CAST") onClick(item, menuModel.currentMenu.value as M)
}
}

View file

@ -0,0 +1,121 @@
/*
* Copyright (c) 2023 Auxio Project
* SortModeAdapter.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.list.sort
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import org.oxycblt.auxio.databinding.ItemSortModeBinding
import org.oxycblt.auxio.list.ClickableListListener
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.list.adapter.FlexibleListAdapter
import org.oxycblt.auxio.list.recycler.DialogRecyclerView
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater
/**
* A [FlexibleListAdapter] that displays a list of [Sort.Mode]s.
*
* @param listener A [ClickableListListener] to bind interactions to.
* @author Alexander Capehart (OxygenCobalt)
*/
class SortModeAdapter(private val listener: ClickableListListener<Sort.Mode>) :
FlexibleListAdapter<Sort.Mode, SortModeViewHolder>(SortModeViewHolder.DIFF_CALLBACK) {
/** The currently selected [Sort.Mode] item in this adapter. */
var currentMode: Sort.Mode? = null
private set
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
SortModeViewHolder.from(parent)
override fun onBindViewHolder(holder: SortModeViewHolder, position: Int) {
throw NotImplementedError()
}
override fun onBindViewHolder(holder: SortModeViewHolder, position: Int, payload: List<Any>) {
val mode = getItem(position)
if (payload.isEmpty()) {
holder.bind(mode, listener)
}
holder.setSelected(mode == currentMode)
}
/**
* Select a new [Sort.Mode] option, unselecting the prior one. Does nothing if [mode] equals
* [currentMode].
*
* @param mode The new [Sort.Mode] to select. Should be in the adapter data.
*/
fun setSelected(mode: Sort.Mode) {
if (mode == currentMode) return
val oldMode = currentList.indexOf(currentMode)
val newMode = currentList.indexOf(mode)
currentMode = mode
if (oldMode > -1) {
notifyItemChanged(oldMode, PAYLOAD_SELECTION_CHANGED)
}
notifyItemChanged(newMode, PAYLOAD_SELECTION_CHANGED)
}
private companion object {
val PAYLOAD_SELECTION_CHANGED = Any()
}
}
/**
* A [DialogRecyclerView.ViewHolder] that displays a [Sort.Mode].
*
* @author Alexander Capehart (OxygenCobalt)
*/
class SortModeViewHolder private constructor(private val binding: ItemSortModeBinding) :
DialogRecyclerView.ViewHolder(binding.root) {
/**
* Bind new data to this instance.
*
* @param mode The new [Sort.Mode] to bind.
* @param listener A [ClickableListListener] to bind interactions to.
*/
fun bind(mode: Sort.Mode, listener: ClickableListListener<Sort.Mode>) {
listener.bind(mode, this)
binding.sortRadio.text = binding.context.getString(mode.stringRes)
}
/**
* Set if this view should be shown as selected or not.
*
* @param selected True if selected, false if not.
*/
fun setSelected(selected: Boolean) {
binding.sortRadio.isChecked = selected
}
companion object {
fun from(parent: View) =
SortModeViewHolder(ItemSortModeBinding.inflate(parent.context.inflater))
val DIFF_CALLBACK =
object : DiffUtil.ItemCallback<Sort.Mode>() {
override fun areItemsTheSame(oldItem: Sort.Mode, newItem: Sort.Mode) =
oldItem == newItem
override fun areContentsTheSame(oldItem: Sort.Mode, newItem: Sort.Mode) =
oldItem == newItem
}
}
}

View file

@ -182,7 +182,7 @@ class SearchFragment : ListFragment<Music, FragmentSearchBinding>() {
}
}
override fun onOpenMenu(item: Music, anchor: View) {
override fun onOpenMenu(item: Music) {
when (item) {
is Song -> listModel.openMenu(R.menu.item_song, item, searchModel.playWith)
is Album -> listModel.openMenu(R.menu.item_album, item)

View file

@ -5,18 +5,17 @@
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
android:id="@+id/menu_handle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!--
Required to use a LinearLayout here for space allocation to stop the BottomSheetDialog
from flipping out and not allowing the RecyclerView to scroll fully.
-->
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
android:id="@+id/menu_handle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/sort_name"
@ -43,9 +42,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/dirs_mode_header"
android:id="@+id/sort_header"
style="@style/Widget.Auxio.TextView.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -53,20 +51,19 @@
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/folder_mode_group"
android:id="@+id/sort_direction_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_tiny"
android:gravity="center"
android:layout_marginHorizontal="@dimen/spacing_medium"
app:checkedButton="@+id/dirs_mode_exclude"
app:layout_constraintTop_toBottomOf="@+id/dirs_mode_header"
app:selectionRequired="true"
app:layout_constraintTop_toBottomOf="@+id/sort_header"
app:selectionRequired="false"
app:singleSelection="true"
tools:layout_editor_absoluteX="24dp">
<org.oxycblt.auxio.ui.RippleFixMaterialButton
android:id="@+id/dirs_mode_exclude"
android:id="@+id/sort_direction_asc"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -75,16 +72,17 @@
tools:icon="@drawable/ic_check_24" />
<org.oxycblt.auxio.ui.RippleFixMaterialButton
android:id="@+id/dirs_mode_include"
android:id="@+id/sort_direction_dsc"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/lbl_sort_dec" />
android:text="@string/lbl_sort_dsc" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<org.oxycblt.auxio.ui.RippleFixMaterialButton
android:id="@+id/sort_cancel"
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -105,7 +103,7 @@
android:text="@string/lbl_ok"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/folder_mode_group" />
app:layout_constraintTop_toBottomOf="@+id/sort_direction_group" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single"
android:id="@+id/sort_modes">
<item
android:id="@+id/option_sort_disc"
android:title="@string/lbl_disc" />
<item
android:id="@+id/option_sort_track"
android:title="@string/lbl_track" />
</group>
<group android:checkableBehavior="single"
android:id="@+id/sort_direction">
<item
android:id="@+id/option_sort_asc"
android:title="@string/lbl_sort_asc" />
<item
android:id="@+id/option_sort_dec"
android:title="@string/lbl_sort_dec" />
</group>
</menu>

View file

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single"
android:id="@+id/sort_modes">
<item
android:id="@+id/option_sort_name"
android:title="@string/lbl_name" />
<item
android:id="@+id/option_sort_album"
android:title="@string/lbl_album" />
<item
android:id="@+id/option_sort_year"
android:title="@string/lbl_date" />
<item
android:id="@+id/option_sort_duration"
android:title="@string/lbl_duration" />
</group>
<group android:checkableBehavior="single"
android:id="@+id/sort_direction">
<item
android:id="@+id/option_sort_asc"
android:title="@string/lbl_sort_asc" />
<item
android:id="@+id/option_sort_dec"
android:title="@string/lbl_sort_dec" />
</group>
</menu>

View file

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single"
android:id="@+id/sort_modes">
<item
android:id="@+id/option_sort_name"
android:title="@string/lbl_name" />
<item
android:id="@+id/option_sort_artist"
android:title="@string/lbl_artist" />
<item
android:id="@+id/option_sort_album"
android:title="@string/lbl_album" />
<item
android:id="@+id/option_sort_year"
android:title="@string/lbl_date" />
<item
android:id="@+id/option_sort_duration"
android:title="@string/lbl_duration" />
</group>
<group android:checkableBehavior="single"
android:id="@+id/sort_direction">
<item
android:id="@+id/option_sort_asc"
android:title="@string/lbl_sort_asc" />
<item
android:id="@+id/option_sort_dec"
android:title="@string/lbl_sort_dec" />
</group>
</menu>

View file

@ -9,46 +9,10 @@
app:showAsAction="ifRoom" />
<item
android:id="@+id/submenu_sorting"
android:id="@+id/action_sort"
android:icon="@drawable/ic_sort_24"
android:title="@string/lbl_sort"
app:showAsAction="ifRoom">
<menu>
<group android:checkableBehavior="single"
android:id="@+id/sort_modes">
<item
android:id="@+id/option_sort_name"
android:title="@string/lbl_name" />
<item
android:id="@+id/option_sort_artist"
android:title="@string/lbl_artist" />
<item
android:id="@+id/option_sort_album"
android:title="@string/lbl_album" />
<item
android:id="@+id/option_sort_year"
android:title="@string/lbl_date" />
<item
android:id="@+id/option_sort_duration"
android:title="@string/lbl_duration" />
<item
android:id="@+id/option_sort_count"
android:title="@string/lbl_song_count" />
<item
android:id="@+id/option_sort_date_added"
android:title="@string/lbl_date_added" />
</group>
<group android:checkableBehavior="single"
android:id="@+id/sort_direction">
<item
android:id="@+id/option_sort_asc"
android:title="@string/lbl_sort_asc" />
<item
android:id="@+id/option_sort_dec"
android:title="@string/lbl_sort_dec" />
</group>
</menu>
</item>
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_settings"

View file

@ -12,6 +12,21 @@
<action
android:id="@+id/search"
app:destination="@id/search_fragment" />
<action
android:id="@+id/sort_songs"
app:destination="@+id/song_sort_dialog" />
<action
android:id="@+id/sort_albums"
app:destination="@+id/album_sort_dialog" />
<action
android:id="@+id/sort_artists"
app:destination="@+id/artist_sort_dialog" />
<action
android:id="@+id/sort_genres"
app:destination="@+id/genre_sort_dialog" />
<action
android:id="@+id/sort_playlists"
app:destination="@+id/playlist_sort_dialog" />
<action
android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" />
@ -65,6 +80,36 @@
app:destination="@id/play_from_genre_dialog" />
</fragment>
<dialog
android:id="@+id/song_sort_dialog"
android:name="org.oxycblt.auxio.home.sort.SongSortDialog"
android:label="song_sort_dialog"
tools:layout="@layout/dialog_sort" />
<dialog
android:id="@+id/album_sort_dialog"
android:name="org.oxycblt.auxio.home.sort.AlbumSortDialog"
android:label="song_sort_dialog"
tools:layout="@layout/dialog_sort" />
<dialog
android:id="@+id/artist_sort_dialog"
android:name="org.oxycblt.auxio.home.sort.ArtistSortDialog"
android:label="song_sort_dialog"
tools:layout="@layout/dialog_sort" />
<dialog
android:id="@+id/genre_sort_dialog"
android:name="org.oxycblt.auxio.home.sort.GenreSortDialog"
android:label="song_sort_dialog"
tools:layout="@layout/dialog_sort" />
<dialog
android:id="@+id/playlist_sort_dialog"
android:name="org.oxycblt.auxio.home.sort.PlaylistSortDialog"
android:label="song_sort_dialog"
tools:layout="@layout/dialog_sort" />
<dialog
android:id="@+id/song_detail_dialog"
android:name="org.oxycblt.auxio.detail.SongDetailDialog"
@ -138,6 +183,9 @@
<argument
android:name="albumUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/sort"
app:destination="@+id/album_song_sort_dialog" />
<action
android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" />
@ -167,6 +215,12 @@
app:destination="@id/sort_dialog" />
</fragment>
<dialog
android:id="@+id/album_song_sort_dialog"
android:name="org.oxycblt.auxio.detail.sort.AlbumSongSortDialog"
android:label="AlbumSongSortDialog"
tools:layout="@layout/dialog_sort" />
<fragment
android:id="@+id/artist_detail_fragment"
android:name="org.oxycblt.auxio.detail.ArtistDetailFragment"
@ -175,6 +229,9 @@
<argument
android:name="artistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/sort"
app:destination="@+id/artist_song_sort_dialog" />
<action
android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" />
@ -198,6 +255,12 @@
app:destination="@id/play_from_genre_dialog" />
</fragment>
<dialog
android:id="@+id/artist_song_sort_dialog"
android:name="org.oxycblt.auxio.detail.sort.ArtistSongSortDialog"
android:label="ArtistSongSortDialog"
tools:layout="@layout/dialog_sort" />
<fragment
android:id="@+id/genre_detail_fragment"
android:name="org.oxycblt.auxio.detail.GenreDetailFragment"
@ -206,6 +269,9 @@
<argument
android:name="genreUid"
app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/sort"
app:destination="@+id/genre_song_sort_dialog" />
<action
android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" />
@ -232,6 +298,12 @@
app:destination="@id/play_from_artist_dialog" />
</fragment>
<dialog
android:id="@+id/genre_song_sort_dialog"
android:name="org.oxycblt.auxio.detail.sort.GenreSongSortDialog"
android:label="GenreSongSortDialog"
tools:layout="@layout/dialog_sort" />
<fragment
android:id="@+id/playlist_detail_fragment"
android:name="org.oxycblt.auxio.detail.PlaylistDetailFragment"

View file

@ -157,7 +157,7 @@
<string name="lbl_shuffle_shortcut_long">تشغيل كل الاغاني بشكل عشوائي</string>
<string name="lbl_ok">حسنا</string>
<string name="lbl_state_restored">اعادة الحالة</string>
<string name="lbl_sort_dec">تنازلي</string>
<string name="lbl_sort_dsc">تنازلي</string>
<string name="lbl_song_detail">عرض الخصائص</string>
<string name="lbl_state_wiped">مسح الحالة</string>
<string name="lbl_live_group">مباشر</string>

View file

@ -61,7 +61,7 @@
<string name="lbl_date_added">تاريخ الإضافة</string>
<string name="lbl_sort">فرز</string>
<string name="lbl_sort_asc">تصاعدياً</string>
<string name="lbl_sort_dec">تنازلياً</string>
<string name="lbl_sort_dsc">تنازلياً</string>
<string name="lbl_playback">يتم الآن تشغيل</string>
<string name="lbl_equalizer">المُعادِل</string>
<string name="lbl_play">تشغيل</string>

View file

@ -77,7 +77,7 @@
<string name="lbl_shuffle_shortcut_short">Ператасаваць</string>
<string name="lbl_cancel">Адмяніць</string>
<string name="lbl_sort_asc">Па ўзрастанні</string>
<string name="lbl_sort_dec">Па змяншэнні</string>
<string name="lbl_sort_dsc">Па змяншэнні</string>
<string name="lbl_play_next">Гуляць далей</string>
<string name="lbl_queue_add">Дадаць у чаргу</string>
<string name="lbl_equalizer">Эквалайзер</string>

View file

@ -279,7 +279,7 @@
<string name="set_playback">Přehrávání</string>
<string name="set_library">Knihovna</string>
<string name="set_state">Perzistence</string>
<string name="lbl_sort_dec">Sestupně</string>
<string name="lbl_sort_dsc">Sestupně</string>
<string name="lbl_playlists">Seznamy skladeb</string>
<string name="desc_playlist_image">Obrázek seznamu skladeb pro %s</string>
<string name="lbl_playlist">Seznam skladeb</string>

View file

@ -270,7 +270,7 @@
<string name="set_audio_desc">Ton und Wiedergabeverhalten konfigurieren</string>
<string name="set_state">Persistenz</string>
<string name="set_replay_gain">Lautstärkeanpassung ReplayGain</string>
<string name="lbl_sort_dec">Absteigend</string>
<string name="lbl_sort_dsc">Absteigend</string>
<string name="desc_playlist_image">Wiedergabelistenbild für %s</string>
<string name="lbl_playlist">Wiedergabeliste</string>
<string name="lbl_playlists">Wiedergabelisten</string>

View file

@ -274,7 +274,7 @@
<string name="set_ui_desc">Cambiar el tema y los colores de la aplicación</string>
<string name="set_personalize_desc">Personalizar los controles y el comportamiento de la interfaz de usuario</string>
<string name="set_library">Biblioteca</string>
<string name="lbl_sort_dec">Descendente</string>
<string name="lbl_sort_dsc">Descendente</string>
<string name="lbl_playlists">Listas de reproducción</string>
<string name="desc_playlist_image">Imagen de la lista de reproducción para %s</string>
<string name="lbl_playlist">Lista de reproducción</string>

View file

@ -33,7 +33,7 @@
<string name="lbl_disc">Levy</string>
<string name="lbl_track">Raita</string>
<string name="lbl_date_added">Lisäyspäivä</string>
<string name="lbl_sort_dec">Laskevasti</string>
<string name="lbl_sort_dsc">Laskevasti</string>
<string name="lbl_playback">Nyt toistetaan</string>
<string name="lbl_equalizer">Taajuuskorjain</string>
<string name="lbl_play">Toista</string>

View file

@ -157,7 +157,7 @@
<string name="lng_observing">Surveillance de votre bibliothèque musicale pour les changements…</string>
<string name="set_round_mode">Couvertures arrondies</string>
<string name="set_round_mode_desc">Activer les coins arrondis sur des éléments d\'interface utilisateur supplémentaires (nécessite que les couvertures d\'album soient arrondies)</string>
<string name="lbl_sort_dec">Descendant</string>
<string name="lbl_sort_dsc">Descendant</string>
<string name="lbl_state_restored">Etat restauré</string>
<string name="set_personalize_desc">Personnaliser les commandes et le comportement de l\'interface utilisateur</string>
<string name="set_action_mode_next">Passer au suivant</string>

View file

@ -124,7 +124,7 @@
<string name="lbl_ep_live">EP en directo</string>
<string name="lbl_ep_remix">EP remix</string>
<string name="lbl_sort_asc">Ascendente</string>
<string name="lbl_sort_dec">Descendente</string>
<string name="lbl_sort_dsc">Descendente</string>
<string name="lbl_equalizer">Ecualizador</string>
<string name="lbl_shuffle_selected">Aleatorio seleccionado</string>
<string name="lbl_sample_rate">Frecuencia de mostraxe</string>

View file

@ -99,7 +99,7 @@
<string name="set_play_song_from_all">सभी गीतों से चलाएं</string>
<string name="fmt_deletion_info">%s हटाएँ\? इसे पूर्ववत नहीं किया जा सकता।</string>
<string name="fmt_lib_song_count">लोड किए गए गाने: %d</string>
<string name="lbl_sort_dec">अवरोही</string>
<string name="lbl_sort_dsc">अवरोही</string>
<string name="lbl_play_selected">चयनित चलाएँ</string>
<string name="lbl_shuffle_selected">फेरबदल का चयन किया गया</string>
<string name="lbl_state_wiped">स्थिति साफ की गई</string>

View file

@ -255,7 +255,7 @@
<string name="lbl_reset">Resetiraj</string>
<string name="set_replay_gain">ReplayGain izjednačavanje glasnoće</string>
<string name="set_dirs_list">Mape</string>
<string name="lbl_sort_dec">Silazni</string>
<string name="lbl_sort_dsc">Silazni</string>
<string name="set_ui_desc">Promijenite temu i boje aplikacije</string>
<string name="set_personalize_desc">Prilagodite kontrole i ponašanje korisničkog sučelja</string>
<string name="set_content_desc">Upravljajte učitavanjem glazbe i slika</string>

View file

@ -74,7 +74,7 @@
<string name="lbl_ep_remix">Remix EP</string>
<string name="lbl_name">Név</string>
<string name="lbl_date">Dátum</string>
<string name="lbl_sort_dec">Csökkenő</string>
<string name="lbl_sort_dsc">Csökkenő</string>
<string name="lbl_play_selected">Kiválasztott lejátszása</string>
<string name="lbl_new_playlist">Új lejátszólista</string>
<string name="def_genre">Ismeretlen műfaj</string>

View file

@ -274,7 +274,7 @@
<string name="set_state">Persistenza</string>
<string name="set_personalize_desc">Personalizza controlli e comportamento dell\'UI</string>
<string name="set_audio_desc">Configura comportamento di suono e riproduzione</string>
<string name="lbl_sort_dec">Discendente</string>
<string name="lbl_sort_dsc">Discendente</string>
<string name="lbl_playlist">Playlist</string>
<string name="lbl_playlists">Playlist</string>
<string name="set_intelligent_sorting">Ordinazione intelligente</string>

View file

@ -36,7 +36,7 @@
<string name="lbl_date_added">תאריך הוספה</string>
<string name="lbl_sort">מיון</string>
<string name="lbl_sort_asc">עולה</string>
<string name="lbl_sort_dec">יורד</string>
<string name="lbl_sort_dsc">יורד</string>
<string name="lbl_playback">מושמע כעת</string>
<string name="lbl_equalizer">איקוולייזר</string>
<string name="lbl_play">ניגון</string>

View file

@ -60,7 +60,7 @@
<string name="lbl_file_name">ファイル名</string>
<string name="lbl_date_added">追加した日付け</string>
<string name="lbl_sample_rate">サンプルレート</string>
<string name="lbl_sort_dec">降順</string>
<string name="lbl_sort_dsc">降順</string>
<string name="lbl_play">再生</string>
<string name="lbl_shuffle">シャフル</string>
<string name="lbl_shuffle_selected">選択曲をシャフル</string>

View file

@ -270,7 +270,7 @@
<string name="set_state">지속</string>
<string name="set_behavior">동작</string>
<string name="set_personalize_desc">UI 제어 및 동작 커스텀</string>
<string name="lbl_sort_dec">내림차순</string>
<string name="lbl_sort_dsc">내림차순</string>
<string name="lbl_playlist">재생목록</string>
<string name="lbl_playlists">재생목록</string>
<string name="desc_playlist_image">%s의 재생 목록 이미지</string>

View file

@ -267,7 +267,7 @@
<string name="set_replay_gain">ReplayGain</string>
<string name="set_dirs_list">Aplankalai</string>
<string name="set_state">Pastovumas</string>
<string name="lbl_sort_dec">Mažėjantis</string>
<string name="lbl_sort_dsc">Mažėjantis</string>
<string name="set_intelligent_sorting_desc">Teisingai surūšiuoti pavadinimus, kurie prasideda skaičiais arba žodžiais, tokiais kaip „the“ (geriausiai veikia su anglų kalbos muzika)</string>
<string name="set_intelligent_sorting">Išmanusis rūšiavimas</string>
<string name="lbl_playlist">Grojaraštis</string>

View file

@ -90,7 +90,7 @@
<string name="lbl_artist_details">കലാകാരനിലേക്ക് പോകുക</string>
<string name="lbl_song_detail">സവിശേഷതകൾ കാണുക</string>
<string name="lbl_state_saved">സ്ഥിതി സംരക്ഷിച്ചു</string>
<string name="lbl_sort_dec">അവരോഹണം</string>
<string name="lbl_sort_dsc">അവരോഹണം</string>
<string name="lbl_state_restored">സ്ഥിതി പുനഃസ്ഥാപിച്ചു</string>
<string name="lbl_wiki">വിക്കി</string>
<string name="lbl_state_wiped">സ്ഥിതി മായ്ച്ചു</string>

View file

@ -201,7 +201,7 @@
<item quantity="one">%d album</item>
<item quantity="other">%d album</item>
</plurals>
<string name="lbl_sort_dec">Synkende</string>
<string name="lbl_sort_dsc">Synkende</string>
<string name="lbl_track">Spor</string>
<string name="lbl_date_added">Dato tillagt</string>
<string name="lbl_sort">Sorter</string>

View file

@ -246,7 +246,7 @@
<string name="lbl_genre">Genre</string>
<string name="set_separators_and">Ampersand (&amp;)</string>
<string name="lbl_edit">Bewerken</string>
<string name="lbl_sort_dec">Aflopend</string>
<string name="lbl_sort_dsc">Aflopend</string>
<string name="err_did_not_wipe">Kan status niet wissen</string>
<string name="desc_playlist_image">Afspeellijst-afbeelding voor %s</string>
<string name="def_song_count">Geen nummers</string>

View file

@ -75,7 +75,7 @@
<string name="info_app_desc">ਐਂਡਰੌਇਡ ਲਈ ਇੱਕ ਸਰਲ, ਤਰਕਸੰਗਤ ਸੰਗੀਤ ਪਲੇਅਰ।</string>
<string name="lbl_search">ਖੋਜੋ</string>
<string name="lbl_song_count">ਗੀਤ ਦੀ ਗਿਣਤੀ</string>
<string name="lbl_sort_dec">ਘਟਦੇ ਹੋਏ</string>
<string name="lbl_sort_dsc">ਘਟਦੇ ਹੋਏ</string>
<string name="lbl_play_selected">ਚੁਣਿਆ ਹੋਇਆ ਚਲਾਓ</string>
<string name="lbl_artist_details">ਕਲਾਕਾਰ \'ਤੇ ਜਾਓ</string>
<string name="lbl_file_name">ਫਾਈਲ ਦਾ ਨਾਮ</string>

View file

@ -275,7 +275,7 @@
<string name="set_music">Muzyka</string>
<string name="err_did_not_wipe">Nie można wyczyścić stanu odtwarzania</string>
<string name="err_did_not_save">Nie można zapisać stanu odtwarzania</string>
<string name="lbl_sort_dec">Malejąco</string>
<string name="lbl_sort_dsc">Malejąco</string>
<string name="lbl_playlists">Playlisty</string>
<string name="lbl_playlist">Playlista</string>
<string name="desc_playlist_image">Obraz playlisty %s</string>

View file

@ -272,7 +272,7 @@
<string name="set_state">Persistência</string>
<string name="set_behavior">Comportamento</string>
<string name="set_dirs_list">Pastas</string>
<string name="lbl_sort_dec">Descendente</string>
<string name="lbl_sort_dsc">Descendente</string>
<string name="set_intelligent_sorting">Ignorar artigos ao classificar</string>
<string name="set_intelligent_sorting_desc">Ignore palavras como \"the\" ao classificar por nome (funciona melhor com músicas em inglês)</string>
</resources>

View file

@ -260,7 +260,7 @@
<item quantity="other">%d artistas</item>
</plurals>
<string name="set_replay_gain">Equalização de volume ReplayGain</string>
<string name="lbl_sort_dec">Descendente</string>
<string name="lbl_sort_dsc">Descendente</string>
<string name="set_ui_desc">Mude o tema e as cores do app</string>
<string name="set_personalize_desc">Personalize os controles e o comportamento da interface do usuário</string>
<string name="set_content_desc">Controle como a música e as imagens são carregadas</string>

View file

@ -137,7 +137,7 @@
<string name="lbl_play_selected">Redare selecție</string>
<string name="lbl_playlist">Listă de redare</string>
<string name="lbl_playlists">Liste de redare</string>
<string name="lbl_sort_dec">Descrescător</string>
<string name="lbl_sort_dsc">Descrescător</string>
<string name="lbl_shuffle_selected">Selecție aleatorie aleasă</string>
<string name="set_action_mode_next">Treceți la următoarea</string>
<string name="set_play_song_from_artist">Redă de la artist</string>

View file

@ -277,7 +277,7 @@
<string name="set_playback">Воспроизведение</string>
<string name="set_dirs_list">Папки</string>
<string name="set_state">Состояние воспроизведения</string>
<string name="lbl_sort_dec">По убыванию</string>
<string name="lbl_sort_dsc">По убыванию</string>
<string name="lbl_playlist">Плейлист</string>
<string name="lbl_playlists">Плейлисты</string>
<string name="desc_playlist_image">Обложка плейлиста для %s</string>

View file

@ -39,7 +39,7 @@
<string name="lbl_track">Spår</string>
<string name="lbl_date_added">Datum tillagt</string>
<string name="lbl_sort_asc">Stigande</string>
<string name="lbl_sort_dec">Fallande</string>
<string name="lbl_sort_dsc">Fallande</string>
<string name="lbl_playback">Nu spelar</string>
<string name="lbl_equalizer">Utjämnare</string>
<string name="lbl_play">Spela</string>

View file

@ -263,7 +263,7 @@
<string name="set_playback">Oynatma</string>
<string name="set_library">Kütüphane</string>
<string name="set_state">Kalıcılık</string>
<string name="lbl_sort_dec">Azalan</string>
<string name="lbl_sort_dsc">Azalan</string>
<string name="set_ui_desc">Uygulamanın temasını ve renklerini değiştirin</string>
<string name="set_dirs_list">Klasörler</string>
<string name="set_personalize_desc">Arayüz kontrollerini ve davranışını özelleştirin</string>

View file

@ -274,7 +274,7 @@
<string name="set_state">Стан відтворення</string>
<string name="set_audio_desc">Налаштуйте звук і поведінку при відтворенні</string>
<string name="set_dirs_list">Папки</string>
<string name="lbl_sort_dec">За спаданням</string>
<string name="lbl_sort_dsc">За спаданням</string>
<string name="desc_playlist_image">Зображення списку відтворення для %s</string>
<string name="lbl_playlist">Список відтворення</string>
<string name="lbl_playlists">Списки відтворення</string>

View file

@ -268,7 +268,7 @@
<string name="set_dirs_list">文件夹</string>
<string name="set_music">音乐</string>
<string name="set_audio_desc">配置声音和播放行为</string>
<string name="lbl_sort_dec">降序</string>
<string name="lbl_sort_dsc">降序</string>
<string name="lbl_playlist">播放列表</string>
<string name="lbl_playlists">播放列表</string>
<string name="desc_playlist_image">%s 的播放列表图片</string>

View file

@ -105,7 +105,7 @@
<string name="lbl_sort">Sort</string>
<string name="lbl_sort_asc">Ascending</string>
<string name="lbl_sort_dec">Descending</string>
<string name="lbl_sort_dsc">Descending</string>
<string name="lbl_playback">Now playing</string>
<string name="lbl_equalizer">Equalizer</string>