From d3f38ce56cb7d9a980f716e65ceb912f89b36f6b Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Sat, 23 Dec 2023 19:01:57 -0500
Subject: [PATCH] android: Migrate theme settings to ini

---
 .../features/settings/model/BooleanSetting.kt |  3 +-
 .../features/settings/model/IntSetting.kt     |  4 +-
 .../features/settings/model/Settings.kt       |  1 +
 .../settings/ui/SettingsFragmentPresenter.kt  | 74 ++++++++-----------
 .../yuzu_emu/utils/DirectoryInitialization.kt | 33 +++++++++
 .../org/yuzu/yuzu_emu/utils/PreferenceUtil.kt | 37 ++++++++++
 .../org/yuzu/yuzu_emu/utils/ThemeHelper.kt    | 31 ++++----
 .../app/src/main/jni/android_settings.h       |  5 ++
 8 files changed, 127 insertions(+), 61 deletions(-)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PreferenceUtil.kt

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index 16f06cd0af..110d15f1c5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -18,7 +18,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
     RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
     RENDERER_DEBUG("debug"),
     PICTURE_IN_PICTURE("picture_in_picture"),
-    USE_CUSTOM_RTC("custom_rtc_enabled");
+    USE_CUSTOM_RTC("custom_rtc_enabled"),
+    BLACK_BACKGROUNDS("black_backgrounds");
 
     override fun getBoolean(needsGlobal: Boolean): Boolean =
         NativeConfig.getBoolean(key, needsGlobal)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
index df760440f2..b0193d83e9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
@@ -19,7 +19,9 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
     RENDERER_SCREEN_LAYOUT("screen_layout"),
     RENDERER_ASPECT_RATIO("aspect_ratio"),
     AUDIO_OUTPUT_ENGINE("output_engine"),
