ui: clean up dead code
Clean up the last remaining legacy SortMode remnants. Also reformat all ViewHolder IDs to be properly unique and sequential.
This commit is contained in:
parent
c9dd3b97a2
commit
08169b6167
16 changed files with 160 additions and 340 deletions
|
@ -23,7 +23,6 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.home.LibSortMode
|
|
||||||
import org.oxycblt.auxio.music.ActionHeader
|
import org.oxycblt.auxio.music.ActionHeader
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
|
@ -88,7 +87,7 @@ class DetailViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
data.addAll(LibSortMode.ASCENDING.sortGenre(curGenre.value!!))
|
data.addAll(SortMode.ASCENDING.sortGenre(curGenre.value!!))
|
||||||
|
|
||||||
mGenreData.value = data
|
mGenreData.value = data
|
||||||
}
|
}
|
||||||
|
@ -108,7 +107,7 @@ class DetailViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
data.addAll(LibSortMode.YEAR.sortAlbums(artist.albums))
|
data.addAll(SortMode.YEAR.sortAlbums(artist.albums))
|
||||||
|
|
||||||
data.add(
|
data.add(
|
||||||
ActionHeader(
|
ActionHeader(
|
||||||
|
@ -121,7 +120,7 @@ class DetailViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
data.addAll(LibSortMode.YEAR.sortArtist(artist))
|
data.addAll(SortMode.YEAR.sortArtist(artist))
|
||||||
|
|
||||||
mArtistData.value = data.toList()
|
mArtistData.value = data.toList()
|
||||||
}
|
}
|
||||||
|
@ -144,7 +143,7 @@ class DetailViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
data.addAll(LibSortMode.ASCENDING.sortAlbum(curAlbum.value!!))
|
data.addAll(SortMode.ASCENDING.sortAlbum(curAlbum.value!!))
|
||||||
|
|
||||||
mAlbumData.value = data
|
mAlbumData.value = data
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ class AlbumDetailAdapter(
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return when (getItem(position)) {
|
return when (getItem(position)) {
|
||||||
is Album -> ALBUM_HEADER_ITEM_TYPE
|
is Album -> ALBUM_DETAIL_ITEM_TYPE
|
||||||
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
|
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
|
||||||
is Song -> ALBUM_SONG_ITEM_TYPE
|
is Song -> ALBUM_SONG_ITEM_TYPE
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ class AlbumDetailAdapter(
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
ALBUM_HEADER_ITEM_TYPE -> AlbumHeaderViewHolder(
|
ALBUM_DETAIL_ITEM_TYPE -> AlbumDetailViewHolder(
|
||||||
ItemDetailBinding.inflate(parent.context.inflater)
|
ItemDetailBinding.inflate(parent.context.inflater)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ class AlbumDetailAdapter(
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
|
|
||||||
when (item) {
|
when (item) {
|
||||||
is Album -> (holder as AlbumHeaderViewHolder).bind(item)
|
is Album -> (holder as AlbumDetailViewHolder).bind(item)
|
||||||
is Song -> (holder as AlbumSongViewHolder).bind(item)
|
is Song -> (holder as AlbumSongViewHolder).bind(item)
|
||||||
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
|
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ class AlbumDetailAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class AlbumHeaderViewHolder(
|
inner class AlbumDetailViewHolder(
|
||||||
private val binding: ItemDetailBinding
|
private val binding: ItemDetailBinding
|
||||||
) : BaseViewHolder<Album>(binding) {
|
) : BaseViewHolder<Album>(binding) {
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ class AlbumDetailAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ALBUM_HEADER_ITEM_TYPE = 0xA007
|
const val ALBUM_DETAIL_ITEM_TYPE = 0xA006
|
||||||
const val ALBUM_SONG_ITEM_TYPE = 0xA008
|
const val ALBUM_SONG_ITEM_TYPE = 0xA007
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ class ArtistDetailAdapter(
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return when (getItem(position)) {
|
return when (getItem(position)) {
|
||||||
is Artist -> ARTIST_HEADER_ITEM_TYPE
|
is Artist -> ARTIST_DETAIL_ITEM_TYPE
|
||||||
is Album -> ARTIST_ALBUM_ITEM_TYPE
|
is Album -> ARTIST_ALBUM_ITEM_TYPE
|
||||||
is Song -> ARTIST_SONG_ITEM_TYPE
|
is Song -> ARTIST_SONG_ITEM_TYPE
|
||||||
is Header -> HeaderViewHolder.ITEM_TYPE
|
is Header -> HeaderViewHolder.ITEM_TYPE
|
||||||
|
@ -71,7 +71,7 @@ class ArtistDetailAdapter(
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
ARTIST_HEADER_ITEM_TYPE -> ArtistHeaderViewHolder(
|
ARTIST_DETAIL_ITEM_TYPE -> ArtistDetailViewHolder(
|
||||||
ItemDetailBinding.inflate(parent.context.inflater)
|
ItemDetailBinding.inflate(parent.context.inflater)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ class ArtistDetailAdapter(
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
|
|
||||||
when (item) {
|
when (item) {
|
||||||
is Artist -> (holder as ArtistHeaderViewHolder).bind(item)
|
is Artist -> (holder as ArtistDetailViewHolder).bind(item)
|
||||||
is Album -> (holder as ArtistAlbumViewHolder).bind(item)
|
is Album -> (holder as ArtistAlbumViewHolder).bind(item)
|
||||||
is Song -> (holder as ArtistSongViewHolder).bind(item)
|
is Song -> (holder as ArtistSongViewHolder).bind(item)
|
||||||
is Header -> (holder as HeaderViewHolder).bind(item)
|
is Header -> (holder as HeaderViewHolder).bind(item)
|
||||||
|
@ -181,7 +181,7 @@ class ArtistDetailAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ArtistHeaderViewHolder(
|
inner class ArtistDetailViewHolder(
|
||||||
private val binding: ItemDetailBinding
|
private val binding: ItemDetailBinding
|
||||||
) : BaseViewHolder<Artist>(binding) {
|
) : BaseViewHolder<Artist>(binding) {
|
||||||
|
|
||||||
|
@ -241,8 +241,8 @@ class ArtistDetailAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ARTIST_HEADER_ITEM_TYPE = 0xA009
|
const val ARTIST_DETAIL_ITEM_TYPE = 0xA008
|
||||||
const val ARTIST_ALBUM_ITEM_TYPE = 0xA00A
|
const val ARTIST_ALBUM_ITEM_TYPE = 0xA009
|
||||||
const val ARTIST_SONG_ITEM_TYPE = 0xA00C
|
const val ARTIST_SONG_ITEM_TYPE = 0xA00A
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ class GenreDetailAdapter(
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return when (getItem(position)) {
|
return when (getItem(position)) {
|
||||||
is Genre -> GENRE_HEADER_ITEM_TYPE
|
is Genre -> GENRE_DETAIL_ITEM_TYPE
|
||||||
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
|
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
|
||||||
is Song -> GENRE_SONG_ITEM_TYPE
|
is Song -> GENRE_SONG_ITEM_TYPE
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class GenreDetailAdapter(
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
GENRE_HEADER_ITEM_TYPE -> GenreHeaderViewHolder(
|
GENRE_DETAIL_ITEM_TYPE -> GenreDetailViewHolder(
|
||||||
ItemDetailBinding.inflate(parent.context.inflater)
|
ItemDetailBinding.inflate(parent.context.inflater)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ class GenreDetailAdapter(
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
|
|
||||||
when (item) {
|
when (item) {
|
||||||
is Genre -> (holder as GenreHeaderViewHolder).bind(item)
|
is Genre -> (holder as GenreDetailViewHolder).bind(item)
|
||||||
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
|
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
|
||||||
is Song -> (holder as GenreSongViewHolder).bind(item)
|
is Song -> (holder as GenreSongViewHolder).bind(item)
|
||||||
else -> {}
|
else -> {}
|
||||||
|
@ -128,7 +128,7 @@ class GenreDetailAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class GenreHeaderViewHolder(
|
inner class GenreDetailViewHolder(
|
||||||
private val binding: ItemDetailBinding
|
private val binding: ItemDetailBinding
|
||||||
) : BaseViewHolder<Genre>(binding) {
|
) : BaseViewHolder<Genre>(binding) {
|
||||||
override fun onBind(data: Genre) {
|
override fun onBind(data: Genre) {
|
||||||
|
@ -173,7 +173,7 @@ class GenreDetailAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val GENRE_HEADER_ITEM_TYPE = 0xA00D
|
const val GENRE_DETAIL_ITEM_TYPE = 0xA00B
|
||||||
const val GENRE_SONG_ITEM_TYPE = 0xA00E
|
const val GENRE_SONG_ITEM_TYPE = 0xA00C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
|
import org.oxycblt.auxio.ui.SortMode
|
||||||
import org.oxycblt.auxio.util.applyEdge
|
import org.oxycblt.auxio.util.applyEdge
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logE
|
import org.oxycblt.auxio.util.logE
|
||||||
|
@ -93,7 +94,7 @@ class HomeFragment : Fragment() {
|
||||||
item.isChecked = true
|
item.isChecked = true
|
||||||
|
|
||||||
homeModel.updateCurrentSort(
|
homeModel.updateCurrentSort(
|
||||||
requireNotNull(LibSortMode.fromId(item.itemId))
|
requireNotNull(SortMode.fromId(item.itemId))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,7 +221,7 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
private fun updateSortMenu(
|
private fun updateSortMenu(
|
||||||
item: MenuItem,
|
item: MenuItem,
|
||||||
toHighlight: LibSortMode,
|
toHighlight: SortMode,
|
||||||
isVisible: (Int) -> Boolean = { true }
|
isVisible: (Int) -> Boolean = { true }
|
||||||
) {
|
) {
|
||||||
for (option in item.subMenu) {
|
for (option in item.subMenu) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
|
import org.oxycblt.auxio.ui.SortMode
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ViewModel for managing [HomeFragment]'s data and sorting modes.
|
* The ViewModel for managing [HomeFragment]'s data and sorting modes.
|
||||||
|
@ -55,16 +56,16 @@ class HomeViewModel : ViewModel() {
|
||||||
private val mCurTab = MutableLiveData(mTabs.value!![0])
|
private val mCurTab = MutableLiveData(mTabs.value!![0])
|
||||||
val curTab: LiveData<DisplayMode> = mCurTab
|
val curTab: LiveData<DisplayMode> = mCurTab
|
||||||
|
|
||||||
var genreSortMode = LibSortMode.ASCENDING
|
var genreSortMode = SortMode.ASCENDING
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var artistSortMode = LibSortMode.ASCENDING
|
var artistSortMode = SortMode.ASCENDING
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var albumSortMode = LibSortMode.ASCENDING
|
var albumSortMode = SortMode.ASCENDING
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var songSortMode = LibSortMode.ASCENDING
|
var songSortMode = SortMode.ASCENDING
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private val musicStore = MusicStore.getInstance()
|
private val musicStore = MusicStore.getInstance()
|
||||||
|
@ -86,9 +87,9 @@ class HomeViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the currently displayed item's [LibSortMode].
|
* Update the currently displayed item's [SortMode].
|
||||||
*/
|
*/
|
||||||
fun updateCurrentSort(sort: LibSortMode) {
|
fun updateCurrentSort(sort: SortMode) {
|
||||||
when (mCurTab.value) {
|
when (mCurTab.value) {
|
||||||
DisplayMode.SHOW_SONGS -> {
|
DisplayMode.SHOW_SONGS -> {
|
||||||
songSortMode = sort
|
songSortMode = sort
|
||||||
|
|
|
@ -1,166 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2021 Auxio Project
|
|
||||||
* LibSortMode.kt is part of Auxio.
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.oxycblt.auxio.home
|
|
||||||
|
|
||||||
import androidx.annotation.IdRes
|
|
||||||
import org.oxycblt.auxio.R
|
|
||||||
import org.oxycblt.auxio.music.Album
|
|
||||||
import org.oxycblt.auxio.music.Artist
|
|
||||||
import org.oxycblt.auxio.music.BaseModel
|
|
||||||
import org.oxycblt.auxio.music.Genre
|
|
||||||
import org.oxycblt.auxio.music.Song
|
|
||||||
import org.oxycblt.auxio.ui.sliceArticle
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The enum for the current sort state.
|
|
||||||
* This enum is semantic depending on the context it is used. Documentation describing each
|
|
||||||
* sorting functions behavior can be found in the function definition.
|
|
||||||
* @param itemId Menu ID associated with this enum
|
|
||||||
* @author OxygenCobalt
|
|
||||||
*/
|
|
||||||
enum class LibSortMode(@IdRes val itemId: Int) {
|
|
||||||
ASCENDING(R.id.option_sort_asc),
|
|
||||||
DESCENDING(R.id.option_sort_dsc),
|
|
||||||
ARTIST(R.id.option_sort_artist),
|
|
||||||
ALBUM(R.id.option_sort_album),
|
|
||||||
YEAR(R.id.option_sort_year);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort a list of songs.
|
|
||||||
*
|
|
||||||
* **Behavior:**
|
|
||||||
* - [ASCENDING] & [DESCENDING]: See [sortModels]
|
|
||||||
* - [ARTIST]: Grouped by album and then sorted [ASCENDING] based off the artist name.
|
|
||||||
* - [ALBUM]: Grouped by album and sorted [ASCENDING]
|
|
||||||
* - [YEAR]: Grouped by album and sorted by year
|
|
||||||
*
|
|
||||||
* The grouping mode for songs in an album will be by track, [ASCENDING].
|
|
||||||
* @see sortAlbums
|
|
||||||
*/
|
|
||||||
fun sortSongs(songs: Collection<Song>): List<Song> {
|
|
||||||
return when (this) {
|
|
||||||
ASCENDING, DESCENDING -> sortModels(songs)
|
|
||||||
|
|
||||||
else -> sortAlbums(songs.groupBy { it.album }.keys).flatMap { album ->
|
|
||||||
ASCENDING.sortAlbum(album)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort a list of albums.
|
|
||||||
*
|
|
||||||
* **Behavior:**
|
|
||||||
* - [ASCENDING] & [DESCENDING]: See [sortModels]
|
|
||||||
* - [ARTIST]: Grouped by artist and sorted [ASCENDING]
|
|
||||||
* - [ALBUM]: [ASCENDING]
|
|
||||||
* - [YEAR]: Sorted by year
|
|
||||||
*
|
|
||||||
* The grouping mode for albums in an artist will be [YEAR].
|
|
||||||
*/
|
|
||||||
fun sortAlbums(albums: Collection<Album>): List<Album> {
|
|
||||||
return when (this) {
|
|
||||||
ASCENDING, DESCENDING -> sortModels(albums)
|
|
||||||
|
|
||||||
ARTIST -> ASCENDING.sortModels(albums.groupBy { it.artist }.keys)
|
|
||||||
.flatMap { YEAR.sortAlbums(it.albums) }
|
|
||||||
|
|
||||||
ALBUM -> ASCENDING.sortModels(albums)
|
|
||||||
|
|
||||||
YEAR -> albums.sortedByDescending { it.year }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort a list of generic [BaseModel] instances.
|
|
||||||
*
|
|
||||||
* **Behavior:**
|
|
||||||
* - [ASCENDING]: Sorted by name, ascending
|
|
||||||
* - [DESCENDING]: Sorted by name, descending
|
|
||||||
* - Same list is returned otherwise.
|
|
||||||
*
|
|
||||||
* Names will be treated as case-insensitive. Articles like "the" and "a" will be skipped
|
|
||||||
* to line up with MediaStore behavior.
|
|
||||||
*/
|
|
||||||
fun <T : BaseModel> sortModels(models: Collection<T>): List<T> {
|
|
||||||
return when (this) {
|
|
||||||
ASCENDING -> models.sortedWith(
|
|
||||||
compareBy(String.CASE_INSENSITIVE_ORDER) { model ->
|
|
||||||
model.name.sliceArticle()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
DESCENDING -> models.sortedWith(
|
|
||||||
compareByDescending(String.CASE_INSENSITIVE_ORDER) { model ->
|
|
||||||
model.name.sliceArticle()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> models.toList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort the songs in an album.
|
|
||||||
*
|
|
||||||
* **Behavior:**
|
|
||||||
* - [ASCENDING]: By track, ascending
|
|
||||||
* - [DESCENDING]: By track, descending
|
|
||||||
* - Same song list is returned otherwise.
|
|
||||||
*/
|
|
||||||
fun sortAlbum(album: Album): List<Song> {
|
|
||||||
return when (this) {
|
|
||||||
ASCENDING -> album.songs.sortedBy { it.track }
|
|
||||||
DESCENDING -> album.songs.sortedByDescending { it.track }
|
|
||||||
else -> album.songs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort the songs in an artist.
|
|
||||||
* @see sortSongs
|
|
||||||
*/
|
|
||||||
fun sortArtist(artist: Artist): List<Song> {
|
|
||||||
return sortSongs(artist.songs)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort the songs in a genre.
|
|
||||||
* @see sortSongs
|
|
||||||
*/
|
|
||||||
fun sortGenre(genre: Genre): List<Song> {
|
|
||||||
return sortSongs(genre.songs)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Convert a menu [id] to an instance of [LibSortMode].
|
|
||||||
*/
|
|
||||||
fun fromId(@IdRes id: Int): LibSortMode? {
|
|
||||||
return when (id) {
|
|
||||||
ASCENDING.itemId -> ASCENDING
|
|
||||||
DESCENDING.itemId -> DESCENDING
|
|
||||||
ARTIST.itemId -> ARTIST
|
|
||||||
ALBUM.itemId -> ALBUM
|
|
||||||
YEAR.itemId -> YEAR
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -331,9 +331,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||||
* Add an [Album] to the user queue
|
* Add an [Album] to the user queue
|
||||||
*/
|
*/
|
||||||
fun addToUserQueue(album: Album) {
|
fun addToUserQueue(album: Album) {
|
||||||
val songs = SortMode.NUMERIC_DOWN.getSortedSongList(album.songs)
|
// TODO: Make this reflect the sort mode
|
||||||
|
playbackManager.addToUserQueue(SortMode.ASCENDING.sortAlbum(album))
|
||||||
playbackManager.addToUserQueue(songs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -169,7 +169,6 @@ class QueueAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val QUEUE_SONG_ITEM_TYPE = 0xA005
|
const val QUEUE_SONG_ITEM_TYPE = 0xA00D
|
||||||
const val USER_QUEUE_HEADER_ITEM_TYPE = 0xA006
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Parent
|
import org.oxycblt.auxio.music.Parent
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
|
import org.oxycblt.auxio.ui.SortMode
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logE
|
import org.oxycblt.auxio.util.logE
|
||||||
|
|
||||||
|
@ -731,21 +732,21 @@ class PlaybackStateManager private constructor() {
|
||||||
* Create an ordered queue based on an [Album].
|
* Create an ordered queue based on an [Album].
|
||||||
*/
|
*/
|
||||||
private fun orderSongsInAlbum(album: Album): MutableList<Song> {
|
private fun orderSongsInAlbum(album: Album): MutableList<Song> {
|
||||||
return settingsManager.albumSortMode.getSortedSongList(album.songs).toMutableList()
|
return SortMode.ASCENDING.sortAlbum(album).toMutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an ordered queue based on an [Artist].
|
* Create an ordered queue based on an [Artist].
|
||||||
*/
|
*/
|
||||||
private fun orderSongsInArtist(artist: Artist): MutableList<Song> {
|
private fun orderSongsInArtist(artist: Artist): MutableList<Song> {
|
||||||
return settingsManager.artistSortMode.getSortedArtistSongList(artist.songs).toMutableList()
|
return SortMode.YEAR.sortArtist(artist).toMutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an ordered queue based on a [Genre].
|
* Create an ordered queue based on a [Genre].
|
||||||
*/
|
*/
|
||||||
private fun orderSongsInGenre(genre: Genre): MutableList<Song> {
|
private fun orderSongsInGenre(genre: Genre): MutableList<Song> {
|
||||||
return settingsManager.genreSortMode.getSortedSongList(genre.songs).toMutableList()
|
return SortMode.ASCENDING.sortGenre(genre).toMutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.oxycblt.auxio.accent.ACCENTS
|
||||||
import org.oxycblt.auxio.accent.Accent
|
import org.oxycblt.auxio.accent.Accent
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.SortMode
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around the [SharedPreferences] class that writes & reads values without a context.
|
* Wrapper around the [SharedPreferences] class that writes & reads values without a context.
|
||||||
|
@ -114,33 +113,6 @@ class SettingsManager private constructor(context: Context) :
|
||||||
val pauseOnLoop: Boolean
|
val pauseOnLoop: Boolean
|
||||||
get() = sharedPrefs.getBoolean(KEY_LOOP_PAUSE, false)
|
get() = sharedPrefs.getBoolean(KEY_LOOP_PAUSE, false)
|
||||||
|
|
||||||
var albumSortMode: SortMode
|
|
||||||
get() = sharedPrefs.getData(KEY_ALBUM_SORT_MODE, SortMode::fromInt) ?: SortMode.NUMERIC_DOWN
|
|
||||||
set(value) {
|
|
||||||
sharedPrefs.edit {
|
|
||||||
putInt(KEY_ALBUM_SORT_MODE, value.toInt())
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var artistSortMode: SortMode
|
|
||||||
get() = sharedPrefs.getData(KEY_ARTIST_SORT_MODE, SortMode::fromInt) ?: SortMode.NUMERIC_DOWN
|
|
||||||
set(value) {
|
|
||||||
sharedPrefs.edit {
|
|
||||||
putInt(KEY_ARTIST_SORT_MODE, value.toInt())
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var genreSortMode: SortMode
|
|
||||||
get() = sharedPrefs.getData(KEY_GENRE_SORT_MODE, SortMode::fromInt) ?: SortMode.ALPHA_DOWN
|
|
||||||
set(value) {
|
|
||||||
sharedPrefs.edit {
|
|
||||||
putInt(KEY_GENRE_SORT_MODE, value.toInt())
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The current filter mode of the search tab */
|
/** The current filter mode of the search tab */
|
||||||
var searchFilterMode: DisplayMode?
|
var searchFilterMode: DisplayMode?
|
||||||
get() = sharedPrefs.getData(KEY_SEARCH_FILTER_MODE, DisplayMode::fromSearchInt)
|
get() = sharedPrefs.getData(KEY_SEARCH_FILTER_MODE, DisplayMode::fromSearchInt)
|
||||||
|
|
|
@ -29,7 +29,7 @@ enum class DisplayMode {
|
||||||
SHOW_SONGS;
|
SHOW_SONGS;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CONST_SHOW_ALL = 0xA107
|
private const val CONST_NULL = 0xA107
|
||||||
private const val CONST_SHOW_GENRES = 0xA108
|
private const val CONST_SHOW_GENRES = 0xA108
|
||||||
private const val CONST_SHOW_ARTISTS = 0xA109
|
private const val CONST_SHOW_ARTISTS = 0xA109
|
||||||
private const val CONST_SHOW_ALBUMS = 0xA10A
|
private const val CONST_SHOW_ALBUMS = 0xA10A
|
||||||
|
@ -41,7 +41,7 @@ enum class DisplayMode {
|
||||||
SHOW_ALBUMS -> CONST_SHOW_ALBUMS
|
SHOW_ALBUMS -> CONST_SHOW_ALBUMS
|
||||||
SHOW_ARTISTS -> CONST_SHOW_ARTISTS
|
SHOW_ARTISTS -> CONST_SHOW_ARTISTS
|
||||||
SHOW_GENRES -> CONST_SHOW_GENRES
|
SHOW_GENRES -> CONST_SHOW_GENRES
|
||||||
null -> CONST_SHOW_ALL
|
null -> CONST_NULL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,125 +18,146 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.ui
|
package org.oxycblt.auxio.ui
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.IdRes
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.music.Album
|
||||||
|
import org.oxycblt.auxio.music.Artist
|
||||||
|
import org.oxycblt.auxio.music.BaseModel
|
||||||
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The legacy enum for sorting. This is set to be removed soon.
|
* The enum for the current sort state.
|
||||||
* @property iconRes The icon for this [SortMode]
|
* This enum is semantic depending on the context it is used. Documentation describing each
|
||||||
|
* sorting functions behavior can be found in the function definition.
|
||||||
|
* @param itemId Menu ID associated with this enum
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
enum class SortMode(@DrawableRes val iconRes: Int) {
|
enum class SortMode(@IdRes val itemId: Int) {
|
||||||
// Icons for each mode are assigned to the enums themselves
|
ASCENDING(R.id.option_sort_asc),
|
||||||
NONE(R.drawable.ic_sort),
|
DESCENDING(R.id.option_sort_dsc),
|
||||||
ALPHA_UP(R.drawable.ic_sort),
|
ARTIST(R.id.option_sort_artist),
|
||||||
ALPHA_DOWN(R.drawable.ic_sort),
|
ALBUM(R.id.option_sort_album),
|
||||||
NUMERIC_UP(R.drawable.ic_sort),
|
YEAR(R.id.option_sort_year);
|
||||||
NUMERIC_DOWN(R.drawable.ic_sort);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a sorted list of songs for a SortMode. Supports alpha + numeric sorting.
|
* Sort a list of songs.
|
||||||
* @param songs An unsorted list of songs.
|
*
|
||||||
* @return The sorted list of songs.
|
* **Behavior:**
|
||||||
|
* - [ASCENDING] & [DESCENDING]: See [sortModels]
|
||||||
|
* - [ARTIST]: Grouped by album and then sorted [ASCENDING] based off the artist name.
|
||||||
|
* - [ALBUM]: Grouped by album and sorted [ASCENDING]
|
||||||
|
* - [YEAR]: Grouped by album and sorted by year
|
||||||
|
*
|
||||||
|
* The grouping mode for songs in an album will be by track, [ASCENDING].
|
||||||
|
* @see sortAlbums
|
||||||
*/
|
*/
|
||||||
fun getSortedSongList(songs: List<Song>): List<Song> {
|
fun sortSongs(songs: Collection<Song>): List<Song> {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
ALPHA_UP -> songs.sortedWith(
|
ASCENDING, DESCENDING -> sortModels(songs)
|
||||||
compareByDescending(String.CASE_INSENSITIVE_ORDER) {
|
|
||||||
it.name.sliceArticle()
|
else -> sortAlbums(songs.groupBy { it.album }.keys).flatMap { album ->
|
||||||
|
ASCENDING.sortAlbum(album)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
ALPHA_DOWN -> songs.sortedWith(
|
|
||||||
compareBy(String.CASE_INSENSITIVE_ORDER) {
|
|
||||||
it.name.sliceArticle()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
NUMERIC_UP -> songs.sortedWith(compareByDescending { it.track })
|
|
||||||
NUMERIC_DOWN -> songs.sortedWith(compareBy { it.track })
|
|
||||||
|
|
||||||
else -> songs
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a sorted list of songs with regards to an artist.
|
* Sort a list of albums.
|
||||||
* @param songs An unsorted list of songs
|
*
|
||||||
* @return The sorted list of songs
|
* **Behavior:**
|
||||||
|
* - [ASCENDING] & [DESCENDING]: See [sortModels]
|
||||||
|
* - [ARTIST]: Grouped by artist and sorted [ASCENDING]
|
||||||
|
* - [ALBUM]: [ASCENDING]
|
||||||
|
* - [YEAR]: Sorted by year
|
||||||
|
*
|
||||||
|
* The grouping mode for albums in an artist will be [YEAR].
|
||||||
*/
|
*/
|
||||||
fun getSortedArtistSongList(songs: List<Song>): List<Song> {
|
fun sortAlbums(albums: Collection<Album>): List<Album> {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
ALPHA_UP -> songs.sortedWith(
|
ASCENDING, DESCENDING -> sortModels(albums)
|
||||||
compareByDescending(String.CASE_INSENSITIVE_ORDER) {
|
|
||||||
it.name.sliceArticle()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
ALPHA_DOWN -> songs.sortedWith(
|
ARTIST -> ASCENDING.sortModels(albums.groupBy { it.artist }.keys)
|
||||||
compareBy(String.CASE_INSENSITIVE_ORDER) {
|
.flatMap { YEAR.sortAlbums(it.albums) }
|
||||||
it.name.sliceArticle()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
NUMERIC_UP -> {
|
ALBUM -> ASCENDING.sortModels(albums)
|
||||||
val list = mutableListOf<Song>()
|
|
||||||
|
|
||||||
songs.groupBy { it.album }.entries.sortedBy { it.key.year }.forEach { entry ->
|
YEAR -> albums.sortedByDescending { it.year }
|
||||||
list.addAll(entry.value.sortedWith(compareBy { it.track }))
|
|
||||||
}
|
|
||||||
|
|
||||||
list
|
|
||||||
}
|
|
||||||
|
|
||||||
NUMERIC_DOWN -> {
|
|
||||||
val list = mutableListOf<Song>()
|
|
||||||
|
|
||||||
songs.groupBy { it.album }.entries.sortedWith(compareByDescending { it.key.year }).forEach { entry ->
|
|
||||||
list.addAll(entry.value.sortedWith(compareBy { it.track }))
|
|
||||||
}
|
|
||||||
|
|
||||||
list
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> songs
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the constant for this mode. Used to write a compressed variant to SettingsManager
|
* Sort a list of generic [BaseModel] instances.
|
||||||
* @return The int constant for this mode.
|
*
|
||||||
|
* **Behavior:**
|
||||||
|
* - [ASCENDING]: Sorted by name, ascending
|
||||||
|
* - [DESCENDING]: Sorted by name, descending
|
||||||
|
* - Same list is returned otherwise.
|
||||||
|
*
|
||||||
|
* Names will be treated as case-insensitive. Articles like "the" and "a" will be skipped
|
||||||
|
* to line up with MediaStore behavior.
|
||||||
*/
|
*/
|
||||||
fun toInt(): Int {
|
fun <T : BaseModel> sortModels(models: Collection<T>): List<T> {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
NONE -> CONST_NONE
|
ASCENDING -> models.sortedWith(
|
||||||
ALPHA_UP -> CONST_ALPHA_UP
|
compareBy(String.CASE_INSENSITIVE_ORDER) { model ->
|
||||||
ALPHA_DOWN -> CONST_ALPHA_DOWN
|
model.name.sliceArticle()
|
||||||
NUMERIC_UP -> CONST_NUMERIC_UP
|
|
||||||
NUMERIC_DOWN -> CONST_NUMERIC_DOWN
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
DESCENDING -> models.sortedWith(
|
||||||
|
compareByDescending(String.CASE_INSENSITIVE_ORDER) { model ->
|
||||||
|
model.name.sliceArticle()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> models.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the songs in an album.
|
||||||
|
*
|
||||||
|
* **Behavior:**
|
||||||
|
* - [ASCENDING]: By track, ascending
|
||||||
|
* - [DESCENDING]: By track, descending
|
||||||
|
* - Same song list is returned otherwise.
|
||||||
|
*/
|
||||||
|
fun sortAlbum(album: Album): List<Song> {
|
||||||
|
return when (this) {
|
||||||
|
ASCENDING -> album.songs.sortedBy { it.track }
|
||||||
|
DESCENDING -> album.songs.sortedByDescending { it.track }
|
||||||
|
else -> album.songs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the songs in an artist.
|
||||||
|
* @see sortSongs
|
||||||
|
*/
|
||||||
|
fun sortArtist(artist: Artist): List<Song> {
|
||||||
|
return sortSongs(artist.songs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the songs in a genre.
|
||||||
|
* @see sortSongs
|
||||||
|
*/
|
||||||
|
fun sortGenre(genre: Genre): List<Song> {
|
||||||
|
return sortSongs(genre.songs)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CONST_NONE = 0xA10C
|
|
||||||
const val CONST_ALPHA_UP = 0xA10D
|
|
||||||
const val CONST_ALPHA_DOWN = 0xA10E
|
|
||||||
const val CONST_NUMERIC_UP = 0xA10F
|
|
||||||
const val CONST_NUMERIC_DOWN = 0xA110
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an enum for an int constant
|
* Convert a menu [id] to an instance of [SortMode].
|
||||||
* @return The [SortMode] if the constant is valid, null otherwise.
|
|
||||||
*/
|
*/
|
||||||
fun fromInt(value: Int): SortMode? {
|
fun fromId(@IdRes id: Int): SortMode? {
|
||||||
return when (value) {
|
return when (id) {
|
||||||
CONST_NONE -> NONE
|
ASCENDING.itemId -> ASCENDING
|
||||||
CONST_ALPHA_UP -> ALPHA_UP
|
DESCENDING.itemId -> DESCENDING
|
||||||
CONST_ALPHA_DOWN -> ALPHA_DOWN
|
ARTIST.itemId -> ARTIST
|
||||||
CONST_NUMERIC_UP -> NUMERIC_UP
|
ALBUM.itemId -> ALBUM
|
||||||
CONST_NUMERIC_DOWN -> NUMERIC_DOWN
|
YEAR.itemId -> YEAR
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,7 +266,7 @@ class ActionHeaderViewHolder private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ITEM_TYPE = 0xA999 // TODO: Give this an ID
|
const val ITEM_TYPE = 0xA005
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an instance of [ActionHeaderViewHolder]
|
* Create an instance of [ActionHeaderViewHolder]
|
||||||
|
|
|
@ -45,18 +45,17 @@ To prevent any strange bugs, all integer representations must be unique. A table
|
||||||
0xA002 | ArtistViewHolder
|
0xA002 | ArtistViewHolder
|
||||||
0xA003 | GenreViewHolder
|
0xA003 | GenreViewHolder
|
||||||
0xA004 | HeaderViewHolder
|
0xA004 | HeaderViewHolder
|
||||||
|
0xA005 | ActionHeaderViewHolder
|
||||||
|
|
||||||
0xA005 | QueueSongViewHolder
|
0xA005 | AlbumDetailViewHolder
|
||||||
0xA006 | UserQueueHeaderViewHolder
|
0xA006 | AlbumSongViewHolder
|
||||||
|
0xA007 | ArtistDetailViewHolder
|
||||||
|
0xA008 | ArtistAlbumViewHolder
|
||||||
|
0xA009 | ArtistSongViewHolder
|
||||||
|
0xA010 | GenreDetailViewHolder
|
||||||
|
0xA011 | GenreSongViewHolder
|
||||||
|
|
||||||
0xA007 | AlbumHeaderViewHolder
|
0xA00A | QueueSongViewHolder
|
||||||
0xA008 | AlbumSongViewHolder
|
|
||||||
0xA009 | ArtistHeaderViewHolder
|
|
||||||
0xA00A | ArtistAlbumViewHolder
|
|
||||||
0xA00B | ArtistSongHeaderViewHolder
|
|
||||||
0xA00C | ArtistSongViewHolder
|
|
||||||
0xA00D | GenreHeaderViewHolder
|
|
||||||
0xA00E | GenreSongViewHolder
|
|
||||||
|
|
||||||
0xA0A0 | Auxio notification code
|
0xA0A0 | Auxio notification code
|
||||||
0xA0C0 | Auxio request code
|
0xA0C0 | Auxio request code
|
||||||
|
@ -77,12 +76,6 @@ To prevent any strange bugs, all integer representations must be unique. A table
|
||||||
0xA109 | DisplayMode.SHOW_ARTISTS
|
0xA109 | DisplayMode.SHOW_ARTISTS
|
||||||
0xA10A | DisplayMode.SHOW_ALBUMS
|
0xA10A | DisplayMode.SHOW_ALBUMS
|
||||||
0xA10B | DisplayMode.SHOW_SONGS
|
0xA10B | DisplayMode.SHOW_SONGS
|
||||||
|
|
||||||
0xA10C | SortMode.NONE
|
|
||||||
0xA10D | SortMode.ALPHA_UP
|
|
||||||
0xA10E | SortMode.ALPHA_DOWN
|
|
||||||
0xA10F | SortMode.NUMERIC_UP
|
|
||||||
0xA110 | SortMode.NUMERIC_DOWN
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Package structure overview
|
#### Package structure overview
|
||||||
|
|
Loading…
Reference in a new issue