sort: completely rework class
Completely refactor the Sort class to be more efficient and straightforward. The original Sort class had *major* shortcomings, it was slow, poorly organized, and relied on abusing compareBy to implement special things like article sort. This rework eliminates all of that in favor of a new system relying on custom comparators and chaining to achieve something much faster and maintainable.
This commit is contained in:
parent
90f10f2a84
commit
9c07ad2d34
11 changed files with 201 additions and 129 deletions
|
@ -36,6 +36,9 @@ import org.oxycblt.auxio.settings.SettingsManager
|
|||
* - Rework RecyclerView management and item dragging
|
||||
* - Rework sealed classes to minimize whens and maximize overrides
|
||||
* ```
|
||||
*
|
||||
* TODO: Dumpster int-codes for a 4-byte identifier (can still be in the form of an int) For
|
||||
* example, instead of 0xA111 for ReplayGainMode.TRACK, you would instead have RTCK
|
||||
*/
|
||||
@Suppress("UNUSED")
|
||||
class AuxioApp : Application(), ImageLoaderFactory {
|
||||
|
|
|
@ -74,7 +74,7 @@ private constructor(
|
|||
private val artist: Artist,
|
||||
) : BaseFetcher() {
|
||||
override suspend fun fetch(): FetchResult? {
|
||||
val albums = Sort.ByName(true).sortAlbums(artist.albums)
|
||||
val albums = Sort.ByName(true).albums(artist.albums)
|
||||
val results = albums.mapAtMost(4) { album -> fetchArt(context, album) }
|
||||
|
||||
return createMosaic(context, results, size)
|
||||
|
|
|
@ -162,7 +162,7 @@ class DetailViewModel : ViewModel() {
|
|||
mShowMenu.value = MenuConfig(view, settingsManager.detailGenreSort)
|
||||
}))
|
||||
|
||||
data.addAll(settingsManager.detailGenreSort.sortGenre(curGenre.value!!))
|
||||
data.addAll(settingsManager.detailGenreSort.genre(curGenre.value!!))
|
||||
|
||||
mGenreData.value = data
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ class DetailViewModel : ViewModel() {
|
|||
|
||||
data.add(Header(id = -2, string = R.string.lbl_albums))
|
||||
|
||||
data.addAll(Sort.ByYear(false).sortAlbums(artist.albums))
|
||||
data.addAll(Sort.ByYear(false).albums(artist.albums))
|
||||
|
||||
data.add(
|
||||
ActionHeader(
|
||||
|
@ -187,7 +187,7 @@ class DetailViewModel : ViewModel() {
|
|||
mShowMenu.value = MenuConfig(view, settingsManager.detailArtistSort)
|
||||
}))
|
||||
|
||||
data.addAll(settingsManager.detailArtistSort.sortArtist(artist))
|
||||
data.addAll(settingsManager.detailArtistSort.artist(artist))
|
||||
|
||||
mArtistData.value = data.toList()
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ class DetailViewModel : ViewModel() {
|
|||
mShowMenu.value = MenuConfig(view, settingsManager.detailAlbumSort)
|
||||
}))
|
||||
|
||||
data.addAll(settingsManager.detailAlbumSort.sortAlbum(curAlbum.value!!))
|
||||
data.addAll(settingsManager.detailAlbumSort.album(curAlbum.value!!))
|
||||
|
||||
mAlbumData.value = data
|
||||
}
|
||||
|
|
|
@ -81,10 +81,10 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback {
|
|||
|
||||
viewModelScope.launch {
|
||||
val musicStore = MusicStore.awaitInstance()
|
||||
mSongs.value = settingsManager.libSongSort.sortSongs(musicStore.songs)
|
||||
mAlbums.value = settingsManager.libAlbumSort.sortAlbums(musicStore.albums)
|
||||
mArtists.value = settingsManager.libArtistSort.sortParents(musicStore.artists)
|
||||
mGenres.value = settingsManager.libGenreSort.sortParents(musicStore.genres)
|
||||
mSongs.value = settingsManager.libSongSort.songs(musicStore.songs)
|
||||
mAlbums.value = settingsManager.libAlbumSort.albums(musicStore.albums)
|
||||
mArtists.value = settingsManager.libArtistSort.artists(musicStore.artists)
|
||||
mGenres.value = settingsManager.libGenreSort.genres(musicStore.genres)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,19 +113,19 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback {
|
|||
when (mCurTab.value) {
|
||||
DisplayMode.SHOW_SONGS -> {
|
||||
settingsManager.libSongSort = sort
|
||||
mSongs.value = sort.sortSongs(mSongs.value!!)
|
||||
mSongs.value = sort.songs(mSongs.value!!)
|
||||
}
|
||||
DisplayMode.SHOW_ALBUMS -> {
|
||||
settingsManager.libAlbumSort = sort
|
||||
mAlbums.value = sort.sortAlbums(mAlbums.value!!)
|
||||
mAlbums.value = sort.albums(mAlbums.value!!)
|
||||
}
|
||||
DisplayMode.SHOW_ARTISTS -> {
|
||||
settingsManager.libArtistSort = sort
|
||||
mArtists.value = sort.sortParents(mArtists.value!!)
|
||||
mArtists.value = sort.artists(mArtists.value!!)
|
||||
}
|
||||
DisplayMode.SHOW_GENRES -> {
|
||||
settingsManager.libGenreSort = sort
|
||||
mGenres.value = sort.sortParents(mGenres.value!!)
|
||||
mGenres.value = sort.genres(mGenres.value!!)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
|
|
@ -69,19 +69,11 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
|||
* - Added documentation
|
||||
*
|
||||
* @author Hai Zhang, OxygenCobalt
|
||||
*
|
||||
* TODO: Fix strange touch behavior when the pointer is slightly outside of the view.
|
||||
*
|
||||
* TODO: Really try to make this view less insane.
|
||||
*/
|
||||
class FastScrollRecyclerView
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||
EdgeRecyclerView(context, attrs, defStyleAttr) {
|
||||
private val minTouchTargetSize =
|
||||
context.getDimenSizeSafe(R.dimen.fast_scroll_thumb_touch_target_size)
|
||||
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||
|
||||
// Thumb
|
||||
private val thumbView =
|
||||
View(context).apply {
|
||||
|
@ -102,6 +94,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
}
|
||||
}
|
||||
|
||||
private val scrollPositionChildRect = Rect()
|
||||
|
||||
// Popup
|
||||
private val popupView =
|
||||
FastScrollPopupView(context).apply {
|
||||
|
@ -118,7 +112,11 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
|
||||
private var showingPopup = false
|
||||
|
||||
// Touch events
|
||||
// Touch
|
||||
private val minTouchTargetSize =
|
||||
context.getDimenSizeSafe(R.dimen.fast_scroll_thumb_touch_target_size)
|
||||
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||
|
||||
private var downX = 0f
|
||||
private var downY = 0f
|
||||
private var lastY = 0f
|
||||
|
@ -151,8 +149,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
onDragListener?.invoke(value)
|
||||
}
|
||||
|
||||
private val childRect = Rect()
|
||||
|
||||
/** Callback to provide a string to be shown on the popup when an item is passed */
|
||||
var popupProvider: ((Int) -> String)? = null
|
||||
|
||||
|
@ -303,8 +299,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
|
||||
// Combine the previous item dimensions with the current item top to find our scroll
|
||||
// position
|
||||
getDecoratedBoundsWithMargins(getChildAt(0), childRect)
|
||||
val scrollOffset = paddingTop + (firstAdapterPos * itemHeight) - childRect.top
|
||||
getDecoratedBoundsWithMargins(getChildAt(0), scrollPositionChildRect)
|
||||
val scrollOffset = paddingTop + (firstAdapterPos * itemHeight) - scrollPositionChildRect.top
|
||||
|
||||
// Then calculate the thumb position, which is just:
|
||||
// [proportion of scroll position to scroll range] * [total thumb range]
|
||||
|
@ -497,8 +493,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
}
|
||||
|
||||
val itemView = getChildAt(0)
|
||||
getDecoratedBoundsWithMargins(itemView, childRect)
|
||||
return childRect.height()
|
||||
getDecoratedBoundsWithMargins(itemView, scrollPositionChildRect)
|
||||
return scrollPositionChildRect.height()
|
||||
}
|
||||
|
||||
private val itemCount: Int
|
||||
|
|
|
@ -27,6 +27,7 @@ import androidx.core.database.getStringOrNull
|
|||
import androidx.core.text.isDigitsOnly
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.music.excluded.ExcludedDatabase
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
||||
/**
|
||||
|
@ -277,9 +278,17 @@ class MusicLoader {
|
|||
// Use the song with the latest year as our metadata song.
|
||||
// This allows us to replicate the LAST_YEAR field, which is useful as it means that
|
||||
// weird years like "0" wont show up if there are alternatives.
|
||||
// TODO: Weigh songs with null years lower than songs with zero years
|
||||
val templateSong =
|
||||
requireNotNull(albumSongs.maxByOrNull { it.internalMediaStoreYear ?: 0 })
|
||||
// Note: Normally we could want to use something like maxByWith, but apparently
|
||||
// that does not exist in the kotlin stdlib yet.
|
||||
val comparator = Sort.NullableComparator<Int>()
|
||||
var templateSong = albumSongs[0]
|
||||
for (i in 1..albumSongs.lastIndex) {
|
||||
val candidate = albumSongs[i]
|
||||
if (comparator.compare(templateSong.track, candidate.track) < 0) {
|
||||
templateSong = candidate
|
||||
}
|
||||
}
|
||||
|
||||
val albumName = templateSong.internalMediaStoreAlbumName
|
||||
val albumYear = templateSong.internalMediaStoreYear
|
||||
val albumCoverUri =
|
||||
|
|
|
@ -240,7 +240,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
|
||||
/** Add an [Album] to the top of the queue. */
|
||||
fun playNext(album: Album) {
|
||||
playbackManager.playNext(settingsManager.detailAlbumSort.sortAlbum(album))
|
||||
playbackManager.playNext(settingsManager.detailAlbumSort.album(album))
|
||||
}
|
||||
|
||||
/** Add a [Song] to the end of the queue. */
|
||||
|
@ -250,7 +250,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
|
||||
/** Add an [Album] to the end of the queue. */
|
||||
fun addToQueue(album: Album) {
|
||||
playbackManager.addToQueue(settingsManager.detailAlbumSort.sortAlbum(album))
|
||||
playbackManager.addToQueue(settingsManager.detailAlbumSort.album(album))
|
||||
}
|
||||
|
||||
// --- STATUS FUNCTIONS ---
|
||||
|
|
|
@ -376,13 +376,13 @@ class PlaybackStateManager private constructor() {
|
|||
mQueue =
|
||||
when (mPlaybackMode) {
|
||||
PlaybackMode.ALL_SONGS ->
|
||||
settingsManager.libSongSort.sortSongs(musicStore.songs).toMutableList()
|
||||
settingsManager.libSongSort.songs(musicStore.songs).toMutableList()
|
||||
PlaybackMode.IN_ALBUM ->
|
||||
settingsManager.detailAlbumSort.sortAlbum(mParent as Album).toMutableList()
|
||||
settingsManager.detailAlbumSort.album(mParent as Album).toMutableList()
|
||||
PlaybackMode.IN_ARTIST ->
|
||||
settingsManager.detailArtistSort.sortArtist(mParent as Artist).toMutableList()
|
||||
settingsManager.detailArtistSort.artist(mParent as Artist).toMutableList()
|
||||
PlaybackMode.IN_GENRE ->
|
||||
settingsManager.detailGenreSort.sortGenre(mParent as Genre).toMutableList()
|
||||
settingsManager.detailGenreSort.genre(mParent as Genre).toMutableList()
|
||||
}
|
||||
|
||||
if (keepSong) {
|
||||
|
|
|
@ -88,28 +88,28 @@ class SearchViewModel : ViewModel() {
|
|||
if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_ARTISTS) {
|
||||
musicStore.artists.filterByOrNull(query)?.let { artists ->
|
||||
results.add(Header(-1, R.string.lbl_artists))
|
||||
results.addAll(sort.sortParents(artists))
|
||||
results.addAll(sort.artists(artists))
|
||||
}
|
||||
}
|
||||
|
||||
if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_ALBUMS) {
|
||||
musicStore.albums.filterByOrNull(query)?.let { albums ->
|
||||
results.add(Header(-2, R.string.lbl_albums))
|
||||
results.addAll(sort.sortAlbums(albums))
|
||||
results.addAll(sort.albums(albums))
|
||||
}
|
||||
}
|
||||
|
||||
if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_GENRES) {
|
||||
musicStore.genres.filterByOrNull(query)?.let { genres ->
|
||||
results.add(Header(-3, R.string.lbl_genres))
|
||||
results.addAll(sort.sortParents(genres))
|
||||
results.addAll(sort.genres(genres))
|
||||
}
|
||||
}
|
||||
|
||||
if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_SONGS) {
|
||||
musicStore.songs.filterByOrNull(query)?.let { songs ->
|
||||
results.add(Header(-4, R.string.lbl_songs))
|
||||
results.addAll(sort.sortSongs(songs))
|
||||
results.addAll(sort.songs(songs))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@ import org.oxycblt.auxio.music.Album
|
|||
import org.oxycblt.auxio.music.Artist
|
||||
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.util.logW
|
||||
|
||||
/**
|
||||
* A data class representing the sort modes used in Auxio.
|
||||
|
@ -43,14 +43,95 @@ import org.oxycblt.auxio.music.Song
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
sealed class Sort(open val isAscending: Boolean) {
|
||||
open fun songs(songs: Collection<Song>): List<Song> {
|
||||
logW("This sort is not supported for songs")
|
||||
return songs.toList()
|
||||
}
|
||||
|
||||
open fun albums(albums: Collection<Album>): List<Album> {
|
||||
logW("This sort is not supported for albums")
|
||||
return albums.toList()
|
||||
}
|
||||
|
||||
open fun artists(artists: Collection<Artist>): List<Artist> {
|
||||
logW("This sort is not supported for artists")
|
||||
return artists.toList()
|
||||
}
|
||||
|
||||
open fun genres(genres: Collection<Genre>): List<Genre> {
|
||||
logW("This sort is not supported for genres")
|
||||
return genres.toList()
|
||||
}
|
||||
|
||||
/** Sort by the names of an item */
|
||||
class ByName(override val isAscending: Boolean) : Sort(isAscending)
|
||||
/** Sort by the artist of an item, only supported by [Album] and [Song] */
|
||||
class ByArtist(override val isAscending: Boolean) : Sort(isAscending)
|
||||
class ByName(override val isAscending: Boolean) : Sort(isAscending) {
|
||||
override fun songs(songs: Collection<Song>): List<Song> {
|
||||
return songs.sortedWith(compareByDynamic(NameComparator()) { it })
|
||||
}
|
||||
|
||||
override fun albums(albums: Collection<Album>): List<Album> {
|
||||
return albums.sortedWith(compareByDynamic(NameComparator()) { it })
|
||||
}
|
||||
|
||||
override fun artists(artists: Collection<Artist>): List<Artist> {
|
||||
return artists.sortedWith(compareByDynamic(NameComparator()) { it })
|
||||
}
|
||||
|
||||
override fun genres(genres: Collection<Genre>): List<Genre> {
|
||||
return genres.sortedWith(compareByDynamic(NameComparator()) { it })
|
||||
}
|
||||
}
|
||||
|
||||
/** Sort by the album of an item, only supported by [Song] */
|
||||
class ByAlbum(override val isAscending: Boolean) : Sort(isAscending)
|
||||
class ByAlbum(override val isAscending: Boolean) : Sort(isAscending) {
|
||||
override fun songs(songs: Collection<Song>): List<Song> {
|
||||
return songs.sortedWith(
|
||||
MultiComparator(
|
||||
compareByDynamic(NameComparator()) { it.album },
|
||||
compareBy(NullableComparator()) { it.track },
|
||||
compareBy(NameComparator()) { it }))
|
||||
}
|
||||
}
|
||||
|
||||
/** Sort by the artist of an item, only supported by [Album] and [Song] */
|
||||
class ByArtist(override val isAscending: Boolean) : Sort(isAscending) {
|
||||
override fun songs(songs: Collection<Song>): List<Song> {
|
||||
return songs.sortedWith(
|
||||
MultiComparator(
|
||||
compareByDynamic(NameComparator()) { it.album.artist },
|
||||
compareByDescending(NullableComparator()) { it.album.year },
|
||||
compareByDescending(NameComparator()) { it.album },
|
||||
compareBy(NullableComparator()) { it.track },
|
||||
compareBy(NameComparator()) { it }))
|
||||
}
|
||||
|
||||
override fun albums(albums: Collection<Album>): List<Album> {
|
||||
return albums.sortedWith(
|
||||
MultiComparator(
|
||||
compareByDynamic(NameComparator()) { it.artist },
|
||||
compareByDescending(NullableComparator()) { it.year },
|
||||
compareBy(NameComparator()) { it }))
|
||||
}
|
||||
}
|
||||
|
||||
/** Sort by the year of an item, only supported by [Album] and [Song] */
|
||||
class ByYear(override val isAscending: Boolean) : Sort(isAscending)
|
||||
class ByYear(override val isAscending: Boolean) : Sort(isAscending) {
|
||||
override fun songs(songs: Collection<Song>): List<Song> {
|
||||
return songs.sortedWith(
|
||||
MultiComparator(
|
||||
compareByDynamic(NullableComparator()) { it.album.year },
|
||||
compareByDescending(NameComparator()) { it.album },
|
||||
compareBy(NullableComparator()) { it.track },
|
||||
compareBy(NameComparator()) { it }))
|
||||
}
|
||||
|
||||
override fun albums(albums: Collection<Album>): List<Album> {
|
||||
return albums.sortedWith(
|
||||
MultiComparator(
|
||||
compareByDynamic(NullableComparator()) { it.year },
|
||||
compareBy(NameComparator()) { it }))
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the corresponding item id for this sort. */
|
||||
val itemId: Int
|
||||
|
@ -89,72 +170,31 @@ sealed class Sort(open val isAscending: Boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a list of [Song] instances to reflect this specific sort.
|
||||
*
|
||||
* Albums are sorted by ascending track, artists are sorted with [ByYear] descending.
|
||||
*
|
||||
* @return A sorted list of songs
|
||||
*/
|
||||
fun sortSongs(songs: Collection<Song>): List<Song> {
|
||||
return when (this) {
|
||||
is ByName -> songs.stringSort { it.resolvedName }
|
||||
else ->
|
||||
sortAlbums(songs.groupBy { it.album }.keys).flatMap { album ->
|
||||
album.songs.intSort(true) { it.track ?: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a list of [Album] instances to reflect this specific sort.
|
||||
*
|
||||
* Artists are sorted with [ByYear] descending.
|
||||
*
|
||||
* @return A sorted list of albums
|
||||
*/
|
||||
fun sortAlbums(albums: Collection<Album>): List<Album> {
|
||||
return when (this) {
|
||||
is ByName, is ByAlbum -> albums.stringSort { it.resolvedName }
|
||||
is ByArtist ->
|
||||
sortParents(albums.groupBy { it.artist }.keys).flatMap {
|
||||
ByYear(false).sortAlbums(it.albums)
|
||||
}
|
||||
is ByYear -> albums.intSort { it.year ?: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a list of [MusicParent] instances to reflect this specific sort.
|
||||
*
|
||||
* @return A sorted list of the specific parent
|
||||
*/
|
||||
fun <T : MusicParent> sortParents(parents: Collection<T>): List<T> {
|
||||
return parents.stringSort { it.resolvedName }
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the songs in an album.
|
||||
* @see sortSongs
|
||||
* @see songs
|
||||
*/
|
||||
fun sortAlbum(album: Album): List<Song> {
|
||||
return album.songs.intSort { it.track ?: 0 }
|
||||
fun album(album: Album): List<Song> {
|
||||
return album.songs.sortedWith(
|
||||
MultiComparator(
|
||||
compareByDynamic(NullableComparator()) { it.track },
|
||||
compareBy(NameComparator()) { it }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the songs in an artist.
|
||||
* @see sortSongs
|
||||
* @see songs
|
||||
*/
|
||||
fun sortArtist(artist: Artist): List<Song> {
|
||||
return sortSongs(artist.songs)
|
||||
fun artist(artist: Artist): List<Song> {
|
||||
return songs(artist.songs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the songs in a genre.
|
||||
* @see sortSongs
|
||||
* @see songs
|
||||
*/
|
||||
fun sortGenre(genre: Genre): List<Song> {
|
||||
return sortSongs(genre.songs)
|
||||
fun genre(genre: Genre): List<Song> {
|
||||
return songs(genre.songs)
|
||||
}
|
||||
|
||||
/** Convert this sort to it's integer representation. */
|
||||
|
@ -167,35 +207,59 @@ sealed class Sort(open val isAscending: Boolean) {
|
|||
}.shl(1) or if (isAscending) 1 else 0
|
||||
}
|
||||
|
||||
private fun <T : Music> Collection<T>.stringSort(
|
||||
asc: Boolean = isAscending,
|
||||
selector: (T) -> String
|
||||
): List<T> {
|
||||
// Chain whatever item call with sliceArticle for correctness
|
||||
val chained: (T) -> String = { selector(it).sliceArticle() }
|
||||
|
||||
val comparator =
|
||||
if (asc) {
|
||||
compareBy(String.CASE_INSENSITIVE_ORDER, chained)
|
||||
} else {
|
||||
compareByDescending(String.CASE_INSENSITIVE_ORDER, chained)
|
||||
}
|
||||
|
||||
return sortedWith(comparator)
|
||||
protected inline fun <T : Music, K> compareByDynamic(
|
||||
comparator: Comparator<in K>,
|
||||
crossinline selector: (T) -> K
|
||||
): Comparator<T> {
|
||||
return if (isAscending) {
|
||||
compareBy(comparator, selector)
|
||||
} else {
|
||||
compareByDescending(comparator, selector)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : Music> Collection<T>.intSort(
|
||||
asc: Boolean = isAscending,
|
||||
selector: (T) -> Int,
|
||||
): List<T> {
|
||||
val comparator =
|
||||
if (asc) {
|
||||
compareBy(selector)
|
||||
} else {
|
||||
compareByDescending(selector)
|
||||
class NameComparator<T : Music> : Comparator<T> {
|
||||
override fun compare(a: T?, b: T?): Int {
|
||||
if (a == null && b != null) return -1 // -1 -> a < b
|
||||
if (a == null && b == null) return 0 // 0 -> 0 = b
|
||||
if (a != null && b == null) return 1 // 1 -> a > b
|
||||
|
||||
return a!!.resolvedName
|
||||
.sliceArticle()
|
||||
.compareTo(b!!.resolvedName.sliceArticle(), ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
class NullableComparator<T : Comparable<T>> : Comparator<T?> {
|
||||
override fun compare(a: T?, b: T?): Int {
|
||||
if (a == null && b != null) return -1 // -1 -> a < b
|
||||
if (a == null && b == null) return 0 // 0 -> 0 = b
|
||||
if (a != null && b == null) return 1 // 1 -> a > b
|
||||
return a!!.compareTo(b!!)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Chains the given comparators together to form one comparator.
|
||||
*
|
||||
* Sorts often need to compare multiple things at once across several hierarchies, with this
|
||||
* class doing such in a more efficient manner than resorting at multiple intervals or grouping
|
||||
* items up. Comparators are checked from first to last, with the first comparator that returns a
|
||||
* non-equal result being propagated upwards.
|
||||
*/
|
||||
class MultiComparator<T>(vararg comparators: Comparator<T>) : Comparator<T> {
|
||||
private val mComparators = comparators
|
||||
|
||||
override fun compare(a: T?, b: T?): Int {
|
||||
for (comparator in mComparators) {
|
||||
val result = comparator.compare(a, b)
|
||||
if (result != 0) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return sortedWith(comparator)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -229,15 +293,15 @@ sealed class Sort(open val isAscending: Boolean) {
|
|||
* languages.
|
||||
*/
|
||||
fun String.sliceArticle(): String {
|
||||
if (length > 5 && startsWith("the ", true)) {
|
||||
if (length > 5 && startsWith("the ", ignoreCase = true)) {
|
||||
return slice(4..lastIndex)
|
||||
}
|
||||
|
||||
if (length > 4 && startsWith("an ", true)) {
|
||||
if (length > 4 && startsWith("an ", ignoreCase = true)) {
|
||||
return slice(3..lastIndex)
|
||||
}
|
||||
|
||||
if (length > 3 && startsWith("a ", true)) {
|
||||
if (length > 3 && startsWith("a ", ignoreCase = true)) {
|
||||
return slice(2..lastIndex)
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<dimen name="elevation_small">2dp</dimen>
|
||||
<dimen name="elevation_normal">4dp</dimen>
|
||||
|
||||
<dimen name="fast_scroll_popup_min_width">80dp</dimen>
|
||||
<dimen name="fast_scroll_popup_min_width">78dp</dimen>
|
||||
<dimen name="fast_scroll_popup_min_height">64dp</dimen>
|
||||
<dimen name="fast_scroll_popup_padding_start">@dimen/spacing_medium</dimen>
|
||||
<dimen name="fast_scroll_popup_padding_end">28dp</dimen>
|
||||
|
|
Loading…
Reference in a new issue