-    MAX_ANISOTROPY("max_anisotropy");
+    MAX_ANISOTROPY("max_anisotropy"),
+    THEME("theme"),
+    THEME_MODE("theme_mode");
 
     override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index 9551fc05e4..360bdaf3ef 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -54,6 +54,7 @@ object Settings {
     const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
     const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
 
+    // Deprecated theme preference keys
     const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
     const val PREF_THEME = "Theme"
     const val PREF_THEME_MODE = "ThemeMode"
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index db1a1076cd..2ad2f49664 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -3,10 +3,8 @@
 
 package org.yuzu.yuzu_emu.features.settings.ui
 
-import android.content.SharedPreferences
 import android.os.Build
 import android.widget.Toast
-import androidx.preference.PreferenceManager
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
@@ -29,9 +27,6 @@ class SettingsFragmentPresenter(
 ) {
     private var settingsList = ArrayList<SettingsItem>()
 
-    private val preferences: SharedPreferences
-        get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-
     // Extension for altering settings list based on each setting's properties
     fun ArrayList<SettingsItem>.add(key: String) {
         val item = SettingsItem.settingsItems[key]!!
@@ -170,25 +165,19 @@ class SettingsFragmentPresenter(
     private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
         sl.apply {
             val theme: AbstractIntSetting = object : AbstractIntSetting {
-                override fun getInt(needsGlobal: Boolean): Int =
-                    preferences.getInt(Settings.PREF_THEME, 0)
-
+                override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME.getInt()
                 override fun setInt(value: Int) {
-                    preferences.edit()
-                        .putInt(Settings.PREF_THEME, value)
-                        .apply()
+                    IntSetting.THEME.setInt(value)
                     settingsViewModel.setShouldRecreate(true)
                 }
 
-                override val key: String = Settings.PREF_THEME
-                override val isRuntimeModifiable: Boolean = false
-                override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
-                override val defaultValue: Int = 0
-                override fun reset() {
-                    preferences.edit()
-                        .putInt(Settings.PREF_THEME, defaultValue)
-                        .apply()
-                }
+                override val key: String = IntSetting.THEME.key
+                override val isRuntimeModifiable: Boolean = IntSetting.THEME.isRuntimeModifiable
+                override fun getValueAsString(needsGlobal: Boolean): String =
+                    IntSetting.THEME.getValueAsString()
+
+                override val defaultValue: Int = IntSetting.THEME.defaultValue
+                override fun reset() = IntSetting.THEME.setInt(defaultValue)
             }
 
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -214,24 +203,22 @@ class SettingsFragmentPresenter(
             }
 
             val themeMode: AbstractIntSetting = object : AbstractIntSetting {
-                override fun getInt(needsGlobal: Boolean): Int =
-                    preferences.getInt(Settings.PREF_THEME_MODE, -1)
-
+                override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME_MODE.getInt()
                 override fun setInt(value: Int) {
-                    preferences.edit()
-                        .putInt(Settings.PREF_THEME_MODE, value)
-                        .apply()
+                    IntSetting.THEME_MODE.setInt(value)
                     settingsViewModel.setShouldRecreate(true)
                 }
 
-                override val key: String = Settings.PREF_THEME_MODE
-                override val isRuntimeModifiable: Boolean = false
-                override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
-                override val defaultValue: Int = -1
+                override val key: String = IntSetting.THEME_MODE.key
+                override val isRuntimeModifiable: Boolean =
+                    IntSetting.THEME_MODE.isRuntimeModifiable
+
+                override fun getValueAsString(needsGlobal: Boolean): String =
+                    IntSetting.THEME_MODE.getValueAsString()
+
+                override val defaultValue: Int = IntSetting.THEME_MODE.defaultValue
                 override fun reset() {
-                    preferences.edit()
-                        .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
-                        .apply()
+                    IntSetting.THEME_MODE.setInt(defaultValue)
                     settingsViewModel.setShouldRecreate(true)
                 }
             }
@@ -248,25 +235,24 @@ class SettingsFragmentPresenter(
 
             val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
                 override fun getBoolean(needsGlobal: Boolean): Boolean =
-                    preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
+                    BooleanSetting.BLACK_BACKGROUNDS.getBoolean()
 
                 override fun setBoolean(value: Boolean) {
-                    preferences.edit()
-                        .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
-                        .apply()
+                    BooleanSetting.BLACK_BACKGROUNDS.setBoolean(value)
                     settingsViewModel.setShouldRecreate(true)
                 }
 
-                override val key: String = Settings.PREF_BLACK_BACKGROUNDS
-                override val isRuntimeModifiable: Boolean = false
+                override val key: String = BooleanSetting.BLACK_BACKGROUNDS.key
+                override val isRuntimeModifiable: Boolean =
+                    BooleanSetting.BLACK_BACKGROUNDS.isRuntimeModifiable
+
                 override fun getValueAsString(needsGlobal: Boolean): String =
-                    getBoolean().toString()
+                    BooleanSetting.BLACK_BACKGROUNDS.getValueAsString()
 
-                override val defaultValue: Boolean = false
+                override val defaultValue: Boolean = BooleanSetting.BLACK_BACKGROUNDS.defaultValue
                 override fun reset() {
-                    preferences.edit()
-                        .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
-                        .apply()
+                    BooleanSetting.BLACK_BACKGROUNDS
+                        .setBoolean(BooleanSetting.BLACK_BACKGROUNDS.defaultValue)
                     settingsViewModel.setShouldRecreate(true)
                 }
             }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
index 0197fd712f..d4a9da06fd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
@@ -3,9 +3,14 @@
 
 package org.yuzu.yuzu_emu.utils
 
+import androidx.preference.PreferenceManager
 import java.io.IOException
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.IntSetting
+import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.utils.PreferenceUtil.migratePreference
 
 object DirectoryInitialization {
     private var userPath: String? = null
@@ -17,6 +22,7 @@ object DirectoryInitialization {
             initializeInternalStorage()
             NativeLibrary.initializeSystem(false)
             NativeConfig.initializeGlobalConfig()
+            migrateSettings()
             areDirectoriesReady = true
         }
     }
@@ -35,4 +41,31 @@ object DirectoryInitialization {
             e.printStackTrace()
         }
     }
+
+    private fun migrateSettings() {
+        val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+        var saveConfig = false
+        val theme = preferences.migratePreference<Int>(Settings.PREF_THEME)
+        if (theme != null) {
+            IntSetting.THEME.setInt(theme)
+            saveConfig = true
+        }
+
+        val themeMode = preferences.migratePreference<Int>(Settings.PREF_THEME_MODE)
+        if (themeMode != null) {
+            IntSetting.THEME_MODE.setInt(themeMode)
+            saveConfig = true
+        }
+
+        val blackBackgrounds =
+            preferences.migratePreference<Boolean>(Settings.PREF_BLACK_BACKGROUNDS)
+        if (blackBackgrounds != null) {
+            BooleanSetting.BLACK_BACKGROUNDS.setBoolean(blackBackgrounds)
+            saveConfig = true
+        }
+
+        if (saveConfig) {
+            NativeConfig.saveGlobalConfig()
+        }
+    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PreferenceUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PreferenceUtil.kt
new file mode 100644
index 0000000000..a233ba25c7
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PreferenceUtil.kt
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+import android.content.SharedPreferences
+
+object PreferenceUtil {
+    /**
+     * Retrieves a shared preference value and then deletes the value in storage.
+     * @param key Associated key for the value in this preferences instance
+     * @return Typed value associated with [key]. Null if no such key exists.
+     */
+    inline fun <reified T> SharedPreferences.migratePreference(key: String): T? {
+        if (!this.contains(key)) {
+            return null
+        }
+
+        val value: Any = when (T::class) {
+            String::class -> this.getString(key, "")!!
+
+            Boolean::class -> this.getBoolean(key, false)
+
+            Int::class -> this.getInt(key, 0)
+
+            Float::class -> this.getFloat(key, 0f)
+
+            Long::class -> this.getLong(key, 0)
+
+            else -> throw IllegalStateException("Tried to migrate preference with invalid type!")
+        }
+        deletePreference(key)
+        return value as T
+    }
+
+    fun SharedPreferences.deletePreference(key: String) = this.edit().remove(key).apply()
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
index f312e24cf1..792f6a2538 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
@@ -10,33 +10,26 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.app.AppCompatDelegate
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsControllerCompat
-import androidx.preference.PreferenceManager
 import kotlin.math.roundToInt
 import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.ui.main.ThemeProvider
 
 object ThemeHelper {
     const val SYSTEM_BAR_ALPHA = 0.9f
 
-    private const val DEFAULT = 0
-    private const val MATERIAL_YOU = 1
-
     fun setTheme(activity: AppCompatActivity) {
-        val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
         setThemeMode(activity)
-        when (preferences.getInt(Settings.PREF_THEME, 0)) {
-            DEFAULT -> activity.setTheme(R.style.Theme_Yuzu_Main)
-            MATERIAL_YOU -> activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou)
+        when (Theme.from(IntSetting.THEME.getInt())) {
+            Theme.Default -> activity.setTheme(R.style.Theme_Yuzu_Main)
+            Theme.MaterialYou -> activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou)
         }
 
         // Using a specific night mode check because this could apply incorrectly when using the
         // light app mode, dark system mode, and black backgrounds. Launching the settings activity
         // will then show light mode colors/navigation bars but with black backgrounds.
-        if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) &&
-            isNightMode(activity)
-        ) {
+        if (BooleanSetting.BLACK_BACKGROUNDS.getBoolean() && isNightMode(activity)) {
             activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark)
         }
     }
@@ -60,8 +53,7 @@ object ThemeHelper {
     }
 
     fun setThemeMode(activity: AppCompatActivity) {
-        val themeMode = PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
-            .getInt(Settings.PREF_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
+        val themeMode = IntSetting.THEME_MODE.getInt()
         activity.delegate.localNightMode = themeMode
         val windowController = WindowCompat.getInsetsController(
             activity.window,
@@ -95,3 +87,12 @@ object ThemeHelper {
         windowController.isAppearanceLightNavigationBars = false
     }
 }
+
+enum class Theme(val int: Int) {
+    Default(0),
+    MaterialYou(1);
+
+    companion object {
+        fun from(int: Int): Theme = entries.firstOrNull { it.int == int } ?: Default
+    }
+}
diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h
index 3733f5a3c5..1e4906b9a5 100644
--- a/src/android/app/src/main/jni/android_settings.h
+++ b/src/android/app/src/main/jni/android_settings.h
@@ -33,6 +33,11 @@ struct Values {
 
     Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path",
                                                                 Settings::Category::GpuDriver};
+
+    Settings::Setting<s32> theme{linkage, 0, "theme", Settings::Category::Android};
+    Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
+    Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
+                                              Settings::Category::Android};
 };
 
 extern Values values;
-- 
GitLab