Add accent customization

Add the ability to customize the accent.
This commit is contained in:
OxygenCobalt 2020-11-28 20:25:41 -07:00
parent 2dc7ba3420
commit a5f59858bd
15 changed files with 272 additions and 19 deletions

View file

@ -94,6 +94,8 @@ dependencies {
// Fast-Scroll
implementation 'com.reddit:indicator-fast-scroll:1.3.0'
implementation 'com.afollestad.material-dialogs:core:3.3.0'
// --- DEV ---
// Lint

View file

@ -21,3 +21,5 @@
#-renamesourcefileattribute SourceFile
-dontobfuscate
-keep class org.oxycblt.auxio.settings.SettingListFragment

View file

@ -22,6 +22,8 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), SettingsManager.
settingsManager.getTheme()
)
accent = settingsManager.getAccent()
// Apply the theme
setTheme(accent.second)
@ -50,7 +52,11 @@ class MainActivity : AppCompatActivity(R.layout.activity_main), SettingsManager.
SettingsManager.getInstance().removeCallback(this)
}
override fun onThemeUpdate(value: Int) {
AppCompatDelegate.setDefaultNightMode(value)
override fun onThemeUpdate(newTheme: Int) {
AppCompatDelegate.setDefaultNightMode(newTheme)
}
override fun onAccentUpdate(newAccent: Pair<Int, Int>) {
recreate()
}
}

View file

@ -1,11 +1,99 @@
package org.oxycblt.auxio.settings
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.settings.adapters.AccentAdapter
import org.oxycblt.auxio.ui.ACCENTS
import org.oxycblt.auxio.ui.accent
import org.oxycblt.auxio.ui.getAccentItemSummary
class SettingListFragment : PreferenceFragmentCompat(), SettingsManager.Callback {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
findPreference<Preference>(SettingsManager.Keys.KEY_ACCENT)?.apply {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
showAccentDialog()
true
}
summary = getAccentItemSummary(requireActivity(), accent)
}
Log.d(this::class.simpleName, "Fragment created.")
}
class SettingListFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.prefs_main, rootKey)
}
override fun onResume() {
super.onResume()
SettingsManager.getInstance().addCallback(this)
}
override fun onPause() {
super.onPause()
SettingsManager.getInstance().removeCallback(this)
}
private fun showAccentDialog() {
MaterialDialog(requireActivity()).show {
title(R.string.label_settings_accent)
// Roll my own RecyclerView since [To no surprise whatsoever] Material Dialogs
// has a bug where ugly dividers will show with the RecyclerView even if you disable them.
// This is why I hate using third party libraries.
val recycler = RecyclerView(requireContext()).apply {
adapter = AccentAdapter {
if (it.first != accent.first) {
SettingsManager.getInstance().setAccent(it)
}
this@show.dismiss()
}
post {
// Combine the width of the recyclerview with the width of an item in order
// to center the currently selected accent.
val childWidth = getChildAt(0).width / 2
(layoutManager as LinearLayoutManager)
.scrollToPositionWithOffset(
ACCENTS.indexOf(accent),
(width / 2) - childWidth
)
}
layoutManager = LinearLayoutManager(
requireContext()
).also { it.orientation = LinearLayoutManager.HORIZONTAL }
}
customView(view = recycler)
view.invalidateDividers(showTop = false, showBottom = false)
negativeButton(android.R.string.cancel)
show()
}
}
override fun onAccentUpdate(newAccent: Pair<Int, Int>) {
findPreference<Preference>(getString(R.string.label_settings_accent))?.apply {
summary = getAccentItemSummary(requireActivity(), accent)
}
}
}

View file

@ -5,6 +5,7 @@ import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager
import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.ui.ACCENTS
/**
* Wrapper around the [SharedPreferences] class that writes & reads values without a context.
@ -30,6 +31,39 @@ class SettingsManager private constructor(context: Context) : SharedPreferences.
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 setLibrarySortMode(sortMode: SortMode) {
sharedPrefs.edit()
.putInt(Keys.KEY_LIBRARY_SORT_MODE, sortMode.toConstant())
@ -45,18 +79,7 @@ class SettingsManager private constructor(context: Context) : SharedPreferences.
) ?: SortMode.ALPHA_DOWN
}
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
}
}
// --- OVERRIDES ---
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
@ -100,6 +123,7 @@ class SettingsManager private constructor(context: Context) : SharedPreferences.
object Keys {
const val KEY_LIBRARY_SORT_MODE = "KEY_LIBRARY_SORT_MODE"
const val KEY_THEME = "KEY_THEME"
const val KEY_ACCENT = "KEY_ACCENT"
}
private object Theme {
@ -113,6 +137,7 @@ class SettingsManager private constructor(context: Context) : SharedPreferences.
* [SharedPreferences.OnSharedPreferenceChangeListener].
*/
interface Callback {
fun onThemeUpdate(value: Int) {}
fun onThemeUpdate(newTheme: Int) {}
fun onAccentUpdate(newAccent: Pair<Int, Int>) {}
}
}

