sort: refactor sorting
Refactor sorting again to support free-floating ascending/descending values on every single sort mode. This enables greater freedom in how users can sort their music and allows me to finally get rid of the old legacy sematic sorting modes that chose their ascending/decending order depending on how they wanted it.
This commit is contained in:
parent
e697908a2f
commit
06a7d8258b
22 changed files with 406 additions and 318 deletions
|
@ -49,6 +49,8 @@ abstract class AuxioFetcher : Fetcher {
|
|||
* https://github.com/kabouzeid/Phonograph
|
||||
*/
|
||||
protected fun createMosaic(context: Context, streams: List<InputStream>): FetchResult? {
|
||||
logD("idiot")
|
||||
|
||||
if (streams.size < 4) {
|
||||
return streams.getOrNull(0)?.let { stream ->
|
||||
return SourceResult(
|
||||
|
|
|
@ -79,7 +79,7 @@ fun ImageView.bindArtistImage(artist: Artist?) {
|
|||
fun ImageView.bindGenreImage(genre: Genre?) {
|
||||
dispose()
|
||||
|
||||
load(genre?.songs?.get(0)?.album) {
|
||||
load(genre) {
|
||||
error(R.drawable.ic_genre)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,8 +71,7 @@ class ArtistImageFetcher private constructor(
|
|||
private val artist: Artist
|
||||
) : AuxioFetcher() {
|
||||
override suspend fun fetch(): FetchResult? {
|
||||
val end = min(4, artist.albums.size)
|
||||
val results = artist.albums.mapN(end) { album ->
|
||||
val results = artist.albums.mapAtMost(4) { album ->
|
||||
fetchArt(context, album)
|
||||
}
|
||||
|
||||
|
@ -92,8 +91,7 @@ class GenreImageFetcher private constructor(
|
|||
) : AuxioFetcher() {
|
||||
override suspend fun fetch(): FetchResult? {
|
||||
val albums = genre.songs.groupBy { it.album }.keys
|
||||
val end = min(4, albums.size)
|
||||
val results = albums.mapN(end) { album ->
|
||||
val results = albums.mapAtMost(4) { album ->
|
||||
fetchArt(context, album)
|
||||
}
|
||||
|
||||
|
@ -108,14 +106,15 @@ class GenreImageFetcher private constructor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Map only [n] items from a collection. [transform] is called for each item that is eligible.
|
||||
* Map at most [n] items from a collection. [transform] is called for each item that is eligible.
|
||||
* If null is returned, then that item will be skipped.
|
||||
*/
|
||||
private inline fun <T : Any, R : Any> Iterable<T>.mapN(n: Int, transform: (T) -> R?): List<R> {
|
||||
private inline fun <T : Any, R : Any> Collection<T>.mapAtMost(n: Int, transform: (T) -> R?): List<R> {
|
||||
val until = min(size, n)
|
||||
val out = mutableListOf<R>()
|
||||
|
||||
for (item in this) {
|
||||
if (out.size >= n) {
|
||||
if (out.size >= until) {
|
||||
break
|
||||
}
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ class AlbumDetailFragment : DetailFragment() {
|
|||
detailModel.showMenu.observe(viewLifecycleOwner) { config ->
|
||||
if (config != null) {
|
||||
showMenu(config) { id ->
|
||||
id == R.id.option_sort_asc || id == R.id.option_sort_dsc
|
||||
id == R.id.option_sort_asc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ import org.oxycblt.auxio.R
|
|||
import org.oxycblt.auxio.databinding.FragmentDetailBinding
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.ui.SortMode
|
||||
import org.oxycblt.auxio.ui.memberBinding
|
||||
import org.oxycblt.auxio.util.applySpans
|
||||
|
||||
|
@ -123,8 +122,14 @@ abstract class DetailFragment : Fragment() {
|
|||
inflate(R.menu.menu_detail_sort)
|
||||
|
||||
setOnMenuItemClickListener { item ->
|
||||
item.isChecked = true
|
||||
detailModel.finishShowMenu(SortMode.fromId(item.itemId)!!)
|
||||
if (item.itemId == R.id.option_sort_asc) {
|
||||
item.isChecked = !item.isChecked
|
||||
detailModel.finishShowMenu(config.sortMode.ascending(item.isChecked))
|
||||
} else {
|
||||
item.isChecked = true
|
||||
detailModel.finishShowMenu(config.sortMode.assignId(item.itemId))
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -139,6 +144,7 @@ abstract class DetailFragment : Fragment() {
|
|||
}
|
||||
|
||||
menu.findItem(config.sortMode.itemId).isChecked = true
|
||||
menu.findItem(R.id.option_sort_asc).isChecked = config.sortMode.isAscending
|
||||
|
||||
show()
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ import org.oxycblt.auxio.music.HeaderString
|
|||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.SortMode
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
|
||||
/**
|
||||
* ViewModel that stores data for the [DetailFragment]s. This includes:
|
||||
|
@ -64,7 +64,7 @@ class DetailViewModel : ViewModel() {
|
|||
private val mAlbumData = MutableLiveData(listOf<BaseModel>())
|
||||
val albumData: LiveData<List<BaseModel>> get() = mAlbumData
|
||||
|
||||
data class MenuConfig(val anchor: View, val sortMode: SortMode)
|
||||
data class MenuConfig(val anchor: View, val sortMode: Sort)
|
||||
|
||||
private val mShowMenu = MutableLiveData<MenuConfig?>(null)
|
||||
val showMenu: LiveData<MenuConfig?> = mShowMenu
|
||||
|
@ -105,10 +105,10 @@ class DetailViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Mark that the menu process is done with the new [SortMode].
|
||||
* Mark that the menu process is done with the new [Sort].
|
||||
* Pass null if there was no change.
|
||||
*/
|
||||
fun finishShowMenu(newMode: SortMode?) {
|
||||
fun finishShowMenu(newMode: Sort?) {
|
||||
mShowMenu.value = null
|
||||
|
||||
if (newMode != null) {
|
||||
|
@ -185,7 +185,7 @@ class DetailViewModel : ViewModel() {
|
|||
)
|
||||
)
|
||||
|
||||
data.addAll(SortMode.YEAR.sortAlbums(artist.albums))
|
||||
data.addAll(Sort.ByYear(false).sortAlbums(artist.albums))
|
||||
|
||||
data.add(
|
||||
ActionHeader(
|
||||
|
|
|
@ -47,7 +47,6 @@ import org.oxycblt.auxio.music.MusicViewModel
|
|||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.SortMode
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logE
|
||||
|
||||
|
@ -95,13 +94,22 @@ class HomeFragment : Fragment() {
|
|||
|
||||
R.id.submenu_sorting -> { }
|
||||
|
||||
R.id.option_sort_asc -> {
|
||||
item.isChecked = !item.isChecked
|
||||
val new = homeModel.getSortForDisplay(homeModel.curTab.value!!)
|
||||
.ascending(item.isChecked)
|
||||
|
||||
homeModel.updateCurrentSort(new)
|
||||
}
|
||||
|
||||
// Sorting option was selected, mark it as selected and update the mode
|
||||
else -> {
|
||||
item.isChecked = true
|
||||
|
||||
homeModel.updateCurrentSort(
|
||||
requireNotNull(SortMode.fromId(item.itemId))
|
||||
)
|
||||
val new = homeModel.getSortForDisplay(homeModel.curTab.value!!)
|
||||
.assignId(item.itemId)
|
||||
|
||||
homeModel.updateCurrentSort(requireNotNull(new))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,11 +216,11 @@ class HomeFragment : Fragment() {
|
|||
}
|
||||
|
||||
DisplayMode.SHOW_ARTISTS -> updateSortMenu(sortItem, tab) { id ->
|
||||
id == R.id.option_sort_asc || id == R.id.option_sort_dsc
|
||||
id == R.id.option_sort_asc
|
||||
}
|
||||
|
||||
DisplayMode.SHOW_GENRES -> updateSortMenu(sortItem, tab) { id ->
|
||||
id == R.id.option_sort_asc || id == R.id.option_sort_dsc
|
||||
id == R.id.option_sort_asc
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,6 +271,10 @@ class HomeFragment : Fragment() {
|
|||
option.isChecked = true
|
||||
}
|
||||
|
||||
if (option.itemId == R.id.option_sort_asc) {
|
||||
option.isChecked = toHighlight.isAscending
|
||||
}
|
||||
|
||||
option.isVisible = isVisible(option.itemId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.oxycblt.auxio.music.Song
|
|||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.tabs.Tab
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.SortMode
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
|
||||
/**
|
||||
* The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state.
|
||||
|
@ -87,7 +87,7 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.MusicCal
|
|||
mRecreateTabs.value = false
|
||||
}
|
||||
|
||||
fun getSortForDisplay(displayMode: DisplayMode): SortMode {
|
||||
fun getSortForDisplay(displayMode: DisplayMode): Sort {
|
||||
return when (displayMode) {
|
||||
DisplayMode.SHOW_SONGS -> settingsManager.libSongSort
|
||||
DisplayMode.SHOW_ALBUMS -> settingsManager.libAlbumSort
|
||||
|
@ -97,9 +97,9 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.MusicCal
|
|||
}
|
||||
|
||||
/**
|
||||
* Update the currently displayed item's [SortMode].
|
||||
* Update the currently displayed item's [Sort].
|
||||
*/
|
||||
fun updateCurrentSort(sort: SortMode) {
|
||||
fun updateCurrentSort(sort: Sort) {
|
||||
when (mCurTab.value) {
|
||||
DisplayMode.SHOW_SONGS -> {
|
||||
settingsManager.libSongSort = sort
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.oxycblt.auxio.home.HomeFragmentDirections
|
|||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.ui.AlbumViewHolder
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.SortMode
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.ui.newMenu
|
||||
import org.oxycblt.auxio.ui.sliceArticle
|
||||
|
||||
|
@ -59,13 +59,13 @@ class AlbumListFragment : HomeListFragment() {
|
|||
val album = homeModel.albums.value!![idx]
|
||||
|
||||
when (homeModel.getSortForDisplay(DisplayMode.SHOW_ALBUMS)) {
|
||||
SortMode.ASCENDING, SortMode.DESCENDING -> album.name.sliceArticle()
|
||||
is Sort.ByName -> album.name.sliceArticle()
|
||||
.first().uppercase()
|
||||
|
||||
SortMode.ARTIST -> album.artist.resolvedName.sliceArticle()
|
||||
is Sort.ByArtist -> album.artist.resolvedName.sliceArticle()
|
||||
.first().uppercase()
|
||||
|
||||
SortMode.YEAR -> album.year.toString()
|
||||
is Sort.ByYear -> album.year.toString()
|
||||
|
||||
else -> ""
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.oxycblt.auxio.R
|
|||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.SongViewHolder
|
||||
import org.oxycblt.auxio.ui.SortMode
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.ui.newMenu
|
||||
import org.oxycblt.auxio.ui.sliceArticle
|
||||
|
||||
|
@ -55,17 +55,17 @@ class SongListFragment : HomeListFragment() {
|
|||
val song = homeModel.songs.value!![idx]
|
||||
|
||||
when (homeModel.getSortForDisplay(DisplayMode.SHOW_SONGS)) {
|
||||
SortMode.ASCENDING, SortMode.DESCENDING -> song.name.sliceArticle()
|
||||
is Sort.ByName -> song.name.sliceArticle()
|
||||
.first().uppercase()
|
||||
|
||||
SortMode.ARTIST ->
|
||||
is Sort.ByArtist ->
|
||||
song.album.artist.resolvedName
|
||||
.sliceArticle().first().uppercase()
|
||||
|
||||
SortMode.ALBUM -> song.album.name.sliceArticle()
|
||||
is Sort.ByAlbum -> song.album.name.sliceArticle()
|
||||
.first().uppercase()
|
||||
|
||||
SortMode.YEAR -> song.album.year.toString()
|
||||
is Sort.ByYear -> song.album.year.toString()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ import android.provider.MediaStore.Audio.Media
|
|||
import androidx.core.database.getStringOrNull
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.excluded.ExcludedDatabase
|
||||
import org.oxycblt.auxio.ui.SortMode
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
||||
/**
|
||||
|
@ -251,7 +251,7 @@ class MusicLoader(private val context: Context) {
|
|||
}
|
||||
|
||||
albums.removeAll { it.songs.isEmpty() }
|
||||
albums = SortMode.ASCENDING.sortAlbums(albums).toMutableList()
|
||||
albums = Sort.ByName(true).sortAlbums(albums).toMutableList()
|
||||
|
||||
logD("Songs successfully linked into ${albums.size} albums")
|
||||
}
|
||||
|
@ -280,7 +280,7 @@ class MusicLoader(private val context: Context) {
|
|||
)
|
||||
}
|
||||
|
||||
artists = SortMode.ASCENDING.sortParents(artists).toMutableList()
|
||||
artists = Sort.ByName(true).sortParents(artists).toMutableList()
|
||||
|
||||
logD("Albums successfully linked into ${artists.size} artists")
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.oxycblt.auxio.accent.Accent
|
|||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
import org.oxycblt.auxio.settings.tabs.Tab
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.SortMode
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
|
||||
/**
|
||||
* Wrapper around the [SharedPreferences] class that writes & reads values without a context.
|
||||
|
@ -125,9 +125,9 @@ class SettingsManager private constructor(context: Context) :
|
|||
}
|
||||
|
||||
/** The song sort mode on HomeFragment **/
|
||||
var libSongSort: SortMode
|
||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_LIB_SONGS_SORT, Int.MIN_VALUE))
|
||||
?: SortMode.ASCENDING
|
||||
var libSongSort: Sort
|
||||
get() = Sort.fromInt(sharedPrefs.getInt(KEY_LIB_SONGS_SORT, Int.MIN_VALUE))
|
||||
?: Sort.ByName(true)
|
||||
set(value) {
|
||||
sharedPrefs.edit {
|
||||
putInt(KEY_LIB_SONGS_SORT, value.toInt())
|
||||
|
@ -136,9 +136,9 @@ class SettingsManager private constructor(context: Context) :
|
|||
}
|
||||
|
||||
/** The album sort mode on HomeFragment **/
|
||||
var libAlbumSort: SortMode
|
||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_LIB_ALBUMS_SORT, Int.MIN_VALUE))
|
||||
?: SortMode.ASCENDING
|
||||
var libAlbumSort: Sort
|
||||
get() = Sort.fromInt(sharedPrefs.getInt(KEY_LIB_ALBUMS_SORT, Int.MIN_VALUE))
|
||||
?: Sort.ByName(true)
|
||||
set(value) {
|
||||
sharedPrefs.edit {
|
||||
putInt(KEY_LIB_ALBUMS_SORT, value.toInt())
|
||||
|
@ -147,9 +147,9 @@ class SettingsManager private constructor(context: Context) :
|
|||
}
|
||||
|
||||
/** The artist sort mode on HomeFragment **/
|
||||
var libArtistSort: SortMode
|
||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_LIB_ARTISTS_SORT, Int.MIN_VALUE))
|
||||
?: SortMode.ASCENDING
|
||||
var libArtistSort: Sort
|
||||
get() = Sort.fromInt(sharedPrefs.getInt(KEY_LIB_ARTISTS_SORT, Int.MIN_VALUE))
|
||||
?: Sort.ByName(true)
|
||||
set(value) {
|
||||
sharedPrefs.edit {
|
||||
putInt(KEY_LIB_ARTISTS_SORT, value.toInt())
|
||||
|
@ -158,20 +158,20 @@ class SettingsManager private constructor(context: Context) :
|
|||
}
|
||||
|
||||
/** The genre sort mode on HomeFragment **/
|
||||
var libGenreSort: SortMode
|
||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_LIB_GENRE_SORT, Int.MIN_VALUE))
|
||||
?: SortMode.ASCENDING
|
||||
var libGenreSort: Sort
|
||||
get() = Sort.fromInt(sharedPrefs.getInt(KEY_LIB_GENRES_SORT, Int.MIN_VALUE))
|
||||
?: Sort.ByName(true)
|
||||
set(value) {
|
||||
sharedPrefs.edit {
|
||||
putInt(KEY_LIB_GENRE_SORT, value.toInt())
|
||||
putInt(KEY_LIB_GENRES_SORT, value.toInt())
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
/** The detail album sort mode **/
|
||||
var detailAlbumSort: SortMode
|
||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_DETAIL_ALBUM_SORT, Int.MIN_VALUE))
|
||||
?: SortMode.ASCENDING
|
||||
var detailAlbumSort: Sort
|
||||
get() = Sort.fromInt(sharedPrefs.getInt(KEY_DETAIL_ALBUM_SORT, Int.MIN_VALUE))
|
||||
?: Sort.ByName(true)
|
||||
set(value) {
|
||||
sharedPrefs.edit {
|
||||
putInt(KEY_DETAIL_ALBUM_SORT, value.toInt())
|
||||
|
@ -180,9 +180,9 @@ class SettingsManager private constructor(context: Context) :
|
|||
}
|
||||
|
||||
/** The detail artist sort mode **/
|
||||
var detailArtistSort: SortMode
|
||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_DETAIL_ARTIST_SORT, Int.MIN_VALUE))
|
||||
?: SortMode.YEAR
|
||||
var detailArtistSort: Sort
|
||||
get() = Sort.fromInt(sharedPrefs.getInt(KEY_DETAIL_ARTIST_SORT, Int.MIN_VALUE))
|
||||
?: Sort.ByYear(false)
|
||||
set(value) {
|
||||
sharedPrefs.edit {
|
||||
putInt(KEY_DETAIL_ARTIST_SORT, value.toInt())
|
||||
|
@ -191,9 +191,9 @@ class SettingsManager private constructor(context: Context) :
|
|||
}
|
||||
|
||||
/** The detail genre sort mode **/
|
||||
var detailGenreSort: SortMode
|
||||
get() = SortMode.fromInt(sharedPrefs.getInt(KEY_DETAIL_GENRE_SORT, Int.MIN_VALUE))
|
||||
?: SortMode.ASCENDING
|
||||
var detailGenreSort: Sort
|
||||
get() = Sort.fromInt(sharedPrefs.getInt(KEY_DETAIL_GENRE_SORT, Int.MIN_VALUE))
|
||||
?: Sort.ByName(true)
|
||||
set(value) {
|
||||
sharedPrefs.edit {
|
||||
putInt(KEY_DETAIL_GENRE_SORT, value.toInt())
|
||||
|
@ -249,11 +249,14 @@ class SettingsManager private constructor(context: Context) :
|
|||
}
|
||||
|
||||
companion object {
|
||||
// Preference keys
|
||||
// The old way of naming keys was to prefix them with KEY_. Now it's to prefix them with
|
||||
// auxio_.
|
||||
const val KEY_THEME = "KEY_THEME2"
|
||||
const val KEY_BLACK_THEME = "KEY_BLACK_THEME"
|
||||
const val KEY_ACCENT = "KEY_ACCENT3"
|
||||
const val KEY_ACCENT = "auxio_accent"
|
||||
|
||||
const val KEY_LIB_TABS = "KEY_LIB_TABS"
|
||||
const val KEY_LIB_TABS = "auxio_lib_tabs"
|
||||
const val KEY_SHOW_COVERS = "KEY_SHOW_COVERS"
|
||||
const val KEY_QUALITY_COVERS = "KEY_QUALITY_COVERS"
|
||||
const val KEY_USE_ALT_NOTIFICATION_ACTION = "KEY_ALT_NOTIF_ACTION"
|
||||
|
@ -266,19 +269,19 @@ class SettingsManager private constructor(context: Context) :
|
|||
const val KEY_PREV_REWIND = "KEY_PREV_REWIND"
|
||||
const val KEY_LOOP_PAUSE = "KEY_LOOP_PAUSE"
|
||||
|
||||
const val KEY_SAVE_STATE = "KEY_SAVE_STATE"
|
||||
const val KEY_SAVE_STATE = "auxio_save_state"
|
||||
const val KEY_BLACKLIST = "KEY_BLACKLIST"
|
||||
|
||||
const val KEY_SEARCH_FILTER_MODE = "KEY_SEARCH_FILTER"
|
||||
|
||||
const val KEY_LIB_SONGS_SORT = "KEY_SONGS_SORT"
|
||||
const val KEY_LIB_ALBUMS_SORT = "KEY_ALBUMS_SORT"
|
||||
const val KEY_LIB_ARTISTS_SORT = "KEY_ARTISTS_SORT"
|
||||
const val KEY_LIB_GENRE_SORT = "KEY_GENRE_SORT"
|
||||
const val KEY_LIB_SONGS_SORT = "auxio_songs_sort"
|
||||
const val KEY_LIB_ALBUMS_SORT = "auxio_albums_sort"
|
||||
const val KEY_LIB_ARTISTS_SORT = "auxio_artists_sort"
|
||||
const val KEY_LIB_GENRES_SORT = "auxio_genres_sort"
|
||||
|
||||
const val KEY_DETAIL_ALBUM_SORT = "KEY_ALBUM_SORT"
|
||||
const val KEY_DETAIL_ARTIST_SORT = "KEY_ARTIST_SORT"
|
||||
const val KEY_DETAIL_GENRE_SORT = "KEY_GENRE_SORT"
|
||||
const val KEY_DETAIL_ALBUM_SORT = "auxio_album_sort"
|
||||
const val KEY_DETAIL_ARTIST_SORT = "auxio_artist_sort"
|
||||
const val KEY_DETAIL_GENRE_SORT = "auxio_genre_sort"
|
||||
|
||||
@Volatile
|
||||
private var INSTANCE: SettingsManager? = null
|
||||
|
|
248
app/src/main/java/org/oxycblt/auxio/ui/Sort.kt
Normal file
248
app/src/main/java/org/oxycblt/auxio/ui/Sort.kt
Normal file
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Auxio Project
|
||||
* Sort.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.ui
|
||||
|
||||
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.Genre
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.Song
|
||||
|
||||
/**
|
||||
* A data class representing the sort modes used in Auxio.
|
||||
*
|
||||
* Sorting can be done by Name, Artist, Album, or Year. Sorting of names is always case-insensitive
|
||||
* and article-aware. Certain datatypes may only support a subset of sorts since certain sorts
|
||||
* cannot be easily applied to them (For Example, [Artist] and [ByYear] or [ByAlbum]).
|
||||
*
|
||||
* Internally, sorts are saved as an integer in the following format
|
||||
*
|
||||
* 0b(SORT INT)A
|
||||
*
|
||||
* Where SORT INT is the corresponding integer value of this specific sort and A is a bit
|
||||
* representing whether this sort is ascending or descending.
|
||||
*
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
sealed class Sort(open val isAscending: Boolean) {
|
||||
/** 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)
|
||||
/** Sort by the album of an item, only supported by [Song] */
|
||||
class ByAlbum(override val isAscending: Boolean) : Sort(isAscending)
|
||||
/** Sort by the year of an item, only supported by [Album] and [Song] */
|
||||
class ByYear(override val isAscending: Boolean) : Sort(isAscending)
|
||||
|
||||
/**
|
||||
* Get the corresponding item id for this sort.
|
||||
*/
|
||||
val itemId: Int get() = when (this) {
|
||||
is ByName -> R.id.option_sort_name
|
||||
is ByArtist -> R.id.option_sort_artist
|
||||
is ByAlbum -> R.id.option_sort_album
|
||||
is ByYear -> R.id.option_sort_year
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply [ascending] to the status of this sort.
|
||||
* @return A new [Sort] with the value of [ascending] applied.
|
||||
*/
|
||||
fun ascending(ascending: Boolean): Sort {
|
||||
return when (this) {
|
||||
is ByName -> ByName(ascending)
|
||||
is ByArtist -> ByArtist(ascending)
|
||||
is ByAlbum -> ByAlbum(ascending)
|
||||
is ByYear -> ByYear(ascending)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a new [id] to this sort
|
||||
* @return A new [Sort] corresponding to the [id] given, null if the ID has no analogue.
|
||||
*/
|
||||
fun assignId(@IdRes id: Int): Sort? {
|
||||
return when (id) {
|
||||
R.id.option_sort_name -> ByName(isAscending)
|
||||
R.id.option_sort_artist -> ByArtist(isAscending)
|
||||
R.id.option_sort_album -> ByAlbum(isAscending)
|
||||
R.id.option_sort_year -> ByYear(isAscending)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.name }
|
||||
|
||||
else -> sortAlbums(songs.groupBy { it.album }.keys).flatMap { album ->
|
||||
album.songs.intSort(true) { it.track }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun sortAlbum(album: Album): List<Song> {
|
||||
return album.songs.intSort { it.track }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this sort to it's integer representation.
|
||||
*/
|
||||
fun toInt(): Int {
|
||||
return when (this) {
|
||||
is ByName -> CONST_NAME
|
||||
is ByArtist -> CONST_ARTIST
|
||||
is ByAlbum -> CONST_ALBUM
|
||||
is ByYear -> CONST_YEAR
|
||||
}.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)
|
||||
}
|
||||
|
||||
private fun <T : Music> Collection<T>.intSort(
|
||||
asc: Boolean = isAscending,
|
||||
selector: (T) -> Int,
|
||||
): List<T> {
|
||||
val comparator = if (asc) {
|
||||
compareBy(selector)
|
||||
} else {
|
||||
compareByDescending(selector)
|
||||
}
|
||||
|
||||
return sortedWith(comparator)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONST_NAME = 0xA10C
|
||||
private const val CONST_ARTIST = 0xA10d
|
||||
private const val CONST_ALBUM = 0xA10E
|
||||
private const val CONST_YEAR = 0xA10F
|
||||
|
||||
/**
|
||||
* Convert a sort's integer representation into a [Sort] instance.
|
||||
*
|
||||
* @return A [Sort] instance, null if the data is malformed.
|
||||
*/
|
||||
fun fromInt(value: Int): Sort? {
|
||||
val ascending = (value and 1) == 1
|
||||
|
||||
return when (value.shr(1)) {
|
||||
CONST_NAME -> ByName(ascending)
|
||||
CONST_ARTIST -> ByArtist(ascending)
|
||||
CONST_ALBUM -> ByAlbum(ascending)
|
||||
CONST_YEAR -> ByYear(ascending)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Slice a string so that any preceding articles like The/A(n) are truncated.
|
||||
* This is hilariously anglo-centric, but its mostly for MediaStore compat and hopefully
|
||||
* shouldn't run with other languages.
|
||||
*/
|
||||
fun String.sliceArticle(): String {
|
||||
if (length > 5 && startsWith("the ", true)) {
|
||||
return slice(4..lastIndex)
|
||||
}
|
||||
|
||||
if (length > 4 && startsWith("an ", true)) {
|
||||
return slice(3..lastIndex)
|
||||
}
|
||||
|
||||
if (length > 3 && startsWith("a ", true)) {
|
||||
return slice(2..lastIndex)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
|
@ -1,230 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Auxio Project
|
||||
* SortMode.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.ui
|
||||
|
||||
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.Genre
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.Song
|
||||
|
||||
/**
|
||||
* 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 SortMode(@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]: By name after article, ascending
|
||||
* - [DESCENDING]: By name after article, descending
|
||||
* - [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 -> songs.sortedWith(
|
||||
compareBy(String.CASE_INSENSITIVE_ORDER) { song ->
|
||||
song.name.sliceArticle()
|
||||
}
|
||||
)
|
||||
|
||||
DESCENDING -> songs.sortedWith(
|
||||
compareByDescending(String.CASE_INSENSITIVE_ORDER) { song ->
|
||||
song.name.sliceArticle()
|
||||
}
|
||||
)
|
||||
|
||||
else -> sortAlbums(songs.groupBy { it.album }.keys).flatMap { album ->
|
||||
ASCENDING.sortAlbum(album)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a list of albums.
|
||||
*
|
||||
* **Behavior:**
|
||||
* - [ASCENDING]: By name after article, ascending
|
||||
* - [DESCENDING]: By name after article, descending
|
||||
* - [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 -> sortParents(albums)
|
||||
|
||||
ARTIST -> ASCENDING.sortParents(albums.groupBy { it.artist }.keys)
|
||||
.flatMap { YEAR.sortAlbums(it.albums) }
|
||||
|
||||
ALBUM -> ASCENDING.sortParents(albums)
|
||||
|
||||
YEAR -> albums.sortedByDescending { it.year }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a generic list of [MusicParent] instances.
|
||||
*
|
||||
* **Behavior:**
|
||||
* - [ASCENDING]: By name after article, ascending
|
||||
* - [DESCENDING]: By name after article, descending
|
||||
* - Same parent list is returned otherwise.
|
||||
*/
|
||||
fun <T : MusicParent> sortParents(parents: Collection<T>): List<T> {
|
||||
return when (this) {
|
||||
ASCENDING -> parents.sortedWith(
|
||||
compareBy(String.CASE_INSENSITIVE_ORDER) { model ->
|
||||
model.resolvedName.sliceArticle()
|
||||
}
|
||||
)
|
||||
|
||||
DESCENDING -> parents.sortedWith(
|
||||
compareByDescending(String.CASE_INSENSITIVE_ORDER) { model ->
|
||||
model.resolvedName.sliceArticle()
|
||||
}
|
||||
)
|
||||
|
||||
else -> parents.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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this mode into an integer constant. Use this when writing a [SortMode]
|
||||
* to storage, as it will be more efficent.
|
||||
*/
|
||||
fun toInt(): Int {
|
||||
return when (this) {
|
||||
ASCENDING -> CONST_ASCENDING
|
||||
DESCENDING -> CONST_DESCENDING
|
||||
ARTIST -> CONST_ARTIST
|
||||
ALBUM -> CONST_ALBUM
|
||||
YEAR -> CONST_YEAR
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONST_ASCENDING = 0xA10C
|
||||
private const val CONST_DESCENDING = 0xA10D
|
||||
private const val CONST_ARTIST = 0xA10E
|
||||
private const val CONST_ALBUM = 0xA10F
|
||||
private const val CONST_YEAR = 0xA110
|
||||
|
||||
/**
|
||||
* Returns a [SortMode] depending on the integer constant, use this when restoring
|
||||
* a [SortMode] from storage.
|
||||
*/
|
||||
fun fromInt(value: Int): SortMode? {
|
||||
return when (value) {
|
||||
CONST_ASCENDING -> ASCENDING
|
||||
CONST_DESCENDING -> DESCENDING
|
||||
CONST_ARTIST -> ARTIST
|
||||
CONST_ALBUM -> ALBUM
|
||||
CONST_YEAR -> YEAR
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a menu [id] to an instance of [SortMode].
|
||||
*/
|
||||
fun fromId(@IdRes id: Int): SortMode? {
|
||||
return when (id) {
|
||||
ASCENDING.itemId -> ASCENDING
|
||||
DESCENDING.itemId -> DESCENDING
|
||||
ARTIST.itemId -> ARTIST
|
||||
ALBUM.itemId -> ALBUM
|
||||
YEAR.itemId -> YEAR
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Slice a string so that any preceding articles like The/A(n) are truncated.
|
||||
* This is hilariously anglo-centric, but its mostly for MediaStore compat and hopefully
|
||||
* shouldn't run with other languages.
|
||||
*/
|
||||
fun String.sliceArticle(): String {
|
||||
if (length > 5 && startsWith("the ", true)) {
|
||||
return slice(4..lastIndex)
|
||||
}
|
||||
|
||||
if (length > 4 && startsWith("an ", true)) {
|
||||
return slice(3..lastIndex)
|
||||
}
|
||||
|
||||
if (length > 3 && startsWith("a ", true)) {
|
||||
return slice(2..lastIndex)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
|
@ -24,7 +24,6 @@ import androidx.annotation.LayoutRes
|
|||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.playback.state.LoopMode
|
||||
import org.oxycblt.auxio.playback.system.PlaybackService
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.newBroadcastIntent
|
||||
import org.oxycblt.auxio.util.newMainIntent
|
||||
|
||||
|
@ -58,7 +57,6 @@ private fun RemoteViews.applyCover(context: Context, state: WidgetState): Remote
|
|||
R.id.widget_cover, context.getString(R.string.desc_album_cover, state.song.album.name)
|
||||
)
|
||||
} else {
|
||||
logD("WHY ARE YOU NOT WORKING")
|
||||
setImageViewResource(R.id.widget_cover, R.drawable.ic_widget_album)
|
||||
setContentDescription(R.id.widget_cover, context.getString(R.string.desc_no_cover))
|
||||
}
|
||||
|
|
|
@ -70,7 +70,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/spacing_small"
|
||||
android:text="@string/lbl_shuffle"
|
||||
android:clipToPadding="false"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/detail_play_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/detail_play_button"
|
||||
|
|
|
@ -2,11 +2,8 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group android:checkableBehavior="single">
|
||||
<item
|
||||
android:id="@+id/option_sort_asc"
|
||||
android:title="@string/lbl_sort_asc" />
|
||||
<item
|
||||
android:id="@+id/option_sort_dsc"
|
||||
android:title="@string/lbl_sort_dsc" />
|
||||
android:id="@+id/option_sort_name"
|
||||
android:title="@string/lbl_sort_name" />
|
||||
<item
|
||||
android:id="@+id/option_sort_artist"
|
||||
android:title="@string/lbl_sort_artist" />
|
||||
|
@ -17,4 +14,8 @@
|
|||
android:id="@+id/option_sort_year"
|
||||
android:title="@string/lbl_sort_year" />
|
||||
</group>
|
||||
<group android:checkableBehavior="all">
|
||||
<item android:id="@+id/option_sort_asc"
|
||||
android:title="@string/lbl_sort_asc" />
|
||||
</group>
|
||||
</menu>
|
|
@ -16,11 +16,8 @@
|
|||
<menu>
|
||||
<group android:checkableBehavior="single">
|
||||
<item
|
||||
android:id="@+id/option_sort_asc"
|
||||
android:title="@string/lbl_sort_asc" />
|
||||
<item
|
||||
android:id="@+id/option_sort_dsc"
|
||||
android:title="@string/lbl_sort_dsc" />
|
||||
android:id="@+id/option_sort_name"
|
||||
android:title="@string/lbl_sort_name" />
|
||||
<item
|
||||
android:id="@+id/option_sort_artist"
|
||||
android:title="@string/lbl_sort_artist" />
|
||||
|
@ -31,6 +28,10 @@
|
|||
android:id="@+id/option_sort_year"
|
||||
android:title="@string/lbl_sort_year" />
|
||||
</group>
|
||||
<group android:checkableBehavior="all">
|
||||
<item android:id="@+id/option_sort_asc"
|
||||
android:title="@string/lbl_sort_asc" />
|
||||
</group>
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
|
|
|
@ -6,6 +6,26 @@
|
|||
<attr name="entryValues" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="SlidingUpPanelLayout">
|
||||
<attr name="umanoPanelHeight" format="dimension" />
|
||||
<attr name="umanoShadowHeight" format="dimension" />
|
||||
<attr name="umanoParallaxOffset" format="dimension" />
|
||||
<attr name="umanoFadeColor" format="color" />
|
||||
<attr name="umanoFlingVelocity" format="integer" />
|
||||
<attr name="umanoDragView" format="reference" />
|
||||
<attr name="umanoScrollableView" format="reference" />
|
||||
<attr name="umanoOverlay" format="boolean"/>
|
||||
<attr name="umanoClipPanel" format="boolean"/>
|
||||
<attr name="umanoAnchorPoint" format="float" />
|
||||
<attr name="umanoInitialState" format="enum">
|
||||
<enum name="expanded" value="0" />
|
||||
<enum name="collapsed" value="1" />
|
||||
<enum name="anchored" value="2" />
|
||||
<enum name="hidden" value="3" />
|
||||
</attr>
|
||||
<attr name="umanoScrollInterpolator" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
<string-array name="entires_theme">
|
||||
<item>@string/set_theme_auto</item>
|
||||
<item>@string/set_theme_day</item>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
<string name="lbl_filter_all">All</string>
|
||||
|
||||
<string name="lbl_sort">Sort</string>
|
||||
<string name="lbl_sort_name">Name</string>
|
||||
<string name="lbl_sort_asc">Ascending</string>
|
||||
<string name="lbl_sort_dsc">Descending</string>
|
||||
<string name="lbl_sort_artist">Artist</string>
|
||||
|
|
|
@ -165,4 +165,32 @@
|
|||
<item name="maxImageSize">@dimen/size_play_fab_icon</item>
|
||||
<item name="fabCustomSize">@dimen/size_btn_large</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.MaterialComponents.Button.IconOnly">
|
||||
<item name="iconPadding">0dp</item>
|
||||
<item name="android:insetTop">0dp</item>
|
||||
<item name="android:insetBottom">0dp</item>
|
||||
<item name="android:paddingLeft">12dp</item>
|
||||
<item name="android:paddingRight">12dp</item>
|
||||
<item name="android:minWidth">@dimen/size_btn_small</item>
|
||||
<item name="android:minHeight">@dimen/size_btn_small</item>
|
||||
<item name="iconGravity">textStart</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.MaterialComponents.Button.UnelevatedButton.IconOnly" parent="Widget.MaterialComponents.Button.IconOnly">
|
||||
<item name="android:stateListAnimator">@animator/mtrl_btn_unelevated_state_list_anim</item>
|
||||
<item name="elevation">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.MaterialComponents.Button.TextButton.IconOnly" parent="Widget.MaterialComponents.Button.UnelevatedButton.IconOnly">
|
||||
<item name="android:textColor">@color/mtrl_text_btn_text_color_selector</item>
|
||||
<item name="iconTint">?attr/colorControlNormal</item>
|
||||
<item name="backgroundTint">@color/mtrl_btn_text_btn_bg_color_selector</item>
|
||||
<item name="rippleColor">?attr/colorControlHighlight</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.MaterialComponents.Button.OutlinedButton.IconOnly" parent="Widget.MaterialComponents.Button.TextButton.IconOnly">
|
||||
<item name="strokeColor">@color/mtrl_btn_stroke_color_selector</item>
|
||||
<item name="strokeWidth">@dimen/mtrl_btn_stroke_size</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<Preference
|
||||
app:icon="@drawable/ic_accent"
|
||||
app:key="KEY_ACCENT3"
|
||||
app:key="auxio_accent"
|
||||
app:title="@string/set_accent" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
|
@ -35,7 +35,7 @@
|
|||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="KEY_LIB_TABS"
|
||||
app:key="auxio_lib_tabs"
|
||||
app:summary="@string/set_lib_tabs_desc"
|
||||
app:title="@string/set_lib_tabs" />
|
||||
|
||||
|
@ -130,7 +130,7 @@
|
|||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="KEY_SAVE_STATE"
|
||||
app:key="auxio_save_state"
|
||||
app:summary="@string/set_save_desc"
|
||||
app:title="@string/set_save" />
|
||||
|
||||
|
|
Loading…
Reference in a new issue