Standardize settings entirely

Update the structure of the settings system to do several things:
- Finally use int preferences everywhere instead of the mix of strings & ints from before
- Create a new preference named `IntListPreference` that enables the use of integers in list preferences
- Actually centralize array values and default values into a single integers xml
- Isolate all the new migration code into a single file
- Refreshed the int tables used by data objects [DB version has been updated, will not update when other changes]

The only preference not migrated is doAtEnd since that is being planned to be retired in a future LoopMode update.
This commit is contained in:
OxygenCobalt 2021-04-03 10:53:16 -06:00
parent 355b3885e3
commit 0305eb0beb
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
29 changed files with 575 additions and 280 deletions

View file

@ -9,8 +9,8 @@ android {
defaultConfig { defaultConfig {
applicationId "org.oxycblt.auxio" applicationId "org.oxycblt.auxio"
versionName "1.3.3" versionName "1.4.0"
versionCode 5 versionCode 6
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 30
@ -65,7 +65,7 @@ dependencies {
// General // General
implementation "androidx.core:core-ktx:1.3.2" implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.activity:activity-ktx:1.2.2" implementation "androidx.activity:activity-ktx:1.2.2"
implementation "androidx.fragment:fragment-ktx:1.3.1" implementation "androidx.fragment:fragment-ktx:1.3.2"
// UI // UI
implementation "androidx.recyclerview:recyclerview:1.1.0" implementation "androidx.recyclerview:recyclerview:1.1.0"
@ -73,7 +73,7 @@ dependencies {
implementation "androidx.dynamicanimation:dynamicanimation:1.0.0" implementation "androidx.dynamicanimation:dynamicanimation:1.0.0"
// Lifecycle // Lifecycle
def lifecycle_version = "2.3.0" def lifecycle_version = "2.3.1"
implementation "androidx.lifecycle:lifecycle-common:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"

View file

@ -3,8 +3,6 @@ package org.oxycblt.auxio.database
import android.database.Cursor import android.database.Cursor
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.os.Looper import android.os.Looper
import org.oxycblt.auxio.logE
import java.lang.Exception
/** /**
* Shortcut for querying all items in a database and running [block] with the cursor returned. * Shortcut for querying all items in a database and running [block] with the cursor returned.

View file

@ -228,7 +228,7 @@ class PlaybackStateDatabase(context: Context) :
companion object { companion object {
const val DB_NAME = "auxio_state_database.db" const val DB_NAME = "auxio_state_database.db"
const val DB_VERSION = 3 const val DB_VERSION = 4
const val TABLE_NAME_STATE = "playback_state_table" const val TABLE_NAME_STATE = "playback_state_table"
const val TABLE_NAME_QUEUE = "queue_table" const val TABLE_NAME_QUEUE = "queue_table"

View file

@ -5,7 +5,7 @@ import android.net.Uri
// --- MUSIC MODELS --- // --- MUSIC MODELS ---
// TODO: Implement some kind of hash system, removing the need to redundant names but alsow without the volitility of id // TODO: Implement some kind of hash system, removing the need to redundant names but alsow without the volitility of id
// They need to be completely unique, however, and from whatever information I have about them. // They need to be completely unique, however, and from whatever information I have about them on creation
/** /**
* The base data object for all music. * The base data object for all music.

View file

@ -31,9 +31,9 @@ enum class LoopMode {
} }
companion object { companion object {
private const val CONST_NONE = 0xA050 const val CONST_NONE = 0xA100
private const val CONST_ONCE = 0xA051 const val CONST_ONCE = 0xA101
private const val CONST_INFINITE = 0xA052 const val CONST_INFINITE = 0xA102
/** /**
* Convert an int [constant] into a LoopMode * Convert an int [constant] into a LoopMode

View file

@ -28,10 +28,10 @@ enum class PlaybackMode {
} }
companion object { companion object {
private const val CONST_IN_ARTIST = 0xA040 const val CONST_IN_GENRE = 0xA103
private const val CONST_IN_GENRE = 0xA041 const val CONST_IN_ARTIST = 0xA104
private const val CONST_IN_ALBUM = 0xA042 const val CONST_IN_ALBUM = 0xA105
private const val CONST_ALL_SONGS = 0xA043 const val CONST_ALL_SONGS = 0xA106
/** /**
* Get a [PlaybackMode] for an int [constant] * Get a [PlaybackMode] for an int [constant]

View file

@ -810,6 +810,10 @@ class PlaybackStateManager private constructor() {
companion object { companion object {
private const val REWIND_THRESHOLD = 3000L private const val REWIND_THRESHOLD = 3000L
const val AT_END_LOOP_PAUSE = 0
const val AT_END_LOOP = 1
const val AT_END_STOP = 2
@Volatile @Volatile
private var INSTANCE: PlaybackStateManager? = null private var INSTANCE: PlaybackStateManager? = null

View file

@ -170,7 +170,7 @@ class PlaybackNotification private constructor(
companion object { companion object {
const val CHANNEL_ID = "CHANNEL_AUXIO_PLAYBACK" const val CHANNEL_ID = "CHANNEL_AUXIO_PLAYBACK"
const val NOTIFICATION_ID = 0xA0A0 const val NOTIFICATION_ID = 0xA0A0
const val REQUEST_CODE = 0xA0C0 const val REQUEST_CODE = 0xA0AA
// Build type is added to the codes so that dual release/debug installations dont conflict // Build type is added to the codes so that dual release/debug installations dont conflict
// with each other. // with each other.

View file

@ -28,7 +28,23 @@ enum class DisplayMode(@DrawableRes val iconRes: Int) {
} }
} }
fun toInt(): Int {
return when (this) {
SHOW_ALL -> CONST_SHOW_ALL
SHOW_GENRES -> CONST_SHOW_GENRES
SHOW_ARTISTS -> CONST_SHOW_ARTISTS
SHOW_ALBUMS -> CONST_SHOW_ALBUMS
SHOW_SONGS -> CONST_SHOW_SONGS
}
}
companion object { companion object {
const val CONST_SHOW_ALL = 0xA107
const val CONST_SHOW_GENRES = 0xA108
const val CONST_SHOW_ARTISTS = 0xA109
const val CONST_SHOW_ALBUMS = 0xA10A
const val CONST_SHOW_SONGS = 0xA10B
/** /**
* A valueOf wrapper that will return a default value if given a null/invalid string. * A valueOf wrapper that will return a default value if given a null/invalid string.
*/ */
@ -55,5 +71,21 @@ enum class DisplayMode(@DrawableRes val iconRes: Int) {
else -> SHOW_ALL else -> SHOW_ALL
} }
} }
/**
* Get an enum for an int constant
* @return The [DisplayMode] if the constant is valid, null otherwise.
*/
fun fromInt(value: Int): DisplayMode? {
return when (value) {
CONST_SHOW_ALL -> SHOW_ALL
CONST_SHOW_GENRES -> SHOW_GENRES
CONST_SHOW_ARTISTS -> SHOW_ARTISTS
CONST_SHOW_ALBUMS -> SHOW_ALBUMS
CONST_SHOW_SONGS -> SHOW_SONGS
else -> null
}
}
} }
} }