View file

@ -0,0 +1,54 @@
package org.oxycblt.auxio.settings.adapters
import android.content.res.ColorStateList
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemAccentBinding
import org.oxycblt.auxio.ui.ACCENTS
import org.oxycblt.auxio.ui.accent
import org.oxycblt.auxio.ui.toColor
class AccentAdapter(
private val doOnAccentConfirm: (accent: Pair<Int, Int>) -> Unit
) : RecyclerView.Adapter<AccentAdapter.ViewHolder>() {
override fun getItemCount(): Int = ACCENTS.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ItemAccentBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(ACCENTS[position])
}
inner class ViewHolder(
private val binding: ItemAccentBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(accentData: Pair<Int, Int>) {
binding.accent.setOnClickListener {
Log.d(this::class.simpleName, accentData.toString())
doOnAccentConfirm(accentData)
}
binding.accent.apply {
imageTintList = if (accentData.first != accent.first) {
ColorStateList.valueOf(
android.R.color.transparent.toColor(context)
)
} else {
ColorStateList.valueOf(
R.color.background.toColor(context)
)
}
backgroundTintList = ColorStateList.valueOf(
accentData.first.toColor(context)
)
}
}
}
}

View file

@ -8,11 +8,12 @@ import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import org.oxycblt.auxio.R
import java.util.Locale
// Functions for managing colors/accents/whatever.
// Pairs of the base accent and its theme
private val ACCENTS = listOf(
val ACCENTS = listOf(
Pair(R.color.red, R.style.Theme_Red), // 0
Pair(R.color.pink, R.style.Theme_Pink), // 1
Pair(R.color.purple, R.style.Theme_Purple), // 2
@ -34,7 +35,7 @@ private val ACCENTS = listOf(
Pair(R.color.blue_grey, R.style.Theme_BlueGrey) // 18
)
val accent = ACCENTS[5]
lateinit var accent: Pair<Int, Int>
/**
* Gets the transparent form of a color.
@ -96,3 +97,8 @@ fun resolveAttr(context: Context, @AttrRes attr: Int): Int {
return color.toColor(context)
}
fun getAccentItemSummary(context: Context, newAccent: Pair<Int, Int>): String {
return context.resources.getResourceEntryName(newAccent.first)
.replace("_", " ").capitalize(Locale.getDefault())
}

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z" />
</vector>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/background">
<path
android:fillColor="@android:color/white"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="oval"
xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:width="48dp"
android:height="48dp" />
<solid
android:color="@android:color/white" />
</shape>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/margin_mid_small">
<ImageButton
android:id="@+id/accent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/margin_medium"
android:scaleType="fitCenter"
android:background="@drawable/ui_circular_button"
android:src="@drawable/ic_check"
tools:backgroundTint="?attr/colorPrimary"
tools:ignore="contentDescription" />
</FrameLayout>
</layout>

View file

@ -5,6 +5,7 @@
<item>@string/label_settings_theme_light</item>
<item>@string/label_settings_theme_dark</item>
</array>
<array name="theme_values">
<item>AUTO</item>
<item>LIGHT</item>

View file

@ -34,6 +34,7 @@
<string name="label_settings_theme_light">Light</string>
<string name="label_settings_theme_dark">Dark</string>
<string name="label_settings_theme_choose">Choose theme</string>
<string name="label_settings_accent">Accent</string>
<!-- Debug Namespace | Debug labels -->
<string name="debug_state_saved">State saved</string>

View file

@ -13,6 +13,12 @@
<item name="colorControlNormal">@color/control_color</item>
<item name="alertDialogTheme">@style/Theme.CustomDialog</item>
<item name="indicatorFastScrollerStyle">@style/FastScrollTheme</item>
<item name="md_divider_color">@android:color/transparent</item>
<item name="md_background_color">@color/background</item>
<item name="md_corner_radius">0dp</item>
<item name="md_color_button_text">@color/control_color</item>
<item name="md_font_title">@font/inter_black</item>
</style>
<!-- Hack to fix the weird icon/underline with LibraryFragment's SearchView -->
@ -56,6 +62,7 @@
<item name="android:windowTitleStyle">@style/TextAppearance.Dialog.Title</item>
<item name="colorPrimary">@color/control_color</item>
<item name="colorSecondary">@color/control_color</item>
<item name="dialogCornerRadius">0dp</item>
</style>
<style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.Material.Title">

View file

@ -11,5 +11,12 @@
android:entries="@array/theme_entries"
android:entryValues="@array/theme_values"
app:useSimpleSummaryProvider="true" />
<Preference
app:key="KEY_ACCENT"
android:title="@string/label_settings_accent"
android:icon="@drawable/ic_accent"
app:summary="@string/label_settings_accent"
app:enableCopying="true" />
</PreferenceCategory>
</PreferenceScreen>