list: make sorting direction explicit

Make sorting direction (ascending/descending) explicit in the UI and in
the code.

Instead of a boolean flag, two distinct "ascending" and "descending"
options are used instead. This should be much clearer.
This commit is contained in:
Alexander Capehart 2023-02-02 21:42:48 -07:00
parent 8536c3da31
commit 3c211a1d17
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
24 changed files with 217 additions and 151 deletions

View file

@ -8,6 +8,8 @@
#### What's Improved
- Auxio will now accept zeroed track/disc numbers in the presence of non-zero total
track/disc fields
- Music loading has been made slightly faster
- Improved sort menu usability
#### What's Fixed
- Fixed non-functioning "repeat all" repeat mode

View file

@ -40,7 +40,7 @@ import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.util.*
@ -151,14 +151,19 @@ class AlbumDetailFragment :
openMenu(anchor, R.menu.menu_album_sort) {
val sort = detailModel.albumSongSort
unlikelyToBeNull(menu.findItem(sort.mode.itemId)).isChecked = true
unlikelyToBeNull(menu.findItem(R.id.option_sort_asc)).isChecked = sort.isAscending
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 =
if (item.itemId == R.id.option_sort_asc) {
sort.withAscending(item.isChecked)
} else {
sort.withMode(unlikelyToBeNull(Sort.Mode.fromItemId(item.itemId)))
when (item.itemId) {
R.id.option_sort_asc -> sort.withDirection(Sort.Direction.ASCENDING)
R.id.option_sort_dec -> sort.withDirection(Sort.Direction.DESCENDING)
else -> sort.withMode(unlikelyToBeNull(Sort.Mode.fromItemId(item.itemId)))
}
true
}

View file

@ -39,7 +39,7 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.util.collect
@ -167,15 +167,20 @@ class ArtistDetailFragment :
openMenu(anchor, R.menu.menu_artist_sort) {
val sort = detailModel.artistSongSort
unlikelyToBeNull(menu.findItem(sort.mode.itemId)).isChecked = true
unlikelyToBeNull(menu.findItem(R.id.option_sort_asc)).isChecked = sort.isAscending
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 =
if (item.itemId == R.id.option_sort_asc) {
sort.withAscending(item.isChecked)
} else {
sort.withMode(unlikelyToBeNull(Sort.Mode.fromItemId(item.itemId)))
when (item.itemId) {
R.id.option_sort_asc -> sort.withDirection(Sort.Direction.ASCENDING)
R.id.option_sort_dec -> sort.withDirection(Sort.Direction.DESCENDING)
else -> sort.withMode(unlikelyToBeNull(Sort.Mode.fromItemId(item.itemId)))
}
true

View file

@ -34,7 +34,7 @@ import org.oxycblt.auxio.list.BasicHeader
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.library.Library
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.music.metadata.AudioInfo
import org.oxycblt.auxio.music.metadata.Disc
import org.oxycblt.auxio.music.metadata.ReleaseType
@ -280,7 +280,7 @@ constructor(
private fun refreshArtistList(artist: Artist) {
logD("Refreshing artist data")
val data = mutableListOf<Item>(artist)
val albums = Sort(Sort.Mode.ByDate, false).albums(artist.albums)
val albums = Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING).albums(artist.albums)
val byReleaseGroup =
albums.groupBy {

View file

@ -40,7 +40,7 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.util.collect
@ -166,14 +166,19 @@ class GenreDetailFragment :
openMenu(anchor, R.menu.menu_genre_sort) {
val sort = detailModel.genreSongSort
unlikelyToBeNull(menu.findItem(sort.mode.itemId)).isChecked = true
unlikelyToBeNull(menu.findItem(R.id.option_sort_asc)).isChecked = sort.isAscending
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 =
if (item.itemId == R.id.option_sort_asc) {
sort.withAscending(item.isChecked)
} else {
sort.withMode(unlikelyToBeNull(Sort.Mode.fromItemId(item.itemId)))
when (item.itemId) {
R.id.option_sort_asc -> sort.withDirection(Sort.Direction.ASCENDING)
R.id.option_sort_dec -> sort.withDirection(Sort.Direction.DESCENDING)
else -> sort.withMode(unlikelyToBeNull(Sort.Mode.fromItemId(item.itemId)))
}
true
}

View file

@ -23,6 +23,7 @@ import android.view.MenuItem
import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.MenuCompat
import androidx.core.view.isVisible
import androidx.core.view.iterator
import androidx.core.view.updatePadding
@ -53,7 +54,7 @@ import org.oxycblt.auxio.list.selection.SelectionFragment
import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.library.Library
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.music.system.Indexer
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.MainNavigationAction
@ -104,7 +105,10 @@ class HomeFragment :
// --- UI SETUP ---
binding.homeAppbar.addOnOffsetChangedListener(this)
binding.homeToolbar.setOnMenuItemClickListener(this)
binding.homeToolbar.apply {
setOnMenuItemClickListener(this@HomeFragment)
MenuCompat.setGroupDividerEnabled(menu, true)
}
// Load the track color in manually as it's unclear whether the track actually supports
// using a ColorStateList in the resources
@ -213,11 +217,18 @@ class HomeFragment :
// Junk click event when opening the menu
}
R.id.option_sort_asc -> {
item.isChecked = !item.isChecked
item.isChecked = true
homeModel.setSortForCurrentTab(
homeModel
.getSortForTab(homeModel.currentTabMode.value)
.withAscending(item.isChecked))
.withDirection(Sort.Direction.ASCENDING))
}
R.id.option_sort_dec -> {
item.isChecked = true
homeModel.setSortForCurrentTab(
homeModel
.getSortForTab(homeModel.currentTabMode.value)
.withDirection(Sort.Direction.DESCENDING))
}
else -> {
// Sorting option was selected, mark it as selected and update the mode
@ -270,6 +281,7 @@ class HomeFragment :
// Only allow sorting by name, count, and duration for artists
MusicMode.ARTISTS -> { 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
@ -277,6 +289,7 @@ class HomeFragment :
// Only allow sorting by name, count, and duration for genres
MusicMode.GENRES -> { 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
@ -292,7 +305,10 @@ class HomeFragment :
// Check the ascending option and corresponding sort option to align with
// the current sort of the tab.
if (option.itemId == toHighlight.mode.itemId ||
(option.itemId == R.id.option_sort_asc && toHighlight.isAscending)) {
(option.itemId == R.id.option_sort_asc &&
toHighlight.direction == Sort.Direction.ASCENDING) ||
(option.itemId == R.id.option_sort_dec &&
toHighlight.direction == Sort.Direction.DESCENDING)) {
option.isChecked = true
}

View file

@ -25,7 +25,7 @@ import kotlinx.coroutines.flow.StateFlow
import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.library.Library
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.playback.PlaybackSettings
import org.oxycblt.auxio.util.logD

View file

@ -37,7 +37,7 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.AlbumViewHolder
import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.playback.secsToMs

View file

@ -38,7 +38,7 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.ui.NavigationViewModel

View file

@ -38,7 +38,7 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.ui.NavigationViewModel

View file

@ -40,7 +40,7 @@ import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.formatDurationMs
import org.oxycblt.auxio.playback.secsToMs

View file

@ -35,7 +35,7 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
/**
* A [Keyer] implementation for [Music] data.
@ -91,7 +91,7 @@ private constructor(
) : Fetcher {
override suspend fun fetch(): FetchResult? {
// Pick the "most prominent" albums (i.e albums with the most songs) to show in the image.
val albums = Sort(Sort.Mode.ByCount, false).albums(artist.albums)
val albums = Sort(Sort.Mode.ByCount, Sort.Direction.DESCENDING).albums(artist.albums)
val results = albums.mapAtMostNotNull(4) { album -> Covers.fetch(context, album) }
return Images.createMosaic(context, results, size)
}

View file

@ -21,6 +21,8 @@ import android.view.MenuItem
import android.view.View
import androidx.annotation.MenuRes
import androidx.appcompat.widget.PopupMenu
import androidx.core.internal.view.SupportMenu
import androidx.core.view.MenuCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import org.oxycblt.auxio.MainFragmentDirections
@ -237,6 +239,8 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
currentMenu =
PopupMenu(requireContext(), anchor).apply {
inflate(menuRes)
logD(menu is SupportMenu)
MenuCompat.setGroupDividerEnabled(menu, true)
block()
setOnDismissListener { currentMenu = null }
show()

View file

@ -15,14 +15,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.library
package org.oxycblt.auxio.list
import androidx.annotation.IdRes
import kotlin.math.max
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.library.Sort.Mode
import org.oxycblt.auxio.list.Sort.Mode
import org.oxycblt.auxio.music.metadata.Date
import org.oxycblt.auxio.music.metadata.Disc
@ -32,23 +32,23 @@ import org.oxycblt.auxio.music.metadata.Disc
* This can be used not only to sort items, but also represent a sorting mode within the UI.
*
* @param mode A [Mode] dictating how to sort the list.
* @param isAscending Whether to sort in ascending or descending order.
* @param direction The [Direction] to sort in.
* @author Alexander Capehart (OxygenCobalt)
*/
data class Sort(val mode: Mode, val isAscending: Boolean) {
data class Sort(val mode: Mode, val direction: Direction) {
/**
* Create a new [Sort] with the same [mode], but different [isAscending] value.
* @param isAscending Whether the new sort should be in ascending order or not.
* @return A new sort with the same mode, but with the new [isAscending] value applied.
* Create a new [Sort] with the same [mode], but a different [Direction].
* @param direction The new [Direction] to sort in.
* @return A new sort with the same mode, but with the new [Direction] value applied.
*/
fun withAscending(isAscending: Boolean) = Sort(mode, isAscending)
fun withDirection(direction: Direction) = Sort(mode, direction)
/**
* Create a new [Sort] with the same [isAscending] value, but different [mode] value.
* Create a new [Sort] with the same [direction] value, but different [mode] value.
* @param mode Tbe new mode to use for the Sort.
* @return A new sort with the same [isAscending] value, but with the new [mode] applied.
* @return A new sort with the same [direction] value, but with the new [mode] applied.
*/
fun withMode(mode: Mode) = Sort(mode, isAscending)
fun withMode(mode: Mode) = Sort(mode, direction)
/**
* Sort a list of [Song]s.
@ -99,7 +99,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
* @param songs The [Song]s to sort.
*/
private fun songsInPlace(songs: MutableList<out Song>) {
songs.sortWith(mode.getSongComparator(isAscending))
songs.sortWith(mode.getSongComparator(direction))
}
/**
@ -107,7 +107,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
* @param albums The [Album]s to sort.
*/
private fun albumsInPlace(albums: MutableList<out Album>) {
albums.sortWith(mode.getAlbumComparator(isAscending))
albums.sortWith(mode.getAlbumComparator(direction))
}
/**
@ -115,7 +115,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
* @param artists The [Album]s to sort.
*/
private fun artistsInPlace(artists: MutableList<out Artist>) {
artists.sortWith(mode.getArtistComparator(isAscending))
artists.sortWith(mode.getArtistComparator(direction))
}
/**
@ -123,7 +123,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
* @param genres The [Genre]s to sort.
*/
private fun genresInPlace(genres: MutableList<out Genre>) {
genres.sortWith(mode.getGenreComparator(isAscending))
genres.sortWith(mode.getGenreComparator(direction))
}
/**
@ -134,8 +134,14 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
// Sort's integer representation is formatted as AMMMM, where A is a bitflag
// representing if the sort is in ascending or descending order, and M is the
// integer representation of the sort mode.
get() = mode.intCode.shl(1) or if (isAscending) 1 else 0
get() =
mode.intCode.shl(1) or
when (direction) {
Direction.ASCENDING -> 1
Direction.DESCENDING -> 0
}
/** Describes the type of data to sort with. */
sealed class Mode {
/** The integer representation of this sort mode. */
abstract val intCode: Int
@ -144,37 +150,37 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
/**
* Get a [Comparator] that sorts [Song]s according to this [Mode].
* @param isAscending Whether to sort in ascending or descending order.
* @param direction The direction to sort in.
* @return A [Comparator] that can be used to sort a [Song] list according to this [Mode].
*/
open fun getSongComparator(isAscending: Boolean): Comparator<Song> {
open fun getSongComparator(direction: Direction): Comparator<Song> {
throw UnsupportedOperationException()
}
/**
* Get a [Comparator] that sorts [Album]s according to this [Mode].
* @param isAscending Whether to sort in ascending or descending order.
* @param direction The direction to sort in.
* @return A [Comparator] that can be used to sort a [Album] list according to this [Mode].
*/
open fun getAlbumComparator(isAscending: Boolean): Comparator<Album> {
open fun getAlbumComparator(direction: Direction): Comparator<Album> {
throw UnsupportedOperationException()
}
/**
* Return a [Comparator] that sorts [Artist]s according to this [Mode].
* @param isAscending Whether to sort in ascending or descending order.
* @param direction The direction to sort in.
* @return A [Comparator] that can be used to sort a [Artist] list according to this [Mode].
*/
open fun getArtistComparator(isAscending: Boolean): Comparator<Artist> {
open fun getArtistComparator(direction: Direction): Comparator<Artist> {
throw UnsupportedOperationException()
}
/**
* Return a [Comparator] that sorts [Genre]s according to this [Mode].
* @param isAscending Whether to sort in ascending or descending order.
* @param direction The direction to sort in.
* @return A [Comparator] that can be used to sort a [Genre] list according to this [Mode].
*/
open fun getGenreComparator(isAscending: Boolean): Comparator<Genre> {
open fun getGenreComparator(direction: Direction): Comparator<Genre> {
throw UnsupportedOperationException()
}
@ -189,17 +195,17 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
override val itemId: Int
get() = R.id.option_sort_name
override fun getSongComparator(isAscending: Boolean) =
compareByDynamic(isAscending, BasicComparator.SONG)
override fun getSongComparator(direction: Direction) =
compareByDynamic(direction, BasicComparator.SONG)
override fun getAlbumComparator(isAscending: Boolean) =
compareByDynamic(isAscending, BasicComparator.ALBUM)
override fun getAlbumComparator(direction: Direction) =
compareByDynamic(direction, BasicComparator.ALBUM)
override fun getArtistComparator(isAscending: Boolean) =
compareByDynamic(isAscending, BasicComparator.ARTIST)
override fun getArtistComparator(direction: Direction) =
compareByDynamic(direction, BasicComparator.ARTIST)
override fun getGenreComparator(isAscending: Boolean) =
compareByDynamic(isAscending, BasicComparator.GENRE)
override fun getGenreComparator(direction: Direction) =
compareByDynamic(direction, BasicComparator.GENRE)
}
/**
@ -213,9 +219,9 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
override val itemId: Int
get() = R.id.option_sort_album
override fun getSongComparator(isAscending: Boolean): Comparator<Song> =
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
compareByDynamic(isAscending, BasicComparator.ALBUM) { it.album },
compareByDynamic(direction, BasicComparator.ALBUM) { it.album },
compareBy(NullableComparator.DISC) { it.disc },
compareBy(NullableComparator.INT) { it.track },
compareBy(BasicComparator.SONG))
@ -232,18 +238,18 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
override val itemId: Int
get() = R.id.option_sort_artist
override fun getSongComparator(isAscending: Boolean): Comparator<Song> =
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
compareByDynamic(isAscending, ListComparator.ARTISTS) { it.artists },
compareByDynamic(direction, ListComparator.ARTISTS) { it.artists },
compareByDescending(NullableComparator.DATE_RANGE) { it.album.dates },
compareByDescending(BasicComparator.ALBUM) { it.album },
compareBy(NullableComparator.DISC) { it.disc },
compareBy(NullableComparator.INT) { it.track },
compareBy(BasicComparator.SONG))
override fun getAlbumComparator(isAscending: Boolean): Comparator<Album> =
override fun getAlbumComparator(direction: Direction): Comparator<Album> =
MultiComparator(
compareByDynamic(isAscending, ListComparator.ARTISTS) { it.artists },
compareByDynamic(direction, ListComparator.ARTISTS) { it.artists },
compareByDescending(NullableComparator.DATE_RANGE) { it.dates },
compareBy(BasicComparator.ALBUM))
}
@ -260,17 +266,17 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
override val itemId: Int
get() = R.id.option_sort_year
override fun getSongComparator(isAscending: Boolean): Comparator<Song> =
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
compareByDynamic(isAscending, NullableComparator.DATE_RANGE) { it.album.dates },
compareByDynamic(direction, NullableComparator.DATE_RANGE) { it.album.dates },
compareByDescending(BasicComparator.ALBUM) { it.album },
compareBy(NullableComparator.DISC) { it.disc },
compareBy(NullableComparator.INT) { it.track },
compareBy(BasicComparator.SONG))
override fun getAlbumComparator(isAscending: Boolean): Comparator<Album> =
override fun getAlbumComparator(direction: Direction): Comparator<Album> =
MultiComparator(
compareByDynamic(isAscending, NullableComparator.DATE_RANGE) { it.dates },
compareByDynamic(direction, NullableComparator.DATE_RANGE) { it.dates },
compareBy(BasicComparator.ALBUM))
}
@ -282,25 +288,22 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
override val itemId: Int
get() = R.id.option_sort_duration
override fun getSongComparator(isAscending: Boolean): Comparator<Song> =
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
compareByDynamic(isAscending) { it.durationMs },
compareBy(BasicComparator.SONG))
compareByDynamic(direction) { it.durationMs }, compareBy(BasicComparator.SONG))
override fun getAlbumComparator(isAscending: Boolean): Comparator<Album> =
override fun getAlbumComparator(direction: Direction): Comparator<Album> =
MultiComparator(
compareByDynamic(isAscending) { it.durationMs },
compareBy(BasicComparator.ALBUM))
compareByDynamic(direction) { it.durationMs }, compareBy(BasicComparator.ALBUM))
override fun getArtistComparator(isAscending: Boolean): Comparator<Artist> =
override fun getArtistComparator(direction: Direction): Comparator<Artist> =
MultiComparator(
compareByDynamic(isAscending, NullableComparator.LONG) { it.durationMs },
compareByDynamic(direction, NullableComparator.LONG) { it.durationMs },
compareBy(BasicComparator.ARTIST))
override fun getGenreComparator(isAscending: Boolean): Comparator<Genre> =
override fun getGenreComparator(direction: Direction): Comparator<Genre> =
MultiComparator(
compareByDynamic(isAscending) { it.durationMs },
compareBy(BasicComparator.GENRE))
compareByDynamic(direction) { it.durationMs }, compareBy(BasicComparator.GENRE))
}
/**
@ -314,20 +317,18 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
override val itemId: Int
get() = R.id.option_sort_count
override fun getAlbumComparator(isAscending: Boolean): Comparator<Album> =
override fun getAlbumComparator(direction: Direction): Comparator<Album> =
MultiComparator(
compareByDynamic(isAscending) { it.songs.size },
compareBy(BasicComparator.ALBUM))
compareByDynamic(direction) { it.songs.size }, compareBy(BasicComparator.ALBUM))
override fun getArtistComparator(isAscending: Boolean): Comparator<Artist> =
override fun getArtistComparator(direction: Direction): Comparator<Artist> =
MultiComparator(
compareByDynamic(isAscending, NullableComparator.INT) { it.songs.size },
compareByDynamic(direction, NullableComparator.INT) { it.songs.size },
compareBy(BasicComparator.ARTIST))
override fun getGenreComparator(isAscending: Boolean): Comparator<Genre> =
override fun getGenreComparator(direction: Direction): Comparator<Genre> =
MultiComparator(
compareByDynamic(isAscending) { it.songs.size },
compareBy(BasicComparator.GENRE))
compareByDynamic(direction) { it.songs.size }, compareBy(BasicComparator.GENRE))
}
/**
@ -341,9 +342,9 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
override val itemId: Int
get() = R.id.option_sort_disc
override fun getSongComparator(isAscending: Boolean): Comparator<Song> =
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
compareByDynamic(isAscending, NullableComparator.DISC) { it.disc },
compareByDynamic(direction, NullableComparator.DISC) { it.disc },
compareBy(NullableComparator.INT) { it.track },
compareBy(BasicComparator.SONG))
}
@ -359,10 +360,10 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
override val itemId: Int
get() = R.id.option_sort_track
override fun getSongComparator(isAscending: Boolean): Comparator<Song> =
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
compareBy(NullableComparator.DISC) { it.disc },
compareByDynamic(isAscending, NullableComparator.INT) { it.track },
compareByDynamic(direction, NullableComparator.INT) { it.track },
compareBy(BasicComparator.SONG))
}
@ -378,48 +379,47 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
override val itemId: Int
get() = R.id.option_sort_date_added
override fun getSongComparator(isAscending: Boolean): Comparator<Song> =
override fun getSongComparator(direction: Direction): Comparator<Song> =
MultiComparator(
compareByDynamic(isAscending) { it.dateAdded }, compareBy(BasicComparator.SONG))
compareByDynamic(direction) { it.dateAdded }, compareBy(BasicComparator.SONG))
override fun getAlbumComparator(isAscending: Boolean): Comparator<Album> =
override fun getAlbumComparator(direction: Direction): Comparator<Album> =
MultiComparator(
compareByDynamic(isAscending) { album -> album.dateAdded },
compareByDynamic(direction) { album -> album.dateAdded },
compareBy(BasicComparator.ALBUM))
}
/**
* Utility function to create a [Comparator] in a dynamic way determined by [isAscending].
* @param isAscending Whether to sort in ascending or descending order.
* Utility function to create a [Comparator] in a dynamic way determined by [direction].
* @param direction The [Direction] to sort in.
* @see compareBy
* @see compareByDescending
*/
protected inline fun <T : Music, K : Comparable<K>> compareByDynamic(
isAscending: Boolean,
direction: Direction,
crossinline selector: (T) -> K
) =
if (isAscending) {
compareBy(selector)
} else {
compareByDescending(selector)
when (direction) {
Direction.ASCENDING -> compareBy(selector)
Direction.DESCENDING -> compareByDescending(selector)
}
/**
* Utility function to create a [Comparator] in a dynamic way determined by [isAscending]
* @param isAscending Whether to sort in ascending or descending order.
* Utility function to create a [Comparator] in a dynamic way determined by [direction]
* @param direction The [Direction] to sort in.
* @param comparator A [Comparator] to wrap.
* @return A new [Comparator] with the specified configuration.
* @see compareBy
* @see compareByDescending
*/
protected fun <T : Music> compareByDynamic(
isAscending: Boolean,
direction: Direction,
comparator: Comparator<in T>
): Comparator<T> = compareByDynamic(isAscending, comparator) { it }
): Comparator<T> = compareByDynamic(direction, comparator) { it }
/**
* Utility function to create a [Comparator] a dynamic way determined by [isAscending]
* @param isAscending Whether to sort in ascending or descending order.
* Utility function to create a [Comparator] a dynamic way determined by [direction]
* @param direction The [Direction] to sort in.
* @param comparator A [Comparator] to wrap.
* @param selector Called to obtain a specific attribute to sort by.
* @return A new [Comparator] with the specified configuration.
@ -427,14 +427,13 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
* @see compareByDescending
*/
protected inline fun <T : Music, K> compareByDynamic(
isAscending: Boolean,
direction: Direction,
comparator: Comparator<in K>,
crossinline selector: (T) -> K
) =
if (isAscending) {
compareBy(comparator, selector)
} else {
compareByDescending(comparator, selector)
when (direction) {
Direction.ASCENDING -> compareBy(comparator, selector)
Direction.DESCENDING -> compareByDescending(comparator, selector)
}
/**
@ -596,6 +595,12 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
}
}
/** The direction to sort items in. */
enum class Direction {
ASCENDING,
DESCENDING
}
companion object {
/**
* Convert a [Sort] integer representation into an instance.
@ -607,9 +612,9 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
// Sort's integer representation is formatted as AMMMM, where A is a bitflag
// representing on if the mode is ascending or descending, and M is the integer
// representation of the sort mode.
val isAscending = (intCode and 1) == 1
val direction = if ((intCode and 1) == 1) Direction.ASCENDING else Direction.DESCENDING
val mode = Mode.fromIntCode(intCode.shr(1)) ?: return null
return Sort(mode, isAscending)
return Sort(mode, direction)
}
}
}

View file

@ -21,7 +21,7 @@ import android.content.Context
import android.os.storage.StorageManager
import androidx.core.content.edit
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.music.storage.Directory
import org.oxycblt.auxio.music.storage.MusicDirectories
import org.oxycblt.auxio.settings.Settings
@ -116,7 +116,7 @@ private class RealMusicSettings(context: Context) :
get() =
Sort.fromIntCode(
sharedPreferences.getInt(getString(R.string.set_key_songs_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByName, true)
?: Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
set(value) {
sharedPreferences.edit {
putInt(getString(R.string.set_key_songs_sort), value.intCode)
@ -128,7 +128,7 @@ private class RealMusicSettings(context: Context) :
get() =
Sort.fromIntCode(
sharedPreferences.getInt(getString(R.string.set_key_albums_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByName, true)
?: Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
set(value) {
sharedPreferences.edit {
putInt(getString(R.string.set_key_albums_sort), value.intCode)
@ -140,7 +140,7 @@ private class RealMusicSettings(context: Context) :
get() =
Sort.fromIntCode(
sharedPreferences.getInt(getString(R.string.set_key_artists_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByName, true)
?: Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
set(value) {
sharedPreferences.edit {
putInt(getString(R.string.set_key_artists_sort), value.intCode)
@ -152,7 +152,7 @@ private class RealMusicSettings(context: Context) :
get() =
Sort.fromIntCode(
sharedPreferences.getInt(getString(R.string.set_key_genres_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByName, true)
?: Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
set(value) {
sharedPreferences.edit {
putInt(getString(R.string.set_key_genres_sort), value.intCode)
@ -166,7 +166,7 @@ private class RealMusicSettings(context: Context) :
Sort.fromIntCode(
sharedPreferences.getInt(
getString(R.string.set_key_album_songs_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByDisc, true)
?: Sort(Sort.Mode.ByDisc, Sort.Direction.ASCENDING)
// Correct legacy album sort modes to Disc
if (sort.mode is Sort.Mode.ByName) {
@ -187,7 +187,7 @@ private class RealMusicSettings(context: Context) :
Sort.fromIntCode(
sharedPreferences.getInt(
getString(R.string.set_key_artist_songs_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByDate, false)
?: Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING)
set(value) {
sharedPreferences.edit {
putInt(getString(R.string.set_key_artist_songs_sort), value.intCode)
@ -200,7 +200,7 @@ private class RealMusicSettings(context: Context) :
Sort.fromIntCode(
sharedPreferences.getInt(
getString(R.string.set_key_genre_songs_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByName, true)
?: Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
set(value) {
sharedPreferences.edit {
putInt(getString(R.string.set_key_genre_songs_sort), value.intCode)

View file

@ -20,6 +20,7 @@ package org.oxycblt.auxio.music.library
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.storage.contentResolverSafe
import org.oxycblt.auxio.music.storage.useQuery
@ -76,7 +77,7 @@ interface Library {
companion object {
/**
* Create a library-backed instance of [Library].
* Create an instance of [Library].
* @param rawSongs [RawSong]s to create the library out of.
* @param settings [MusicSettings] required.
*/
@ -93,10 +94,10 @@ private class RealLibrary(rawSongs: List<RawSong>, settings: MusicSettings) : Li
// Use a mapping to make finding information based on it's UID much faster.
private val uidMap = buildMap {
songs.forEach { this[it.uid] = it.finalize() }
albums.forEach { this[it.uid] = it.finalize() }
artists.forEach { this[it.uid] = it.finalize() }
genres.forEach { this[it.uid] = it.finalize() }
songs.forEach { put(it.uid, it.finalize()) }
albums.forEach { put(it.uid, it.finalize()) }
artists.forEach { put(it.uid, it.finalize()) }
genres.forEach { put(it.uid, it.finalize()) }
}
/**
@ -131,7 +132,8 @@ private class RealLibrary(rawSongs: List<RawSong>, settings: MusicSettings) : Li
* grouping.
*/
private fun buildSongs(rawSongs: List<RawSong>, settings: MusicSettings) =
Sort(Sort.Mode.ByName, true).songs(rawSongs.map { RealSong(it, settings) }.distinct())
Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
.songs(rawSongs.map { RealSong(it, settings) }.distinct())
/**
* Build a list of [Album]s from the given [Song]s.

View file

@ -24,6 +24,7 @@ import java.text.CollationKey
import java.text.Collator
import kotlin.math.max
import org.oxycblt.auxio.R
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -418,7 +419,7 @@ class RealArtist(private val rawArtist: RawArtist, songAlbums: List<Music>) : Ar
fun finalize(): Artist {
check(songs.isNotEmpty() || albums.isNotEmpty()) { "Malformed artist: Empty" }
genres =
Sort(Sort.Mode.ByName, true)
Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
.genres(songs.flatMapTo(mutableSetOf()) { it.genres })
.sortedByDescending { genre -> songs.count { it.genres.contains(genre) } }
return this
@ -458,10 +459,10 @@ class RealGenre(private val rawGenre: RawGenre, override val songs: List<RealSon
}
albums =
Sort(Sort.Mode.ByName, true).albums(distinctAlbums).sortedByDescending { album ->
album.songs.count { it.genres.contains(this) }
}
artists = Sort(Sort.Mode.ByName, true).artists(distinctArtists)
Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
.albums(distinctAlbums)
.sortedByDescending { album -> album.songs.count { it.genres.contains(this) } }
artists = Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING).artists(distinctArtists)
durationMs = totalDuration
}

View file

@ -32,7 +32,7 @@ import org.oxycblt.auxio.list.BasicHeader
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.library.Library
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.playback.PlaybackSettings
import org.oxycblt.auxio.util.logD
@ -176,6 +176,6 @@ constructor(
}
private companion object {
val SORT = Sort(Sort.Mode.ByName, true)
val SORT = Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
}
}

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<group android:checkableBehavior="single"
android:id="@+id/sort_modes">
<item
android:id="@+id/option_sort_disc"
android:title="@string/lbl_sort_disc" />
@ -8,9 +9,13 @@
android:id="@+id/option_sort_track"
android:title="@string/lbl_sort_track" />
</group>
<group android:checkableBehavior="all">
<group android:checkableBehavior="single"
android:id="@+id/sort_direction">
<item
android:id="@+id/option_sort_asc"
android:title="@string/lbl_sort_asc" />
<item
android:id="@+id/option_sort_dec"
android:title="@string/lbl_sort_dec" />
</group>
</menu>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<group android:checkableBehavior="single"
android:id="@+id/sort_modes">
<item
android:id="@+id/option_sort_name"
android:title="@string/lbl_sort_name" />
@ -14,9 +15,13 @@
android:id="@+id/option_sort_duration"
android:title="@string/lbl_sort_duration" />
</group>
<group android:checkableBehavior="all">
<group android:checkableBehavior="single"
android:id="@+id/sort_direction">
<item
android:id="@+id/option_sort_asc"
android:title="@string/lbl_sort_asc" />
<item
android:id="@+id/option_sort_dec"
android:title="@string/lbl_sort_dec" />
</group>
</menu>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<group android:checkableBehavior="single"
android:id="@+id/sort_modes">
<item
android:id="@+id/option_sort_name"
android:title="@string/lbl_sort_name" />
@ -17,9 +18,13 @@
android:id="@+id/option_sort_duration"
android:title="@string/lbl_sort_duration" />
</group>
<group android:checkableBehavior="all">
<group android:checkableBehavior="single"
android:id="@+id/sort_direction">
<item
android:id="@+id/option_sort_asc"
android:title="@string/lbl_sort_asc" />
<item
android:id="@+id/option_sort_dec"
android:title="@string/lbl_sort_dec" />
</group>
</menu>

View file

@ -14,7 +14,8 @@
android:title="@string/lbl_sort"
app:showAsAction="ifRoom">
<menu>
<group android:checkableBehavior="single">
<group android:checkableBehavior="single"
android:id="@+id/sort_modes">
<item
android:id="@+id/option_sort_name"
android:title="@string/lbl_sort_name" />
@ -37,10 +38,14 @@
android:id="@+id/option_sort_date_added"
android:title="@string/lbl_sort_date_added" />
</group>
<group android:checkableBehavior="all">
<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>

View file

@ -92,6 +92,7 @@
<string name="lbl_sort_track">Track</string>
<string name="lbl_sort_date_added">Date added</string>
<string name="lbl_sort_asc">Ascending</string>
<string name="lbl_sort_dec">Descending</string>
<string name="lbl_playback">Now playing</string>
<string name="lbl_equalizer">Equalizer</string>

View file

@ -17,7 +17,7 @@
package org.oxycblt.auxio.music
import org.oxycblt.auxio.music.library.Sort
import org.oxycblt.auxio.list.Sort
import org.oxycblt.auxio.music.storage.MusicDirectories
interface FakeMusicSettings : MusicSettings {