View file

@ -174,13 +174,11 @@ enum class SortMode(@DrawableRes val iconRes: Int) {
} }
companion object { companion object {
private const val CONST_NONE = 0xA060 const val CONST_NONE = 0xA10C
private const val CONST_ALPHA_UP = 0xA061 const val CONST_ALPHA_UP = 0xA10D
private const val CONST_ALPHA_DOWN = 0xA062 const val CONST_ALPHA_DOWN = 0xA10E
private const val CONST_NUMERIC_UP = 0xA063 const val CONST_NUMERIC_UP = 0xA10F
private const val CONST_NUMERIC_DOWN = 0xA065 const val CONST_NUMERIC_DOWN = 0xA110
const val CONST_SORT_DEFAULT = CONST_ALPHA_DOWN
/** /**
* Get an enum for an int constant * Get an enum for an int constant

View file

@ -14,23 +14,6 @@ import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.inflater import org.oxycblt.auxio.ui.inflater
/*
* A table of all ViewHolder codes. Please add to these so that all viewholder codes are unique.
* SongViewHolder -> 0xA000
* AlbumViewHolder -> 0xA001
* ArtistViewHolder -> 0xA002
* GenreViewHolder -> 0xA003
* HeaderViewHolder -> 0xA004
* QueueSongViewHolder -> 0xA005
* UserQueueHeaderViewHolder -> 0xA006
* AlbumHeaderViewHolder -> 0xA007
* AlbumSongViewHolder -> 0xA008
* ArtistHeaderViewHolder -> 0xA009
* ArtistAlbumViewHolder -> 0xA00A
* GenreHeaderViewHolder -> 0xA00B
* GenreSongViewHolder -> 0xA00C
*/
/** /**
* The Shared ViewHolder for a [Song]. Instantiation should be done with [from]. * The Shared ViewHolder for a [Song]. Instantiation should be done with [from].
*/ */

View file

@ -4,21 +4,6 @@ import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
/**
* Convert a string representing a theme entry name to an actual theme int that can be used.
* This is only done because PreferenceFragment does not like int arrays for some...reason.
* @return The proper theme int for this value.
*/
fun String.toThemeInt(): Int {
return when (this) {
SettingsManager.EntryValues.THEME_AUTO -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
SettingsManager.EntryValues.THEME_LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
SettingsManager.EntryValues.THEME_DARK -> AppCompatDelegate.MODE_NIGHT_YES
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
}
/** /**
* Convert an theme integer into an icon that can be used. * Convert an theme integer into an icon that can be used.
* @return An icon for this theme. * @return An icon for this theme.

View file

@ -0,0 +1,175 @@
package org.oxycblt.auxio.settings
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.ui.ACCENTS
import org.oxycblt.auxio.ui.Accent
// A couple of utils for migrating from old settings values to the new the new
// formats used in 1.3.2 & 1.4.0
fun handleThemeCompat(prefs: SharedPreferences): Int {
if (prefs.contains(OldKeys.KEY_THEME)) {
// Before the creation of IntListPreference, I used strings to represent the themes.
// I no longer need to do this.
val newValue = when (prefs.getString(OldKeys.KEY_THEME, EntryValues.THEME_AUTO)) {
EntryValues.THEME_AUTO -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
EntryValues.THEME_LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
EntryValues.THEME_DARK -> AppCompatDelegate.MODE_NIGHT_YES
else -> error("Invalid theme")
}
prefs.edit {
putInt(SettingsManager.KEY_THEME, newValue)
remove(OldKeys.KEY_THEME)
apply()
}
return newValue
}
return prefs.getInt(SettingsManager.KEY_THEME, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
fun handleAccentCompat(prefs: SharedPreferences): Accent {
if (prefs.contains(OldKeys.KEY_ACCENT)) {
var accent = prefs.getInt(OldKeys.KEY_ACCENT, 5)
// Correct any accents over yellow to their correct positions
if (accent > 12) {
accent--
}
// Correct neutral accents to the closest accent [Grey]
if (accent == 18) {
accent = 16
}
// If there are still any issues with indices, just correct them so a crash doesnt occur.
if (accent > ACCENTS.lastIndex) {
accent = ACCENTS.lastIndex
}
prefs.edit {
putInt(SettingsManager.KEY_ACCENT, accent)
remove(OldKeys.KEY_ACCENT)
apply()
}
return ACCENTS[accent]
}
return ACCENTS[prefs.getInt(SettingsManager.KEY_ACCENT, 5)]
}
fun handleLibDisplayCompat(prefs: SharedPreferences): DisplayMode {
if (prefs.contains(OldKeys.KEY_LIB_MODE)) {
val mode = handleStringDisplayMode(
prefs.getString(OldKeys.KEY_LIB_MODE, EntryValues.SHOW_ARTISTS),
) ?: DisplayMode.SHOW_ARTISTS
prefs.edit {
putInt(SettingsManager.KEY_LIB_DISPLAY_MODE, mode.toInt())
remove(OldKeys.KEY_LIB_MODE)
apply()
}
return mode
}
return DisplayMode.fromInt(
prefs.getInt(SettingsManager.KEY_LIB_DISPLAY_MODE, DisplayMode.CONST_SHOW_ARTISTS)
) ?: DisplayMode.SHOW_ARTISTS
}
fun handleSongPlayModeCompat(prefs: SharedPreferences): PlaybackMode {
if (prefs.contains(OldKeys.KEY_SONG_PLAYBACK_MODE)) {
val mode = when (prefs.getString(OldKeys.KEY_SONG_PLAYBACK_MODE, EntryValues.ALL_SONGS)) {
EntryValues.IN_GENRE -> PlaybackMode.IN_GENRE
EntryValues.IN_ARTIST -> PlaybackMode.IN_ARTIST
EntryValues.IN_ALBUM -> PlaybackMode.IN_ALBUM
EntryValues.ALL_SONGS -> PlaybackMode.ALL_SONGS
else -> PlaybackMode.ALL_SONGS
}
prefs.edit {
putInt(SettingsManager.KEY_SONG_PLAYBACK_MODE, mode.toInt())
remove(OldKeys.KEY_SONG_PLAYBACK_MODE)
apply()
}
return mode
}
return PlaybackMode.fromInt(
prefs.getInt(SettingsManager.KEY_SONG_PLAYBACK_MODE, PlaybackMode.CONST_ALL_SONGS)
) ?: PlaybackMode.ALL_SONGS
}
fun handleSearchModeCompat(prefs: SharedPreferences): DisplayMode {
if (prefs.contains(OldKeys.KEY_SEARCH_FILTER)) {
val mode = handleStringDisplayMode(
prefs.getString(OldKeys.KEY_SEARCH_FILTER, EntryValues.SHOW_ALL)
) ?: DisplayMode.SHOW_ALL
prefs.edit {
putInt(SettingsManager.KEY_SEARCH_FILTER_MODE, mode.toInt())
remove(OldKeys.KEY_SEARCH_FILTER)
apply()
}
return mode
}
return DisplayMode.fromInt(
prefs.getInt(SettingsManager.KEY_SEARCH_FILTER_MODE, DisplayMode.CONST_SHOW_ALL)
) ?: DisplayMode.SHOW_ARTISTS
}
private fun handleStringDisplayMode(string: String?): DisplayMode? {
return when (string) {
EntryValues.SHOW_GENRES -> DisplayMode.SHOW_GENRES
EntryValues.SHOW_ARTISTS -> DisplayMode.SHOW_ARTISTS
EntryValues.SHOW_ALBUMS -> DisplayMode.SHOW_ALBUMS
EntryValues.SHOW_SONGS -> DisplayMode.SHOW_SONGS
EntryValues.SHOW_ALL -> DisplayMode.SHOW_ALL
else -> null
}
}
/**
* Cache of the old keys used in Auxio.
*/
private object OldKeys {
const val KEY_ACCENT = "KEY_ACCENT"
const val KEY_THEME = "KEY_THEME"
const val KEY_LIB_MODE = "KEY_LIBRARY_DISPLAY_MODE"
const val KEY_SONG_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE"
const val KEY_SEARCH_FILTER = "KEY_SEARCH"
}
/**
* Static cache of old string values used in Auxio
*/
private object EntryValues {
const val THEME_AUTO = "AUTO"
const val THEME_LIGHT = "LIGHT"
const val THEME_DARK = "DARK"
const val SHOW_GENRES = "SHOW_GENRES"
const val SHOW_ARTISTS = "SHOW_ARTISTS"
const val SHOW_ALBUMS = "SHOW_ALBUMS"
const val SHOW_SONGS = "SHOW_SONGS"
const val SHOW_ALL = "SHOW_ALL"
const val IN_GENRE = "IN_GENRE"
const val IN_ARTIST = "IN_ARTIST"
const val IN_ALBUM = "IN_ALBUM"
const val ALL_SONGS = "ALL_SONGS"
}

View file

@ -15,12 +15,13 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.DisplayMode import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.settings.accent.AccentDialog import org.oxycblt.auxio.settings.accent.AccentDialog
import org.oxycblt.auxio.settings.blacklist.BlacklistDialog import org.oxycblt.auxio.settings.blacklist.BlacklistDialog
import org.oxycblt.auxio.settings.ui.IntListPrefDialog
import org.oxycblt.auxio.settings.ui.IntListPreference
import org.oxycblt.auxio.ui.Accent import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.createToast import org.oxycblt.auxio.ui.createToast
/** /**
* The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat]. * The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat].
* TODO: Roll your own ListPreference that supports int prefs [and is a bottom sheet]
* @author OxygenCobalt * @author OxygenCobalt
*/ */
@Suppress("UNUSED") @Suppress("UNUSED")
@ -44,6 +45,13 @@ class SettingsListFragment : PreferenceFragmentCompat() {
setPreferencesFromResource(R.xml.prefs_main, rootKey) setPreferencesFromResource(R.xml.prefs_main, rootKey)
} }
override fun onDisplayPreferenceDialog(preference: Preference?) {
if (preference is IntListPreference) {
IntListPrefDialog(preference).show(childFragmentManager, IntListPrefDialog.TAG)
} else {
super.onDisplayPreferenceDialog(preference)
}
}
/** /**
* Recursively call [handlePreference] on a preference. * Recursively call [handlePreference] on a preference.
*/ */
@ -64,44 +72,35 @@ class SettingsListFragment : PreferenceFragmentCompat() {
private fun handlePreference(pref: Preference) { private fun handlePreference(pref: Preference) {
pref.apply { pref.apply {
when (key) { when (key) {
SettingsManager.Keys.KEY_THEME -> { SettingsManager.KEY_THEME -> {
setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon()) setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon())
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value -> onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value ->
AppCompatDelegate.setDefaultNightMode((value as String).toThemeInt()) AppCompatDelegate.setDefaultNightMode(value as Int)
setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon()) setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon())
true true
} }
} }
SettingsManager.Keys.KEY_ACCENT -> { SettingsManager.KEY_ACCENT -> {
onPreferenceClickListener = Preference.OnPreferenceClickListener { onPreferenceClickListener = Preference.OnPreferenceClickListener {
AccentDialog().show(childFragmentManager, TAG_ACCENT_DIALOG) AccentDialog().show(childFragmentManager, AccentDialog.TAG)
true true
} }
summary = Accent.get().getDetailedSummary(context) summary = Accent.get().getDetailedSummary(context)
} }
SettingsManager.Keys.KEY_EDGE_TO_EDGE -> { SettingsManager.KEY_LIB_DISPLAY_MODE -> {
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ ->
requireActivity().recreate()
true
}
}
SettingsManager.Keys.KEY_LIBRARY_DISPLAY_MODE -> {
setIcon(settingsManager.libraryDisplayMode.iconRes) setIcon(settingsManager.libraryDisplayMode.iconRes)
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value -> onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value ->
setIcon(DisplayMode.valueOfOrFallback(value as String).iconRes) setIcon(DisplayMode.fromInt(value as Int)!!.iconRes)
true true
} }
} }
SettingsManager.Keys.KEY_SHOW_COVERS -> { SettingsManager.KEY_SHOW_COVERS -> {
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ ->
Coil.imageLoader(requireContext()).apply { Coil.imageLoader(requireContext()).apply {
bitmapPool.clear() bitmapPool.clear()
@ -114,7 +113,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
} }
} }
SettingsManager.Keys.KEY_QUALITY_COVERS -> { SettingsManager.KEY_QUALITY_COVERS -> {
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ ->
// Clear out any cached images, before recreating the activity // Clear out any cached images, before recreating the activity
Coil.imageLoader(requireContext()).apply { Coil.imageLoader(requireContext()).apply {
@ -128,7 +127,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
} }
} }
SettingsManager.Keys.KEY_SAVE_STATE -> { SettingsManager.KEY_SAVE_STATE -> {
onPreferenceClickListener = Preference.OnPreferenceClickListener { onPreferenceClickListener = Preference.OnPreferenceClickListener {
playbackModel.savePlaybackState(requireContext()) { playbackModel.savePlaybackState(requireContext()) {
getString(R.string.label_state_saved).createToast(requireContext()) getString(R.string.label_state_saved).createToast(requireContext())
@ -137,17 +136,13 @@ class SettingsListFragment : PreferenceFragmentCompat() {
} }
} }
SettingsManager.Keys.KEY_BLACKLIST -> { SettingsManager.KEY_BLACKLIST -> {
onPreferenceClickListener = Preference.OnPreferenceClickListener { onPreferenceClickListener = Preference.OnPreferenceClickListener {
BlacklistDialog().show(childFragmentManager, TAG_ACCENT_DIALOG) BlacklistDialog().show(childFragmentManager, BlacklistDialog.TAG)
true true
} }
} }
} }
} }
} }
companion object {
const val TAG_ACCENT_DIALOG = "ACCENT_DIALOG"
}
} }

View file

@ -2,8 +2,8 @@ package org.oxycblt.auxio.settings
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.recycler.DisplayMode import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.recycler.SortMode import org.oxycblt.auxio.recycler.SortMode
@ -16,9 +16,16 @@ import org.oxycblt.auxio.ui.Accent
*/ */
class SettingsManager private constructor(context: Context) : class SettingsManager private constructor(context: Context) :
SharedPreferences.OnSharedPreferenceChangeListener { SharedPreferences.OnSharedPreferenceChangeListener {
private val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context) private val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
init { init {
// If needed, we need to touch the song mode pref so that it migrates before something
// else is written by the Preference.
if (!sharedPrefs.contains(KEY_SONG_PLAYBACK_MODE)) {
handleSongPlayModeCompat(sharedPrefs)
}
sharedPrefs.registerOnSharedPreferenceChangeListener(this) sharedPrefs.registerOnSharedPreferenceChangeListener(this)
} }
@ -26,125 +33,93 @@ class SettingsManager private constructor(context: Context) :
/** The current theme */ /** The current theme */
val theme: Int val theme: Int
get() = sharedPrefs.getString(Keys.KEY_THEME, EntryValues.THEME_AUTO)!!.toThemeInt() get() = handleThemeCompat(sharedPrefs)
/** The current accent. */ /** The current accent. */
var accent: Accent var accent: Accent
get() { get() = handleAccentCompat(sharedPrefs)
@Suppress("DEPRECATION")
if (sharedPrefs.contains(Keys.KEY_ACCENT_OLD)) {
logD("Migrating from old accent to new accent.")
val newAccent = handleAccentCompat(
sharedPrefs.getInt(Keys.KEY_ACCENT_OLD, 5)
)
// When converted, write them to the new accent pref and delete the old one.
sharedPrefs.edit()
.putInt(Keys.KEY_ACCENT, newAccent)
.remove(Keys.KEY_ACCENT_OLD)
.apply()
}
return ACCENTS[sharedPrefs.getInt(Keys.KEY_ACCENT, 5)]
}
set(value) { set(value) {
val accentIndex = ACCENTS.indexOf(value) val accentIndex = ACCENTS.indexOf(value)
check(accentIndex != -1) { "Invalid accent" } check(accentIndex != -1) { "Invalid accent" }
sharedPrefs.edit() sharedPrefs.edit {
.putInt(Keys.KEY_ACCENT, accentIndex) putInt(KEY_ACCENT, accentIndex)
.apply() apply()
}
} }
/** Whether to colorize the notification */ /** Whether to colorize the notification */
val colorizeNotif: Boolean val colorizeNotif: Boolean
get() = sharedPrefs.getBoolean(Keys.KEY_COLORIZE_NOTIFICATION, true) get() = sharedPrefs.getBoolean(KEY_COLORIZE_NOTIFICATION, true)
/** /**
* Whether to display the LoopMode or the shuffle status on the notification. * Whether to display the LoopMode or the shuffle status on the notification.
* False if loop, true if shuffle. * False if loop, true if shuffle.
*/ */
val useAltNotifAction: Boolean val useAltNotifAction: Boolean
get() = sharedPrefs.getBoolean(Keys.KEY_USE_ALT_NOTIFICATION_ACTION, false) get() = sharedPrefs.getBoolean(KEY_USE_ALT_NOTIFICATION_ACTION, false)
/** What to display on the library. */ /** What to display on the library. */
val libraryDisplayMode: DisplayMode val libraryDisplayMode: DisplayMode
get() = DisplayMode.valueOfOrFallback( get() = handleLibDisplayCompat(sharedPrefs)
sharedPrefs.getString(
Keys.KEY_LIBRARY_DISPLAY_MODE,
DisplayMode.SHOW_ARTISTS.toString()
)
)
/** /**
* Whether to even loading embedded covers * Whether to even loading embedded covers
* TODO: Make the UI result of this better? * TODO: Make the UI result of this better?
*/ */
val showCovers: Boolean val showCovers: Boolean
get() = sharedPrefs.getBoolean(Keys.KEY_SHOW_COVERS, true) get() = sharedPrefs.getBoolean(KEY_SHOW_COVERS, true)
/** Whether to ignore MediaStore covers */ /** Whether to ignore MediaStore covers */
val useQualityCovers: Boolean val useQualityCovers: Boolean
get() = sharedPrefs.getBoolean(Keys.KEY_QUALITY_COVERS, false) get() = sharedPrefs.getBoolean(KEY_QUALITY_COVERS, false)
/** Whether to do Audio focus. */ /** Whether to do Audio focus. */
val doAudioFocus: Boolean val doAudioFocus: Boolean
get() = sharedPrefs.getBoolean(Keys.KEY_AUDIO_FOCUS, true) get() = sharedPrefs.getBoolean(KEY_AUDIO_FOCUS, true)
/** Whether to resume/stop playback when a headset is connected/disconnected. */ /** Whether to resume/stop playback when a headset is connected/disconnected. */
val doPlugMgt: Boolean val doPlugMgt: Boolean
get() = sharedPrefs.getBoolean(Keys.KEY_PLUG_MANAGEMENT, true) get() = sharedPrefs.getBoolean(KEY_PLUG_MANAGEMENT, true)
/** What queue to create when a song is selected (ex. From All Songs or Search) */ /** What queue to create when a song is selected (ex. From All Songs or Search) */
val songPlaybackMode: PlaybackMode val songPlaybackMode: PlaybackMode
get() = PlaybackMode.valueOfOrFallback( get() = handleSongPlayModeCompat(sharedPrefs)
sharedPrefs.getString(
Keys.KEY_SONG_PLAYBACK_MODE,
PlaybackMode.ALL_SONGS.toString()
)
)
/** What to do at the end of a playlist. */ /** What to do at the end of a playlist. */
val doAtEnd: String val doAtEnd: String
get() = sharedPrefs.getString(Keys.KEY_AT_END, EntryValues.AT_END_LOOP_PAUSE) get() = sharedPrefs.getString(KEY_AT_END, EntryValues.AT_END_LOOP_PAUSE)
?: EntryValues.AT_END_LOOP_PAUSE ?: EntryValues.AT_END_LOOP_PAUSE
/** Whether shuffle should stay on when a new song is selected. */ /** Whether shuffle should stay on when a new song is selected. */
val keepShuffle: Boolean val keepShuffle: Boolean
get() = sharedPrefs.getBoolean(Keys.KEY_KEEP_SHUFFLE, true) get() = sharedPrefs.getBoolean(KEY_KEEP_SHUFFLE, true)
/** Whether to rewind when the back button is pressed. */ /** Whether to rewind when the back button is pressed. */
val rewindWithPrev: Boolean val rewindWithPrev: Boolean
get() = sharedPrefs.getBoolean(Keys.KEY_PREV_REWIND, true) get() = sharedPrefs.getBoolean(KEY_PREV_REWIND, true)
/** The current [SortMode] of the library. */ /** The current [SortMode] of the library. */
var librarySortMode: SortMode var librarySortMode: SortMode
get() = SortMode.fromInt( get() = SortMode.fromInt(
sharedPrefs.getInt( sharedPrefs.getInt(KEY_LIBRARY_SORT_MODE, SortMode.CONST_ALPHA_DOWN)
Keys.KEY_LIBRARY_SORT_MODE, SortMode.CONST_SORT_DEFAULT
)
) ?: SortMode.ALPHA_DOWN ) ?: SortMode.ALPHA_DOWN
set(value) { set(value) {
sharedPrefs.edit() sharedPrefs.edit {
.putInt(Keys.KEY_LIBRARY_SORT_MODE, value.toInt()) putInt(KEY_LIBRARY_SORT_MODE, value.toInt())
.apply() apply()
}
} }
/** The current filter mode of the search tab */ /** The current filter mode of the search tab */
var searchFilterMode: DisplayMode var searchFilterMode: DisplayMode
get() = DisplayMode.valueOfOrFallback( get() = handleSearchModeCompat(sharedPrefs)
sharedPrefs.getString(
Keys.KEY_SEARCH_FILTER_MODE, DisplayMode.SHOW_ALL.toString()
),
fallback = DisplayMode.SHOW_ALL
)
set(value) { set(value) {
sharedPrefs.edit() sharedPrefs.edit {
.putString(Keys.KEY_SEARCH_FILTER_MODE, value.toString()) putInt(KEY_SEARCH_FILTER_MODE, value.toInt())
.apply() apply()
}
} }
// --- CALLBACKS --- // --- CALLBACKS ---
@ -163,89 +138,31 @@ class SettingsManager private constructor(context: Context) :
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) { when (key) {
Keys.KEY_COLORIZE_NOTIFICATION -> callbacks.forEach { KEY_COLORIZE_NOTIFICATION -> callbacks.forEach {
it.onColorizeNotifUpdate(colorizeNotif) it.onColorizeNotifUpdate(colorizeNotif)
} }
Keys.KEY_USE_ALT_NOTIFICATION_ACTION -> callbacks.forEach { KEY_USE_ALT_NOTIFICATION_ACTION -> callbacks.forEach {
it.onNotifActionUpdate(useAltNotifAction) it.onNotifActionUpdate(useAltNotifAction)
} }
Keys.KEY_LIBRARY_DISPLAY_MODE -> callbacks.forEach { KEY_LIB_DISPLAY_MODE -> callbacks.forEach {
it.onLibDisplayModeUpdate(libraryDisplayMode) it.onLibDisplayModeUpdate(libraryDisplayMode)
} }
Keys.KEY_SHOW_COVERS -> callbacks.forEach { KEY_SHOW_COVERS -> callbacks.forEach {
it.onShowCoverUpdate(showCovers) it.onShowCoverUpdate(showCovers)
} }
Keys.KEY_QUALITY_COVERS -> callbacks.forEach { KEY_QUALITY_COVERS -> callbacks.forEach {
it.onQualityCoverUpdate(useQualityCovers) it.onQualityCoverUpdate(useQualityCovers)
} }
} }
} }
/**
* Convert the old accent format of <1.3.1 to the accent format of 1.3.2-Onwards,
* where many accents were changed or removed.
*/
private fun handleAccentCompat(oldAccent: Int): Int {
var newAccent = oldAccent
// Correct any accents over yellow to their correct positions
if (oldAccent > 12) {
newAccent--
}
// Correct neutral accents to the closest accent [Grey]
if (newAccent == 18) {
newAccent = 16
}
// If there are still any issues with indices, just correct them so a crash doesnt occur.
if (newAccent > ACCENTS.lastIndex) {
newAccent = ACCENTS.lastIndex
}
return newAccent
}
/**
* SharedPreferences keys.
*/
object Keys {
const val KEY_THEME = "KEY_THEME"
const val KEY_ACCENT = "KEY_ACCENT2"
const val KEY_EDGE_TO_EDGE = "KEY_EDGE"
const val KEY_LIBRARY_DISPLAY_MODE = "KEY_LIBRARY_DISPLAY_MODE"
const val KEY_SHOW_COVERS = "KEY_SHOW_COVERS"
const val KEY_QUALITY_COVERS = "KEY_QUALITY_COVERS"
const val KEY_COLORIZE_NOTIFICATION = "KEY_COLOR_NOTIF"
const val KEY_USE_ALT_NOTIFICATION_ACTION = "KEY_ALT_NOTIF_ACTION"
const val KEY_AUDIO_FOCUS = "KEY_AUDIO_FOCUS"
const val KEY_PLUG_MANAGEMENT = "KEY_PLUG_MGT"
const val KEY_SONG_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE"
const val KEY_AT_END = "KEY_AT_END"
const val KEY_KEEP_SHUFFLE = "KEY_KEEP_SHUFFLE"
const val KEY_PREV_REWIND = "KEY_PREV_REWIND"
const val KEY_LIBRARY_SORT_MODE = "KEY_LIBRARY_SORT_MODE"
const val KEY_SEARCH_FILTER_MODE = "KEY_SEARCH"
const val KEY_SAVE_STATE = "KEY_SAVE_STATE"
const val KEY_BLACKLIST = "KEY_BLACKLIST"
@Deprecated("Use the new KEY_ACCENT instead.")
const val KEY_ACCENT_OLD = "KEY_ACCENT"
}
/** /**
* Values for some settings entries that arent important enough to recieve an enum. * Values for some settings entries that arent important enough to recieve an enum.
*/ */
object EntryValues { object EntryValues {
const val THEME_AUTO = "AUTO"
const val THEME_LIGHT = "LIGHT"
const val THEME_DARK = "DARK"
/** Pause and loop at the end. Similar to Spotify. */ /** Pause and loop at the end. Similar to Spotify. */
const val AT_END_LOOP_PAUSE = "LOOP_PAUSE" const val AT_END_LOOP_PAUSE = "LOOP_PAUSE"
@ -270,6 +187,29 @@ class SettingsManager private constructor(context: Context) :
} }
companion object { companion object {
const val KEY_THEME = "KEY_THEME2"
const val KEY_ACCENT = "KEY_ACCENT2"
const val KEY_LIB_DISPLAY_MODE = "KEY_LIB_MODE"
const val KEY_SHOW_COVERS = "KEY_SHOW_COVERS"
const val KEY_QUALITY_COVERS = "KEY_QUALITY_COVERS"
const val KEY_COLORIZE_NOTIFICATION = "KEY_COLOR_NOTIF"
const val KEY_USE_ALT_NOTIFICATION_ACTION = "KEY_ALT_NOTIF_ACTION"
const val KEY_AUDIO_FOCUS = "KEY_AUDIO_FOCUS"
const val KEY_PLUG_MANAGEMENT = "KEY_PLUG_MGT"
const val KEY_SONG_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2"
const val KEY_AT_END = "KEY_AT_END"
const val KEY_KEEP_SHUFFLE = "KEY_KEEP_SHUFFLE"
const val KEY_PREV_REWIND = "KEY_PREV_REWIND"
const val KEY_SAVE_STATE = "KEY_SAVE_STATE"
const val KEY_BLACKLIST = "KEY_BLACKLIST"
const val KEY_LIBRARY_SORT_MODE = "KEY_LIBRARY_SORT_MODE"
const val KEY_SEARCH_FILTER_MODE = "KEY_SEARCH_FILTER"
@Volatile @Volatile
private var INSTANCE: SettingsManager? = null private var INSTANCE: SettingsManager? = null

View file

@ -80,6 +80,7 @@ class AccentDialog : LifecycleDialog() {
} }
companion object { companion object {
const val TAG = "TAG_ACCENT_DIALOG"
const val KEY_PENDING_ACCENT = "AXKEY_PEND_ACCENT" const val KEY_PENDING_ACCENT = "AXKEY_PEND_ACCENT"
} }
} }

