Refactor settings management

Heavily refactor the structure of settings to fix some bugs.
This commit is contained in:
OxygenCobalt 2020-12-03 18:35:47 -07:00
parent 21dbad7091
commit b8b6e8421b
14 changed files with 148 additions and 223 deletions

View file

@ -7,14 +7,12 @@ import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.WindowInsets
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.databinding.DataBindingUtil
import org.oxycblt.auxio.databinding.ActivityMainBinding
import org.oxycblt.auxio.playback.PlaybackService
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.SettingsViewModel
import org.oxycblt.auxio.ui.accent
import org.oxycblt.auxio.ui.handleTransparentSystemBars
import org.oxycblt.auxio.ui.toColor
@ -24,8 +22,6 @@ import org.oxycblt.auxio.ui.toColor
// TODO: Landscape UI layouts
// FIXME: Compat issue with Versions 5 that leads to progress bar looking off
class MainActivity : AppCompatActivity() {
private val settingsModel: SettingsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -37,42 +33,16 @@ class MainActivity : AppCompatActivity() {
val settingsManager = SettingsManager.init(applicationContext)
AppCompatDelegate.setDefaultNightMode(
settingsManager.getTheme()
)
AppCompatDelegate.setDefaultNightMode(settingsManager.theme)
accent = settingsManager.getAccent()
accent = settingsManager.accent
// Apply the theme
setTheme(accent.second)
if (settingsManager.getEdgeToEdge() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
if (settingsManager.edgeEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
doEdgeToEdgeSetup(binding)
}
// --- VIEWMODEL SETUP ---
settingsModel.theme.observe(this) {
if (it != null) {
doThemeRecreate(it)
}
}
settingsModel.accent.observe(this) {
if (it != null) {
recreate()
settingsModel.doneWithAccentUpdate()
}
}
settingsModel.edge.observe(this) {
if (it != null) {
recreate()
settingsModel.doneWithEdgeUpdate()
}
}
}
override fun onStart() {
@ -88,8 +58,7 @@ class MainActivity : AppCompatActivity() {
window?.apply {
statusBarColor = Color.TRANSPARENT
// Use a heavily transparent scrim on the nav bar as otherwise the transparency wont
// work.
// Use a heavily transparent scrim on the nav bar as full transparency is borked
navigationBarColor = R.color.nav_color.toColor(this@MainActivity)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

View file

@ -39,12 +39,13 @@ class LibraryViewModel : ViewModel() {
private var mSearchHasFocus = false
val searchHasFocus: Boolean get() = mSearchHasFocus
init {
val prefsManager = SettingsManager.getInstance()
private val settingsManager = SettingsManager.getInstance()
init {
// The SortMode isn't really that urgent, so throw it into a coroutine because I can
viewModelScope.launch {
mSortMode.value = withContext(Dispatchers.IO) {
prefsManager.getLibrarySortMode()
settingsManager.librarySortMode
}
}
}
@ -133,13 +134,7 @@ class LibraryViewModel : ViewModel() {
if (mode != mSortMode.value) {
mSortMode.value = mode
viewModelScope.launch {
withContext(Dispatchers.IO) {
val prefsManager = SettingsManager.getInstance()
prefsManager.setLibrarySortMode(mSortMode.value!!)
}
}
settingsManager.librarySortMode = mode
}
}
}

View file

@ -0,0 +1,30 @@
package org.oxycblt.auxio.settings
import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatDelegate
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.
*/
fun String.toThemeInt(): Int {
return when (this) {
SettingsManager.EntryNames.THEME_AUTO -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
SettingsManager.EntryNames.THEME_LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
SettingsManager.EntryNames.THEME_DARK -> AppCompatDelegate.MODE_NIGHT_YES
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
}
@DrawableRes
fun Int.toThemeIcon(): Int {
return when (this) {
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -> R.drawable.ic_auto
AppCompatDelegate.MODE_NIGHT_NO -> R.drawable.ic_day
AppCompatDelegate.MODE_NIGHT_YES -> R.drawable.ic_night
else -> R.drawable.ic_auto
}
}

View file

@ -6,9 +6,10 @@ import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.fragment.app.activityViewModels
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.children
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog
@ -20,26 +21,48 @@ import org.oxycblt.auxio.ui.accent
import org.oxycblt.auxio.ui.getDetailedAccentSummary
class SettingsListFragment : PreferenceFragmentCompat() {
private val settingsModel: SettingsViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// --- PREFERENCE ITEM SETUP ---
val themePref = findPreference<Preference>(SettingsManager.Keys.KEY_THEME)?.apply {
setIcon(
when (AppCompatDelegate.getDefaultNightMode()) {
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -> R.drawable.ic_auto
AppCompatDelegate.MODE_NIGHT_NO -> R.drawable.ic_day
AppCompatDelegate.MODE_NIGHT_YES -> R.drawable.ic_night
else -> R.drawable.ic_auto
}
)
preferenceScreen.children.forEach {
recursivelyHandleChildren(it)
}
val accentPref = findPreference<Preference>(SettingsManager.Keys.KEY_ACCENT)?.apply {
Log.d(this::class.simpleName, "Fragment created.")
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.prefs_main, rootKey)
}
private fun recursivelyHandleChildren(pref: Preference) {
if (pref is PreferenceCategory) {
pref.children.forEach {
recursivelyHandleChildren(it)
}
} else {
handlePreference(pref)
}
}
private fun handlePreference(it: Preference) {
it.apply {
when (it.key) {
SettingsManager.Keys.KEY_THEME -> {
setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon())
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value ->
AppCompatDelegate.setDefaultNightMode((value as String).toThemeInt())
setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon())
true
}
}
SettingsManager.Keys.KEY_ACCENT -> {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
showAccentDialog()
true
@ -48,35 +71,15 @@ class SettingsListFragment : PreferenceFragmentCompat() {
summary = getDetailedAccentSummary(requireActivity(), accent)
}
// --- VIEWMODEL SETUP ---
SettingsManager.Keys.KEY_EDGE_TO_EDGE -> {
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ ->
requireActivity().recreate()
settingsModel.theme.observe(viewLifecycleOwner) {
if (it != null) {
themePref?.setIcon(
when (it) {
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -> R.drawable.ic_auto
AppCompatDelegate.MODE_NIGHT_NO -> R.drawable.ic_day
AppCompatDelegate.MODE_NIGHT_YES -> R.drawable.ic_night
else -> R.drawable.ic_auto
}
)
settingsModel.doneWithThemeUpdate()
true
}
}
settingsModel.accent.observe(viewLifecycleOwner) {
if (it != null) {
accentPref?.summary = getDetailedAccentSummary(requireActivity(), it)
}
}
Log.d(this::class.simpleName, "Fragment created.")
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.prefs_main, rootKey)
}
private fun showAccentDialog() {
@ -89,7 +92,9 @@ class SettingsListFragment : PreferenceFragmentCompat() {
val recycler = RecyclerView(requireContext()).apply {
adapter = AccentAdapter {
if (it.first != accent.first) {
SettingsManager.getInstance().setAccent(it)
SettingsManager.getInstance().accent = it
requireActivity().recreate()
}
this@show.dismiss()

View file

@ -19,6 +19,51 @@ class SettingsManager private constructor(context: Context) :
sharedPrefs.registerOnSharedPreferenceChangeListener(this)
}
val theme: Int
get() {
return sharedPrefs.getString(Keys.KEY_THEME, EntryNames.THEME_AUTO)?.toThemeInt()
?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
var accent: Pair<Int, Int>
get() {
val accentIndex = sharedPrefs.getInt(Keys.KEY_ACCENT, 5)
// Accent is stored as an index [to be efficient], so retrieve it when done.
return ACCENTS[accentIndex]
}
set(value) {
val accentIndex = ACCENTS.indexOf(value)
check(accentIndex != -1) { "Invalid accent" }
sharedPrefs.edit()
.putInt(Keys.KEY_ACCENT, accentIndex)
.apply()
}
val edgeEnabled: Boolean
get() {
return sharedPrefs.getBoolean(Keys.KEY_EDGE_TO_EDGE, false)
}
var librarySortMode: SortMode
get() {
return SortMode.fromInt(
sharedPrefs.getInt(
Keys.KEY_LIBRARY_SORT_MODE,
SortMode.CONSTANT_ALPHA_DOWN
)
) ?: SortMode.ALPHA_DOWN
}
set(value) {
sharedPrefs.edit()
.putInt(Keys.KEY_LIBRARY_SORT_MODE, value.toInt())
.apply()
}
// --- CALLBACKS ---
private val callbacks = mutableListOf<Callback>()
fun addCallback(callback: Callback) {
@ -29,74 +74,9 @@ class SettingsManager private constructor(context: Context) :
callbacks.remove(callback)
}
fun getTheme(): Int {
// Turn the string from SharedPreferences into an actual theme value that can
// be used, as apparently the preference system provided by androidx doesn't like integers
// for some...reason.
return when (sharedPrefs.getString(Keys.KEY_THEME, Theme.THEME_AUTO)) {
Theme.THEME_AUTO -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
Theme.THEME_LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
Theme.THEME_DARK -> AppCompatDelegate.MODE_NIGHT_YES
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
}
fun getAccent(): Pair<Int, Int> {
val accentIndex = sharedPrefs.getInt(Keys.KEY_ACCENT, 5)
return ACCENTS[accentIndex]
}
fun setAccent(accent: Pair<Int, Int>) {
val accentIndex = ACCENTS.indexOf(accent)
check(accentIndex != -1) { "Invalid accent" }
sharedPrefs.edit()
.putInt(Keys.KEY_ACCENT, accentIndex)
.apply()
callbacks.forEach {
it.onAccentUpdate(getAccent())
}
}
fun getEdgeToEdge(): Boolean {
return sharedPrefs.getBoolean(Keys.KEY_EDGE_TO_EDGE, false)
}
fun setLibrarySortMode(sortMode: SortMode) {
sharedPrefs.edit()
.putInt(Keys.KEY_LIBRARY_SORT_MODE, sortMode.toInt())
.apply()
}
fun getLibrarySortMode(): SortMode {
return SortMode.fromInt(
sharedPrefs.getInt(
Keys.KEY_LIBRARY_SORT_MODE,
SortMode.CONSTANT_ALPHA_DOWN
)
) ?: SortMode.ALPHA_DOWN
}
// --- OVERRIDES ---
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
Keys.KEY_THEME -> {
callbacks.forEach {
it.onThemeUpdate(getTheme())
}
}
Keys.KEY_EDGE_TO_EDGE -> {
callbacks.forEach {
it.onEdgeToEdgeUpdate(getEdgeToEdge())
}
}
}
}
companion object {
@ -135,7 +115,7 @@ class SettingsManager private constructor(context: Context) :
const val KEY_EDGE_TO_EDGE = "KEY_EDGE"
}
private object Theme {
object EntryNames {
const val THEME_AUTO = "AUTO"
const val THEME_LIGHT = "LIGHT"
const val THEME_DARK = "DARK"
@ -145,9 +125,5 @@ class SettingsManager private constructor(context: Context) :
* An interface for receiving some settings updates.
* [SharedPreferences.OnSharedPreferenceChangeListener].
*/
interface Callback {
fun onThemeUpdate(newTheme: Int) {}
fun onAccentUpdate(newAccent: Pair<Int, Int>) {}
fun onEdgeToEdgeUpdate(isEdgeToEdge: Boolean) {}
}
interface Callback
}

View file

@ -1,58 +0,0 @@
package org.oxycblt.auxio.settings
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
/**
* A [ViewModel] that provides a better interface for observing settings updates compared to
* [SettingsManager.Callback] or the default SharedPreferences listener.
* // TODO: Roll values & updates into this viewmodel
* @author OxygenCobalt
*/
class SettingsViewModel : ViewModel(), SettingsManager.Callback {
private val mTheme = MutableLiveData<Int?>()
val theme: LiveData<Int?> get() = mTheme
private val mAccent = MutableLiveData<Pair<Int, Int>?>()
val accent: LiveData<Pair<Int, Int>?> get() = mAccent
private val mEdge = MutableLiveData<Boolean?>()
val edge: LiveData<Boolean?> = mEdge
private val settingsManager = SettingsManager.getInstance()
init {
settingsManager.addCallback(this)
}
fun doneWithThemeUpdate() {
mTheme.value = null
}
fun doneWithAccentUpdate() {
mAccent.value = null
}
fun doneWithEdgeUpdate() {
mEdge.value = null
}
override fun onThemeUpdate(newTheme: Int) {
mTheme.value = newTheme
}
override fun onAccentUpdate(newAccent: Pair<Int, Int>) {
mAccent.value = newAccent
}
override fun onEdgeToEdgeUpdate(isEdgeToEdge: Boolean) {
mEdge.value = isEdgeToEdge
}
override fun onCleared() {
super.onCleared()
settingsManager.removeCallback(this)
}
}

View file

@ -27,6 +27,7 @@
<androidx.appcompat.widget.Toolbar
android:id="@+id/album_toolbar"
style="@style/Toolbar.Style.Icon"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:menu="@menu/menu_album_actions"
app:title="@string/label_library" />

View file

@ -27,6 +27,7 @@
<androidx.appcompat.widget.Toolbar
android:id="@+id/artist_toolbar"
style="@style/Toolbar.Style.Icon"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:menu="@menu/menu_detail"
app:title="@string/label_library" />

View file

@ -27,6 +27,7 @@
<androidx.appcompat.widget.Toolbar
android:id="@+id/genre_toolbar"
style="@style/Toolbar.Style.Icon"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:menu="@menu/menu_detail"
app:title="@string/label_library" />

View file

@ -14,6 +14,8 @@
android:id="@+id/library_toolbar"
style="@style/Toolbar.Style"
android:theme="@style/Toolbar.Style.Search"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:menu="@menu/menu_library"
app:title="@string/label_library" />

View file

@ -14,6 +14,7 @@
<androidx.appcompat.widget.Toolbar
android:id="@+id/queue_toolbar"
style="@style/Toolbar.Style.Icon"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:navigationIcon="@drawable/ic_down"
app:title="@string/label_queue" />

View file

@ -10,6 +10,7 @@
<androidx.appcompat.widget.Toolbar
android:id="@+id/song_toolbar"
style="@style/Toolbar.Style"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:title="@string/setting_title" />

View file

@ -13,6 +13,7 @@
<androidx.appcompat.widget.Toolbar
android:id="@+id/song_toolbar"
style="@style/Toolbar.Style"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"

View file

@ -47,7 +47,7 @@
<item name="colorControlHighlight">@color/selection_color</item>
</style>
<!-- Toolbar Title Theme -->
<!-- Toolbar Title EntryNames -->
<style name="TextAppearance.Toolbar.Header" parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
<item name="android:fontFamily">@font/inter_black</item>
<item name="android:textColor">?attr/colorPrimary</item>
@ -73,7 +73,7 @@
<item name="cornerRadius">0dp</item>
</style>
<!-- Custom Dialog Theme -->
<!-- Custom Dialog EntryNames -->
<style name="Theme.CustomDialog" parent="Theme.MaterialComponents.DayNight.Dialog">
<item name="colorBackgroundFloating">@color/background</item>
<item name="android:windowTitleStyle">@style/TextAppearance.Dialog.Title</item>