From 2dc7ba34202d68196e1675e903f91ffb4eb08c77 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sat, 28 Nov 2020 16:17:54 -0700 Subject: [PATCH] Implement theme customization Implement the ability to change the theme to auto/light/dark. --- app/build.gradle | 3 + .../java/org/oxycblt/auxio/MainActivity.kt | 30 ++++- .../oxycblt/auxio/library/LibraryViewModel.kt | 6 +- .../org/oxycblt/auxio/prefs/PrefsManager.kt | 71 ----------- .../auxio/settings/SettingListFragment.kt | 11 ++ .../auxio/settings/SettingsFragment.kt | 20 +++ .../oxycblt/auxio/settings/SettingsManager.kt | 118 ++++++++++++++++++ app/src/main/res/drawable/ic_day.xml | 11 ++ app/src/main/res/layout/fragment_settings.xml | 29 +++++ app/src/main/res/layout/item_prefs_header.xml | 17 +++ app/src/main/res/menu/menu_nav.xml | 4 + app/src/main/res/navigation/nav_explore.xml | 5 + app/src/main/res/values/arrays.xml | 13 ++ app/src/main/res/values/strings.xml | 6 + app/src/main/res/values/styles.xml | 17 ++- app/src/main/res/xml/prefs_main.xml | 15 +++ 16 files changed, 297 insertions(+), 79 deletions(-) delete mode 100644 app/src/main/java/org/oxycblt/auxio/prefs/PrefsManager.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/settings/SettingListFragment.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt create mode 100644 app/src/main/res/drawable/ic_day.xml create mode 100644 app/src/main/res/layout/fragment_settings.xml create mode 100644 app/src/main/res/layout/item_prefs_header.xml create mode 100644 app/src/main/res/values/arrays.xml create mode 100644 app/src/main/res/xml/prefs_main.xml diff --git a/app/build.gradle b/app/build.gradle index d634d7a01..496b40ba8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -75,6 +75,9 @@ dependencies { // Media implementation 'androidx.media:media:1.2.0' + // Preferences + implementation 'androidx.preference:preference-ktx:1.1.1' + // --- THIRD PARTY --- // ExoPlayer diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index a64680cdb..0d335d7db 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -5,18 +5,22 @@ import android.content.Intent import android.util.AttributeSet import android.view.View import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate import org.oxycblt.auxio.playback.PlaybackService -import org.oxycblt.auxio.prefs.PrefsManager +import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.ui.accent // FIXME: Fix bug where fast navigation will break the animations and // lead to nothing being displayed [Possibly Un-fixable] // TODO: Landscape UI layouts // FIXME: Compat issue with Versions 5 that leads to progress bar looking off -class MainActivity : AppCompatActivity(R.layout.activity_main) { - +class MainActivity : AppCompatActivity(R.layout.activity_main), SettingsManager.Callback { override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? { - val prefsManager = PrefsManager.init(this) + val settingsManager = SettingsManager.init(applicationContext) + + AppCompatDelegate.setDefaultNightMode( + settingsManager.getTheme() + ) // Apply the theme setTheme(accent.second) @@ -31,4 +35,22 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { startService(it) } } + + override fun onResume() { + super.onResume() + + // Perform callback additions/removals in onPause/onResume so that they are always + // ran when the activity is recreated. + SettingsManager.getInstance().addCallback(this) + } + + override fun onPause() { + super.onPause() + + SettingsManager.getInstance().removeCallback(this) + } + + override fun onThemeUpdate(value: Int) { + AppCompatDelegate.setDefaultNightMode(value) + } } diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt index 4ad22735f..8b5ead606 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt @@ -13,9 +13,9 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.MusicStore -import org.oxycblt.auxio.prefs.PrefsManager import org.oxycblt.auxio.recycler.ShowMode import org.oxycblt.auxio.recycler.SortMode +import org.oxycblt.auxio.settings.SettingsManager /** * A [ViewModel] that manages what [LibraryFragment] is currently showing, and also the search @@ -40,7 +40,7 @@ class LibraryViewModel : ViewModel() { val searchHasFocus: Boolean get() = mSearchHasFocus init { - val prefsManager = PrefsManager.getInstance() + val prefsManager = SettingsManager.getInstance() viewModelScope.launch { mSortMode.value = withContext(Dispatchers.IO) { @@ -135,7 +135,7 @@ class LibraryViewModel : ViewModel() { viewModelScope.launch { withContext(Dispatchers.IO) { - val prefsManager = PrefsManager.getInstance() + val prefsManager = SettingsManager.getInstance() prefsManager.setLibrarySortMode(mSortMode.value!!) } diff --git a/app/src/main/java/org/oxycblt/auxio/prefs/PrefsManager.kt b/app/src/main/java/org/oxycblt/auxio/prefs/PrefsManager.kt deleted file mode 100644 index dd169cc5c..000000000 --- a/app/src/main/java/org/oxycblt/auxio/prefs/PrefsManager.kt +++ /dev/null @@ -1,71 +0,0 @@ -package org.oxycblt.auxio.prefs - -import android.content.Context -import android.content.SharedPreferences -import org.oxycblt.auxio.recycler.SortMode - -/** - * Wrapper around the [SharedPreferences] class that writes & reads values. - * Please run any getter/setter in a coroutine. Its not required, but it prevents slowdowns - * on older devices. - * @author OxygenCobalt - */ -class PrefsManager private constructor(context: Context) { - private val sharedPrefs = context.getSharedPreferences( - "auxio_prefs", Context.MODE_PRIVATE - ) - - private lateinit var mLibrarySortMode: SortMode - - fun setLibrarySortMode(sortMode: SortMode) { - mLibrarySortMode = sortMode - - sharedPrefs.edit() - .putInt(Keys.KEY_LIBRARY_SORT_MODE, sortMode.toConstant()) - .apply() - } - - fun getLibrarySortMode(): SortMode { - if (!::mLibrarySortMode.isInitialized) { - mLibrarySortMode = SortMode.fromConstant( - sharedPrefs.getInt( - Keys.KEY_LIBRARY_SORT_MODE, - SortMode.CONSTANT_ALPHA_DOWN - ) - ) ?: SortMode.ALPHA_DOWN - } - - return mLibrarySortMode - } - - companion object { - @Volatile - private lateinit var INSTANCE: PrefsManager - - /** - * Init the single instance of [PrefsManager]. Done so that every object - * can have access to it regardless of if it has a context. - */ - fun init(context: Context): PrefsManager { - synchronized(this) { - INSTANCE = PrefsManager(context) - - return getInstance() - } - } - - /** - * Get the single instance of [PrefsManager]. - */ - fun getInstance(): PrefsManager { - check(::INSTANCE.isInitialized) { - "PrefsManager must be initialized with init() before getting its instance." - } - return INSTANCE - } - } - - object Keys { - const val KEY_LIBRARY_SORT_MODE = "KEY_LIBRARY_SORT_MODE" - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingListFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingListFragment.kt new file mode 100644 index 000000000..8cc29119b --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingListFragment.kt @@ -0,0 +1,11 @@ +package org.oxycblt.auxio.settings + +import android.os.Bundle +import androidx.preference.PreferenceFragmentCompat +import org.oxycblt.auxio.R + +class SettingListFragment : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.prefs_main, rootKey) + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt new file mode 100644 index 000000000..962e92a4a --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt @@ -0,0 +1,20 @@ +package org.oxycblt.auxio.settings + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import org.oxycblt.auxio.databinding.FragmentSettingsBinding + +class SettingsFragment : Fragment() { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val binding = FragmentSettingsBinding.inflate(inflater) + + return binding.root + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt new file mode 100644 index 000000000..20b01baa4 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt @@ -0,0 +1,118 @@ +package org.oxycblt.auxio.settings + +import android.content.Context +import android.content.SharedPreferences +import androidx.appcompat.app.AppCompatDelegate +import androidx.preference.PreferenceManager +import org.oxycblt.auxio.recycler.SortMode + +/** + * Wrapper around the [SharedPreferences] class that writes & reads values without a context. + * + * **Note:** Run any getter in a IO coroutine if possible, as SharedPrefs will read from disk + * the first time it occurs. + * @author OxygenCobalt + */ +class SettingsManager private constructor(context: Context) : SharedPreferences.OnSharedPreferenceChangeListener { + private val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context) + + init { + sharedPrefs.registerOnSharedPreferenceChangeListener(this) + } + + private val callbacks = mutableListOf() + + fun addCallback(callback: Callback) { + callbacks.add(callback) + } + + fun removeCallback(callback: Callback) { + callbacks.remove(callback) + } + + fun setLibrarySortMode(sortMode: SortMode) { + sharedPrefs.edit() + .putInt(Keys.KEY_LIBRARY_SORT_MODE, sortMode.toConstant()) + .apply() + } + + fun getLibrarySortMode(): SortMode { + return SortMode.fromConstant( + sharedPrefs.getInt( + Keys.KEY_LIBRARY_SORT_MODE, + SortMode.CONSTANT_ALPHA_DOWN + ) + ) ?: 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 + } + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + when (key) { + Keys.KEY_THEME -> { + callbacks.forEach { + it.onThemeUpdate(getTheme()) + } + } + } + } + + companion object { + @Volatile + private lateinit var INSTANCE: SettingsManager + + /** + * Init the single instance of [SettingsManager]. Done so that every object + * can have access to it regardless of if it has a context. + */ + fun init(context: Context): SettingsManager { + if (!::INSTANCE.isInitialized) { + synchronized(this) { + INSTANCE = SettingsManager(context) + } + } + + return getInstance() + } + + /** + * Get the single instance of [SettingsManager]. + */ + fun getInstance(): SettingsManager { + check(::INSTANCE.isInitialized) { + "PrefsManager must be initialized with init() before getting its instance." + } + return INSTANCE + } + } + + object Keys { + const val KEY_LIBRARY_SORT_MODE = "KEY_LIBRARY_SORT_MODE" + const val KEY_THEME = "KEY_THEME" + } + + private object Theme { + const val THEME_AUTO = "AUTO" + const val THEME_LIGHT = "LIGHT" + const val THEME_DARK = "DARK" + } + + /** + * A safe interface for receiving preference updates, use this instead of + * [SharedPreferences.OnSharedPreferenceChangeListener]. + */ + interface Callback { + fun onThemeUpdate(value: Int) {} + } +} diff --git a/app/src/main/res/drawable/ic_day.xml b/app/src/main/res/drawable/ic_day.xml new file mode 100644 index 000000000..8f3ea64a2 --- /dev/null +++ b/app/src/main/res/drawable/ic_day.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml new file mode 100644 index 000000000..af8869d67 --- /dev/null +++ b/app/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_prefs_header.xml b/app/src/main/res/layout/item_prefs_header.xml new file mode 100644 index 000000000..df94184a5 --- /dev/null +++ b/app/src/main/res/layout/item_prefs_header.xml @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_nav.xml b/app/src/main/res/menu/menu_nav.xml index 363ffb6ec..785183127 100644 --- a/app/src/main/res/menu/menu_nav.xml +++ b/app/src/main/res/menu/menu_nav.xml @@ -8,4 +8,8 @@ android:id="@+id/songs_fragment" android:title="@string/label_songs" android:icon="@drawable/ic_song" /> + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_explore.xml b/app/src/main/res/navigation/nav_explore.xml index 63c0c6a4c..a3d616da6 100644 --- a/app/src/main/res/navigation/nav_explore.xml +++ b/app/src/main/res/navigation/nav_explore.xml @@ -88,4 +88,9 @@ android:name="org.oxycblt.auxio.songs.SongsFragment" android:label="fragment_songs" tools:layout="@layout/fragment_songs" /> + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 000000000..6e9bd2bcd --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,13 @@ + + + + @string/label_settings_theme_auto + @string/label_settings_theme_light + @string/label_settings_theme_dark + + + AUTO + LIGHT + DARK + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fcf2a312d..61ee9e70f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -28,6 +28,12 @@ Music Playback The music playback service for Auxio. Settings + Appearance + Theme + Auto + Light + Dark + Choose theme State saved diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b3df616b4..c9eb164f3 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -5,12 +5,14 @@ @color/background @android:color/black @font/inter - @style/FastScrollTheme @drawable/ui_cursor true + none @style/Widget.CustomPopup @color/control_color + @style/Theme.CustomDialog + @style/FastScrollTheme @@ -48,6 +50,19 @@ @color/background + + + + + +