View file

@ -156,4 +156,8 @@ class BlacklistDialog : LifecycleDialog() {
private fun getRootPath(): String { private fun getRootPath(): String {
return Environment.getExternalStorageDirectory().absolutePath return Environment.getExternalStorageDirectory().absolutePath
} }
companion object {
const val TAG = "TAG_BLACKLIST_DIALOG"
}
} }

View file

@ -0,0 +1,21 @@
package org.oxycblt.auxio.settings.ui
import androidx.appcompat.app.AlertDialog
class IntListPrefDialog(private val pref: IntListPreference) : LifecycleDialog() {
override fun onConfigDialog(builder: AlertDialog.Builder) {
builder.setTitle(pref.title)
builder.setSingleChoiceItems(pref.entries, pref.getValueIndex()) { _, index ->
pref.setValue(pref.values[index])
dismiss()
}
builder.setNegativeButton(android.R.string.cancel, null)
}
companion object {
const val TAG = "TAG_INT_PREF_DIALOG"
}
}

View file

@ -0,0 +1,85 @@
package org.oxycblt.auxio.settings.ui
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import androidx.preference.DialogPreference
import org.oxycblt.auxio.R
import androidx.preference.R as prefR
class IntListPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = prefR.attr.dialogPreferenceStyle,
defStyleRes: Int = 0
) : DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
val entries: Array<CharSequence>
val values: IntArray
var currentValue: Int? = null
private set
private val default: Int
init {
val prefAttrs = context.obtainStyledAttributes(
attrs, R.styleable.IntListPreference, defStyleAttr, defStyleRes
)
entries = prefAttrs.getTextArray(R.styleable.IntListPreference_entries)
values = context.resources.getIntArray(
prefAttrs.getResourceId(R.styleable.IntListPreference_entryValues, -1)
)
default = prefAttrs.getInt(prefR.styleable.Preference_defaultValue, Int.MIN_VALUE)
prefAttrs.recycle()
summaryProvider = IntListSummaryProvider()
}
override fun onGetDefaultValue(a: TypedArray, index: Int) = a.getInt(index, -1)
override fun onSetInitialValue(defaultValue: Any?) {
super.onSetInitialValue(defaultValue)
if (defaultValue != null) {
// If were given a default value, we need to assign it.
setValue(defaultValue as Int)
} else {
currentValue = getPersistedInt(default)
}
}
fun getValueIndex(): Int {
val curValue = currentValue
if (curValue != null) {
return values.indexOf(curValue)
}
return -1
}
fun setValue(value: Int) {
if (value != currentValue) {
currentValue = value
callChangeListener(value)
persistInt(value)
notifyChanged()
}
}
private inner class IntListSummaryProvider : SummaryProvider<IntListPreference> {
override fun provideSummary(preference: IntListPreference?): CharSequence {
val index = getValueIndex()
if (index != -1) {
return entries[index]
}
return context.getString(prefR.string.not_set)
}
}
}

View file

@ -47,7 +47,7 @@ data class Accent(@ColorRes val color: Int, @StyleRes val theme: Int, @StringRes
/** /**
* Get a [ColorStateList] of the accent * Get a [ColorStateList] of the accent
*/ */
fun getStateList(context: Context): ColorStateList = color.toStateList(context) fun getStateList(context: Context) = color.toStateList(context)
/** /**
* Get the name (in bold) and the hex value of a accent. * Get the name (in bold) and the hex value of a accent.

View file

@ -62,7 +62,6 @@
<string name="setting_theme_day">Hell</string> <string name="setting_theme_day">Hell</string>
<string name="setting_theme_night">Dunkel</string> <string name="setting_theme_night">Dunkel</string>
<string name="setting_accent">Akzentfarbe</string> <string name="setting_accent">Akzentfarbe</string>
<string name="setting_accent_unknown">Unbekannte Akzentfarbe</string>
<string name="setting_display">Anzeige</string> <string name="setting_display">Anzeige</string>
<string name="setting_lib_display">Musikbibliothekitems</string> <string name="setting_lib_display">Musikbibliothekitems</string>
@ -97,7 +96,7 @@
<string name="setting_content_save">Wiedergabezustand abspeichern</string> <string name="setting_content_save">Wiedergabezustand abspeichern</string>
<string name="setting_content_save_desc">Der aktuell Wiedergabezustand jetzt abspeichern</string> <string name="setting_content_save_desc">Der aktuell Wiedergabezustand jetzt abspeichern</string>
<string name="setting_content_blacklist">Ausgeschlossene Ordner</string> <string name="setting_content_blacklist">Ausgeschlossene Ordner</string>
<string name="setting_content_blacklist_desc">Die Inhalte der ausgeschlossene Ordner sind versteckt von deinen Musikbibliothek</string> <string name="setting_content_blacklist_desc">Die Inhalte der ausgeschlossene Ordner sind versteckt von deiner Musikbibliothek</string>
<!-- Error Namespace | Error Labels --> <!-- Error Namespace | Error Labels -->
<string name="error_no_music">Keine Musik gefunden</string> <string name="error_no_music">Keine Musik gefunden</string>

View file

@ -6,11 +6,11 @@
<item>@string/setting_theme_night</item> <item>@string/setting_theme_night</item>
</string-array> </string-array>
<string-array name="values_theme" translatable="false"> <integer-array name="values_theme">
<item>AUTO</item> <item>@integer/theme_auto</item>
<item>LIGHT</item> <item>@integer/theme_light</item>
<item>DARK</item> <item>@integer/theme_dark</item>
</string-array> </integer-array>
<string-array name="entries_lib_display"> <string-array name="entries_lib_display">
<item>@string/label_genres</item> <item>@string/label_genres</item>
@ -18,11 +18,11 @@
<item>@string/label_albums</item> <item>@string/label_albums</item>
</string-array> </string-array>
<string-array name="values_lib_display" translatable="false"> <integer-array name="values_lib_display">
<item>SHOW_GENRES</item> <item>@integer/display_genre</item>
<item>SHOW_ARTISTS</item> <item>@integer/display_artist</item>
<item>SHOW_ALBUMS</item> <item>@integer/display_album</item>
</string-array> </integer-array>
<array name="entries_song_playback_mode"> <array name="entries_song_playback_mode">
<item>@string/label_play_all_songs</item> <item>@string/label_play_all_songs</item>
@ -31,11 +31,11 @@
<item>@string/label_play_genre</item> <item>@string/label_play_genre</item>
</array> </array>
<string-array name="values_song_playback_mode" translatable="false"> <string-array name="values_song_playback_mode">
<item>ALL_SONGS</item> <item>@integer/play_mode_songs</item>
<item>IN_ARTIST</item> <item>@integer/play_mode_artist</item>
<item>IN_ALBUM</item> <item>@integer/play_mode_album</item>
<item>IN_GENRE</item> <item>@integer/play_mode_genre</item>
</string-array> </string-array>
<string-array name="entries_at_end"> <string-array name="entries_at_end">
@ -44,7 +44,7 @@
<item>@string/setting_behavior_end_stop</item> <item>@string/setting_behavior_end_stop</item>
</string-array> </string-array>
<string-array name="values_at_end" translatable="false"> <string-array name="values_at_end">
<item>LOOP_PAUSE</item> <item>LOOP_PAUSE</item>
<item>LOOP</item> <item>LOOP</item>
<item>STOP</item> <item>STOP</item>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="theme_auto">-1</integer>
<integer name="theme_light">1</integer>
<integer name="theme_dark">2</integer>
<integer name="display_genre">0xA108</integer>
<integer name="display_artist">0xA109</integer>
<integer name="display_album">0xA10A</integer>
<integer name="play_mode_genre">0xA103</integer>
<integer name="play_mode_artist">0xA104</integer>
<integer name="play_mode_album">0xA105</integer>
<integer name="play_mode_songs">0xA106</integer>
</resources>

View file

@ -63,7 +63,6 @@
<string name="setting_theme_day">Light</string> <string name="setting_theme_day">Light</string>
<string name="setting_theme_night">Dark</string> <string name="setting_theme_night">Dark</string>
<string name="setting_accent">Accent</string> <string name="setting_accent">Accent</string>
<string name="setting_accent_unknown">Unknown Accent</string>
<string name="setting_display">Display</string> <string name="setting_display">Display</string>
<string name="setting_lib_display">Library Items</string> <string name="setting_lib_display">Library Items</string>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="IntListPreference">
<attr name="entries" format="reference" />
<attr name="entryValues" format="reference" />
</declare-styleable>
</resources>

View file

@ -179,7 +179,7 @@
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:clickable">true</item> <item name="android:clickable">true</item>
<item name="android:focusable">true</item> <item name="android:focusable">true</item>
<item name="android:theme">@style/Theme.MaterialComponents</item> <item name="android:theme">@style/Theme.MaterialComponents.DayNight</item>
<item name="rippleColor">@color/selection_color</item> <item name="rippleColor">@color/selection_color</item>
<item name="fontFamily">@font/inter_semibold</item> <item name="fontFamily">@font/inter_semibold</item>
<item name="textAllCaps">false</item> <item name="textAllCaps">false</item>

View file

@ -1,148 +1,150 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:tools="http://schemas.android.com/tools">
<PreferenceCategory <PreferenceCategory
android:layout="@layout/item_header" app:layout="@layout/item_header"
android:title="@string/setting_ui"> app:title="@string/setting_ui">
<ListPreference
android:entries="@array/entires_theme" <org.oxycblt.auxio.settings.ui.IntListPreference
android:entryValues="@array/values_theme" app:defaultValue="@integer/theme_auto"
android:icon="@drawable/ic_day" app:entries="@array/entires_theme"
android:title="@string/setting_theme" app:entryValues="@array/values_theme"
app:defaultValue="AUTO" app:iconSpaceReserved="false"
app:key="KEY_THEME" app:key="KEY_THEME2"
app:useSimpleSummaryProvider="true" /> app:icon="@drawable/ic_day"
app:title="@string/setting_theme" />
<Preference <Preference
android:icon="@drawable/ic_accent"
android:title="@string/setting_accent"
app:allowDividerBelow="false" app:allowDividerBelow="false"
app:icon="@drawable/ic_accent"
app:key="KEY_ACCENT2" app:key="KEY_ACCENT2"
app:summary="@string/setting_accent_unknown" /> app:summary="@string/color_label_blue"
app:title="@string/setting_accent" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:layout="@layout/item_header" app:layout="@layout/item_header"
android:title="@string/setting_display"> app:title="@string/setting_display">
<ListPreference <org.oxycblt.auxio.settings.ui.IntListPreference
android:icon="@drawable/ic_artist" app:defaultValue="@integer/display_artist"
android:title="@string/setting_lib_display"
app:defaultValue="SHOW_ARTISTS"
app:entries="@array/entries_lib_display" app:entries="@array/entries_lib_display"
app:entryValues="@array/values_lib_display" app:entryValues="@array/values_lib_display"
app:key="KEY_LIBRARY_DISPLAY_MODE" app:icon="@drawable/ic_artist"
app:key="KEY_LIB_MODE"
app:title="@string/setting_lib_display"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:title="@string/setting_show_covers"
app:defaultValue="true" app:defaultValue="true"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_SHOW_COVERS" app:key="KEY_SHOW_COVERS"
app:summary="@string/setting_show_covers_desc" /> app:summary="@string/setting_show_covers_desc"
app:title="@string/setting_show_covers" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:title="@string/setting_quality_covers"
app:defaultValue="false" app:defaultValue="false"
app:dependency="KEY_SHOW_COVERS" app:dependency="KEY_SHOW_COVERS"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_QUALITY_COVERS" app:key="KEY_QUALITY_COVERS"
app:summary="@string/setting_quality_covers_desc" /> app:summary="@string/setting_quality_covers_desc"
app:title="@string/setting_quality_covers" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:title="@string/setting_color_notif"
app:defaultValue="true" app:defaultValue="true"
app:dependency="KEY_SHOW_COVERS" app:dependency="KEY_SHOW_COVERS"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_COLOR_NOTIF" app:key="KEY_COLOR_NOTIF"
app:summary="@string/setting_color_desc" /> app:summary="@string/setting_color_desc"
app:title="@string/setting_color_notif" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:title="@string/setting_use_alt_action"
app:allowDividerBelow="false" app:allowDividerBelow="false"
app:defaultValue="false" app:defaultValue="false"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_ALT_NOTIF_ACTION" app:key="KEY_ALT_NOTIF_ACTION"
app:summaryOff="@string/setting_use_alt_loop" app:summaryOff="@string/setting_use_alt_loop"
app:summaryOn="@string/setting_use_alt_shuffle" /> app:summaryOn="@string/setting_use_alt_shuffle"
app:title="@string/setting_use_alt_action" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:layout="@layout/item_header" app:layout="@layout/item_header"
android:title="@string/setting_audio"> app:title="@string/setting_audio">
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:title="@string/setting_audio_focus"
app:defaultValue="true" app:defaultValue="true"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_AUDIO_FOCUS" app:key="KEY_AUDIO_FOCUS"
app:summary="@string/setting_audio_focus_desc" /> app:summary="@string/setting_audio_focus_desc"
app:title="@string/setting_audio_focus" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:title="@string/setting_audio_plug_mgt"
app:allowDividerBelow="false" app:allowDividerBelow="false"
app:defaultValue="true" app:defaultValue="true"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_PLUG_MGT" app:key="KEY_PLUG_MGT"
app:summary="@string/setting_audio_plug_mgt_desc" /> app:summary="@string/setting_audio_plug_mgt_desc"
app:title="@string/setting_audio_plug_mgt" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:layout="@layout/item_header" app:layout="@layout/item_header"
android:title="@string/setting_behavior"> app:title="@string/setting_behavior">
<ListPreference <org.oxycblt.auxio.settings.ui.IntListPreference
android:title="@string/setting_behavior_song_playback_mode" app:defaultValue="@integer/play_mode_songs"
app:defaultValue="ALL_SONGS"
app:entries="@array/entries_song_playback_mode" app:entries="@array/entries_song_playback_mode"
app:entryValues="@array/values_song_playback_mode" app:entryValues="@array/values_song_playback_mode"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_SONG_PLAY_MODE" app:key="KEY_SONG_PLAY_MODE2"
app:title="@string/setting_behavior_song_playback_mode"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<ListPreference <ListPreference
android:title="@string/setting_behavior_at_end"
app:defaultValue="LOOP_PAUSE" app:defaultValue="LOOP_PAUSE"
app:entries="@array/entries_at_end" app:entries="@array/entries_at_end"
app:entryValues="@array/values_at_end" app:entryValues="@array/values_at_end"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_AT_END" app:key="KEY_AT_END"
app:title="@string/setting_behavior_at_end"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:title="@string/setting_behavior_keep_shuffle"
app:defaultValue="true" app:defaultValue="true"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_KEEP_SHUFFLE" app:key="KEY_KEEP_SHUFFLE"
app:summary="@string/setting_behavior_keep_shuffle_desc" /> app:summary="@string/setting_behavior_keep_shuffle_desc"
app:title="@string/setting_behavior_keep_shuffle" />
<SwitchPreference <SwitchPreference
android:title="@string/setting_behavior_rewind_prev"
app:allowDividerBelow="false" app:allowDividerBelow="false"
app:defaultValue="true" app:defaultValue="true"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_PREV_REWIND" app:key="KEY_PREV_REWIND"
app:summary="@string/setting_behavior_rewind_prev_desc" /> app:summary="@string/setting_behavior_rewind_prev_desc"
app:title="@string/setting_behavior_rewind_prev" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:layout="@layout/item_header" app:layout="@layout/item_header"
android:title="@string/setting_content"> app:title="@string/setting_content">
<Preference <Preference
android:title="@string/setting_content_save"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_SAVE_STATE" app:key="KEY_SAVE_STATE"
app:summary="@string/setting_content_save_desc" /> app:summary="@string/setting_content_save_desc"
app:title="@string/setting_content_save" />
<Preference <Preference
android:title="@string/setting_content_blacklist"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="KEY_BLACKLIST" app:key="KEY_BLACKLIST"
app:summary="@string/setting_content_blacklist_desc" /> app:summary="@string/setting_content_blacklist_desc"
app:title="@string/setting_content_blacklist" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View file

@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = "1.4.31" ext.kotlin_version = "1.4.32"
ext.navigation_version = "2.3.4" ext.navigation_version = "2.3.4"
repositories { repositories {

View file

@ -29,6 +29,57 @@ When creating a ViewHolder for a `RecyclerView`, one should use `BaseViewHolder`
Data is often bound using Binding Adapters, which are XML attributes assigned in layout files that can automatically display data, usually written as `app:bindingAdapterName="@{data}"`. Its recommended to use these instead of duplicating code manually. Data is often bound using Binding Adapters, which are XML attributes assigned in layout files that can automatically display data, usually written as `app:bindingAdapterName="@{data}"`. Its recommended to use these instead of duplicating code manually.
#### Integers
Integer representations of data/ui elements are used heavily in Auxio.
To prevent any strange bugs, all integer representations must be unique. A table of all current integers used are shown below:
```
0xA0XX | UI Integer Space [Required by android]
0xA000 | SongViewHolder
0xA001 | AlbumViewHolder
0xA002 | ArtistViewHolder
0xA003 | GenreViewHolder
0xA004 | HeaderViewHolder
0xA005 | QueueSongViewHolder
0xA006 | UserQueueHeaderViewHolder
0xA007 | AlbumHeaderViewHolder
0xA008 | AlbumSongViewHolder
0xA009 | ArtistHeaderViewHolder
0xA00A | ArtistAlbumViewHolder
0xA00B | GenreHeaderViewHolder
0xA00C | GenreSongViewHolder
0xA0A0 | Auxio notification code
0xA0C0 | Auxio request code
0xA1XX | Data Integer Space [Stored for IO efficency]
0xA100 | LoopMode.NONE
0xA101 | LoopMode.ONCE
0xA102 | LoopMode.INFINITE
0xA103 | PlaybackMode.IN_GENRE
0xA104 | PlaybackMode.IN_ARTIST
0xA105 | PlaybackMode.IN_ALBUM
0xA106 | PlaybackMode.ALL_SONGS
0xA107 | DisplayMode.SHOW_ALL
0xA108 | DisplayMode.SHOW_GENRES
0xA109 | DisplayMode.SHOW_ARTISTS
0xA10A | DisplayMode.SHOW_ALBUMS
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
Auxio's package structure is mostly based around the features, and then any sub-features or components involved with that. There are some shared packages however. A diagram of the package structure is shown below: Auxio's package structure is mostly based around the features, and then any sub-features or components involved with that. There are some shared packages however. A diagram of the package structure is shown below:
@ -51,7 +102,8 @@ org.oxycblt.auxio # Main UI's and logging utilities
├──.search # Search UI ├──.search # Search UI
├──.settings # Settings UI and systems ├──.settings # Settings UI and systems
│ ├──.blacklist # Excluded Directories UI/Systems │ ├──.blacklist # Excluded Directories UI/Systems
│ └──.ui # Contains UI's related to the settings view, such as the about screen │ ├──.accent # Accent UI + Systems
│ └──.ui # Settings-Related UIs
├──.songs # Songs UI ├──.songs # Songs UI
└──.ui # Shared user interface utilities └──.ui # Shared user interface utilities
``` ```
@ -120,7 +172,7 @@ Shared RecyclerView utilities, often for adapters and ViewHolders. Important one
#### `.settings` #### `.settings`
The settings system is primarily based off of `SettingsManager`, a wrapper around `SharedPreferences`. This allows settings to be read/written in a much simpler/safer manner and without a context being needed. The Settings UI is largely contained in `SettingsListFragment`, while the `.ui` sub-package contains UIs related to the settings UI, such as the About Dialog. The settings system is primarily based off of `SettingsManager`, a wrapper around `SharedPreferences`. This allows settings to be read/written in a much simpler/safer manner and without a context being needed. The Settings UI is largely contained in `SettingsListFragment`, while the sub-packages contain sub-uis related to the `SettingsListFragment`, such as the custom list preference `IntListPreference` and the about dialog.
#### `.search` #### `.search`