Merge branch 'dev' into weblate-auxio-strings
This commit is contained in:
commit
30890d01ab
65 changed files with 1076 additions and 654 deletions
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,35 +193,8 @@ class AlbumDetailFragment :
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -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?) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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?) {
|
||||||
|
|
|
||||||
|
|
@ -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>?) {
|
||||||
|
|
|
||||||
|
|
@ -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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,44 +229,25 @@ 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) {
|
||||||
|
MusicType.SONGS -> HomeFragmentDirections.sortSongs()
|
||||||
|
MusicType.ALBUMS -> HomeFragmentDirections.sortAlbums()
|
||||||
|
MusicType.ARTISTS -> HomeFragmentDirections.sortArtists()
|
||||||
|
MusicType.GENRES -> HomeFragmentDirections.sortGenres()
|
||||||
|
MusicType.PLAYLISTS -> HomeFragmentDirections.sortPlaylists()
|
||||||
}
|
}
|
||||||
R.id.option_sort_asc -> {
|
findNavController().navigateSafe(directions)
|
||||||
logD("Switching to ascending sorting")
|
|
||||||
item.isChecked = true
|
|
||||||
homeModel.setSortForCurrentTab(
|
|
||||||
homeModel
|
|
||||||
.getSortForTab(homeModel.currentTabType.value)
|
|
||||||
.withDirection(Sort.Direction.ASCENDING))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
R.id.option_sort_dec -> {
|
|
||||||
logD("Switching to descending sorting")
|
|
||||||
item.isChecked = true
|
|
||||||
homeModel.setSortForCurrentTab(
|
|
||||||
homeModel
|
|
||||||
.getSortForTab(homeModel.currentTabType.value)
|
|
||||||
.withDirection(Sort.Direction.DESCENDING))
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val newMode = Sort.Mode.fromItemId(item.itemId)
|
|
||||||
if (newMode != null) {
|
|
||||||
// Sorting option was selected, mark it as selected and update the mode
|
|
||||||
logD("Updating sort mode")
|
|
||||||
item.isChecked = true
|
|
||||||
homeModel.setSortForCurrentTab(
|
|
||||||
homeModel.getSortForTab(homeModel.currentTabType.value).withMode(newMode))
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
logW("Unexpected menu item selected")
|
logW("Unexpected menu item selected")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupPager(binding: FragmentHomeBinding) {
|
private fun setupPager(binding: FragmentHomeBinding) {
|
||||||
binding.homePager.adapter =
|
binding.homePager.adapter =
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
when (val type = _currentTabType.value) {
|
|
||||||
MusicType.SONGS -> {
|
|
||||||
logD("Updating song [$type] sort mode to $sort")
|
|
||||||
musicSettings.songSort = sort
|
|
||||||
_songsInstructions.put(UpdateInstructions.Replace(0))
|
|
||||||
_songsList.value = sort.songs(_songsList.value)
|
|
||||||
}
|
|
||||||
MusicType.ALBUMS -> {
|
|
||||||
logD("Updating album [$type] sort mode to $sort")
|
|
||||||
musicSettings.albumSort = sort
|
musicSettings.albumSort = sort
|
||||||
_albumsInstructions.put(UpdateInstructions.Replace(0))
|
_albumInstructions.put(UpdateInstructions.Replace(0))
|
||||||
_albumsLists.value = sort.albums(_albumsLists.value)
|
_albumList.value = musicSettings.albumSort.albums(_albumList.value)
|
||||||
}
|
}
|
||||||
MusicType.ARTISTS -> {
|
|
||||||
logD("Updating artist [$type] sort mode to $sort")
|
/**
|
||||||
|
* Apply a new [Sort] to [artistList].
|
||||||
|
*
|
||||||
|
* @param sort The [Sort] to apply.
|
||||||
|
*/
|
||||||
|
fun applyArtistSort(sort: Sort) {
|
||||||
musicSettings.artistSort = sort
|
musicSettings.artistSort = sort
|
||||||
_artistsInstructions.put(UpdateInstructions.Replace(0))
|
_artistInstructions.put(UpdateInstructions.Replace(0))
|
||||||
_artistsList.value = sort.artists(_artistsList.value)
|
_artistList.value = musicSettings.artistSort.artists(_artistList.value)
|
||||||
}
|
}
|
||||||
MusicType.GENRES -> {
|
|
||||||
logD("Updating genre [$type] sort mode to $sort")
|
/**
|
||||||
|
* Apply a new [Sort] to [genreList].
|
||||||
|
*
|
||||||
|
* @param sort The [Sort] to apply.
|
||||||
|
*/
|
||||||
|
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")
|
/**
|
||||||
|
* Apply a new [Sort] to [playlistList].
|
||||||
|
*
|
||||||
|
* @param sort The [Sort] to apply.
|
||||||
|
*/
|
||||||
|
fun applyPlaylistSort(sort: Sort) {
|
||||||
musicSettings.playlistSort = sort
|
musicSettings.playlistSort = sort
|
||||||
_playlistsInstructions.put(UpdateInstructions.Replace(0))
|
_playlistInstructions.put(UpdateInstructions.Replace(0))
|
||||||
_playlistsList.value = sort.playlists(_playlistsList.value)
|
_playlistList.value = musicSettings.playlistSort.playlists(_playlistList.value)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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>) {
|
||||||
|
|
|
||||||
|
|
@ -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>) {
|
||||||
|
|
|
||||||
|
|
@ -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>) {
|
||||||
|
|
|
||||||
|
|
@ -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>) {
|
||||||
|
|
|
||||||
|
|
@ -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>) {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
121
app/src/main/java/org/oxycblt/auxio/list/sort/SortModeAdapter.kt
Normal file
121
app/src/main/java/org/oxycblt/auxio/list/sort/SortModeAdapter.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Required to use a LinearLayout here for space allocation to stop the BottomSheetDialog
|
||||||
|
from flipping out and not allowing the RecyclerView to scroll fully.
|
||||||
|
-->
|
||||||
|
|
||||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||||
android:id="@+id/menu_handle"
|
android:id="@+id/menu_handle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -12,12 +17,6 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<!--
|
|
||||||
Required to use a LinearLayout here for space allocation to stop the BottomSheetDialog
|
|
||||||
from flipping out and not allowing the RecyclerView to scroll fully.
|
|
||||||
-->
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/sort_name"
|
android:id="@+id/sort_name"
|
||||||
style="@style/Widget.Auxio.TextView.Primary"
|
style="@style/Widget.Auxio.TextView.Primary"
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,7 @@
|
||||||
<string name="lbl_genre">Genre</string>
|
<string name="lbl_genre">Genre</string>
|
||||||
<string name="set_separators_and">Ampersand (&)</string>
|
<string name="set_separators_and">Ampersand (&)</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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue