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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
@ -75,6 +74,7 @@ class AlbumDetailFragment :
override val listModel: ListViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels()
override val musicModel: MusicViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels()
override val playbackModel: PlaybackViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels()
// Information about what album to display is initially within the navigation arguments // 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. // as a UID, as that is the only safe way to parcel an album.
private val args: AlbumDetailFragmentArgs by navArgs() private val args: AlbumDetailFragmentArgs by navArgs()
@ -110,7 +110,7 @@ class AlbumDetailFragment :
adapter = ConcatAdapter(albumHeaderAdapter, albumListAdapter) adapter = ConcatAdapter(albumHeaderAdapter, albumListAdapter)
(layoutManager as GridLayoutManager).setFullWidthLookup { (layoutManager as GridLayoutManager).setFullWidthLookup {
if (it != 0) { 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 item is Divider || item is Header || item is Disc
} else { } else {
true true
@ -122,7 +122,7 @@ class AlbumDetailFragment :
// DetailViewModel handles most initialization from the navigation argument. // DetailViewModel handles most initialization from the navigation argument.
detailModel.setAlbum(args.albumUid) detailModel.setAlbum(args.albumUid)
collectImmediately(detailModel.currentAlbum, ::updateAlbum) collectImmediately(detailModel.currentAlbum, ::updateAlbum)
collectImmediately(detailModel.albumList, ::updateList) collectImmediately(detailModel.albumSongList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collect(listModel.menu.flow, ::handleMenu) collect(listModel.menu.flow, ::handleMenu)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
@ -138,7 +138,7 @@ class AlbumDetailFragment :
binding.detailRecycler.adapter = null binding.detailRecycler.adapter = null
// Avoid possible race conditions that could cause a bad replace instruction to be consumed // 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. // 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 { override fun onMenuItemClick(item: MenuItem): Boolean {
@ -181,7 +181,7 @@ class AlbumDetailFragment :
playbackModel.play(item, detailModel.playInAlbumWith) 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) listModel.openMenu(R.menu.item_album_song, item, detailModel.playInAlbumWith)
} }
@ -192,36 +192,9 @@ class AlbumDetailFragment :
override fun onShuffle() { override fun onShuffle() {
playbackModel.shuffle(unlikelyToBeNull(detailModel.currentAlbum.value)) playbackModel.shuffle(unlikelyToBeNull(detailModel.currentAlbum.value))
} }
override fun onOpenSortMenu(anchor: View) { override fun onOpenSortMenu() {
findNavController().navigateSafe(AlbumDetailFragmentDirections.sort()) 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() { override fun onNavigateToParentArtist() {
@ -239,7 +212,7 @@ class AlbumDetailFragment :
} }
private fun updateList(list: List<Item>) { private fun updateList(list: List<Item>) {
albumListAdapter.update(list, detailModel.albumInstructions.consume()) albumListAdapter.update(list, detailModel.albumSongInstructions.consume())
} }
private fun handleShow(show: Show?) { private fun handleShow(show: Show?) {
@ -365,7 +338,7 @@ class AlbumDetailFragment :
private fun scrollToAlbumSong(song: Song) { private fun scrollToAlbumSong(song: Song) {
// Calculate where the item for the currently played song is // 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) { if (pos != -1) {
// Only scroll if the song is within this album. // 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs 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.ListFragment
import org.oxycblt.auxio.list.ListViewModel import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.Menu import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
@ -111,7 +109,7 @@ class ArtistDetailFragment :
(layoutManager as GridLayoutManager).setFullWidthLookup { (layoutManager as GridLayoutManager).setFullWidthLookup {
if (it != 0) { if (it != 0) {
val item = val item =
detailModel.artistList.value.getOrElse(it - 1) { detailModel.artistSongList.value.getOrElse(it - 1) {
return@setFullWidthLookup false return@setFullWidthLookup false
} }
item is Divider || item is Header item is Divider || item is Header
@ -125,7 +123,7 @@ class ArtistDetailFragment :
// DetailViewModel handles most initialization from the navigation argument. // DetailViewModel handles most initialization from the navigation argument.
detailModel.setArtist(args.artistUid) detailModel.setArtist(args.artistUid)
collectImmediately(detailModel.currentArtist, ::updateArtist) collectImmediately(detailModel.currentArtist, ::updateArtist)
collectImmediately(detailModel.artistList, ::updateList) collectImmediately(detailModel.artistSongList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collect(listModel.menu.flow, ::handleMenu) collect(listModel.menu.flow, ::handleMenu)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
@ -141,7 +139,7 @@ class ArtistDetailFragment :
binding.detailRecycler.adapter = null binding.detailRecycler.adapter = null
// Avoid possible race conditions that could cause a bad replace instruction to be consumed // 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. // 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 { 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) { when (item) {
is Song -> is Song ->
listModel.openMenu(R.menu.item_artist_song, item, detailModel.playInArtistWith) listModel.openMenu(R.menu.item_artist_song, item, detailModel.playInArtistWith)
@ -201,33 +199,8 @@ class ArtistDetailFragment :
playbackModel.shuffle(unlikelyToBeNull(detailModel.currentArtist.value)) playbackModel.shuffle(unlikelyToBeNull(detailModel.currentArtist.value))
} }
override fun onOpenSortMenu(anchor: View) { override fun onOpenSortMenu() {
openMenu(anchor, R.menu.sort_artist) { findNavController().navigateSafe(ArtistDetailFragmentDirections.sort())
// 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
}
}
} }
private fun updateArtist(artist: Artist?) { private fun updateArtist(artist: Artist?) {
@ -253,7 +226,7 @@ class ArtistDetailFragment :
} }
private fun updateList(list: List<Item>) { private fun updateList(list: List<Item>) {
artistListAdapter.update(list, detailModel.artistInstructions.consume()) artistListAdapter.update(list, detailModel.artistSongInstructions.consume())
} }
private fun handleShow(show: Show?) { private fun handleShow(show: Show?) {

View file

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

View file

@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs 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.ListFragment
import org.oxycblt.auxio.list.ListViewModel import org.oxycblt.auxio.list.ListViewModel
import org.oxycblt.auxio.list.Menu import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
@ -109,7 +107,7 @@ class GenreDetailFragment :
(layoutManager as GridLayoutManager).setFullWidthLookup { (layoutManager as GridLayoutManager).setFullWidthLookup {
if (it != 0) { if (it != 0) {
val item = val item =
detailModel.genreList.value.getOrElse(it - 1) { detailModel.genreSongList.value.getOrElse(it - 1) {
return@setFullWidthLookup false return@setFullWidthLookup false
} }
item is Divider || item is Header item is Divider || item is Header
@ -123,7 +121,7 @@ class GenreDetailFragment :
// DetailViewModel handles most initialization from the navigation argument. // DetailViewModel handles most initialization from the navigation argument.
detailModel.setGenre(args.genreUid) detailModel.setGenre(args.genreUid)
collectImmediately(detailModel.currentGenre, ::updatePlaylist) collectImmediately(detailModel.currentGenre, ::updatePlaylist)
collectImmediately(detailModel.genreList, ::updateList) collectImmediately(detailModel.genreSongList, ::updateList)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collect(listModel.menu.flow, ::handleMenu) collect(listModel.menu.flow, ::handleMenu)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
@ -139,7 +137,7 @@ class GenreDetailFragment :
binding.detailRecycler.adapter = null binding.detailRecycler.adapter = null
// Avoid possible race conditions that could cause a bad replace instruction to be consumed // 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. // 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 { 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) { when (item) {
is Artist -> listModel.openMenu(R.menu.item_parent, item) is Artist -> listModel.openMenu(R.menu.item_parent, item)
is Song -> listModel.openMenu(R.menu.item_song, item, detailModel.playInGenreWith) is Song -> listModel.openMenu(R.menu.item_song, item, detailModel.playInGenreWith)
@ -198,31 +196,8 @@ class GenreDetailFragment :
playbackModel.shuffle(unlikelyToBeNull(detailModel.currentGenre.value)) playbackModel.shuffle(unlikelyToBeNull(detailModel.currentGenre.value))
} }
override fun onOpenSortMenu(anchor: View) { override fun onOpenSortMenu() {
openMenu(anchor, R.menu.sort_genre) { findNavController().navigateSafe(GenreDetailFragmentDirections.sort())
// 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
}
}
} }
private fun updatePlaylist(genre: Genre?) { private fun updatePlaylist(genre: Genre?) {
@ -236,7 +211,7 @@ class GenreDetailFragment :
} }
private fun updateList(list: List<Item>) { private fun updateList(list: List<Item>) {
genreListAdapter.update(list, detailModel.genreInstructions.consume()) genreListAdapter.update(list, detailModel.genreSongInstructions.consume())
} }
private fun handleShow(show: Show?) { private fun handleShow(show: Show?) {

View file

@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
@ -123,7 +122,7 @@ class PlaylistDetailFragment :
(layoutManager as GridLayoutManager).setFullWidthLookup { (layoutManager as GridLayoutManager).setFullWidthLookup {
if (it != 0) { if (it != 0) {
val item = val item =
detailModel.playlistList.value.getOrElse(it - 1) { detailModel.playlistSongList.value.getOrElse(it - 1) {
return@setFullWidthLookup false return@setFullWidthLookup false
} }
item is Divider || item is Header item is Divider || item is Header
@ -137,7 +136,7 @@ class PlaylistDetailFragment :
// DetailViewModel handles most initialization from the navigation argument. // DetailViewModel handles most initialization from the navigation argument.
detailModel.setPlaylist(args.playlistUid) detailModel.setPlaylist(args.playlistUid)
collectImmediately(detailModel.currentPlaylist, ::updatePlaylist) collectImmediately(detailModel.currentPlaylist, ::updatePlaylist)
collectImmediately(detailModel.playlistList, ::updateList) collectImmediately(detailModel.playlistSongList, ::updateList)
collectImmediately(detailModel.editedPlaylist, ::updateEditedList) collectImmediately(detailModel.editedPlaylist, ::updateEditedList)
collect(detailModel.toShow.flow, ::handleShow) collect(detailModel.toShow.flow, ::handleShow)
collect(listModel.menu.flow, ::handleMenu) collect(listModel.menu.flow, ::handleMenu)
@ -168,7 +167,7 @@ class PlaylistDetailFragment :
binding.detailRecycler.adapter = null binding.detailRecycler.adapter = null
// Avoid possible race conditions that could cause a bad replace instruction to be consumed // 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. // during list initialization and crash the app. Could happen if the user is fast enough.
detailModel.playlistInstructions.consume() detailModel.playlistSongInstructions.consume()
} }
override fun onDestinationChanged( override fun onDestinationChanged(
@ -236,7 +235,7 @@ class PlaylistDetailFragment :
requireNotNull(touchHelper) { "ItemTouchHelper was not available" }.startDrag(viewHolder) 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) listModel.openMenu(R.menu.item_playlist_song, item, detailModel.playInPlaylistWith)
} }
@ -252,7 +251,7 @@ class PlaylistDetailFragment :
detailModel.startPlaylistEdit() detailModel.startPlaylistEdit()
} }
override fun onOpenSortMenu(anchor: View) {} override fun onOpenSortMenu() {}
private fun updatePlaylist(playlist: Playlist?) { private fun updatePlaylist(playlist: Playlist?) {
if (playlist == null) { if (playlist == null) {
@ -278,7 +277,7 @@ class PlaylistDetailFragment :
} }
private fun updateList(list: List<Item>) { private fun updateList(list: List<Item>) {
playlistListAdapter.update(list, detailModel.playlistInstructions.consume()) playlistListAdapter.update(list, detailModel.playlistSongInstructions.consume())
} }
private fun updateEditedList(editedPlaylist: List<Song>?) { 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 * Called when the button in a [SortHeader] item is pressed, requesting that the sort menu
* should be opened. * should be opened.
*/ */
fun onOpenSortMenu(anchor: View) fun onOpenSortMenu()
} }
protected companion object { 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 // Add a Tooltip based on the content description so that the purpose of this
// button can be clear. // button can be clear.
TooltipCompat.setTooltipText(this, contentDescription) 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.activity.result.contract.ActivityResultContracts
import androidx.core.view.MenuCompat import androidx.core.view.MenuCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.iterator
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager 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.ListViewModel
import org.oxycblt.auxio.list.Menu import org.oxycblt.auxio.list.Menu
import org.oxycblt.auxio.list.SelectionFragment import org.oxycblt.auxio.list.SelectionFragment
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.music.IndexingProgress import org.oxycblt.auxio.music.IndexingProgress
import org.oxycblt.auxio.music.IndexingState import org.oxycblt.auxio.music.IndexingState
import org.oxycblt.auxio.music.Music 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.logD
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
import org.oxycblt.auxio.util.navigateSafe 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 * The starting [SelectionFragment] of Auxio. Shows the user's music library and enables navigation
@ -172,7 +169,7 @@ class HomeFragment :
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
collect(homeModel.recreateTabs.flow, ::handleRecreate) collect(homeModel.recreateTabs.flow, ::handleRecreate)
collectImmediately(homeModel.currentTabType, ::updateCurrentTab) collectImmediately(homeModel.currentTabType, ::updateCurrentTab)
collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab) collectImmediately(homeModel.songList, homeModel.isFastScrolling, ::updateFab)
collect(listModel.menu.flow, ::handleMenu) collect(listModel.menu.flow, ::handleMenu)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately(musicModel.indexingState, ::updateIndexerState) collectImmediately(musicModel.indexingState, ::updateIndexerState)
@ -232,41 +229,22 @@ class HomeFragment :
} }
// Handle sort menu // Handle sort menu
R.id.submenu_sorting -> { R.id.action_sort -> {
// Junk click event when opening the menu // Junk click event when opening the menu
true val directions =
} when (homeModel.currentTabType.value) {
R.id.option_sort_asc -> { MusicType.SONGS -> HomeFragmentDirections.sortSongs()
logD("Switching to ascending sorting") MusicType.ALBUMS -> HomeFragmentDirections.sortAlbums()
item.isChecked = true MusicType.ARTISTS -> HomeFragmentDirections.sortArtists()
homeModel.setSortForCurrentTab( MusicType.GENRES -> HomeFragmentDirections.sortGenres()
homeModel MusicType.PLAYLISTS -> HomeFragmentDirections.sortPlaylists()
.getSortForTab(homeModel.currentTabType.value) }
.withDirection(Sort.Direction.ASCENDING)) findNavController().navigateSafe(directions)
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))
true true
} }
else -> { else -> {
val newMode = Sort.Mode.fromItemId(item.itemId) logW("Unexpected menu item selected")
if (newMode != null) { false
// 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
}
} }
} }
} }
@ -300,61 +278,6 @@ class HomeFragment :
private fun updateCurrentTab(tabType: MusicType) { private fun updateCurrentTab(tabType: MusicType) {
val binding = requireBinding() 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 // 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 // scrolling state. This prevents the lift state from being confused as one

View file

@ -55,63 +55,83 @@ constructor(
private val musicSettings: MusicSettings private val musicSettings: MusicSettings
) : ViewModel(), MusicRepository.UpdateListener, HomeSettings.Listener { ) : 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. */ /** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */
val songsList: StateFlow<List<Song>> val songList: StateFlow<List<Song>>
get() = _songsList get() = _songList
private val _songsInstructions = MutableEvent<UpdateInstructions>() private val _songInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for how to update [songsList] in the UI. */ /** Instructions for how to update [songList] in the UI. */
val songsInstructions: Event<UpdateInstructions> val songInstructions: Event<UpdateInstructions>
get() = _songsInstructions get() = _songInstructions
private val _albumsLists = MutableStateFlow(listOf<Album>()) /** The current [Sort] used for [songList]. */
/** A list of [Album]s, sorted by the preferred [Sort], to be shown in the home view. */ val songSort: Sort
val albumsList: StateFlow<List<Album>> get() = musicSettings.songSort
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 [PlaySong] instructions to use when playing a [Song]. */ /** The [PlaySong] instructions to use when playing a [Song]. */
val playWith val playWith
get() = playbackSettings.playInListWith 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 * A list of [MusicType] corresponding to the current [Tab] configuration, excluding invisible
* [Tab]s. * [Tab]s.
@ -157,12 +177,12 @@ constructor(
logD("Refreshing library") logD("Refreshing library")
// Get the each list of items in the library to use as our list data. // Get the each list of items in the library to use as our list data.
// Applying the preferred sorting to them. // Applying the preferred sorting to them.
_songsInstructions.put(UpdateInstructions.Diff) _songInstructions.put(UpdateInstructions.Diff)
_songsList.value = musicSettings.songSort.songs(deviceLibrary.songs) _songList.value = musicSettings.songSort.songs(deviceLibrary.songs)
_albumsInstructions.put(UpdateInstructions.Diff) _albumInstructions.put(UpdateInstructions.Diff)
_albumsLists.value = musicSettings.albumSort.albums(deviceLibrary.albums) _albumList.value = musicSettings.albumSort.albums(deviceLibrary.albums)
_artistsInstructions.put(UpdateInstructions.Diff) _artistInstructions.put(UpdateInstructions.Diff)
_artistsList.value = _artistList.value =
musicSettings.artistSort.artists( musicSettings.artistSort.artists(
if (homeSettings.shouldHideCollaborators) { if (homeSettings.shouldHideCollaborators) {
logD("Filtering collaborator artists") logD("Filtering collaborator artists")
@ -172,15 +192,15 @@ constructor(
logD("Using all artists") logD("Using all artists")
deviceLibrary.artists deviceLibrary.artists
}) })
_genresInstructions.put(UpdateInstructions.Diff) _genreInstructions.put(UpdateInstructions.Diff)
_genresList.value = musicSettings.genreSort.genres(deviceLibrary.genres) _genreList.value = musicSettings.genreSort.genres(deviceLibrary.genres)
} }
val userLibrary = musicRepository.userLibrary val userLibrary = musicRepository.userLibrary
if (changes.userLibrary && userLibrary != null) { if (changes.userLibrary && userLibrary != null) {
logD("Refreshing playlists") logD("Refreshing playlists")
_playlistsInstructions.put(UpdateInstructions.Diff) _playlistInstructions.put(UpdateInstructions.Diff)
_playlistsList.value = musicSettings.playlistSort.playlists(userLibrary.playlists) _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. * @param sort The [Sort] to apply.
* @return The [Sort] preferred for that [Tab]
*/ */
fun getSortForTab(tabType: MusicType) = fun applySongSort(sort: Sort) {
when (tabType) { musicSettings.songSort = sort
MusicType.SONGS -> musicSettings.songSort _songInstructions.put(UpdateInstructions.Replace(0))
MusicType.ALBUMS -> musicSettings.albumSort _songList.value = musicSettings.songSort.songs(_songList.value)
MusicType.ARTISTS -> musicSettings.artistSort }
MusicType.GENRES -> musicSettings.genreSort
MusicType.PLAYLISTS -> musicSettings.playlistSort
}
/** /**
* 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) { fun applyAlbumSort(sort: Sort) {
// Can simply re-sort the current list of items without having to access the library. musicSettings.albumSort = sort
when (val type = _currentTabType.value) { _albumInstructions.put(UpdateInstructions.Replace(0))
MusicType.SONGS -> { _albumList.value = musicSettings.albumSort.albums(_albumList.value)
logD("Updating song [$type] sort mode to $sort") }
musicSettings.songSort = sort
_songsInstructions.put(UpdateInstructions.Replace(0)) /**
_songsList.value = sort.songs(_songsList.value) * Apply a new [Sort] to [artistList].
} *
MusicType.ALBUMS -> { * @param sort The [Sort] to apply.
logD("Updating album [$type] sort mode to $sort") */
musicSettings.albumSort = sort fun applyArtistSort(sort: Sort) {
_albumsInstructions.put(UpdateInstructions.Replace(0)) musicSettings.artistSort = sort
_albumsLists.value = sort.albums(_albumsLists.value) _artistInstructions.put(UpdateInstructions.Replace(0))
} _artistList.value = musicSettings.artistSort.artists(_artistList.value)
MusicType.ARTISTS -> { }
logD("Updating artist [$type] sort mode to $sort")
musicSettings.artistSort = sort /**
_artistsInstructions.put(UpdateInstructions.Replace(0)) * Apply a new [Sort] to [genreList].
_artistsList.value = sort.artists(_artistsList.value) *
} * @param sort The [Sort] to apply.
MusicType.GENRES -> { */
logD("Updating genre [$type] sort mode to $sort") fun applyGenreSort(sort: Sort) {
musicSettings.genreSort = sort musicSettings.genreSort = sort
_genresInstructions.put(UpdateInstructions.Replace(0)) _genreInstructions.put(UpdateInstructions.Replace(0))
_genresList.value = sort.genres(_genresList.value) _genreList.value = musicSettings.genreSort.genres(_genreList.value)
} }
MusicType.PLAYLISTS -> {
logD("Updating playlist [$type] sort mode to $sort") /**
musicSettings.playlistSort = sort * Apply a new [Sort] to [playlistList].
_playlistsInstructions.put(UpdateInstructions.Replace(0)) *
_playlistsList.value = sort.playlists(_playlistsList.value) * @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.os.Bundle
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint 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.Album
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.playback.secsToMs import org.oxycblt.auxio.playback.secsToMs
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.logD
/** /**
* A [ListFragment] that shows a list of [Album]s. * A [ListFragment] that shows a list of [Album]s.
@ -81,7 +80,7 @@ class AlbumListFragment :
listener = this@AlbumListFragment listener = this@AlbumListFragment
} }
collectImmediately(homeModel.albumsList, ::updateAlbums) collectImmediately(homeModel.albumList, ::updateAlbums)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -97,9 +96,9 @@ class AlbumListFragment :
} }
override fun getPopup(pos: Int): String? { 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. // 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 // By Name -> Use Name
is Sort.Mode.ByName -> album.name.thumb is Sort.Mode.ByName -> album.name.thumb
@ -141,12 +140,13 @@ class AlbumListFragment :
detailModel.showAlbum(item) detailModel.showAlbum(item)
} }
override fun onOpenMenu(item: Album, anchor: View) { override fun onOpenMenu(item: Album) {
listModel.openMenu(R.menu.item_album, item) listModel.openMenu(R.menu.item_album, item)
} }
private fun updateAlbums(albums: List<Album>) { 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>) { private fun updateSelection(selection: List<Music>) {

View file

@ -20,7 +20,6 @@ package org.oxycblt.auxio.home.list
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint 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.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
@ -76,7 +74,7 @@ class ArtistListFragment :
listener = this@ArtistListFragment listener = this@ArtistListFragment
} }
collectImmediately(homeModel.artistsList, ::updateArtists) collectImmediately(homeModel.artistList, ::updateArtists)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -92,9 +90,9 @@ class ArtistListFragment :
} }
override fun getPopup(pos: Int): String? { 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. // 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 // By Name -> Use Name
is Sort.Mode.ByName -> artist.name.thumb is Sort.Mode.ByName -> artist.name.thumb
@ -117,12 +115,12 @@ class ArtistListFragment :
detailModel.showArtist(item) detailModel.showArtist(item)
} }
override fun onOpenMenu(item: Artist, anchor: View) { override fun onOpenMenu(item: Artist) {
listModel.openMenu(R.menu.item_parent, item) listModel.openMenu(R.menu.item_parent, item)
} }
private fun updateArtists(artists: List<Artist>) { private fun updateArtists(artists: List<Artist>) {
artistAdapter.update(artists, homeModel.artistsInstructions.consume()) artistAdapter.update(artists, homeModel.artistInstructions.consume())
} }
private fun updateSelection(selection: List<Music>) { private fun updateSelection(selection: List<Music>) {

View file

@ -20,7 +20,6 @@ package org.oxycblt.auxio.home.list
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint 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.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
@ -75,7 +73,7 @@ class GenreListFragment :
listener = this@GenreListFragment listener = this@GenreListFragment
} }
collectImmediately(homeModel.genresList, ::updateGenres) collectImmediately(homeModel.genreList, ::updateGenres)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -91,9 +89,9 @@ class GenreListFragment :
} }
override fun getPopup(pos: Int): String? { 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. // 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 // By Name -> Use Name
is Sort.Mode.ByName -> genre.name.thumb is Sort.Mode.ByName -> genre.name.thumb
@ -116,12 +114,12 @@ class GenreListFragment :
detailModel.showGenre(item) detailModel.showGenre(item)
} }
override fun onOpenMenu(item: Genre, anchor: View) { override fun onOpenMenu(item: Genre) {
listModel.openMenu(R.menu.item_parent, item) listModel.openMenu(R.menu.item_parent, item)
} }
private fun updateGenres(genres: List<Genre>) { private fun updateGenres(genres: List<Genre>) {
genreAdapter.update(genres, homeModel.genresInstructions.consume()) genreAdapter.update(genres, homeModel.genreInstructions.consume())
} }
private fun updateSelection(selection: List<Music>) { private fun updateSelection(selection: List<Music>) {

View file

@ -20,7 +20,6 @@ package org.oxycblt.auxio.home.list
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import org.oxycblt.auxio.R 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.list.recycler.PlaylistViewHolder
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
@ -73,7 +71,7 @@ class PlaylistListFragment :
listener = this@PlaylistListFragment listener = this@PlaylistListFragment
} }
collectImmediately(homeModel.playlistsList, ::updatePlaylists) collectImmediately(homeModel.playlistList, ::updatePlaylists)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -89,9 +87,9 @@ class PlaylistListFragment :
} }
override fun getPopup(pos: Int): String? { 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. // 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 // By Name -> Use Name
is Sort.Mode.ByName -> playlist.name.thumb is Sort.Mode.ByName -> playlist.name.thumb
@ -114,12 +112,12 @@ class PlaylistListFragment :
detailModel.showPlaylist(item) detailModel.showPlaylist(item)
} }
override fun onOpenMenu(item: Playlist, anchor: View) { override fun onOpenMenu(item: Playlist) {
listModel.openMenu(R.menu.item_playlist, item) listModel.openMenu(R.menu.item_playlist, item)
} }
private fun updatePlaylists(playlists: List<Playlist>) { private fun updatePlaylists(playlists: List<Playlist>) {
playlistAdapter.update(playlists, homeModel.playlistsInstructions.consume()) playlistAdapter.update(playlists, homeModel.playlistInstructions.consume())
} }
private fun updateSelection(selection: List<Music>) { private fun updateSelection(selection: List<Music>) {

View file

@ -21,7 +21,6 @@ package org.oxycblt.auxio.home.list
import android.os.Bundle import android.os.Bundle
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint 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.list.recycler.SongViewHolder
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
@ -78,7 +76,7 @@ class SongListFragment :
listener = this@SongListFragment listener = this@SongListFragment
} }
collectImmediately(homeModel.songsList, ::updateSongs) collectImmediately(homeModel.songList, ::updateSongs)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -94,11 +92,11 @@ class SongListFragment :
} }
override fun getPopup(pos: Int): String? { 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. // 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 // 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. // 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 // Name -> Use name
is Sort.Mode.ByName -> song.name.thumb is Sort.Mode.ByName -> song.name.thumb
@ -140,12 +138,12 @@ class SongListFragment :
playbackModel.play(item, homeModel.playWith) 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) listModel.openMenu(R.menu.item_song, item, homeModel.playWith)
} }
private fun updateSongs(songs: List<Song>) { private fun updateSongs(songs: List<Song>) {
songAdapter.update(songs, homeModel.songsInstructions.consume()) songAdapter.update(songs, homeModel.songInstructions.consume())
} }
private fun updateSelection(selection: List<Music>) { 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 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.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.util.logD
/** /**
* A Fragment containing a selectable list. * A Fragment containing a selectable list.
@ -34,14 +29,6 @@ import org.oxycblt.auxio.util.logD
*/ */
abstract class ListFragment<in T : Music, VB : ViewBinding> : abstract class ListFragment<in T : Music, VB : ViewBinding> :
SelectionFragment<VB>(), SelectableListListener<T> { 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 * 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]. * 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) { final override fun onSelect(item: T) {
listModel.select(item) 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. * 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 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. * Called when an item in the list requests that it be selected.
@ -148,6 +147,6 @@ interface SelectableListListener<in T> : ClickableListListener<T> {
true true
} }
// Map the menu button to the menu opening listener. // 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 package org.oxycblt.auxio.list
import androidx.annotation.IdRes
import kotlin.math.max import kotlin.math.max
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
@ -163,8 +162,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
sealed interface Mode { sealed interface Mode {
/** The integer representation of this sort mode. */ /** The integer representation of this sort mode. */
val intCode: Int val intCode: Int
/** The item ID of this sort mode in menu resources. */ /** The string resource of the human-readable name of this sort mode. */
val itemId: Int val stringRes: Int
/** /**
* Get a [Comparator] that sorts [Song]s according to this [Mode]. * 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 override val intCode: Int
get() = IntegerTable.SORT_BY_NAME get() = IntegerTable.SORT_BY_NAME
override val itemId: Int override val stringRes: Int
get() = R.id.option_sort_name get() = R.string.lbl_name
override fun getSongComparator(direction: Direction) = override fun getSongComparator(direction: Direction) =
compareByDynamic(direction, BasicComparator.SONG) compareByDynamic(direction, BasicComparator.SONG)
@ -248,8 +247,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int override val intCode: Int
get() = IntegerTable.SORT_BY_ALBUM get() = IntegerTable.SORT_BY_ALBUM
override val itemId: Int override val stringRes: Int
get() = R.id.option_sort_album get() = R.string.lbl_album
override fun getSongComparator(direction: Direction): Comparator<Song> = override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator( MultiComparator(
@ -268,8 +267,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int override val intCode: Int
get() = IntegerTable.SORT_BY_ARTIST get() = IntegerTable.SORT_BY_ARTIST
override val itemId: Int override val stringRes: Int
get() = R.id.option_sort_artist get() = R.string.lbl_artist
override fun getSongComparator(direction: Direction): Comparator<Song> = override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator( MultiComparator(
@ -297,8 +296,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int override val intCode: Int
get() = IntegerTable.SORT_BY_YEAR get() = IntegerTable.SORT_BY_YEAR
override val itemId: Int override val stringRes: Int
get() = R.id.option_sort_year get() = R.string.lbl_date
override fun getSongComparator(direction: Direction): Comparator<Song> = override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator( MultiComparator(
@ -319,8 +318,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int override val intCode: Int
get() = IntegerTable.SORT_BY_DURATION get() = IntegerTable.SORT_BY_DURATION
override val itemId: Int override val stringRes: Int
get() = R.id.option_sort_duration get() = R.string.lbl_duration
override fun getSongComparator(direction: Direction): Comparator<Song> = override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator( MultiComparator(
@ -354,8 +353,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int override val intCode: Int
get() = IntegerTable.SORT_BY_COUNT get() = IntegerTable.SORT_BY_COUNT
override val itemId: Int override val stringRes: Int
get() = R.id.option_sort_count get() = R.string.lbl_song_count
override fun getAlbumComparator(direction: Direction): Comparator<Album> = override fun getAlbumComparator(direction: Direction): Comparator<Album> =
MultiComparator( MultiComparator(
@ -385,8 +384,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int override val intCode: Int
get() = IntegerTable.SORT_BY_DISC get() = IntegerTable.SORT_BY_DISC
override val itemId: Int override val stringRes: Int
get() = R.id.option_sort_disc get() = R.string.lbl_disc
override fun getSongComparator(direction: Direction): Comparator<Song> = override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator( MultiComparator(
@ -404,8 +403,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int override val intCode: Int
get() = IntegerTable.SORT_BY_TRACK get() = IntegerTable.SORT_BY_TRACK
override val itemId: Int override val stringRes: Int
get() = R.id.option_sort_track get() = R.string.lbl_track
override fun getSongComparator(direction: Direction): Comparator<Song> = override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator( MultiComparator(
@ -424,8 +423,8 @@ data class Sort(val mode: Mode, val direction: Direction) {
override val intCode: Int override val intCode: Int
get() = IntegerTable.SORT_BY_DATE_ADDED get() = IntegerTable.SORT_BY_DATE_ADDED
override val itemId: Int override val stringRes: Int
get() = R.id.option_sort_date_added get() = R.string.lbl_date_added
override fun getSongComparator(direction: Direction): Comparator<Song> = override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator( MultiComparator(
@ -458,27 +457,6 @@ data class Sort(val mode: Mode, val direction: Direction) {
ByDateAdded.intCode -> ByDateAdded ByDateAdded.intCode -> ByDateAdded
else -> null 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) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -54,7 +54,7 @@ class MenuItemViewHolder private constructor(private val binding: ItemMenuOption
* Bind new data to this instance. * Bind new data to this instance.
* *
* @param item The new [MenuItem] to bind. * @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>) { fun bind(item: MenuItem, listener: ClickableListListener<MenuItem>) {
listener.bind(item, this) listener.bind(item, this)

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2023 Auxio Project * 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 * 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 * 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/>. * 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.core.view.updatePadding import android.view.MenuInflater
import org.oxycblt.auxio.databinding.DialogSortBinding import android.view.MenuItem
import org.oxycblt.auxio.list.Sort 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.list.adapter.UpdateInstructions
import org.oxycblt.auxio.ui.ViewBindingBottomSheetDialogFragment 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) super.onBindingCreated(binding, savedInstanceState)
binding.root.setOnApplyWindowInsetsListener { v, insets ->
v.updatePadding(bottom = insets.systemBarInsetsCompat.bottom) // --- UI SETUP ---
insets 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) { when (item) {
is Song -> listModel.openMenu(R.menu.item_song, item, searchModel.playWith) is Song -> listModel.openMenu(R.menu.item_song, item, searchModel.playWith)
is Album -> listModel.openMenu(R.menu.item_album, item) is Album -> listModel.openMenu(R.menu.item_album, item)

View file

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

View file

@ -12,6 +12,21 @@
<action <action
android:id="@+id/search" android:id="@+id/search"
app:destination="@id/search_fragment" /> 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 <action
android:id="@+id/show_song" android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" /> app:destination="@id/song_detail_dialog" />
@ -65,6 +80,36 @@
app:destination="@id/play_from_genre_dialog" /> app:destination="@id/play_from_genre_dialog" />
</fragment> </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 <dialog
android:id="@+id/song_detail_dialog" android:id="@+id/song_detail_dialog"
android:name="org.oxycblt.auxio.detail.SongDetailDialog" android:name="org.oxycblt.auxio.detail.SongDetailDialog"
@ -138,6 +183,9 @@
<argument <argument
android:name="albumUid" android:name="albumUid"
app:argType="org.oxycblt.auxio.music.Music$UID" /> app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/sort"
app:destination="@+id/album_song_sort_dialog" />
<action <action
android:id="@+id/show_song" android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" /> app:destination="@id/song_detail_dialog" />
@ -167,6 +215,12 @@
app:destination="@id/sort_dialog" /> app:destination="@id/sort_dialog" />
</fragment> </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 <fragment
android:id="@+id/artist_detail_fragment" android:id="@+id/artist_detail_fragment"
android:name="org.oxycblt.auxio.detail.ArtistDetailFragment" android:name="org.oxycblt.auxio.detail.ArtistDetailFragment"
@ -175,6 +229,9 @@
<argument <argument
android:name="artistUid" android:name="artistUid"
app:argType="org.oxycblt.auxio.music.Music$UID" /> app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/sort"
app:destination="@+id/artist_song_sort_dialog" />
<action <action
android:id="@+id/show_song" android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" /> app:destination="@id/song_detail_dialog" />
@ -198,6 +255,12 @@
app:destination="@id/play_from_genre_dialog" /> app:destination="@id/play_from_genre_dialog" />
</fragment> </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 <fragment
android:id="@+id/genre_detail_fragment" android:id="@+id/genre_detail_fragment"
android:name="org.oxycblt.auxio.detail.GenreDetailFragment" android:name="org.oxycblt.auxio.detail.GenreDetailFragment"
@ -206,6 +269,9 @@
<argument <argument
android:name="genreUid" android:name="genreUid"
app:argType="org.oxycblt.auxio.music.Music$UID" /> app:argType="org.oxycblt.auxio.music.Music$UID" />
<action
android:id="@+id/sort"
app:destination="@+id/genre_song_sort_dialog" />
<action <action
android:id="@+id/show_song" android:id="@+id/show_song"
app:destination="@id/song_detail_dialog" /> app:destination="@id/song_detail_dialog" />
@ -232,6 +298,12 @@
app:destination="@id/play_from_artist_dialog" /> app:destination="@id/play_from_artist_dialog" />
</fragment> </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 <fragment
android:id="@+id/playlist_detail_fragment" android:id="@+id/playlist_detail_fragment"
android:name="org.oxycblt.auxio.detail.PlaylistDetailFragment" android:name="org.oxycblt.auxio.detail.PlaylistDetailFragment"

View file

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

View file

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

View file

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

View file

@ -279,7 +279,7 @@
<string name="set_playback">Přehrávání</string> <string name="set_playback">Přehrávání</string>
<string name="set_library">Knihovna</string> <string name="set_library">Knihovna</string>
<string name="set_state">Perzistence</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="lbl_playlists">Seznamy skladeb</string>
<string name="desc_playlist_image">Obrázek seznamu skladeb pro %s</string> <string name="desc_playlist_image">Obrázek seznamu skladeb pro %s</string>
<string name="lbl_playlist">Seznam skladeb</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_audio_desc">Ton und Wiedergabeverhalten konfigurieren</string>
<string name="set_state">Persistenz</string> <string name="set_state">Persistenz</string>
<string name="set_replay_gain">Lautstärkeanpassung ReplayGain</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="desc_playlist_image">Wiedergabelistenbild für %s</string>
<string name="lbl_playlist">Wiedergabeliste</string> <string name="lbl_playlist">Wiedergabeliste</string>
<string name="lbl_playlists">Wiedergabelisten</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_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_personalize_desc">Personalizar los controles y el comportamiento de la interfaz de usuario</string>
<string name="set_library">Biblioteca</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="lbl_playlists">Listas de reproducción</string>
<string name="desc_playlist_image">Imagen de la lista de reproducción para %s</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> <string name="lbl_playlist">Lista de reproducción</string>

View file

@ -33,7 +33,7 @@
<string name="lbl_disc">Levy</string> <string name="lbl_disc">Levy</string>
<string name="lbl_track">Raita</string> <string name="lbl_track">Raita</string>
<string name="lbl_date_added">Lisäyspäivä</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_playback">Nyt toistetaan</string>
<string name="lbl_equalizer">Taajuuskorjain</string> <string name="lbl_equalizer">Taajuuskorjain</string>
<string name="lbl_play">Toista</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="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">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="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="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_personalize_desc">Personnaliser les commandes et le comportement de l\'interface utilisateur</string>
<string name="set_action_mode_next">Passer au suivant</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_live">EP en directo</string>
<string name="lbl_ep_remix">EP remix</string> <string name="lbl_ep_remix">EP remix</string>
<string name="lbl_sort_asc">Ascendente</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_equalizer">Ecualizador</string>
<string name="lbl_shuffle_selected">Aleatorio seleccionado</string> <string name="lbl_shuffle_selected">Aleatorio seleccionado</string>
<string name="lbl_sample_rate">Frecuencia de mostraxe</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="set_play_song_from_all">सभी गीतों से चलाएं</string>
<string name="fmt_deletion_info">%s हटाएँ\? इसे पूर्ववत नहीं किया जा सकता।</string> <string name="fmt_deletion_info">%s हटाएँ\? इसे पूर्ववत नहीं किया जा सकता।</string>
<string name="fmt_lib_song_count">लोड किए गए गाने: %d</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_play_selected">चयनित चलाएँ</string>
<string name="lbl_shuffle_selected">फेरबदल का चयन किया गया</string> <string name="lbl_shuffle_selected">फेरबदल का चयन किया गया</string>
<string name="lbl_state_wiped">स्थिति साफ की गई</string> <string name="lbl_state_wiped">स्थिति साफ की गई</string>

View file

@ -255,7 +255,7 @@
<string name="lbl_reset">Resetiraj</string> <string name="lbl_reset">Resetiraj</string>
<string name="set_replay_gain">ReplayGain izjednačavanje glasnoće</string> <string name="set_replay_gain">ReplayGain izjednačavanje glasnoće</string>
<string name="set_dirs_list">Mape</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_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_personalize_desc">Prilagodite kontrole i ponašanje korisničkog sučelja</string>
<string name="set_content_desc">Upravljajte učitavanjem glazbe i slika</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_ep_remix">Remix EP</string>
<string name="lbl_name">Név</string> <string name="lbl_name">Név</string>
<string name="lbl_date">Dátum</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_play_selected">Kiválasztott lejátszása</string>
<string name="lbl_new_playlist">Új lejátszólista</string> <string name="lbl_new_playlist">Új lejátszólista</string>
<string name="def_genre">Ismeretlen műfaj</string> <string name="def_genre">Ismeretlen műfaj</string>

View file

@ -274,7 +274,7 @@
<string name="set_state">Persistenza</string> <string name="set_state">Persistenza</string>
<string name="set_personalize_desc">Personalizza controlli e comportamento dell\'UI</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="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_playlist">Playlist</string>
<string name="lbl_playlists">Playlist</string> <string name="lbl_playlists">Playlist</string>
<string name="set_intelligent_sorting">Ordinazione intelligente</string> <string name="set_intelligent_sorting">Ordinazione intelligente</string>

View file

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

View file

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

View file

@ -270,7 +270,7 @@
<string name="set_state">지속</string> <string name="set_state">지속</string>
<string name="set_behavior">동작</string> <string name="set_behavior">동작</string>
<string name="set_personalize_desc">UI 제어 및 동작 커스텀</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_playlist">재생목록</string>
<string name="lbl_playlists">재생목록</string> <string name="lbl_playlists">재생목록</string>
<string name="desc_playlist_image">%s의 재생 목록 이미지</string> <string name="desc_playlist_image">%s의 재생 목록 이미지</string>

View file

@ -267,7 +267,7 @@
<string name="set_replay_gain">ReplayGain</string> <string name="set_replay_gain">ReplayGain</string>
<string name="set_dirs_list">Aplankalai</string> <string name="set_dirs_list">Aplankalai</string>
<string name="set_state">Pastovumas</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_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="set_intelligent_sorting">Išmanusis rūšiavimas</string>
<string name="lbl_playlist">Grojaraštis</string> <string name="lbl_playlist">Grojaraštis</string>

View file

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

View file

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

View file

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

View file

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

View file

@ -275,7 +275,7 @@
<string name="set_music">Muzyka</string> <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_wipe">Nie można wyczyścić stanu odtwarzania</string>
<string name="err_did_not_save">Nie można zapisać 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_playlists">Playlisty</string>
<string name="lbl_playlist">Playlista</string> <string name="lbl_playlist">Playlista</string>
<string name="desc_playlist_image">Obraz playlisty %s</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_state">Persistência</string>
<string name="set_behavior">Comportamento</string> <string name="set_behavior">Comportamento</string>
<string name="set_dirs_list">Pastas</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">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> <string name="set_intelligent_sorting_desc">Ignore palavras como \"the\" ao classificar por nome (funciona melhor com músicas em inglês)</string>
</resources> </resources>

View file

@ -260,7 +260,7 @@
<item quantity="other">%d artistas</item> <item quantity="other">%d artistas</item>
</plurals> </plurals>
<string name="set_replay_gain">Equalização de volume ReplayGain</string> <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_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_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> <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_play_selected">Redare selecție</string>
<string name="lbl_playlist">Listă de redare</string> <string name="lbl_playlist">Listă de redare</string>
<string name="lbl_playlists">Liste 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="lbl_shuffle_selected">Selecție aleatorie aleasă</string>
<string name="set_action_mode_next">Treceți la următoarea</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> <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_playback">Воспроизведение</string>
<string name="set_dirs_list">Папки</string> <string name="set_dirs_list">Папки</string>
<string name="set_state">Состояние воспроизведения</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_playlist">Плейлист</string>
<string name="lbl_playlists">Плейлисты</string> <string name="lbl_playlists">Плейлисты</string>
<string name="desc_playlist_image">Обложка плейлиста для %s</string> <string name="desc_playlist_image">Обложка плейлиста для %s</string>

View file

@ -39,7 +39,7 @@
<string name="lbl_track">Spår</string> <string name="lbl_track">Spår</string>
<string name="lbl_date_added">Datum tillagt</string> <string name="lbl_date_added">Datum tillagt</string>
<string name="lbl_sort_asc">Stigande</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_playback">Nu spelar</string>
<string name="lbl_equalizer">Utjämnare</string> <string name="lbl_equalizer">Utjämnare</string>
<string name="lbl_play">Spela</string> <string name="lbl_play">Spela</string>

View file

@ -263,7 +263,7 @@
<string name="set_playback">Oynatma</string> <string name="set_playback">Oynatma</string>
<string name="set_library">Kütüphane</string> <string name="set_library">Kütüphane</string>
<string name="set_state">Kalıcılık</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_ui_desc">Uygulamanın temasını ve renklerini değiştirin</string>
<string name="set_dirs_list">Klasörler</string> <string name="set_dirs_list">Klasörler</string>
<string name="set_personalize_desc">Arayüz kontrollerini ve davranışını özelleştirin</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_state">Стан відтворення</string>
<string name="set_audio_desc">Налаштуйте звук і поведінку при відтворенні</string> <string name="set_audio_desc">Налаштуйте звук і поведінку при відтворенні</string>
<string name="set_dirs_list">Папки</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="desc_playlist_image">Зображення списку відтворення для %s</string>
<string name="lbl_playlist">Список відтворення</string> <string name="lbl_playlist">Список відтворення</string>
<string name="lbl_playlists">Списки відтворення</string> <string name="lbl_playlists">Списки відтворення</string>

View file

@ -268,7 +268,7 @@
<string name="set_dirs_list">文件夹</string> <string name="set_dirs_list">文件夹</string>
<string name="set_music">音乐</string> <string name="set_music">音乐</string>
<string name="set_audio_desc">配置声音和播放行为</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_playlist">播放列表</string>
<string name="lbl_playlists">播放列表</string> <string name="lbl_playlists">播放列表</string>
<string name="desc_playlist_image">%s 的播放列表图片</string> <string name="desc_playlist_image">%s 的播放列表图片</string>

View file

@ -105,7 +105,7 @@
<string name="lbl_sort">Sort</string> <string name="lbl_sort">Sort</string>
<string name="lbl_sort_asc">Ascending</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_playback">Now playing</string>
<string name="lbl_equalizer">Equalizer</string> <string name="lbl_equalizer">Equalizer</string>