Merge branch 'master' into dev
This commit is contained in:
commit
27fb1d1823
6 changed files with 232 additions and 437 deletions
|
@ -13,6 +13,11 @@
|
||||||
- Music loader no longer spawns thousands of threads when scanning
|
- Music loader no longer spawns thousands of threads when scanning
|
||||||
- Excessive CPU no longer spent showing music loading process
|
- Excessive CPU no longer spent showing music loading process
|
||||||
|
|
||||||
|
## 3.5.1
|
||||||
|
|
||||||
|
#### What's Fixed
|
||||||
|
- Fixed music loading failure from improper sort systems
|
||||||
|
|
||||||
## 3.5.0
|
## 3.5.0
|
||||||
|
|
||||||
#### What's New
|
#### What's New
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
<h1 align="center"><b>Auxio</b></h1>
|
<h1 align="center"><b>Auxio</b></h1>
|
||||||
<h4 align="center">A simple, rational music player for android.</h4>
|
<h4 align="center">A simple, rational music player for android.</h4>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/oxygencobalt/Auxio/releases/tag/v3.5.0">
|
<a href="https://github.com/oxygencobalt/Auxio/releases/tag/v3.5.1">
|
||||||
<img alt="Latest Version" src="https://img.shields.io/static/v1?label=tag&message=v3.5.0&color=64B5F6&style=flat">
|
<img alt="Latest Version" src="https://img.shields.io/static/v1?label=tag&message=v3.5.1&color=64B5F6&style=flat">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/oxygencobalt/Auxio/releases/">
|
<a href="https://github.com/oxygencobalt/Auxio/releases/">
|
||||||
<img alt="Releases" src="https://img.shields.io/github/downloads/OxygenCobalt/Auxio/total.svg?color=4B95DE&style=flat">
|
<img alt="Releases" src="https://img.shields.io/github/downloads/OxygenCobalt/Auxio/total.svg?color=4B95DE&style=flat">
|
||||||
|
|
|
@ -21,8 +21,8 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId namespace
|
applicationId namespace
|
||||||
versionName "3.5.0"
|
versionName "3.5.1"
|
||||||
versionCode 46
|
versionCode 47
|
||||||
|
|
||||||
minSdk 24
|
minSdk 24
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
|
|
|
@ -20,17 +20,11 @@ package org.oxycblt.auxio.list.sort
|
||||||
|
|
||||||
import org.oxycblt.auxio.IntegerTable
|
import org.oxycblt.auxio.IntegerTable
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.list.sort.Sort.Direction
|
|
||||||
import org.oxycblt.auxio.list.sort.Sort.Mode
|
|
||||||
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.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Music
|
|
||||||
import org.oxycblt.auxio.music.Playlist
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.info.Date
|
|
||||||
import org.oxycblt.auxio.music.info.Disc
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A sorting method.
|
* A sorting method.
|
||||||
|
@ -48,9 +42,9 @@ data class Sort(val mode: Mode, val direction: Direction) {
|
||||||
* @param songs The list of [Song]s.
|
* @param songs The list of [Song]s.
|
||||||
* @return A new list of [Song]s sorted by this [Sort]'s configuration.
|
* @return A new list of [Song]s sorted by this [Sort]'s configuration.
|
||||||
*/
|
*/
|
||||||
fun <T : Song> songs(songs: Collection<T>): List<T> {
|
fun songs(songs: Collection<Song>): List<Song> {
|
||||||
val mutable = songs.toMutableList()
|
val mutable = songs.toMutableList()
|
||||||
songsInPlace(mutable)
|
mode.sortSongs(mutable, direction)
|
||||||
return mutable
|
return mutable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,9 +54,9 @@ data class Sort(val mode: Mode, val direction: Direction) {
|
||||||
* @param albums The list of [Album]s.
|
* @param albums The list of [Album]s.
|
||||||
* @return A new list of [Album]s sorted by this [Sort]'s configuration.
|
* @return A new list of [Album]s sorted by this [Sort]'s configuration.
|
||||||
*/
|
*/
|
||||||
fun <T : Album> albums(albums: Collection<T>): List<T> {
|
fun albums(albums: Collection<Album>): List<Album> {
|
||||||
val mutable = albums.toMutableList()
|
val mutable = albums.toMutableList()
|
||||||
albumsInPlace(mutable)
|
mode.sortAlbums(mutable, direction)
|
||||||
return mutable
|
return mutable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,9 +66,9 @@ data class Sort(val mode: Mode, val direction: Direction) {
|
||||||
* @param artists The list of [Artist]s.
|
* @param artists The list of [Artist]s.
|
||||||
* @return A new list of [Artist]s sorted by this [Sort]'s configuration.
|
* @return A new list of [Artist]s sorted by this [Sort]'s configuration.
|
||||||
*/
|
*/
|
||||||
fun <T : Artist> artists(artists: Collection<T>): List<T> {
|
fun artists(artists: Collection<Artist>): List<Artist> {
|
||||||
val mutable = artists.toMutableList()
|
val mutable = artists.toMutableList()
|
||||||
artistsInPlace(mutable)
|
mode.sortArtists(mutable, direction)
|
||||||
return mutable
|
return mutable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,9 +78,9 @@ data class Sort(val mode: Mode, val direction: Direction) {
|
||||||
* @param genres The list of [Genre]s.
|
* @param genres The list of [Genre]s.
|
||||||
* @return A new list of [Genre]s sorted by this [Sort]'s configuration.
|
* @return A new list of [Genre]s sorted by this [Sort]'s configuration.
|
||||||
*/
|
*/
|
||||||
fun <T : Genre> genres(genres: Collection<T>): List<T> {
|
fun genres(genres: Collection<Genre>): List<Genre> {
|
||||||
val mutable = genres.toMutableList()
|
val mutable = genres.toMutableList()
|
||||||
genresInPlace(mutable)
|
mode.sortGenres(mutable, direction)
|
||||||
return mutable
|
return mutable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,37 +90,12 @@ data class Sort(val mode: Mode, val direction: Direction) {
|
||||||
* @param playlists The list of [Playlist]s.
|
* @param playlists The list of [Playlist]s.
|
||||||
* @return A new list of [Playlist]s sorted by this [Sort]'s configuration
|
* @return A new list of [Playlist]s sorted by this [Sort]'s configuration
|
||||||
*/
|
*/
|
||||||
fun <T : Playlist> playlists(playlists: Collection<T>): List<T> {
|
fun playlists(playlists: Collection<Playlist>): List<Playlist> {
|
||||||
val mutable = playlists.toMutableList()
|
val mutable = playlists.toMutableList()
|
||||||
playlistsInPlace(mutable)
|
mode.sortPlaylists(mutable, direction)
|
||||||
return mutable
|
return mutable
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun songsInPlace(songs: MutableList<out Song>) {
|
|
||||||
val comparator = mode.getSongComparator(direction) ?: return
|
|
||||||
songs.sortWith(comparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun albumsInPlace(albums: MutableList<out Album>) {
|
|
||||||
val comparator = mode.getAlbumComparator(direction) ?: return
|
|
||||||
albums.sortWith(comparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun artistsInPlace(artists: MutableList<out Artist>) {
|
|
||||||
val comparator = mode.getArtistComparator(direction) ?: return
|
|
||||||
artists.sortWith(comparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun genresInPlace(genres: MutableList<out Genre>) {
|
|
||||||
val comparator = mode.getGenreComparator(direction) ?: return
|
|
||||||
genres.sortWith(comparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun playlistsInPlace(playlists: MutableList<out Playlist>) {
|
|
||||||
val comparator = mode.getPlaylistComparator(direction) ?: return
|
|
||||||
playlists.sortWith(comparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The integer representation of this instance.
|
* The integer representation of this instance.
|
||||||
*
|
*
|
||||||
|
@ -143,289 +112,270 @@ data class Sort(val mode: Mode, val direction: Direction) {
|
||||||
Direction.DESCENDING -> 0
|
Direction.DESCENDING -> 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Describes the type of data to sort with. */
|
|
||||||
sealed interface Mode {
|
sealed interface Mode {
|
||||||
/** The integer representation of this sort mode. */
|
|
||||||
val intCode: Int
|
val intCode: Int
|
||||||
/** The string resource of the human-readable name of this sort mode. */
|
|
||||||
val stringRes: Int
|
val stringRes: Int
|
||||||
|
|
||||||
/**
|
fun sortSongs(songs: MutableList<Song>, direction: Direction) {
|
||||||
* Get a [Comparator] that sorts [Song]s according to this [Mode].
|
throw NotImplementedError("Sorting songs is not supported for this mode")
|
||||||
*
|
}
|
||||||
* @param direction The direction to sort in.
|
|
||||||
* @return A [Comparator] that can be used to sort a [Song] list according to this [Mode],
|
|
||||||
* or null to not sort at all.
|
|
||||||
*/
|
|
||||||
fun getSongComparator(direction: Direction): Comparator<Song>? = null
|
|
||||||
|
|
||||||
/**
|
fun sortAlbums(albums: MutableList<Album>, direction: Direction) {
|
||||||
* Get a [Comparator] that sorts [Album]s according to this [Mode].
|
throw NotImplementedError("Sorting albums is not supported for this mode")
|
||||||
*
|
}
|
||||||
* @param direction The direction to sort in.
|
|
||||||
* @return A [Comparator] that can be used to sort a [Album] list according to this [Mode],
|
|
||||||
* or null to not sort at all.
|
|
||||||
*/
|
|
||||||
fun getAlbumComparator(direction: Direction): Comparator<Album>? = null
|
|
||||||
|
|
||||||
/**
|
fun sortArtists(artists: MutableList<Artist>, direction: Direction) {
|
||||||
* Return a [Comparator] that sorts [Artist]s according to this [Mode].
|
throw NotImplementedError("Sorting artists is not supported for this mode")
|
||||||
*
|
}
|
||||||
* @param direction The direction to sort in.
|
|
||||||
* @return A [Comparator] that can be used to sort a [Artist] list according to this [Mode].
|
|
||||||
* or null to not sort at all.
|
|
||||||
*/
|
|
||||||
fun getArtistComparator(direction: Direction): Comparator<Artist>? = null
|
|
||||||
|
|
||||||
/**
|
fun sortGenres(genres: MutableList<Genre>, direction: Direction) {
|
||||||
* Return a [Comparator] that sorts [Genre]s according to this [Mode].
|
throw NotImplementedError("Sorting genres is not supported for this mode")
|
||||||
*
|
}
|
||||||
* @param direction The direction to sort in.
|
|
||||||
* @return A [Comparator] that can be used to sort a [Genre] list according to this [Mode].
|
|
||||||
* or null to not sort at all.
|
|
||||||
*/
|
|
||||||
fun getGenreComparator(direction: Direction): Comparator<Genre>? = null
|
|
||||||
|
|
||||||
/**
|
fun sortPlaylists(playlists: MutableList<Playlist>, direction: Direction) {
|
||||||
* Return a [Comparator] that sorts [Playlist]s according to this [Mode].
|
throw NotImplementedError("Sorting playlists is not supported for this mode")
|
||||||
*
|
}
|
||||||
* @param direction The direction to sort in.
|
|
||||||
* @return A [Comparator] that can be used to sort a [Genre] list according to this [Mode].
|
|
||||||
* or null to not sort at all.
|
|
||||||
*/
|
|
||||||
fun getPlaylistComparator(direction: Direction): Comparator<Playlist>? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort by the item's name.
|
|
||||||
*
|
|
||||||
* @see Music.name
|
|
||||||
*/
|
|
||||||
data object ByName : Mode {
|
data object ByName : Mode {
|
||||||
override val intCode: Int
|
override val intCode = IntegerTable.SORT_BY_NAME
|
||||||
get() = IntegerTable.SORT_BY_NAME
|
override val stringRes = R.string.lbl_name
|
||||||
|
|
||||||
override val stringRes: Int
|
override fun sortSongs(songs: MutableList<Song>, direction: Direction) {
|
||||||
get() = R.string.lbl_name
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> songs.sortBy { it.name }
|
||||||
|
Direction.DESCENDING -> songs.sortByDescending { it.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSongComparator(direction: Direction) =
|
override fun sortAlbums(albums: MutableList<Album>, direction: Direction) {
|
||||||
compareByDynamic(direction, BasicComparator.SONG)
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> albums.sortBy { it.name }
|
||||||
|
Direction.DESCENDING -> albums.sortByDescending { it.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAlbumComparator(direction: Direction) =
|
override fun sortArtists(artists: MutableList<Artist>, direction: Direction) {
|
||||||
compareByDynamic(direction, BasicComparator.ALBUM)
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> artists.sortBy { it.name }
|
||||||
|
Direction.DESCENDING -> artists.sortByDescending { it.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getArtistComparator(direction: Direction) =
|
override fun sortGenres(genres: MutableList<Genre>, direction: Direction) {
|
||||||
compareByDynamic(direction, BasicComparator.ARTIST)
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> genres.sortBy { it.name }
|
||||||
|
Direction.DESCENDING -> genres.sortByDescending { it.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getGenreComparator(direction: Direction) =
|
override fun sortPlaylists(playlists: MutableList<Playlist>, direction: Direction) {
|
||||||
compareByDynamic(direction, BasicComparator.GENRE)
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> playlists.sortBy { it.name }
|
||||||
override fun getPlaylistComparator(direction: Direction) =
|
Direction.DESCENDING -> playlists.sortByDescending { it.name }
|
||||||
compareByDynamic(direction, BasicComparator.PLAYLIST)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort by the [Album] of an item. Only available for [Song]s.
|
|
||||||
*
|
|
||||||
* @see Album.name
|
|
||||||
*/
|
|
||||||
data object ByAlbum : Mode {
|
data object ByAlbum : Mode {
|
||||||
override val intCode: Int
|
override val intCode = IntegerTable.SORT_BY_ALBUM
|
||||||
get() = IntegerTable.SORT_BY_ALBUM
|
override val stringRes = R.string.lbl_album
|
||||||
|
|
||||||
override val stringRes: Int
|
override fun sortSongs(songs: MutableList<Song>, direction: Direction) {
|
||||||
get() = R.string.lbl_album
|
songs.sortBy { it.name }
|
||||||
|
songs.sortBy { it.track }
|
||||||
override fun getSongComparator(direction: Direction): Comparator<Song> =
|
songs.sortBy { it.disc }
|
||||||
MultiComparator(
|
when (direction) {
|
||||||
compareByDynamic(direction, BasicComparator.ALBUM) { it.album },
|
Direction.ASCENDING -> songs.sortBy { it.album.name }
|
||||||
compareBy(NullableComparator.DISC) { it.disc },
|
Direction.DESCENDING -> songs.sortByDescending { it.album.name }
|
||||||
compareBy(NullableComparator.INT) { it.track },
|
}
|
||||||
compareBy(BasicComparator.SONG))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort by the [Artist] name of an item. Only available for [Song] and [Album].
|
|
||||||
*
|
|
||||||
* @see Artist.name
|
|
||||||
*/
|
|
||||||
data object ByArtist : Mode {
|
data object ByArtist : Mode {
|
||||||
override val intCode: Int
|
override val intCode = IntegerTable.SORT_BY_ARTIST
|
||||||
get() = IntegerTable.SORT_BY_ARTIST
|
override val stringRes = R.string.lbl_artist
|
||||||
|
|
||||||
override val stringRes: Int
|
override fun sortSongs(songs: MutableList<Song>, direction: Direction) {
|
||||||
get() = R.string.lbl_artist
|
songs.sortBy { it.name }
|
||||||
|
songs.sortBy { it.track }
|
||||||
|
songs.sortBy { it.disc }
|
||||||
|
songs.sortBy { it.album.name }
|
||||||
|
songs.sortByDescending { it.album.dates }
|
||||||
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> songs.sortBy { it.artists.firstOrNull()?.name }
|
||||||
|
Direction.DESCENDING ->
|
||||||
|
songs.sortByDescending { it.artists.firstOrNull()?.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSongComparator(direction: Direction): Comparator<Song> =
|
override fun sortAlbums(albums: MutableList<Album>, direction: Direction) {
|
||||||
MultiComparator(
|
albums.sortBy { it.name }
|
||||||
compareByDynamic(direction, ListComparator.ARTISTS) { it.artists },
|
albums.sortByDescending { it.dates }
|
||||||
compareByDescending(NullableComparator.DATE_RANGE) { it.album.dates },
|
when (direction) {
|
||||||
compareByDescending(BasicComparator.ALBUM) { it.album },
|
Direction.ASCENDING -> albums.sortBy { it.artists.firstOrNull()?.name }
|
||||||
compareBy(NullableComparator.DISC) { it.disc },
|
Direction.DESCENDING ->
|
||||||
compareBy(NullableComparator.INT) { it.track },
|
albums.sortByDescending { it.artists.firstOrNull()?.name }
|
||||||
compareBy(BasicComparator.SONG))
|
}
|
||||||
|
}
|
||||||
override fun getAlbumComparator(direction: Direction): Comparator<Album> =
|
|
||||||
MultiComparator(
|
|
||||||
compareByDynamic(direction, ListComparator.ARTISTS) { it.artists },
|
|
||||||
compareByDescending(NullableComparator.DATE_RANGE) { it.dates },
|
|
||||||
compareBy(BasicComparator.ALBUM))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort by the [Date] of an item. Only available for [Song] and [Album].
|
|
||||||
*
|
|
||||||
* @see Song.date
|
|
||||||
* @see Album.dates
|
|
||||||
*/
|
|
||||||
data object ByDate : Mode {
|
data object ByDate : Mode {
|
||||||
override val intCode: Int
|
override val intCode = IntegerTable.SORT_BY_YEAR
|
||||||
get() = IntegerTable.SORT_BY_YEAR
|
override val stringRes = R.string.lbl_date
|
||||||
|
|
||||||
override val stringRes: Int
|
override fun sortSongs(songs: MutableList<Song>, direction: Direction) {
|
||||||
get() = R.string.lbl_date
|
songs.sortBy { it.name }
|
||||||
|
songs.sortBy { it.track }
|
||||||
|
songs.sortBy { it.disc }
|
||||||
|
songs.sortByDescending { it.album.name }
|
||||||
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> songs.sortBy { it.album.dates }
|
||||||
|
Direction.DESCENDING -> songs.sortByDescending { it.album.dates }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSongComparator(direction: Direction): Comparator<Song> =
|
override fun sortAlbums(albums: MutableList<Album>, direction: Direction) {
|
||||||
MultiComparator(
|
albums.sortBy { it.name }
|
||||||
compareByDynamic(direction, NullableComparator.DATE) { it.date },
|
when (direction) {
|
||||||
compareByDescending(BasicComparator.ALBUM) { it.album },
|
Direction.ASCENDING -> albums.sortBy { it.dates }
|
||||||
compareBy(NullableComparator.DISC) { it.disc },
|
Direction.DESCENDING -> albums.sortByDescending { it.dates }
|
||||||
compareBy(NullableComparator.INT) { it.track },
|
}
|
||||||
compareBy(BasicComparator.SONG))
|
}
|
||||||
|
|
||||||
override fun getAlbumComparator(direction: Direction): Comparator<Album> =
|
|
||||||
MultiComparator(
|
|
||||||
compareByDynamic(direction, NullableComparator.DATE_RANGE) { it.dates },
|
|
||||||
compareBy(BasicComparator.ALBUM))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sort by the duration of an item. */
|
|
||||||
data object ByDuration : Mode {
|
data object ByDuration : Mode {
|
||||||
override val intCode: Int
|
override val intCode = IntegerTable.SORT_BY_DURATION
|
||||||
get() = IntegerTable.SORT_BY_DURATION
|
override val stringRes = R.string.lbl_duration
|
||||||
|
|
||||||
override val stringRes: Int
|
override fun sortSongs(songs: MutableList<Song>, direction: Direction) {
|
||||||
get() = R.string.lbl_duration
|
songs.sortBy { it.name }
|
||||||
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> songs.sortBy { it.durationMs }
|
||||||
|
Direction.DESCENDING -> songs.sortByDescending { it.durationMs }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSongComparator(direction: Direction): Comparator<Song> =
|
override fun sortAlbums(albums: MutableList<Album>, direction: Direction) {
|
||||||
MultiComparator(
|
albums.sortBy { it.name }
|
||||||
compareByDynamic(direction) { it.durationMs }, compareBy(BasicComparator.SONG))
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> albums.sortBy { it.durationMs }
|
||||||
|
Direction.DESCENDING -> albums.sortByDescending { it.durationMs }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAlbumComparator(direction: Direction): Comparator<Album> =
|
override fun sortArtists(artists: MutableList<Artist>, direction: Direction) {
|
||||||
MultiComparator(
|
artists.sortBy { it.name }
|
||||||
compareByDynamic(direction) { it.durationMs }, compareBy(BasicComparator.ALBUM))
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> artists.sortBy { it.durationMs }
|
||||||
|
Direction.DESCENDING -> artists.sortByDescending { it.durationMs }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getArtistComparator(direction: Direction): Comparator<Artist> =
|
override fun sortGenres(genres: MutableList<Genre>, direction: Direction) {
|
||||||
MultiComparator(
|
genres.sortBy { it.name }
|
||||||
compareByDynamic(direction, NullableComparator.LONG) { it.durationMs },
|
when (direction) {
|
||||||
compareBy(BasicComparator.ARTIST))
|
Direction.ASCENDING -> genres.sortBy { it.durationMs }
|
||||||
|
Direction.DESCENDING -> genres.sortByDescending { it.durationMs }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getGenreComparator(direction: Direction): Comparator<Genre> =
|
override fun sortPlaylists(playlists: MutableList<Playlist>, direction: Direction) {
|
||||||
MultiComparator(
|
playlists.sortBy { it.name }
|
||||||
compareByDynamic(direction) { it.durationMs }, compareBy(BasicComparator.GENRE))
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> playlists.sortBy { it.durationMs }
|
||||||
override fun getPlaylistComparator(direction: Direction): Comparator<Playlist> =
|
Direction.DESCENDING -> playlists.sortByDescending { it.durationMs }
|
||||||
MultiComparator(
|
}
|
||||||
compareByDynamic(direction) { it.durationMs },
|
}
|
||||||
compareBy(BasicComparator.PLAYLIST))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sort by the amount of songs an item contains. Only available for MusicParents. */
|
|
||||||
data object ByCount : Mode {
|
data object ByCount : Mode {
|
||||||
override val intCode: Int
|
override val intCode = IntegerTable.SORT_BY_COUNT
|
||||||
get() = IntegerTable.SORT_BY_COUNT
|
override val stringRes = R.string.lbl_song_count
|
||||||
|
|
||||||
override val stringRes: Int
|
override fun sortAlbums(albums: MutableList<Album>, direction: Direction) {
|
||||||
get() = R.string.lbl_song_count
|
albums.sortBy { it.name }
|
||||||
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> albums.sortBy { it.songs.size }
|
||||||
|
Direction.DESCENDING -> albums.sortByDescending { it.songs.size }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAlbumComparator(direction: Direction): Comparator<Album> =
|
override fun sortArtists(artists: MutableList<Artist>, direction: Direction) {
|
||||||
MultiComparator(
|
artists.sortBy { it.name }
|
||||||
compareByDynamic(direction) { it.songs.size }, compareBy(BasicComparator.ALBUM))
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> artists.sortBy { it.songs.size }
|
||||||
|
Direction.DESCENDING -> artists.sortByDescending { it.songs.size }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getArtistComparator(direction: Direction): Comparator<Artist> =
|
override fun sortGenres(genres: MutableList<Genre>, direction: Direction) {
|
||||||
MultiComparator(
|
genres.sortBy { it.name }
|
||||||
compareByDynamic(direction, NullableComparator.INT) { it.songs.size },
|
when (direction) {
|
||||||
compareBy(BasicComparator.ARTIST))
|
Direction.ASCENDING -> genres.sortBy { it.songs.size }
|
||||||
|
Direction.DESCENDING -> genres.sortByDescending { it.songs.size }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getGenreComparator(direction: Direction): Comparator<Genre> =
|
override fun sortPlaylists(playlists: MutableList<Playlist>, direction: Direction) {
|
||||||
MultiComparator(
|
playlists.sortBy { it.name }
|
||||||
compareByDynamic(direction) { it.songs.size }, compareBy(BasicComparator.GENRE))
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> playlists.sortBy { it.songs.size }
|
||||||
override fun getPlaylistComparator(direction: Direction): Comparator<Playlist> =
|
Direction.DESCENDING -> playlists.sortByDescending { it.songs.size }
|
||||||
MultiComparator(
|
}
|
||||||
compareByDynamic(direction) { it.songs.size },
|
}
|
||||||
compareBy(BasicComparator.PLAYLIST))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort by the disc number of an item. Only available for [Song]s.
|
|
||||||
*
|
|
||||||
* @see Song.disc
|
|
||||||
*/
|
|
||||||
data object ByDisc : Mode {
|
data object ByDisc : Mode {
|
||||||
override val intCode: Int
|
override val intCode = IntegerTable.SORT_BY_DISC
|
||||||
get() = IntegerTable.SORT_BY_DISC
|
override val stringRes = R.string.lbl_disc
|
||||||
|
|
||||||
override val stringRes: Int
|
override fun sortSongs(songs: MutableList<Song>, direction: Direction) {
|
||||||
get() = R.string.lbl_disc
|
songs.sortBy { it.name }
|
||||||
|
songs.sortBy { it.track }
|
||||||
override fun getSongComparator(direction: Direction): Comparator<Song> =
|
when (direction) {
|
||||||
MultiComparator(
|
Direction.ASCENDING -> songs.sortBy { it.disc }
|
||||||
compareByDynamic(direction, NullableComparator.DISC) { it.disc },
|
Direction.DESCENDING -> songs.sortByDescending { it.disc }
|
||||||
compareBy(NullableComparator.INT) { it.track },
|
}
|
||||||
compareBy(BasicComparator.SONG))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort by the track number of an item. Only available for [Song]s.
|
|
||||||
*
|
|
||||||
* @see Song.track
|
|
||||||
*/
|
|
||||||
data object ByTrack : Mode {
|
data object ByTrack : Mode {
|
||||||
override val intCode: Int
|
override val intCode = IntegerTable.SORT_BY_TRACK
|
||||||
get() = IntegerTable.SORT_BY_TRACK
|
override val stringRes = R.string.lbl_track
|
||||||
|
|
||||||
override val stringRes: Int
|
override fun sortSongs(songs: MutableList<Song>, direction: Direction) {
|
||||||
get() = R.string.lbl_track
|
songs.sortBy { it.name }
|
||||||
|
when (direction) {
|
||||||
override fun getSongComparator(direction: Direction): Comparator<Song> =
|
Direction.ASCENDING -> songs.sortBy { it.track }
|
||||||
MultiComparator(
|
Direction.DESCENDING -> songs.sortByDescending { it.track }
|
||||||
compareBy(NullableComparator.DISC) { it.disc },
|
}
|
||||||
compareByDynamic(direction, NullableComparator.INT) { it.track },
|
songs.sortBy { it.disc }
|
||||||
compareBy(BasicComparator.SONG))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort by the date an item was added. Only supported by [Song]s and [Album]s.
|
|
||||||
*
|
|
||||||
* @see Song.dateAdded
|
|
||||||
* @see Album.dates
|
|
||||||
*/
|
|
||||||
data object ByDateAdded : Mode {
|
data object ByDateAdded : Mode {
|
||||||
override val intCode: Int
|
override val intCode = IntegerTable.SORT_BY_DATE_ADDED
|
||||||
get() = IntegerTable.SORT_BY_DATE_ADDED
|
override val stringRes = R.string.lbl_date_added
|
||||||
|
|
||||||
override val stringRes: Int
|
override fun sortSongs(songs: MutableList<Song>, direction: Direction) {
|
||||||
get() = R.string.lbl_date_added
|
songs.sortBy { it.name }
|
||||||
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> songs.sortBy { it.dateAdded }
|
||||||
|
Direction.DESCENDING -> songs.sortByDescending { it.dateAdded }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSongComparator(direction: Direction): Comparator<Song> =
|
override fun sortAlbums(albums: MutableList<Album>, direction: Direction) {
|
||||||
MultiComparator(
|
albums.sortBy { it.name }
|
||||||
compareByDynamic(direction) { it.dateAdded }, compareBy(BasicComparator.SONG))
|
when (direction) {
|
||||||
|
Direction.ASCENDING -> albums.sortBy { it.dateAdded }
|
||||||
override fun getAlbumComparator(direction: Direction): Comparator<Album> =
|
Direction.DESCENDING -> albums.sortByDescending { it.dateAdded }
|
||||||
MultiComparator(
|
}
|
||||||
compareByDynamic(direction) { album -> album.dateAdded },
|
}
|
||||||
compareBy(BasicComparator.ALBUM))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
fun fromIntCode(intCode: Int): Mode? =
|
||||||
* Convert a [Mode] integer representation into an instance.
|
|
||||||
*
|
|
||||||
* @param intCode An integer representation of a [Mode]
|
|
||||||
* @return The corresponding [Mode], or null if the [Mode] is invalid.
|
|
||||||
* @see intCode
|
|
||||||
*/
|
|
||||||
fun fromIntCode(intCode: Int) =
|
|
||||||
when (intCode) {
|
when (intCode) {
|
||||||
ByName.intCode -> ByName
|
ByName.intCode -> ByName
|
||||||
ByArtist.intCode -> ByArtist
|
ByArtist.intCode -> ByArtist
|
||||||
|
@ -465,169 +415,3 @@ data class Sort(val mode: Mode, val direction: Direction) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility function to create a [Comparator] in a dynamic way determined by [direction].
|
|
||||||
*
|
|
||||||
* @param direction The [Sort.Direction] to sort in.
|
|
||||||
* @see compareBy
|
|
||||||
* @see compareByDescending
|
|
||||||
*/
|
|
||||||
private inline fun <T : Music, K : Comparable<K>> compareByDynamic(
|
|
||||||
direction: Sort.Direction,
|
|
||||||
crossinline selector: (T) -> K
|
|
||||||
) =
|
|
||||||
when (direction) {
|
|
||||||
Sort.Direction.ASCENDING -> compareBy(selector)
|
|
||||||
Sort.Direction.DESCENDING -> compareByDescending(selector)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility function to create a [Comparator] in a dynamic way determined by [direction]
|
|
||||||
*
|
|
||||||
* @param direction The [Sort.Direction] to sort in.
|
|
||||||
* @param comparator A [Comparator] to wrap.
|
|
||||||
* @return A new [Comparator] with the specified configuration.
|
|
||||||
* @see compareBy
|
|
||||||
* @see compareByDescending
|
|
||||||
*/
|
|
||||||
private fun <T : Music> compareByDynamic(
|
|
||||||
direction: Sort.Direction,
|
|
||||||
comparator: Comparator<in T>
|
|
||||||
): Comparator<T> = compareByDynamic(direction, comparator) { it }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility function to create a [Comparator] a dynamic way determined by [direction]
|
|
||||||
*
|
|
||||||
* @param direction The [Sort.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.
|
|
||||||
* @see compareBy
|
|
||||||
* @see compareByDescending
|
|
||||||
*/
|
|
||||||
private inline fun <T : Music, K> compareByDynamic(
|
|
||||||
direction: Sort.Direction,
|
|
||||||
comparator: Comparator<in K>,
|
|
||||||
crossinline selector: (T) -> K
|
|
||||||
) =
|
|
||||||
when (direction) {
|
|
||||||
Sort.Direction.ASCENDING -> compareBy(comparator, selector)
|
|
||||||
Sort.Direction.DESCENDING -> compareByDescending(comparator, selector)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility function to create a [Comparator] that sorts in ascending order based on the given
|
|
||||||
* [Comparator], with a selector based on the item itself.
|
|
||||||
*
|
|
||||||
* @param comparator The [Comparator] to wrap.
|
|
||||||
* @return A new [Comparator] with the specified configuration.
|
|
||||||
* @see compareBy
|
|
||||||
*/
|
|
||||||
private fun <T : Music> compareBy(comparator: Comparator<T>): Comparator<T> =
|
|
||||||
compareBy(comparator) { it }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [Comparator] that chains several other [Comparator]s together to form one comparison.
|
|
||||||
*
|
|
||||||
* @param comparators The [Comparator]s to chain. These will be iterated through in order during a
|
|
||||||
* comparison, with the first non-equal result becoming the result.
|
|
||||||
*/
|
|
||||||
private class MultiComparator<T>(vararg comparators: Comparator<T>) : Comparator<T> {
|
|
||||||
private val _comparators = comparators
|
|
||||||
|
|
||||||
override fun compare(a: T?, b: T?): Int {
|
|
||||||
for (comparator in _comparators) {
|
|
||||||
val result = comparator.compare(a, b)
|
|
||||||
if (result != 0) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a [Comparator], extending it to compare two lists.
|
|
||||||
*
|
|
||||||
* @param inner The [Comparator] to use.
|
|
||||||
*/
|
|
||||||
private class ListComparator<T>(private val inner: Comparator<T>) : Comparator<List<T>> {
|
|
||||||
override fun compare(a: List<T>, b: List<T>): Int {
|
|
||||||
for (i in 0 until max(a.size, b.size)) {
|
|
||||||
val ai = a.getOrNull(i)
|
|
||||||
val bi = b.getOrNull(i)
|
|
||||||
when {
|
|
||||||
ai != null && bi != null -> {
|
|
||||||
val result = inner.compare(ai, bi)
|
|
||||||
if (result != 0) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ai == null && bi != null -> return -1 // a < b
|
|
||||||
ai == null && bi == null -> return 0 // a = b
|
|
||||||
else -> return 1 // a < b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/** A re-usable configured for [Artist]s.. */
|
|
||||||
val ARTISTS: Comparator<List<Artist>> = ListComparator(BasicComparator.ARTIST)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [Comparator] that compares abstract [Music] values. Internally, this is similar to
|
|
||||||
* [NullableComparator], however comparing [Music.name] instead of [Comparable].
|
|
||||||
*
|
|
||||||
* @see NullableComparator
|
|
||||||
* @see Music.name
|
|
||||||
*/
|
|
||||||
private class BasicComparator<T : Music> private constructor() : Comparator<T> {
|
|
||||||
override fun compare(a: T, b: T) = a.name.compareTo(b.name)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/** A re-usable instance configured for [Song]s. */
|
|
||||||
val SONG: Comparator<Song> = BasicComparator()
|
|
||||||
/** A re-usable instance configured for [Album]s. */
|
|
||||||
val ALBUM: Comparator<Album> = BasicComparator()
|
|
||||||
/** A re-usable instance configured for [Artist]s. */
|
|
||||||
val ARTIST: Comparator<Artist> = BasicComparator()
|
|
||||||
/** A re-usable instance configured for [Genre]s. */
|
|
||||||
val GENRE: Comparator<Genre> = BasicComparator()
|
|
||||||
/** A re-usable instance configured for [Playlist]s. */
|
|
||||||
val PLAYLIST: Comparator<Playlist> = BasicComparator()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [Comparator] that compares two possibly null values. Values will be considered lesser if they
|
|
||||||
* are null, and greater if they are non-null.
|
|
||||||
*/
|
|
||||||
private class NullableComparator<T : Comparable<T>> private constructor() : Comparator<T?> {
|
|
||||||
override fun compare(a: T?, b: T?) =
|
|
||||||
when {
|
|
||||||
a != null && b != null -> a.compareTo(b)
|
|
||||||
a == null && b != null -> -1 // a < b
|
|
||||||
a == null && b == null -> 0 // a = b
|
|
||||||
else -> 1 // a < b
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/** A re-usable instance configured for [Int]s. */
|
|
||||||
val INT = NullableComparator<Int>()
|
|
||||||
/** A re-usable instance configured for [Long]s. */
|
|
||||||
val LONG = NullableComparator<Long>()
|
|
||||||
/** A re-usable instance configured for [Disc]s */
|
|
||||||
val DISC = NullableComparator<Disc>()
|
|
||||||
/** A re-usable instance configured for [Date.Range]s. */
|
|
||||||
val DATE_RANGE = NullableComparator<Date.Range>()
|
|
||||||
|
|
||||||
/** A re-usable instance configured for [Date]s. */
|
|
||||||
val DATE = NullableComparator<Date>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -438,7 +438,8 @@ constructor(
|
||||||
// to cascade to and cancel all other routines before finally bubbling up
|
// to cascade to and cancel all other routines before finally bubbling up
|
||||||
// to the main extractor loop.
|
// to the main extractor loop.
|
||||||
logE("MediaStore extraction failed: $e")
|
logE("MediaStore extraction failed: $e")
|
||||||
incompleteSongs.close(Exception("MediaStore extraction failed: $e"))
|
incompleteSongs.close(
|
||||||
|
Exception("MediaStore extraction failed: ${e.stackTraceToString()}"))
|
||||||
return@async
|
return@async
|
||||||
}
|
}
|
||||||
incompleteSongs.close()
|
incompleteSongs.close()
|
||||||
|
@ -453,7 +454,8 @@ constructor(
|
||||||
tagExtractor.consume(incompleteSongs, completeSongs)
|
tagExtractor.consume(incompleteSongs, completeSongs)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logE("Tag extraction failed: $e")
|
logE("Tag extraction failed: $e")
|
||||||
completeSongs.close(Exception("Tag extraction failed: $e"))
|
completeSongs.close(
|
||||||
|
Exception("Tag extraction failed: ${e.stackTraceToString()}"))
|
||||||
return@async
|
return@async
|
||||||
}
|
}
|
||||||
completeSongs.close()
|
completeSongs.close()
|
||||||
|
@ -470,7 +472,8 @@ constructor(
|
||||||
completeSongs, processedSongs, separators, nameFactory)
|
completeSongs, processedSongs, separators, nameFactory)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logE("DeviceLibrary creation failed: $e")
|
logE("DeviceLibrary creation failed: $e")
|
||||||
processedSongs.close(Exception("DeviceLibrary creation failed: $e"))
|
processedSongs.close(
|
||||||
|
Exception("DeviceLibrary creation failed: ${e.stackTraceToString()}"))
|
||||||
return@async Result.failure(e)
|
return@async Result.failure(e)
|
||||||
}
|
}
|
||||||
processedSongs.close()
|
processedSongs.close()
|
||||||
|
|
3
fastlane/metadata/android/en-US/changelogs/47.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/47.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Auxio 3.5.1 adds support for android auto alongside various playback and music quality of life improvements.
|
||||||
|
This release fixes a critical bug with the music loader.
|
||||||
|
For more information, see https://github.com/OxygenCobalt/Auxio/releases/tag/v3.5.0
|
Loading…
Reference in a new issue