From 6c8f2b355ace41e33e8a1ad2f95d2816893a953b Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Wed, 16 Aug 2023 02:36:56 -0400
Subject: [PATCH 1/9] android: Expose interface for getting settings from
 native code

Completely removes code related to parsing the settings file on the java side. Now all settings are accessed via NativeConfig.kt and config.cpp has been modified to be closer to the core counterpart. Since the core currently uses QSettings, we can't remove reliance from Wini yet. This also includes simplifications to each settings interface to get closer to native code and prepare for per-game settings.
---
 .../java/org/yuzu/yuzu_emu/NativeLibrary.kt   |   8 +-
 .../yuzu_emu/activities/EmulationActivity.kt  |   6 -
 .../settings/model/AbstractBooleanSetting.kt  |   4 +-
 ...ngsViewModel.kt => AbstractByteSetting.kt} |   6 +-
 .../settings/model/AbstractFloatSetting.kt    |   4 +-
 .../settings/model/AbstractIntSetting.kt      |   4 +-
 .../settings/model/AbstractLongSetting.kt     |  10 +
 .../settings/model/AbstractSetting.kt         |  13 +-
 .../settings/model/AbstractShortSetting.kt    |  10 +
 .../settings/model/AbstractStringSetting.kt   |   4 +-
 .../features/settings/model/BooleanSetting.kt |  53 ++-
 .../features/settings/model/ByteSetting.kt    |  25 ++
 .../features/settings/model/FloatSetting.kt   |  32 +-
 .../features/settings/model/IntSetting.kt     | 149 ++-------
 .../features/settings/model/LongSetting.kt    |  25 ++
 .../features/settings/model/SettingSection.kt |  37 --
 .../features/settings/model/Settings.kt       | 316 ++++++++----------
 .../features/settings/model/ShortSetting.kt   |  25 ++
 .../features/settings/model/StringSetting.kt  |  36 +-
 .../settings/model/view/DateTimeSetting.kt    |  18 +-
 .../settings/model/view/SettingsItem.kt       |   2 +-
 .../model/view/SingleChoiceSetting.kt         |   2 +-
 .../settings/model/view/SliderSetting.kt      |  24 +-
 .../model/view/StringSingleChoiceSetting.kt   |   2 +-
 .../settings/model/view/SwitchSetting.kt      |   4 +-
 .../features/settings/ui/SettingsActivity.kt  |  30 +-
 .../settings/ui/SettingsActivityPresenter.kt  |  15 +-
 .../settings/ui/SettingsActivityView.kt       |  19 --
 .../features/settings/ui/SettingsAdapter.kt   |  62 ++--
 .../features/settings/ui/SettingsFragment.kt  |   9 -
 .../settings/ui/SettingsFragmentPresenter.kt  | 181 +++++-----
 .../settings/ui/SettingsFragmentView.kt       |  16 -
 .../ui/viewholder/DateTimeViewHolder.kt       |   2 +-
 .../features/settings/utils/SettingsFile.kt   | 255 ++------------
 .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt |   4 -
 .../java/org/yuzu/yuzu_emu/utils/BiMap.kt     |  25 --
 .../org/yuzu/yuzu_emu/utils/NativeConfig.kt   |  31 ++
 src/android/app/src/main/jni/CMakeLists.txt   |   2 +
 src/android/app/src/main/jni/config.cpp       |  31 +-
 src/android/app/src/main/jni/config.h         |  24 +-
 src/android/app/src/main/jni/native.cpp       |  28 --
 .../app/src/main/jni/native_config.cpp        | 224 +++++++++++++
 src/android/app/src/main/jni/uisettings.cpp   |  10 +
 src/android/app/src/main/jni/uisettings.h     |  29 ++
 .../app/src/main/res/values/arrays.xml        |  10 +-
 .../app/src/main/res/values/strings.xml       |   1 +
 src/common/settings.cpp                       |   2 +
 src/common/settings_common.cpp                |   1 +
 src/common/settings_common.h                  |   2 +
 49 files changed, 866 insertions(+), 966 deletions(-)
 rename src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/{SettingsViewModel.kt => AbstractByteSetting.kt} (59%)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt
 delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt
 delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
 create mode 100644 src/android/app/src/main/jni/native_config.cpp
 create mode 100644 src/android/app/src/main/jni/uisettings.cpp
 create mode 100644 src/android/app/src/main/jni/uisettings.h

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index 9c32e044c0..5a7cf4ed73 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -219,10 +219,6 @@ object NativeLibrary {
 
     external fun reloadSettings()
 
-    external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String?
-
-    external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?)
-
     external fun initGameIni(gameID: String?)
 
     /**
@@ -413,14 +409,17 @@ object NativeLibrary {
                     details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
                 )
             }
+
             CoreError.ErrorSavestate -> {
                 title = emulationActivity.getString(R.string.save_load_error)
                 message = details
             }
+
             CoreError.ErrorUnknown -> {
                 title = emulationActivity.getString(R.string.fatal_error)
                 message = emulationActivity.getString(R.string.fatal_error_message)
             }
+
             else -> {
                 return true
             }
@@ -454,6 +453,7 @@ object NativeLibrary {
                 captionId = R.string.loader_error_video_core
                 descriptionId = R.string.loader_error_video_core_description
             }
+
             else -> {
                 captionId = R.string.loader_error_encrypted
                 descriptionId = R.string.loader_error_encrypted_roms_description
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 7461fb093f..6f52a7a8da 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -28,7 +28,6 @@ import android.view.Surface
 import android.view.View
 import android.view.inputmethod.InputMethodManager
 import android.widget.Toast
-import androidx.activity.viewModels
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
@@ -42,7 +41,6 @@ import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
 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.features.settings.model.SettingsViewModel
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
 import org.yuzu.yuzu_emu.utils.ForegroundService
@@ -72,8 +70,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
     private val actionMute = "ACTION_EMULATOR_MUTE"
     private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
 
-    private val settingsViewModel: SettingsViewModel by viewModels()
-
     override fun onDestroy() {
         stopForegroundService(this)
         super.onDestroy()
@@ -82,8 +78,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
     override fun onCreate(savedInstanceState: Bundle?) {
         ThemeHelper.setTheme(this)
 
-        settingsViewModel.settings.loadSettings()
-
         super.onCreate(savedInstanceState)
 
         binding = ActivityEmulationBinding.inflate(layoutInflater)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
index a6e9833ee7..aeda8d2220 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
@@ -4,5 +4,7 @@
 package org.yuzu.yuzu_emu.features.settings.model
 
 interface AbstractBooleanSetting : AbstractSetting {
-    var boolean: Boolean
+    val boolean: Boolean
+
+    fun setBoolean(value: Boolean)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt
similarity index 59%
rename from src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt
rename to src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt
index bd9233d625..606519ad84 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt
@@ -3,8 +3,8 @@
 
 package org.yuzu.yuzu_emu.features.settings.model
 
-import androidx.lifecycle.ViewModel
+interface AbstractByteSetting : AbstractSetting {
+    val byte: Byte
 
-class SettingsViewModel : ViewModel() {
-    val settings = Settings()
+    fun setByte(value: Byte)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
index 6fe4bc2635..974925eeda 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
@@ -4,5 +4,7 @@
 package org.yuzu.yuzu_emu.features.settings.model
 
 interface AbstractFloatSetting : AbstractSetting {
-    var float: Float
+    val float: Float
+
+    fun setFloat(value: Float)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
index 892b7dcfe1..89b285b108 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
@@ -4,5 +4,7 @@
 package org.yuzu.yuzu_emu.features.settings.model
 
 interface AbstractIntSetting : AbstractSetting {
-    var int: Int
+    val int: Int
+
+    fun setInt(value: Int)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt
new file mode 100644
index 0000000000..4873942db7
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+interface AbstractLongSetting : AbstractSetting {
+    val long: Long
+
+    fun setLong(value: Long)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
index 2585802092..7afed95adf 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
@@ -3,10 +3,17 @@
 
 package org.yuzu.yuzu_emu.features.settings.model
 
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
 interface AbstractSetting {
     val key: String?
-    val section: String?
-    val isRuntimeEditable: Boolean
-    val valueAsString: String
+    val category: Settings.Category
     val defaultValue: Any
+    val valueAsString: String
+        get() = ""
+
+    val isRuntimeModifiable: Boolean
+        get() = NativeConfig.getIsRuntimeModifiable(key!!)
+
+    fun reset() = run { }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt
new file mode 100644
index 0000000000..91407ccbb4
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+interface AbstractShortSetting : AbstractSetting {
+    val short: Short
+
+    fun setShort(value: Short)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
index 0d02c59973..c8935cc48c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
@@ -4,5 +4,7 @@
 package org.yuzu.yuzu_emu.features.settings.model
 
 interface AbstractStringSetting : AbstractSetting {
-    var string: String
+    val string: String
+
+    fun setString(value: String)
 }
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 d41933766d..f7528642e9 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
@@ -3,41 +3,34 @@
 
 package org.yuzu.yuzu_emu.features.settings.model
 
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
 enum class BooleanSetting(
     override val key: String,
-    override val section: String,
-    override val defaultValue: Boolean
+    override val category: Settings.Category
 ) : AbstractBooleanSetting {
-    CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false),
-    FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true),
-    FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true),
-    PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true),
-    USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
+    CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
+    FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
+    FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
+    RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core),
+    USE_DOCKED_MODE("use_docked_mode", Settings.Category.System),
+    RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer),
+    RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer),
+    RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer),
+    RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer),
+    RENDERER_DEBUG("debug", Settings.Category.Renderer),
+    PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android),
+    USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System);
 
-    override var boolean: Boolean = defaultValue
+    override val boolean: Boolean
+        get() = NativeConfig.getBoolean(key, false)
+
+    override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value)
+
+    override val defaultValue: Boolean by lazy { NativeConfig.getBoolean(key, true) }
 
     override val valueAsString: String
-        get() = boolean.toString()
+        get() = if (boolean) "1" else "0"
 
-    override val isRuntimeEditable: Boolean
-        get() {
-            for (setting in NOT_RUNTIME_EDITABLE) {
-                if (setting == this) {
-                    return false
-                }
-            }
-            return true
-        }
-
-    companion object {
-        private val NOT_RUNTIME_EDITABLE = listOf(
-            PICTURE_IN_PICTURE,
-            USE_CUSTOM_RTC
-        )
-
-        fun from(key: String): BooleanSetting? =
-            BooleanSetting.values().firstOrNull { it.key == key }
-
-        fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue }
-    }
+    override fun reset() = NativeConfig.setBoolean(key, defaultValue)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
new file mode 100644
index 0000000000..6ec0a765ef
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+enum class ByteSetting(
+    override val key: String,
+    override val category: Settings.Category
+) : AbstractByteSetting {
+    AUDIO_VOLUME("volume", Settings.Category.Audio);
+
+    override val byte: Byte
+        get() = NativeConfig.getByte(key, false)
+
+    override fun setByte(value: Byte) = NativeConfig.setByte(key, value)
+
+    override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) }
+
+    override val valueAsString: String
+        get() = byte.toString()
+
+    override fun reset() = NativeConfig.setByte(key, defaultValue)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
index e5545a916a..0181d06f21 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
@@ -3,34 +3,24 @@
 
 package org.yuzu.yuzu_emu.features.settings.model
 
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
 enum class FloatSetting(
     override val key: String,
-    override val section: String,
-    override val defaultValue: Float
+    override val category: Settings.Category
 ) : AbstractFloatSetting {
     // No float settings currently exist
-    EMPTY_SETTING("", "", 0f);
+    EMPTY_SETTING("", Settings.Category.UiGeneral);
 
-    override var float: Float = defaultValue
+    override val float: Float
+        get() = NativeConfig.getFloat(key, false)
+
+    override fun setFloat(value: Float) = NativeConfig.setFloat(key, value)
+
+    override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) }
 
     override val valueAsString: String
         get() = float.toString()
 
-    override val isRuntimeEditable: Boolean
-        get() {
-            for (setting in NOT_RUNTIME_EDITABLE) {
-                if (setting == this) {
-                    return false
-                }
-            }
-            return true
-        }
-
-    companion object {
-        private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>()
-
-        fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key }
-
-        fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue }
-    }
+    override fun reset() = NativeConfig.setFloat(key, defaultValue)
 }
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 4427a7d9dc..a647572071 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
@@ -3,139 +3,34 @@
 
 package org.yuzu.yuzu_emu.features.settings.model
 
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
 enum class IntSetting(
     override val key: String,
-    override val section: String,
-    override val defaultValue: Int
+    override val category: Settings.Category
 ) : AbstractIntSetting {
-    RENDERER_USE_SPEED_LIMIT(
-        "use_speed_limit",
-        Settings.SECTION_RENDERER,
-        1
-    ),
-    USE_DOCKED_MODE(
-        "use_docked_mode",
-        Settings.SECTION_SYSTEM,
-        0
-    ),
-    RENDERER_USE_DISK_SHADER_CACHE(
-        "use_disk_shader_cache",
-        Settings.SECTION_RENDERER,
-        1
-    ),
-    RENDERER_FORCE_MAX_CLOCK(
-        "force_max_clock",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_ASYNCHRONOUS_SHADERS(
-        "use_asynchronous_shaders",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_REACTIVE_FLUSHING(
-        "use_reactive_flushing",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_DEBUG(
-        "debug",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_SPEED_LIMIT(
-        "speed_limit",
-        Settings.SECTION_RENDERER,
-        100
-    ),
-    CPU_ACCURACY(
-        "cpu_accuracy",
-        Settings.SECTION_CPU,
-        0
-    ),
-    REGION_INDEX(
-        "region_index",
-        Settings.SECTION_SYSTEM,
-        -1
-    ),
-    LANGUAGE_INDEX(
-        "language_index",
-        Settings.SECTION_SYSTEM,
-        1
-    ),
-    RENDERER_BACKEND(
-        "backend",
-        Settings.SECTION_RENDERER,
-        1
-    ),
-    RENDERER_ACCURACY(
-        "gpu_accuracy",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_RESOLUTION(
-        "resolution_setup",
-        Settings.SECTION_RENDERER,
-        2
-    ),
-    RENDERER_VSYNC(
-        "use_vsync",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_SCALING_FILTER(
-        "scaling_filter",
-        Settings.SECTION_RENDERER,
-        1
-    ),
-    RENDERER_ANTI_ALIASING(
-        "anti_aliasing",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_SCREEN_LAYOUT(
-        "screen_layout",
-        Settings.SECTION_RENDERER,
-        Settings.LayoutOption_MobileLandscape
-    ),
-    RENDERER_ASPECT_RATIO(
-        "aspect_ratio",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    AUDIO_VOLUME(
-        "volume",
-        Settings.SECTION_AUDIO,
-        100
-    );
+    CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
+    REGION_INDEX("region_index", Settings.Category.System),
+    LANGUAGE_INDEX("language_index", Settings.Category.System),
+    RENDERER_BACKEND("backend", Settings.Category.Renderer),
+    RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer),
+    RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer),
+    RENDERER_VSYNC("use_vsync", Settings.Category.Renderer),
+    RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer),
+    RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer),
+    RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android),
+    RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer),
+    AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio);
 
-    override var int: Int = defaultValue
+    override val int: Int
+        get() = NativeConfig.getInt(key, false)
+
+    override fun setInt(value: Int) = NativeConfig.setInt(key, value)
+
+    override val defaultValue: Int by lazy { NativeConfig.getInt(key, true) }
 
     override val valueAsString: String
         get() = int.toString()
 
-    override val isRuntimeEditable: Boolean
-        get() {
-            for (setting in NOT_RUNTIME_EDITABLE) {
-                if (setting == this) {
-                    return false
-                }
-            }
-            return true
-        }
-
-    companion object {
-        private val NOT_RUNTIME_EDITABLE = listOf(
-            RENDERER_USE_DISK_SHADER_CACHE,
-            RENDERER_ASYNCHRONOUS_SHADERS,
-            RENDERER_DEBUG,
-            RENDERER_BACKEND,
-            RENDERER_RESOLUTION,
-            RENDERER_VSYNC
-        )
-
-        fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
-
-        fun clear() = IntSetting.values().forEach { it.int = it.defaultValue }
-    }
+    override fun reset() = NativeConfig.setInt(key, defaultValue)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt
new file mode 100644
index 0000000000..c526fc4cfa
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+enum class LongSetting(
+    override val key: String,
+    override val category: Settings.Category
+) : AbstractLongSetting {
+    CUSTOM_RTC("custom_rtc", Settings.Category.System);
+
+    override val long: Long
+        get() = NativeConfig.getLong(key, false)
+
+    override fun setLong(value: Long) = NativeConfig.setLong(key, value)
+
+    override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) }
+
+    override val valueAsString: String
+        get() = long.toString()
+
+    override fun reset() = NativeConfig.setLong(key, defaultValue)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
deleted file mode 100644
index 474f598a91..0000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.model
-
-/**
- * A semantically-related group of Settings objects. These Settings are
- * internally stored as a HashMap.
- */
-class SettingSection(val name: String) {
-    val settings = HashMap<String, AbstractSetting>()
-
-    /**
-     * Convenience method; inserts a value directly into the backing HashMap.
-     *
-     * @param setting The Setting to be inserted.
-     */
-    fun putSetting(setting: AbstractSetting) {
-        settings[setting.key!!] = setting
-    }
-
-    /**
-     * Convenience method; gets a value directly from the backing HashMap.
-     *
-     * @param key Used to retrieve the Setting.
-     * @return A Setting object (you should probably cast this before using)
-     */
-    fun getSetting(key: String): AbstractSetting? {
-        return settings[key]
-    }
-
-    fun mergeSection(settingSection: SettingSection) {
-        for (setting in settingSection.settings.values) {
-            putSetting(setting)
-        }
-    }
-}
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 a6251bafdd..0702236e81 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
@@ -4,195 +4,151 @@
 package org.yuzu.yuzu_emu.features.settings.model
 
 import android.text.TextUtils
-import java.util.*
+import android.widget.Toast
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 
-class Settings {
-    private var gameId: String? = null
+object Settings {
+    private val context get() = YuzuApplication.appContext
 
-    var isLoaded = false
-
-    /**
-     * A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
-     * when getting a key not already in the map
-     */
-    class SettingsSectionMap : HashMap<String, SettingSection?>() {
-        override operator fun get(key: String): SettingSection? {
-            if (!super.containsKey(key)) {
-                val section = SettingSection(key)
-                super.put(key, section)
-                return section
-            }
-            return super.get(key)
-        }
-    }
-
-    var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
-
-    fun getSection(sectionName: String): SettingSection? {
-        return sections[sectionName]
-    }
-
-    val isEmpty: Boolean
-        get() = sections.isEmpty()
-
-    fun loadSettings(view: SettingsActivityView? = null) {
-        sections = SettingsSectionMap()
-        loadYuzuSettings(view)
-        if (!TextUtils.isEmpty(gameId)) {
-            loadCustomGameSettings(gameId!!, view)
-        }
-        isLoaded = true
-    }
-
-    private fun loadYuzuSettings(view: SettingsActivityView?) {
-        for ((fileName) in configFileSectionsMap) {
-            sections.putAll(SettingsFile.readFile(fileName, view))
-        }
-    }
-
-    private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) {
-        // Custom game settings
-        mergeSections(SettingsFile.readCustomGameSettings(gameId, view))
-    }
-
-    private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) {
-        for ((key, updatedSection) in updatedSections) {
-            if (sections.containsKey(key)) {
-                val originalSection = sections[key]
-                originalSection!!.mergeSection(updatedSection!!)
-            } else {
-                sections[key] = updatedSection
-            }
-        }
-    }
-
-    fun loadSettings(gameId: String, view: SettingsActivityView) {
-        this.gameId = gameId
-        loadSettings(view)
-    }
-
-    fun saveSettings(view: SettingsActivityView) {
+    fun saveSettings(gameId: String = "") {
         if (TextUtils.isEmpty(gameId)) {
-            view.showToastMessage(
-                YuzuApplication.appContext.getString(R.string.ini_saved),
-                false
-            )
-
-            for ((fileName, sectionNames) in configFileSectionsMap) {
-                val iniSections = TreeMap<String, SettingSection>()
-                for (section in sectionNames) {
-                    iniSections[section] = sections[section]!!
-                }
-
-                SettingsFile.saveFile(fileName, iniSections, view)
-            }
+            Toast.makeText(
+                context,
+                context.getString(R.string.ini_saved),
+                Toast.LENGTH_SHORT
+            ).show()
+            SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG)
         } else {
-            // Custom game settings
-            view.showToastMessage(
-                YuzuApplication.appContext.getString(R.string.gameid_saved, gameId),
-                false
-            )
-
-            SettingsFile.saveCustomGameSettings(gameId, sections)
+            // TODO: Save custom game settings
+            Toast.makeText(
+                context,
+                context.getString(R.string.gameid_saved, gameId),
+                Toast.LENGTH_SHORT
+            ).show()
         }
     }
 
-    companion object {
-        const val SECTION_GENERAL = "General"
-        const val SECTION_SYSTEM = "System"
-        const val SECTION_RENDERER = "Renderer"
-        const val SECTION_AUDIO = "Audio"
-        const val SECTION_CPU = "Cpu"
-        const val SECTION_THEME = "Theme"
-        const val SECTION_DEBUG = "Debug"
-
-        const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
-
-        const val PREF_OVERLAY_VERSION = "OverlayVersion"
-        const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
-        const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
-        const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
-        val overlayLayoutPrefs = listOf(
-            PREF_LANDSCAPE_OVERLAY_VERSION,
-            PREF_PORTRAIT_OVERLAY_VERSION,
-            PREF_FOLDABLE_OVERLAY_VERSION
-        )
-
-        const val PREF_CONTROL_SCALE = "controlScale"
-        const val PREF_CONTROL_OPACITY = "controlOpacity"
-        const val PREF_TOUCH_ENABLED = "isTouchEnabled"
-        const val PREF_BUTTON_A = "buttonToggle0"
-        const val PREF_BUTTON_B = "buttonToggle1"
-        const val PREF_BUTTON_X = "buttonToggle2"
-        const val PREF_BUTTON_Y = "buttonToggle3"
-        const val PREF_BUTTON_L = "buttonToggle4"
-        const val PREF_BUTTON_R = "buttonToggle5"
-        const val PREF_BUTTON_ZL = "buttonToggle6"
-        const val PREF_BUTTON_ZR = "buttonToggle7"
-        const val PREF_BUTTON_PLUS = "buttonToggle8"
-        const val PREF_BUTTON_MINUS = "buttonToggle9"
-        const val PREF_BUTTON_DPAD = "buttonToggle10"
-        const val PREF_STICK_L = "buttonToggle11"
-        const val PREF_STICK_R = "buttonToggle12"
-        const val PREF_BUTTON_STICK_L = "buttonToggle13"
-        const val PREF_BUTTON_STICK_R = "buttonToggle14"
-        const val PREF_BUTTON_HOME = "buttonToggle15"
-        const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
-
-        const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
-        const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
-        const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
-        const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
-        const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
-
-        const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
-        const val PREF_THEME = "Theme"
-        const val PREF_THEME_MODE = "ThemeMode"
-        const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
-
-        private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
-
-        val overlayPreferences = listOf(
-            PREF_OVERLAY_VERSION,
-            PREF_CONTROL_SCALE,
-            PREF_CONTROL_OPACITY,
-            PREF_TOUCH_ENABLED,
-            PREF_BUTTON_A,
-            PREF_BUTTON_B,
-            PREF_BUTTON_X,
-            PREF_BUTTON_Y,
-            PREF_BUTTON_L,
-            PREF_BUTTON_R,
-            PREF_BUTTON_ZL,
-            PREF_BUTTON_ZR,
-            PREF_BUTTON_PLUS,
-            PREF_BUTTON_MINUS,
-            PREF_BUTTON_DPAD,
-            PREF_STICK_L,
-            PREF_STICK_R,
-            PREF_BUTTON_HOME,
-            PREF_BUTTON_SCREENSHOT,
-            PREF_BUTTON_STICK_L,
-            PREF_BUTTON_STICK_R
-        )
-
-        const val LayoutOption_Unspecified = 0
-        const val LayoutOption_MobilePortrait = 4
-        const val LayoutOption_MobileLandscape = 5
-
-        init {
-            configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
-                listOf(
-                    SECTION_GENERAL,
-                    SECTION_SYSTEM,
-                    SECTION_RENDERER,
-                    SECTION_AUDIO,
-                    SECTION_CPU
-                )
-        }
+    enum class Category {
+        Android,
+        Audio,
+        Core,
+        Cpu,
+        CpuDebug,
+        CpuUnsafe,
+        Renderer,
+        RendererAdvanced,
+        RendererDebug,
+        System,
+        SystemAudio,
+        DataStorage,
+        Debugging,
+        DebuggingGraphics,
+        Miscellaneous,
+        Network,
+        WebService,
+        AddOns,
+        Controls,
+        Ui,
+        UiGeneral,
+        UiLayout,
+        UiGameList,
+        Screenshots,
+        Shortcuts,
+        Multiplayer,
+        Services,
+        Paths,
+        MaxEnum
     }
+
+    val settingsList = listOf<AbstractSetting>(
+        *BooleanSetting.values(),
+        *ByteSetting.values(),
+        *ShortSetting.values(),
+        *IntSetting.values(),
+        *FloatSetting.values(),
+        *LongSetting.values(),
+        *StringSetting.values()
+    )
+
+    const val SECTION_GENERAL = "General"
+    const val SECTION_SYSTEM = "System"
+    const val SECTION_RENDERER = "Renderer"
+    const val SECTION_AUDIO = "Audio"
+    const val SECTION_CPU = "Cpu"
+    const val SECTION_THEME = "Theme"
+    const val SECTION_DEBUG = "Debug"
+
+    const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
+
+    const val PREF_OVERLAY_VERSION = "OverlayVersion"
+    const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
+    const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
+    const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
+    val overlayLayoutPrefs = listOf(
+        PREF_LANDSCAPE_OVERLAY_VERSION,
+        PREF_PORTRAIT_OVERLAY_VERSION,
+        PREF_FOLDABLE_OVERLAY_VERSION
+    )
+
+    const val PREF_CONTROL_SCALE = "controlScale"
+    const val PREF_CONTROL_OPACITY = "controlOpacity"
+    const val PREF_TOUCH_ENABLED = "isTouchEnabled"
+    const val PREF_BUTTON_A = "buttonToggle0"
+    const val PREF_BUTTON_B = "buttonToggle1"
+    const val PREF_BUTTON_X = "buttonToggle2"
+    const val PREF_BUTTON_Y = "buttonToggle3"
+    const val PREF_BUTTON_L = "buttonToggle4"
+    const val PREF_BUTTON_R = "buttonToggle5"
+    const val PREF_BUTTON_ZL = "buttonToggle6"
+    const val PREF_BUTTON_ZR = "buttonToggle7"
+    const val PREF_BUTTON_PLUS = "buttonToggle8"
+    const val PREF_BUTTON_MINUS = "buttonToggle9"
+    const val PREF_BUTTON_DPAD = "buttonToggle10"
+    const val PREF_STICK_L = "buttonToggle11"
+    const val PREF_STICK_R = "buttonToggle12"
+    const val PREF_BUTTON_STICK_L = "buttonToggle13"
+    const val PREF_BUTTON_STICK_R = "buttonToggle14"
+    const val PREF_BUTTON_HOME = "buttonToggle15"
+    const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
+
+    const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
+    const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
+    const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
+    const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
+    const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
+
+    const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
+    const val PREF_THEME = "Theme"
+    const val PREF_THEME_MODE = "ThemeMode"
+    const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
+
+    val overlayPreferences = listOf(
+        PREF_OVERLAY_VERSION,
+        PREF_CONTROL_SCALE,
+        PREF_CONTROL_OPACITY,
+        PREF_TOUCH_ENABLED,
+        PREF_BUTTON_A,
+        PREF_BUTTON_B,
+        PREF_BUTTON_X,
+        PREF_BUTTON_Y,
+        PREF_BUTTON_L,
+        PREF_BUTTON_R,
+        PREF_BUTTON_ZL,
+        PREF_BUTTON_ZR,
+        PREF_BUTTON_PLUS,
+        PREF_BUTTON_MINUS,
+        PREF_BUTTON_DPAD,
+        PREF_STICK_L,
+        PREF_STICK_R,
+        PREF_BUTTON_HOME,
+        PREF_BUTTON_SCREENSHOT,
+        PREF_BUTTON_STICK_L,
+        PREF_BUTTON_STICK_R
+    )
+
+    const val LayoutOption_Unspecified = 0
+    const val LayoutOption_MobilePortrait = 4
+    const val LayoutOption_MobileLandscape = 5
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt
new file mode 100644
index 0000000000..c9a0c664cb
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+enum class ShortSetting(
+    override val key: String,
+    override val category: Settings.Category
+) : AbstractShortSetting {
+    RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core);
+
+    override val short: Short
+        get() = NativeConfig.getShort(key, false)
+
+    override fun setShort(value: Short) = NativeConfig.setShort(key, value)
+
+    override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) }
+
+    override val valueAsString: String
+        get() = short.toString()
+
+    override fun reset() = NativeConfig.setShort(key, defaultValue)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
index 6621289fd5..9bb3e66d4f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
@@ -3,36 +3,24 @@
 
 package org.yuzu.yuzu_emu.features.settings.model
 
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
 enum class StringSetting(
     override val key: String,
-    override val section: String,
-    override val defaultValue: String
+    override val category: Settings.Category
 ) : AbstractStringSetting {
-    AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"),
-    CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
+    // No string settings currently exist
+    EMPTY_SETTING("", Settings.Category.UiGeneral);
 
-    override var string: String = defaultValue
+    override val string: String
+        get() = NativeConfig.getString(key, false)
+
+    override fun setString(value: String) = NativeConfig.setString(key, value)
+
+    override val defaultValue: String by lazy { NativeConfig.getString(key, true) }
 
     override val valueAsString: String
         get() = string
 
-    override val isRuntimeEditable: Boolean
-        get() {
-            for (setting in NOT_RUNTIME_EDITABLE) {
-                if (setting == this) {
-                    return false
-                }
-            }
-            return true
-        }
-
-    companion object {
-        private val NOT_RUNTIME_EDITABLE = listOf(
-            CUSTOM_RTC
-        )
-
-        fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }
-
-        fun clear() = StringSetting.values().forEach { it.string = it.defaultValue }
-    }
+    override fun reset() = NativeConfig.setString(key, defaultValue)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
index bc0bf77889..7c858916e8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
@@ -3,29 +3,29 @@
 
 package org.yuzu.yuzu_emu.features.settings.model.view
 
+import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
 
 class DateTimeSetting(
     setting: AbstractSetting?,
     titleId: Int,
     descriptionId: Int,
     val key: String? = null,
-    private val defaultValue: String? = null
+    private val defaultValue: Long? = null
 ) : SettingsItem(setting, titleId, descriptionId) {
     override val type = TYPE_DATETIME_SETTING
 
-    val value: String
+    val value: Long
         get() = if (setting != null) {
-            val setting = setting as AbstractStringSetting
-            setting.string
+            val setting = setting as AbstractLongSetting
+            setting.long
         } else {
             defaultValue!!
         }
 
-    fun setSelectedValue(datetime: String): AbstractStringSetting {
-        val stringSetting = setting as AbstractStringSetting
-        stringSetting.string = datetime
-        return stringSetting
+    fun setSelectedValue(datetime: Long): AbstractLongSetting {
+        val longSetting = setting as AbstractLongSetting
+        longSetting.setLong(datetime)
+        return longSetting
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 07520849ec..a6cba977c6 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -23,7 +23,7 @@ abstract class SettingsItem(
     val isEditable: Boolean
         get() {
             if (!NativeLibrary.isRunning()) return true
-            return setting?.isRuntimeEditable ?: false
+            return setting?.isRuntimeModifiable ?: false
         }
 
     companion object {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
index 7306ec458e..b6a8c46128 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
@@ -33,7 +33,7 @@ class SingleChoiceSetting(
      */
     fun setSelectedValue(selection: Int): AbstractIntSetting {
         val intSetting = setting as AbstractIntSetting
-        intSetting.int = selection
+        intSetting.setInt(selection)
         return intSetting
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
index 92d0167ae0..e71a29e354 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
@@ -3,10 +3,12 @@
 
 package org.yuzu.yuzu_emu.features.settings.model.view
 
+import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
 import kotlin.math.roundToInt
 import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
+import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
 import org.yuzu.yuzu_emu.utils.Log
 
 class SliderSetting(
@@ -17,14 +19,16 @@ class SliderSetting(
     val max: Int,
     val units: String,
     val key: String? = null,
-    val defaultValue: Int? = null
+    val defaultValue: Any? = null
 ) : SettingsItem(setting, titleId, descriptionId) {
     override val type = TYPE_SLIDER
 
-    val selectedValue: Int
+    val selectedValue: Any
         get() {
             val setting = setting ?: return defaultValue!!
             return when (setting) {
+                is AbstractByteSetting -> setting.byte.toInt()
+                is AbstractShortSetting -> setting.short.toInt()
                 is AbstractIntSetting -> setting.int
                 is AbstractFloatSetting -> setting.float.roundToInt()
                 else -> {
@@ -43,7 +47,7 @@ class SliderSetting(
      */
     fun setSelectedValue(selection: Int): AbstractIntSetting {
         val intSetting = setting as AbstractIntSetting
-        intSetting.int = selection
+        intSetting.setInt(selection)
         return intSetting
     }
 
@@ -56,7 +60,19 @@ class SliderSetting(
      */
     fun setSelectedValue(selection: Float): AbstractFloatSetting {
         val floatSetting = setting as AbstractFloatSetting
-        floatSetting.float = selection
+        floatSetting.setFloat(selection)
         return floatSetting
     }
+
+    fun setSelectedValue(selection: Short): AbstractShortSetting {
+        val shortSetting = setting as AbstractShortSetting
+        shortSetting.setShort(selection)
+        return shortSetting
+    }
+
+    fun setSelectedValue(selection: Byte): AbstractByteSetting {
+        val byteSetting = setting as AbstractByteSetting
+        byteSetting.setByte(selection)
+        return byteSetting
+    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
index 3b6731dcd5..2195641e3f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
@@ -53,7 +53,7 @@ class StringSingleChoiceSetting(
      */
     fun setSelectedValue(selection: String): AbstractStringSetting {
         val stringSetting = setting as AbstractStringSetting
-        stringSetting.string = selection
+        stringSetting.setString(selection)
         return stringSetting
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
index 90b198718a..4ed8070e5b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
@@ -49,14 +49,14 @@ class SwitchSetting(
         // Try integer setting
         try {
             val setting = setting as AbstractIntSetting
-            setting.int = if (checked) 1 else 0
+            setting.setInt(if (checked) 1 else 0)
             return setting
         } catch (_: ClassCastException) {
         }
 
         // Try boolean setting
         val setting = setting as AbstractBooleanSetting
-        setting.boolean = checked
+        setting.setBoolean(checked)
         return setting
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index e6fffc8328..733a53c8cf 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -21,12 +21,7 @@ import com.google.android.material.color.MaterialColors
 import java.io.IOException
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
-import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
-import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
-import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
-import org.yuzu.yuzu_emu.features.settings.model.StringSetting
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.utils.*
 
@@ -35,10 +30,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
 
     private lateinit var binding: ActivitySettingsBinding
 
-    private val settingsViewModel: SettingsViewModel by viewModels()
-
-    override val settings: Settings get() = settingsViewModel.settings
-
     override fun onCreate(savedInstanceState: Bundle?) {
         ThemeHelper.setTheme(this)
 
@@ -171,14 +162,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
         fragment?.loadSettingsList()
     }
 
-    override fun showToastMessage(message: String, is_long: Boolean) {
-        Toast.makeText(
-            this,
-            message,
-            if (is_long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
-        ).show()
-    }
-
     override fun onSettingChanged() {
         presenter.onSettingChanged()
     }
@@ -187,19 +170,18 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
         // Prevents saving to a non-existent settings file
         presenter.onSettingsReset()
 
-        // Reset the static memory representation of each setting
-        BooleanSetting.clear()
-        FloatSetting.clear()
-        IntSetting.clear()
-        StringSetting.clear()
-
         // Delete settings file because the user may have changed values that do not exist in the UI
         val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
         if (!settingsFile.delete()) {
             throw IOException("Failed to delete $settingsFile")
         }
+        Settings.settingsList.forEach { it.reset() }
 
-        showToastMessage(getString(R.string.settings_reset), true)
+        Toast.makeText(
+            applicationContext,
+            getString(R.string.settings_reset),
+            Toast.LENGTH_LONG
+        ).show()
         finish()
     }
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
index 93e677b21b..fdbad32bf1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
@@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
 
 import android.content.Context
 import android.os.Bundle
-import android.text.TextUtils
 import java.io.File
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.features.settings.model.Settings
@@ -14,8 +13,6 @@ import org.yuzu.yuzu_emu.utils.DirectoryInitialization
 import org.yuzu.yuzu_emu.utils.Log
 
 class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
-    val settings: Settings get() = activityView.settings
-
     private var shouldSave = false
     private lateinit var menuTag: String
     private lateinit var gameId: String
@@ -33,13 +30,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
     }
 
     private fun loadSettingsUI() {
-        if (!settings.isLoaded) {
-            if (!TextUtils.isEmpty(gameId)) {
-                settings.loadSettings(gameId, activityView)
-            } else {
-                settings.loadSettings(activityView)
-            }
-        }
+        // TODO: Load custom settings contextually
         activityView.showSettingsFragment(menuTag, false, gameId)
         activityView.onSettingsFileLoaded()
     }
@@ -67,9 +58,9 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
     fun onStop(finishing: Boolean) {
         if (finishing && shouldSave) {
             Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
-            settings.saveSettings(activityView)
+            Settings.saveSettings()
+            NativeLibrary.reloadSettings()
         }
-        NativeLibrary.reloadSettings()
     }
 
     fun onSettingChanged() {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
index c186fc388c..07a58b4eaa 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
@@ -3,8 +3,6 @@
 
 package org.yuzu.yuzu_emu.features.settings.ui
 
-import org.yuzu.yuzu_emu.features.settings.model.Settings
-
 /**
  * Abstraction for the Activity that manages SettingsFragments.
  */
@@ -17,15 +15,6 @@ interface SettingsActivityView {
      */
     fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String)
 
-    /**
-     * Called by a contained Fragment to get access to the Setting HashMap
-     * loaded from disk, so that each Fragment doesn't need to perform its own
-     * read operation.
-     *
-     * @return A HashMap of Settings.
-     */
-    val settings: Settings
-
     /**
      * Called when a load operation completes.
      */
@@ -36,14 +25,6 @@ interface SettingsActivityView {
      */
     fun onSettingsFileNotFound()
 
-    /**
-     * Display a popup text message on screen.
-     *
-     * @param message The contents of the onscreen message.
-     * @param is_long Whether this should be a long Toast or short one.
-     */
-    fun showToastMessage(message: String, is_long: Boolean)
-
     /**
      * End the activity.
      */
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index 9711e2c51e..e2e8d8bece 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -24,12 +24,10 @@ import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
 import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
 import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
 import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
-import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
+import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
 import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
+import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
 import org.yuzu.yuzu_emu.features.settings.model.view.*
 import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
 
@@ -115,8 +113,7 @@ class SettingsAdapter(
     }
 
     fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
-        val setting = item.setChecked(checked)
-        fragmentView.putSetting(setting)
+        item.setChecked(checked)
         fragmentView.onSettingChanged()
     }
 
@@ -150,7 +147,7 @@ class SettingsAdapter(
     fun onDateTimeClick(item: DateTimeSetting, position: Int) {
         clickedItem = item
         clickedPosition = position
-        val storedTime = java.lang.Long.decode(item.value) * 1000
+        val storedTime = item.value * 1000
 
         // Helper to extract hour and minute from epoch time
         val calendar: Calendar = Calendar.getInstance()
@@ -183,13 +180,11 @@ class SettingsAdapter(
             var epochTime: Long = datePicker.selection!! / 1000
             epochTime += timePicker.hour.toLong() * 60 * 60
             epochTime += timePicker.minute.toLong() * 60
-            val rtcString = epochTime.toString()
-            if (item.value != rtcString) {
+            if (item.value != epochTime) {
                 fragmentView.onSettingChanged()
+                notifyItemChanged(clickedPosition)
+                item.setSelectedValue(epochTime)
             }
-            notifyItemChanged(clickedPosition)
-            val setting = item.setSelectedValue(rtcString)
-            fragmentView.putSetting(setting)
             clickedItem = null
         }
         datePicker.show(
@@ -201,7 +196,7 @@ class SettingsAdapter(
     fun onSliderClick(item: SliderSetting, position: Int) {
         clickedItem = item
         clickedPosition = position
-        sliderProgress = item.selectedValue
+        sliderProgress = item.selectedValue as Int
 
         val inflater = LayoutInflater.from(context)
         val sliderBinding = DialogSliderBinding.inflate(inflater)
@@ -249,8 +244,7 @@ class SettingsAdapter(
                 }
 
                 // Get the backing Setting, which may be null (if for example it was missing from the file)
-                val setting = scSetting.setSelectedValue(value)
-                fragmentView.putSetting(setting)
+                scSetting.setSelectedValue(value)
                 closeDialog()
             }
 
@@ -258,8 +252,7 @@ class SettingsAdapter(
                 val scSetting = clickedItem as StringSingleChoiceSetting
                 val value = scSetting.getValueAt(which)
                 if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
-                val setting = scSetting.setSelectedValue(value!!)
-                fragmentView.putSetting(setting)
+                scSetting.setSelectedValue(value!!)
                 closeDialog()
             }
 
@@ -268,13 +261,25 @@ class SettingsAdapter(
                 if (sliderSetting.selectedValue != sliderProgress) {
                     fragmentView.onSettingChanged()
                 }
-                if (sliderSetting.setting is FloatSetting) {
-                    val value = sliderProgress.toFloat()
-                    val setting = sliderSetting.setSelectedValue(value)
-                    fragmentView.putSetting(setting)
-                } else {
-                    val setting = sliderSetting.setSelectedValue(sliderProgress)
-                    fragmentView.putSetting(setting)
+                when (sliderSetting.setting) {
+                    is ByteSetting -> {
+                        val value = sliderProgress.toByte()
+                        sliderSetting.setSelectedValue(value)
+                    }
+
+                    is ShortSetting -> {
+                        val value = sliderProgress.toShort()
+                        sliderSetting.setSelectedValue(value)
+                    }
+
+                    is FloatSetting -> {
+                        val value = sliderProgress.toFloat()
+                        sliderSetting.setSelectedValue(value)
+                    }
+
+                    else -> {
+                        sliderSetting.setSelectedValue(sliderProgress)
+                    }
                 }
                 closeDialog()
             }
@@ -286,13 +291,8 @@ class SettingsAdapter(
     fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
         MaterialAlertDialogBuilder(context)
             .setMessage(R.string.reset_setting_confirmation)
-            .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int ->
-                when (setting) {
-                    is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
-                    is AbstractFloatSetting -> setting.float = setting.defaultValue as Float
-                    is AbstractIntSetting -> setting.int = setting.defaultValue as Int
-                    is AbstractStringSetting -> setting.string = setting.defaultValue as String
-                }
+            .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
+                setting.reset()
                 notifyItemChanged(position)
                 fragmentView.onSettingChanged()
             }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index 70a74c4dda..dc1bf6eb1d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -15,7 +15,6 @@ import androidx.fragment.app.Fragment
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.divider.MaterialDividerItemDecoration
 import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
 import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
 
 class SettingsFragment : Fragment(), SettingsFragmentView {
@@ -89,14 +88,6 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
         )
     }
 
-    override fun showToastMessage(message: String?, is_long: Boolean) {
-        activityView!!.showToastMessage(message!!, is_long)
-    }
-
-    override fun putSetting(setting: AbstractSetting) {
-        fragmentPresenter.putSetting(setting)
-    }
-
     override fun onSettingChanged() {
         activityView!!.onSettingChanged()
     }
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 59c1d9d545..2bab9e5427 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
@@ -6,16 +6,18 @@ package org.yuzu.yuzu_emu.features.settings.ui
 import android.content.SharedPreferences
 import android.os.Build
 import android.text.TextUtils
+import android.widget.Toast
 import androidx.preference.PreferenceManager
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
 import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
 import org.yuzu.yuzu_emu.features.settings.model.IntSetting
+import org.yuzu.yuzu_emu.features.settings.model.LongSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.StringSetting
+import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
 import org.yuzu.yuzu_emu.features.settings.model.view.*
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
@@ -27,7 +29,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     private var settingsList: ArrayList<SettingsItem>? = null
 
     private val settingsActivity get() = fragmentView.activityView as SettingsActivity
-    private val settings get() = fragmentView.activityView!!.settings
 
     private lateinit var preferences: SharedPreferences
 
@@ -41,17 +42,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
         loadSettingsList()
     }
 
-    fun putSetting(setting: AbstractSetting) {
-        if (setting.section == null || setting.key == null) {
-            return
-        }
-
-        val section = settings.getSection(setting.section!!)!!
-        if (section.getSetting(setting.key!!) == null) {
-            section.putSetting(setting)
-        }
-    }
-
     fun loadSettingsList() {
         if (!TextUtils.isEmpty(gameId)) {
             settingsActivity.setToolbarTitle("Game Settings: $gameId")
@@ -69,7 +59,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             Settings.SECTION_THEME -> addThemeSettings(sl)
             Settings.SECTION_DEBUG -> addDebugSettings(sl)
             else -> {
-                fragmentView.showToastMessage("Unimplemented menu", false)
+                val context = YuzuApplication.appContext
+                Toast.makeText(
+                    context,
+                    context.getString(R.string.unimplemented_menu),
+                    Toast.LENGTH_SHORT
+                ).show()
                 return
             }
         }
@@ -135,23 +130,23 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
         sl.apply {
             add(
                 SwitchSetting(
-                    IntSetting.RENDERER_USE_SPEED_LIMIT,
+                    BooleanSetting.RENDERER_USE_SPEED_LIMIT,
                     R.string.frame_limit_enable,
                     R.string.frame_limit_enable_description,
-                    IntSetting.RENDERER_USE_SPEED_LIMIT.key,
-                    IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
+                    BooleanSetting.RENDERER_USE_SPEED_LIMIT.key,
+                    BooleanSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
                 )
             )
             add(
                 SliderSetting(
-                    IntSetting.RENDERER_SPEED_LIMIT,
+                    ShortSetting.RENDERER_SPEED_LIMIT,
                     R.string.frame_limit_slider,
                     R.string.frame_limit_slider_description,
                     1,
                     200,
                     "%",
-                    IntSetting.RENDERER_SPEED_LIMIT.key,
-                    IntSetting.RENDERER_SPEED_LIMIT.defaultValue
+                    ShortSetting.RENDERER_SPEED_LIMIT.key,
+                    ShortSetting.RENDERER_SPEED_LIMIT.defaultValue
                 )
             )
             add(
@@ -182,11 +177,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
         sl.apply {
             add(
                 SwitchSetting(
-                    IntSetting.USE_DOCKED_MODE,
+                    BooleanSetting.USE_DOCKED_MODE,
                     R.string.use_docked_mode,
                     R.string.use_docked_mode_description,
-                    IntSetting.USE_DOCKED_MODE.key,
-                    IntSetting.USE_DOCKED_MODE.defaultValue
+                    BooleanSetting.USE_DOCKED_MODE.key,
+                    BooleanSetting.USE_DOCKED_MODE.defaultValue
                 )
             )
             add(
@@ -222,11 +217,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             )
             add(
                 DateTimeSetting(
-                    StringSetting.CUSTOM_RTC,
+                    LongSetting.CUSTOM_RTC,
                     R.string.set_custom_rtc,
                     0,
-                    StringSetting.CUSTOM_RTC.key,
-                    StringSetting.CUSTOM_RTC.defaultValue
+                    LongSetting.CUSTOM_RTC.key,
+                    LongSetting.CUSTOM_RTC.defaultValue
                 )
             )
         }
@@ -314,38 +309,38 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             )
             add(
                 SwitchSetting(
-                    IntSetting.RENDERER_USE_DISK_SHADER_CACHE,
+                    BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
                     R.string.use_disk_shader_cache,
                     R.string.use_disk_shader_cache_description,
-                    IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
-                    IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
+                    BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
+                    BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
                 )
             )
             add(
                 SwitchSetting(
-                    IntSetting.RENDERER_FORCE_MAX_CLOCK,
+                    BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
                     R.string.renderer_force_max_clock,
                     R.string.renderer_force_max_clock_description,
-                    IntSetting.RENDERER_FORCE_MAX_CLOCK.key,
-                    IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
+                    BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key,
+                    BooleanSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
                 )
             )
             add(
                 SwitchSetting(
-                    IntSetting.RENDERER_ASYNCHRONOUS_SHADERS,
+                    BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
                     R.string.renderer_asynchronous_shaders,
                     R.string.renderer_asynchronous_shaders_description,
-                    IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
-                    IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
+                    BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
+                    BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
                 )
             )
             add(
                 SwitchSetting(
-                    IntSetting.RENDERER_REACTIVE_FLUSHING,
+                    BooleanSetting.RENDERER_REACTIVE_FLUSHING,
                     R.string.renderer_reactive_flushing,
                     R.string.renderer_reactive_flushing_description,
-                    IntSetting.RENDERER_REACTIVE_FLUSHING.key,
-                    IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
+                    BooleanSetting.RENDERER_REACTIVE_FLUSHING.key,
+                    BooleanSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
                 )
             )
         }
@@ -355,26 +350,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
         settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
         sl.apply {
             add(
-                StringSingleChoiceSetting(
-                    StringSetting.AUDIO_OUTPUT_ENGINE,
+                SingleChoiceSetting(
+                    IntSetting.AUDIO_OUTPUT_ENGINE,
                     R.string.audio_output_engine,
                     0,
-                    settingsActivity.resources.getStringArray(R.array.outputEngineEntries),
-                    settingsActivity.resources.getStringArray(R.array.outputEngineValues),
-                    StringSetting.AUDIO_OUTPUT_ENGINE.key,
-                    StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue
+                    R.array.outputEngineEntries,
+                    R.array.outputEngineValues,
+                    IntSetting.AUDIO_OUTPUT_ENGINE.key,
+                    IntSetting.AUDIO_OUTPUT_ENGINE.defaultValue
                 )
             )
             add(
                 SliderSetting(
-                    IntSetting.AUDIO_VOLUME,
+                    ByteSetting.AUDIO_VOLUME,
                     R.string.audio_volume,
                     R.string.audio_volume_description,
                     0,
                     100,
                     "%",
-                    IntSetting.AUDIO_VOLUME.key,
-                    IntSetting.AUDIO_VOLUME.defaultValue
+                    ByteSetting.AUDIO_VOLUME.key,
+                    ByteSetting.AUDIO_VOLUME.defaultValue
                 )
             )
         }
@@ -384,19 +379,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
         settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme))
         sl.apply {
             val theme: AbstractIntSetting = object : AbstractIntSetting {
-                override var int: Int
+                override val int: Int
                     get() = preferences.getInt(Settings.PREF_THEME, 0)
-                    set(value) {
-                        preferences.edit()
-                            .putInt(Settings.PREF_THEME, value)
-                            .apply()
-                        settingsActivity.recreate()
-                    }
+
+                override fun setInt(value: Int) {
+                    preferences.edit()
+                        .putInt(Settings.PREF_THEME, value)
+                        .apply()
+                    settingsActivity.recreate()
+                }
+
                 override val key: String? = null
-                override val section: String? = null
-                override val isRuntimeEditable: Boolean = false
-                override val valueAsString: String
-                    get() = preferences.getInt(Settings.PREF_THEME, 0).toString()
+                override val category = Settings.Category.UiGeneral
+                override val isRuntimeModifiable: Boolean = false
                 override val defaultValue: Any = 0
             }
 
@@ -423,19 +418,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             }
 
             val themeMode: AbstractIntSetting = object : AbstractIntSetting {
-                override var int: Int
+                override val int: Int
                     get() = preferences.getInt(Settings.PREF_THEME_MODE, -1)
-                    set(value) {
-                        preferences.edit()
-                            .putInt(Settings.PREF_THEME_MODE, value)
-                            .apply()
-                        ThemeHelper.setThemeMode(settingsActivity)
-                    }
+
+                override fun setInt(value: Int) {
+                    preferences.edit()
+                        .putInt(Settings.PREF_THEME_MODE, value)
+                        .apply()
+                    ThemeHelper.setThemeMode(settingsActivity)
+                }
+
                 override val key: String? = null
-                override val section: String? = null
-                override val isRuntimeEditable: Boolean = false
-                override val valueAsString: String
-                    get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString()
+                override val category = Settings.Category.UiGeneral
+                override val isRuntimeModifiable: Boolean = false
                 override val defaultValue: Any = -1
             }
 
@@ -450,20 +445,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             )
 
             val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
-                override var boolean: Boolean
+                override val boolean: Boolean
                     get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
-                    set(value) {
-                        preferences.edit()
-                            .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
-                            .apply()
-                        settingsActivity.recreate()
-                    }
+
+                override fun setBoolean(value: Boolean) {
+                    preferences.edit()
+                        .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
+                        .apply()
+                    settingsActivity.recreate()
+                }
+
                 override val key: String? = null
-                override val section: String? = null
-                override val isRuntimeEditable: Boolean = false
-                override val valueAsString: String
-                    get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
-                        .toString()
+                override val category = Settings.Category.UiGeneral
+                override val isRuntimeModifiable: Boolean = false
                 override val defaultValue: Any = false
             }
 
@@ -494,11 +488,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             )
             add(
                 SwitchSetting(
-                    IntSetting.RENDERER_DEBUG,
+                    BooleanSetting.RENDERER_DEBUG,
                     R.string.renderer_debug,
                     R.string.renderer_debug_description,
-                    IntSetting.RENDERER_DEBUG.key,
-                    IntSetting.RENDERER_DEBUG.defaultValue
+                    BooleanSetting.RENDERER_DEBUG.key,
+                    BooleanSetting.RENDERER_DEBUG.defaultValue
                 )
             )
 
@@ -514,17 +508,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             )
 
             val fastmem = object : AbstractBooleanSetting {
-                override var boolean: Boolean
+                override val boolean: Boolean
                     get() =
                         BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
-                    set(value) {
-                        BooleanSetting.FASTMEM.boolean = value
-                        BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
-                    }
+
+                override fun setBoolean(value: Boolean) {
+                    BooleanSetting.FASTMEM.setBoolean(value)
+                    BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
+                }
+
                 override val key: String? = null
-                override val section: String = Settings.SECTION_CPU
-                override val isRuntimeEditable: Boolean = false
-                override val valueAsString: String = ""
+                override val category = Settings.Category.Cpu
+                override val isRuntimeModifiable: Boolean = false
                 override val defaultValue: Any = true
             }
             add(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
index 1ebe35eaad..a4d7a80aac 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
@@ -3,7 +3,6 @@
 
 package org.yuzu.yuzu_emu.features.settings.ui
 
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
 import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
 
 /**
@@ -36,21 +35,6 @@ interface SettingsFragmentView {
      */
     fun loadSubMenu(menuKey: String)
 
-    /**
-     * Tell the Fragment to tell the containing activity to display a toast message.
-     *
-     * @param message Text to be shown in the Toast
-     * @param is_long Whether this should be a long Toast or short one.
-     */
-    fun showToastMessage(message: String?, is_long: Boolean)
-
-    /**
-     * Have the fragment add a setting to the HashMap.
-     *
-     * @param setting The (possibly previously missing) new setting.
-     */
-    fun putSetting(setting: AbstractSetting)
-
     /**
      * Have the fragment tell the containing Activity that a setting was modified.
      */
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
index 79572fc06b..eb25ea4fb7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
@@ -29,7 +29,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
         }
 
         binding.textSettingValue.visibility = View.VISIBLE
-        val epochTime = setting.value.toLong()
+        val epochTime = setting.value
         val instant = Instant.ofEpochMilli(epochTime * 1000)
         val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
         val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
index 70a52df5d4..2b04d666a9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
@@ -3,18 +3,15 @@
 
 package org.yuzu.yuzu_emu.features.settings.utils
 
+import android.widget.Toast
 import java.io.*
-import java.util.*
 import org.ini4j.Wini
-import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.features.settings.model.*
-import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
-import org.yuzu.yuzu_emu.utils.BiMap
 import org.yuzu.yuzu_emu.utils.DirectoryInitialization
 import org.yuzu.yuzu_emu.utils.Log
+import org.yuzu.yuzu_emu.utils.NativeConfig
 
 /**
  * Contains static methods for interacting with .ini files in which settings are stored.
@@ -22,243 +19,41 @@ import org.yuzu.yuzu_emu.utils.Log
 object SettingsFile {
     const val FILE_NAME_CONFIG = "config"
 
-    private var sectionsMap = BiMap<String?, String?>()
-
-    /**
-     * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves
-     * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
-     * failed.
-     *
-     * @param ini          The ini file to load the settings from
-     * @param isCustomGame
-     * @param view         The current view.
-     * @return An Observable that emits a HashMap of the file's contents, then completes.
-     */
-    private fun readFile(
-        ini: File?,
-        isCustomGame: Boolean,
-        view: SettingsActivityView? = null
-    ): HashMap<String, SettingSection?> {
-        val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
-        var reader: BufferedReader? = null
-        try {
-            reader = BufferedReader(FileReader(ini))
-            var current: SettingSection? = null
-            var line: String?
-            while (reader.readLine().also { line = it } != null) {
-                if (line!!.startsWith("[") && line!!.endsWith("]")) {
-                    current = sectionFromLine(line!!, isCustomGame)
-                    sections[current.name] = current
-                } else if (current != null) {
-                    val setting = settingFromLine(line!!)
-                    if (setting != null) {
-                        current.putSetting(setting)
-                    }
-                }
-            }
-        } catch (e: FileNotFoundException) {
-            Log.error("[SettingsFile] File not found: " + e.message)
-            view?.onSettingsFileNotFound()
-        } catch (e: IOException) {
-            Log.error("[SettingsFile] Error reading from: " + e.message)
-            view?.onSettingsFileNotFound()
-        } finally {
-            if (reader != null) {
-                try {
-                    reader.close()
-                } catch (e: IOException) {
-                    Log.error("[SettingsFile] Error closing: " + e.message)
-                }
-            }
-        }
-        return sections
-    }
-
-    fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> {
-        return readFile(getSettingsFile(fileName), false, view)
-    }
-
-    fun readFile(fileName: String): HashMap<String, SettingSection?> =
-        readFile(getSettingsFile(fileName), false)
-
-    /**
-     * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
-     * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
-     * failed.
-     *
-     * @param gameId the id of the game to load it's settings.
-     * @param view   The current view.
-     */
-    fun readCustomGameSettings(
-        gameId: String,
-        view: SettingsActivityView?
-    ): HashMap<String, SettingSection?> {
-        return readFile(getCustomGameSettingsFile(gameId), true, view)
-    }
-
     /**
      * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
      * telling why it failed.
      *
      * @param fileName The target filename without a path or extension.
-     * @param sections The HashMap containing the Settings we want to serialize.
-     * @param view     The current view.
      */
-    fun saveFile(
-        fileName: String,
-        sections: TreeMap<String, SettingSection>,
-        view: SettingsActivityView
-    ) {
+    fun saveFile(fileName: String) {
         val ini = getSettingsFile(fileName)
         try {
-            val writer = Wini(ini)
-            val keySet: Set<String> = sections.keys
-            for (key in keySet) {
-                val section = sections[key]
-                writeSection(writer, section!!)
+            val wini = Wini(ini)
+            for (specificCategory in Settings.Category.values()) {
+                val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal)
+                for (setting in Settings.settingsList) {
+                    if (setting.key!!.isEmpty()) continue
+
+                    val settingCategoryHeader =
+                        NativeConfig.getConfigHeader(setting.category.ordinal)
+                    val iniSetting: String? = wini.get(categoryHeader, setting.key)
+                    if (iniSetting != null || settingCategoryHeader == categoryHeader) {
+                        wini.put(settingCategoryHeader, setting.key, setting.valueAsString)
+                    }
+                }
             }
-            writer.store()
+            wini.store()
         } catch (e: IOException) {
             Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
-            view.showToastMessage(
-                YuzuApplication.appContext
-                    .getString(R.string.error_saving, fileName, e.message),
-                false
-            )
+            val context = YuzuApplication.appContext
+            Toast.makeText(
+                context,
+                context.getString(R.string.error_saving, fileName, e.message),
+                Toast.LENGTH_SHORT
+            ).show()
         }
     }
 
-    fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) {
-        val sortedSections: Set<String> = TreeSet(sections.keys)
-        for (sectionKey in sortedSections) {
-            val section = sections[sectionKey]
-            val settings = section!!.settings
-            val sortedKeySet: Set<String> = TreeSet(settings.keys)
-            for (settingKey in sortedKeySet) {
-                val setting = settings[settingKey]
-                NativeLibrary.setUserSetting(
-                    gameId,
-                    mapSectionNameFromIni(
-                        section.name
-                    ),
-                    setting!!.key,
-                    setting.valueAsString
-                )
-            }
-        }
-    }
-
-    private fun mapSectionNameFromIni(generalSectionName: String): String? {
-        return if (sectionsMap.getForward(generalSectionName) != null) {
-            sectionsMap.getForward(generalSectionName)
-        } else {
-            generalSectionName
-        }
-    }
-
-    private fun mapSectionNameToIni(generalSectionName: String): String {
-        return if (sectionsMap.getBackward(generalSectionName) != null) {
-            sectionsMap.getBackward(generalSectionName).toString()
-        } else {
-            generalSectionName
-        }
-    }
-
-    fun getSettingsFile(fileName: String): File {
-        return File(
-            DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini"
-        )
-    }
-
-    private fun getCustomGameSettingsFile(gameId: String): File {
-        return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini")
-    }
-
-    private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection {
-        var sectionName: String = line.substring(1, line.length - 1)
-        if (isCustomGame) {
-            sectionName = mapSectionNameToIni(sectionName)
-        }
-        return SettingSection(sectionName)
-    }
-
-    /**
-     * For a line of text, determines what type of data is being represented, and returns
-     * a Setting object containing this data.
-     *
-     * @param line    The line of text being parsed.
-     * @return A typed Setting containing the key/value contained in the line.
-     */
-    private fun settingFromLine(line: String): AbstractSetting? {
-        val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
-        if (splitLine.size != 2) {
-            return null
-        }
-        val key = splitLine[0].trim { it <= ' ' }
-        val value = splitLine[1].trim { it <= ' ' }
-        if (value.isEmpty()) {
-            return null
-        }
-
-        val booleanSetting = BooleanSetting.from(key)
-        if (booleanSetting != null) {
-            booleanSetting.boolean = value.toBoolean()
-            return booleanSetting
-        }
-
-        val intSetting = IntSetting.from(key)
-        if (intSetting != null) {
-            intSetting.int = value.toInt()
-            return intSetting
-        }
-
-        val floatSetting = FloatSetting.from(key)
-        if (floatSetting != null) {
-            floatSetting.float = value.toFloat()
-            return floatSetting
-        }
-
-        val stringSetting = StringSetting.from(key)
-        if (stringSetting != null) {
-            stringSetting.string = value
-            return stringSetting
-        }
-
-        return null
-    }
-
-    /**
-     * Writes the contents of a Section HashMap to disk.
-     *
-     * @param parser  A Wini pointed at a file on disk.
-     * @param section A section containing settings to be written to the file.
-     */
-    private fun writeSection(parser: Wini, section: SettingSection) {
-        // Write the section header.
-        val header = section.name
-
-        // Write this section's values.
-        val settings = section.settings
-        val keySet: Set<String> = settings.keys
-        for (key in keySet) {
-            val setting = settings[key]
-            parser.put(header, setting!!.key, setting.valueAsString)
-        }
-
-        BooleanSetting.values().forEach {
-            if (!keySet.contains(it.key)) {
-                parser.put(header, it.key, it.valueAsString)
-            }
-        }
-        IntSetting.values().forEach {
-            if (!keySet.contains(it.key)) {
-                parser.put(header, it.key, it.valueAsString)
-            }
-        }
-        StringSetting.values().forEach {
-            if (!keySet.contains(it.key)) {
-                parser.put(header, it.key, it.valueAsString)
-            }
-        }
-    }
+    fun getSettingsFile(fileName: String): File =
+        File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index aaf3a0ec1e..d8dbf1f45f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -39,7 +39,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
 import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
 import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
 import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
@@ -54,7 +53,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
 
     private val homeViewModel: HomeViewModel by viewModels()
     private val gamesViewModel: GamesViewModel by viewModels()
-    private val settingsViewModel: SettingsViewModel by viewModels()
 
     override var themeId: Int = 0
 
@@ -62,8 +60,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
         val splashScreen = installSplashScreen()
         splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
 
-        settingsViewModel.settings.loadSettings()
-
         ThemeHelper.setTheme(this)
 
         super.onCreate(savedInstanceState)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
deleted file mode 100644
index 9cfda74ee0..0000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.utils
-
-class BiMap<K, V> {
-    private val forward: MutableMap<K, V> = HashMap()
-    private val backward: MutableMap<V, K> = HashMap()
-
-    @Synchronized
-    fun add(key: K, value: V) {
-        forward[key] = value
-        backward[value] = key
-    }
-
-    @Synchronized
-    fun getForward(key: K): V? {
-        return forward[key]
-    }
-
-    @Synchronized
-    fun getBackward(key: V): K? {
-        return backward[key]
-    }
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
new file mode 100644
index 0000000000..d4d981f9ef
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+object NativeConfig {
+    external fun getBoolean(key: String, getDefault: Boolean): Boolean
+    external fun setBoolean(key: String, value: Boolean)
+
+    external fun getByte(key: String, getDefault: Boolean): Byte
+    external fun setByte(key: String, value: Byte)
+
+    external fun getShort(key: String, getDefault: Boolean): Short
+    external fun setShort(key: String, value: Short)
+
+    external fun getInt(key: String, getDefault: Boolean): Int
+    external fun setInt(key: String, value: Int)
+
+    external fun getFloat(key: String, getDefault: Boolean): Float
+    external fun setFloat(key: String, value: Float)
+
+    external fun getLong(key: String, getDefault: Boolean): Long
+    external fun setLong(key: String, value: Long)
+
+    external fun getString(key: String, getDefault: Boolean): String
+    external fun setString(key: String, value: String)
+
+    external fun getIsRuntimeModifiable(key: String): Boolean
+
+    external fun getConfigHeader(category: Int): String
+}
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
index e2ed08e9fc..e15d1480b3 100644
--- a/src/android/app/src/main/jni/CMakeLists.txt
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -14,6 +14,8 @@ add_library(yuzu-android SHARED
     id_cache.cpp
     id_cache.h
     native.cpp
+    native_config.cpp
+    uisettings.cpp
 )
 
 set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index 9de9bd93e2..34b425cb45 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -16,18 +16,20 @@
 #include "input_common/main.h"
 #include "jni/config.h"
 #include "jni/default_ini.h"
+#include "uisettings.h"
 
 namespace FS = Common::FS;
 
-Config::Config(std::optional<std::filesystem::path> config_path)
-    : config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")},
-      config{std::make_unique<INIReader>(FS::PathToUTF8String(config_loc))} {
-    Reload();
+Config::Config(const std::string& config_name, ConfigType config_type)
+    : type(config_type), global{config_type == ConfigType::GlobalConfig} {
+    Initialize(config_name);
 }
 
 Config::~Config() = default;
 
 bool Config::LoadINI(const std::string& default_contents, bool retry) {
+    void(FS::CreateParentDir(config_loc));
+    config = std::make_unique<INIReader>(FS::PathToUTF8String(config_loc));
     const auto config_loc_str = FS::PathToUTF8String(config_loc);
     if (config->ParseError() < 0) {
         if (retry) {
@@ -301,9 +303,28 @@ void Config::ReadValues() {
 
     // Network
     ReadSetting("Network", Settings::values.network_interface);
+
+    // Android
+    ReadSetting("Android", AndroidSettings::values.picture_in_picture);
+    ReadSetting("Android", AndroidSettings::values.screen_layout);
 }
 
-void Config::Reload() {
+void Config::Initialize(const std::string& config_name) {
+    const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
+    const auto config_file = fmt::format("{}.ini", config_name);
+
+    switch (type) {
+    case ConfigType::GlobalConfig:
+        config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
+        break;
+    case ConfigType::PerGameConfig:
+        config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
+        break;
+    case ConfigType::InputProfile:
+        config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
+        LoadINI(DefaultINI::android_config_file);
+        return;
+    }
     LoadINI(DefaultINI::android_config_file);
     ReadValues();
 }
diff --git a/src/android/app/src/main/jni/config.h b/src/android/app/src/main/jni/config.h
index 0d7d6e94df..e1e8f47ed2 100644
--- a/src/android/app/src/main/jni/config.h
+++ b/src/android/app/src/main/jni/config.h
@@ -13,25 +13,35 @@
 class INIReader;
 
 class Config {
-    std::filesystem::path config_loc;
-    std::unique_ptr<INIReader> config;
-
     bool LoadINI(const std::string& default_contents = "", bool retry = true);
-    void ReadValues();
 
 public:
-    explicit Config(std::optional<std::filesystem::path> config_path = std::nullopt);
+    enum class ConfigType {
+        GlobalConfig,
+        PerGameConfig,
+        InputProfile,
+    };
+
+    explicit Config(const std::string& config_name = "config",
+                    ConfigType config_type = ConfigType::GlobalConfig);
     ~Config();
 
-    void Reload();
+    void Initialize(const std::string& config_name);
 
 private:
     /**
-     * Applies a value read from the sdl2_config to a Setting.
+     * Applies a value read from the config to a Setting.
      *
      * @param group The name of the INI group
      * @param setting The yuzu setting to modify
      */
     template <typename Type, bool ranged>
     void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
+
+    void ReadValues();
+
+    const ConfigType type;
+    std::unique_ptr<INIReader> config;
+    std::string config_loc;
+    const bool global;
 };
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 7e17833a0c..b2adfdedaa 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -824,34 +824,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass cl
     Config{};
 }
 
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
-                                                             jstring j_game_id, jstring j_section,
-                                                             jstring j_key) {
-    std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
-    std::string_view section = env->GetStringUTFChars(j_section, 0);
-    std::string_view key = env->GetStringUTFChars(j_key, 0);
-
-    env->ReleaseStringUTFChars(j_game_id, game_id.data());
-    env->ReleaseStringUTFChars(j_section, section.data());
-    env->ReleaseStringUTFChars(j_key, key.data());
-
-    return env->NewStringUTF("");
-}
-
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
-                                                          jstring j_game_id, jstring j_section,
-                                                          jstring j_key, jstring j_value) {
-    std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
-    std::string_view section = env->GetStringUTFChars(j_section, 0);
-    std::string_view key = env->GetStringUTFChars(j_key, 0);
-    std::string_view value = env->GetStringUTFChars(j_value, 0);
-
-    env->ReleaseStringUTFChars(j_game_id, game_id.data());
-    env->ReleaseStringUTFChars(j_section, section.data());
-    env->ReleaseStringUTFChars(j_key, key.data());
-    env->ReleaseStringUTFChars(j_value, value.data());
-}
-
 void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
                                                        jstring j_game_id) {
     std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
new file mode 100644
index 0000000000..6123b3d08d
--- /dev/null
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -0,0 +1,224 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <string>
+
+#include <jni.h>
+
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "jni/android_common/android_common.h"
+#include "jni/config.h"
+#include "uisettings.h"
+
+template <typename T>
+Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
+    auto key = GetJString(env, jkey);
+    auto basicSetting = Settings::values.linkage.by_key[key];
+    auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
+    if (basicSetting != 0) {
+        return static_cast<Settings::Setting<T>*>(basicSetting);
+    }
+    if (basicAndroidSetting != 0) {
+        return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
+    }
+    LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
+    return nullptr;
+}
+
+extern "C" {
+
+jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
+                                                               jstring jkey, jboolean getDefault) {
+    auto setting = getSetting<bool>(env, jkey);
+    if (setting == nullptr) {
+        return false;
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return setting->GetDefault();
+    }
+
+    return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey,
+                                                           jboolean value) {
+    auto setting = getSetting<bool>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+    setting->SetGlobal(true);
+    setting->SetValue(static_cast<bool>(value));
+}
+
+jbyte Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey,
+                                                         jboolean getDefault) {
+    auto setting = getSetting<u8>(env, jkey);
+    if (setting == nullptr) {
+        return -1;
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return setting->GetDefault();
+    }
+
+    return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey,
+                                                        jbyte value) {
+    auto setting = getSetting<u8>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+    setting->SetGlobal(true);
+    setting->SetValue(value);
+}
+
+jshort Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey,
+                                                           jboolean getDefault) {
+    auto setting = getSetting<u16>(env, jkey);
+    if (setting == nullptr) {
+        return -1;
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return setting->GetDefault();
+    }
+
+    return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey,
+                                                         jshort value) {
+    auto setting = getSetting<u16>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+    setting->SetGlobal(true);
+    setting->SetValue(value);
+}
+
+jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
+                                                       jboolean getDefault) {
+    auto setting = getSetting<int>(env, jkey);
+    if (setting == nullptr) {
+        return -1;
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return setting->GetDefault();
+    }
+
+    return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey,
+                                                       jint value) {
+    auto setting = getSetting<int>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+    setting->SetGlobal(true);
+    setting->SetValue(value);
+}
+
+jfloat Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey,
+                                                           jboolean getDefault) {
+    auto setting = getSetting<float>(env, jkey);
+    if (setting == nullptr) {
+        return -1;
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return setting->GetDefault();
+    }
+
+    return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey,
+                                                         jfloat value) {
+    auto setting = getSetting<float>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+    setting->SetGlobal(true);
+    setting->SetValue(value);
+}
+
+jlong Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey,
+                                                         jboolean getDefault) {
+    auto setting = getSetting<long>(env, jkey);
+    if (setting == nullptr) {
+        return -1;
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return setting->GetDefault();
+    }
+
+    return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey,
+                                                        jlong value) {
+    auto setting = getSetting<long>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+    setting->SetGlobal(true);
+    setting->SetValue(value);
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey,
+                                                             jboolean getDefault) {
+    auto setting = getSetting<std::string>(env, jkey);
+    if (setting == nullptr) {
+        return ToJString(env, "");
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return ToJString(env, setting->GetDefault());
+    }
+
+    return ToJString(env, setting->GetValue());
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey,
+                                                          jstring value) {
+    auto setting = getSetting<std::string>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+
+    setting->SetGlobal(true);
+    setting->SetValue(GetJString(env, value));
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj,
+                                                                           jstring jkey) {
+    auto key = GetJString(env, jkey);
+    auto setting = Settings::values.linkage.by_key[key];
+    if (setting != 0) {
+        return setting->RuntimeModfiable();
+    }
+    LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
+    return true;
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj,
+                                                                         jint jcategory) {
+    auto category = static_cast<Settings::Category>(jcategory);
+    return ToJString(env, Settings::TranslateCategory(category));
+}
+
+} // extern "C"
diff --git a/src/android/app/src/main/jni/uisettings.cpp b/src/android/app/src/main/jni/uisettings.cpp
new file mode 100644
index 0000000000..f2f0bad50b
--- /dev/null
+++ b/src/android/app/src/main/jni/uisettings.cpp
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "uisettings.h"
+
+namespace AndroidSettings {
+
+Values values;
+
+} // namespace AndroidSettings
diff --git a/src/android/app/src/main/jni/uisettings.h b/src/android/app/src/main/jni/uisettings.h
new file mode 100644
index 0000000000..494654af75
--- /dev/null
+++ b/src/android/app/src/main/jni/uisettings.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <common/settings_common.h>
+#include "common/common_types.h"
+#include "common/settings_setting.h"
+
+namespace AndroidSettings {
+
+struct Values {
+    Settings::Linkage linkage;
+
+    // Android
+    Settings::Setting<bool> picture_in_picture{linkage, true, "picture_in_picture",
+                                               Settings::Category::Android};
+    Settings::Setting<s32> screen_layout{linkage,
+                                         5,
+                                         "screen_layout",
+                                         Settings::Category::Android,
+                                         Settings::Specialization::Default,
+                                         true,
+                                         true};
+};
+
+extern Values values;
+
+} // namespace AndroidSettings
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 200b99185f..dc10159c9b 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -243,10 +243,10 @@
         <item>@string/cubeb</item>
         <item>@string/string_null</item>
     </string-array>
-    <string-array name="outputEngineValues">
-        <item>auto</item>
-        <item>cubeb</item>
-        <item>null</item>
-    </string-array>
+    <integer-array name="outputEngineValues">
+        <item>0</item>
+        <item>1</item>
+        <item>3</item>
+    </integer-array>
 
 </resources>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index de1b2909b5..df76563fc2 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -200,6 +200,7 @@
     <string name="ini_saved">Saved settings</string>
     <string name="gameid_saved">Saved settings for %1$s</string>
     <string name="error_saving">Error saving %1$s.ini: %2$s</string>
+    <string name="unimplemented_menu">Unimplemented Menu</string>
     <string name="loading">Loading…</string>
     <string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
     <string name="reset_to_default">Reset to default</string>
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 5240568417..4ecaf550b1 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -159,6 +159,8 @@ float Volume() {
 
 const char* TranslateCategory(Category category) {
     switch (category) {
+    case Category::Android:
+        return "Android";
     case Category::Audio:
         return "Audio";
     case Category::Core:
diff --git a/src/common/settings_common.cpp b/src/common/settings_common.cpp
index 137b65d5f7..5960b78aa5 100644
--- a/src/common/settings_common.cpp
+++ b/src/common/settings_common.cpp
@@ -14,6 +14,7 @@ BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Categ
     : label{name}, category{category_}, id{linkage.count}, save{save_},
       runtime_modifiable{runtime_modifiable_}, specialization{specialization_},
       other_setting{other_setting_} {
+    linkage.by_key.insert({name, this});
     linkage.by_category[category].push_back(this);
     linkage.count++;
 }
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 3082e0ce1a..5b170dfd5d 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -12,6 +12,7 @@
 namespace Settings {
 
 enum class Category : u32 {
+    Android,
     Audio,
     Core,
     Cpu,
@@ -68,6 +69,7 @@ public:
     explicit Linkage(u32 initial_count = 0);
     ~Linkage();
     std::map<Category, std::vector<BasicSetting*>> by_category{};
+    std::map<std::string, Settings::BasicSetting*> by_key{};
     std::vector<std::function<void()>> restore_functions{};
     u32 count;
 };

From f5e6b12c74243736a1dc29d297dd2eb1d0c3a4b2 Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Mon, 21 Aug 2023 21:48:21 -0400
Subject: [PATCH 2/9] android: Trim settings enums and items

Take advantage of the new settings interface to reduce the amount of code we need for each setting item. Additionally make all settings items non-null to improve brevity.
---
 .../settings/model/AbstractSetting.kt         |   2 +-
 .../settings/model/view/DateTimeSetting.kt    |  25 +--
 .../settings/model/view/HeaderSetting.kt      |   2 +-
 .../settings/model/view/RunnableSetting.kt    |   2 +-
 .../settings/model/view/SettingsItem.kt       |  12 +-
 .../model/view/SingleChoiceSetting.kt         |  37 ++--
 .../settings/model/view/SliderSetting.kt      |  63 ++----
 .../model/view/StringSingleChoiceSetting.kt   |  46 +----
 .../settings/model/view/SubmenuSetting.kt     |   2 +-
 .../settings/model/view/SwitchSetting.kt      |  53 ++---
 .../features/settings/ui/SettingsAdapter.kt   |  16 +-
 .../settings/ui/SettingsFragmentPresenter.kt  | 184 +++++-------------
 .../ui/viewholder/DateTimeViewHolder.kt       |   2 +-
 .../ui/viewholder/SingleChoiceViewHolder.kt   |   4 +-
 .../ui/viewholder/SliderViewHolder.kt         |   2 +-
 .../ui/viewholder/SwitchSettingViewHolder.kt  |   2 +-
 16 files changed, 133 insertions(+), 321 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
index 7afed95adf..724a2ecb84 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
@@ -15,5 +15,5 @@ interface AbstractSetting {
     val isRuntimeModifiable: Boolean
         get() = NativeConfig.getIsRuntimeModifiable(key!!)
 
-    fun reset() = run { }
+    fun reset()
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
index 7c858916e8..8bc1641978 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
@@ -4,28 +4,15 @@
 package org.yuzu.yuzu_emu.features.settings.model.view
 
 import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
 
 class DateTimeSetting(
-    setting: AbstractSetting?,
+    private val longSetting: AbstractLongSetting,
     titleId: Int,
-    descriptionId: Int,
-    val key: String? = null,
-    private val defaultValue: Long? = null
-) : SettingsItem(setting, titleId, descriptionId) {
+    descriptionId: Int
+) : SettingsItem(longSetting, titleId, descriptionId) {
     override val type = TYPE_DATETIME_SETTING
 
-    val value: Long
-        get() = if (setting != null) {
-            val setting = setting as AbstractLongSetting
-            setting.long
-        } else {
-            defaultValue!!
-        }
-
-    fun setSelectedValue(datetime: Long): AbstractLongSetting {
-        val longSetting = setting as AbstractLongSetting
-        longSetting.setLong(datetime)
-        return longSetting
-    }
+    var value: Long
+        get() = longSetting.long
+        set(value) = (setting as AbstractLongSetting).setLong(value)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
index a670013111..d31ce1c312 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
@@ -5,6 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.model.view
 
 class HeaderSetting(
     titleId: Int
-) : SettingsItem(null, titleId, 0) {
+) : SettingsItem(emptySetting, titleId, 0) {
     override val type = TYPE_HEADER
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
index caaab50d81..522cc49df1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
@@ -8,6 +8,6 @@ class RunnableSetting(
     descriptionId: Int,
     val isRuntimeRunnable: Boolean,
     val runnable: () -> Unit
-) : SettingsItem(null, titleId, descriptionId) {
+) : SettingsItem(emptySetting, titleId, descriptionId) {
     override val type = TYPE_RUNNABLE
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index a6cba977c6..3bdcc5bca2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu.features.settings.model.view
 
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
+import org.yuzu.yuzu_emu.features.settings.model.Settings
 
 /**
  * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
@@ -14,7 +15,7 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
  * file.)
  */
 abstract class SettingsItem(
-    var setting: AbstractSetting?,
+    val setting: AbstractSetting,
     val nameId: Int,
     val descriptionId: Int
 ) {
@@ -23,7 +24,7 @@ abstract class SettingsItem(
     val isEditable: Boolean
         get() {
             if (!NativeLibrary.isRunning()) return true
-            return setting?.isRuntimeModifiable ?: false
+            return setting.isRuntimeModifiable
         }
 
     companion object {
@@ -35,5 +36,12 @@ abstract class SettingsItem(
         const val TYPE_STRING_SINGLE_CHOICE = 5
         const val TYPE_DATETIME_SETTING = 6
         const val TYPE_RUNNABLE = 7
+
+        val emptySetting = object : AbstractSetting {
+            override val key: String = ""
+            override val category: Settings.Category = Settings.Category.Ui
+            override val defaultValue: Any = false
+            override fun reset() {}
+        }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
index b6a8c46128..705527a733 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
@@ -4,36 +4,27 @@
 package org.yuzu.yuzu_emu.features.settings.model.view
 
 import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
+import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
 
 class SingleChoiceSetting(
-    setting: AbstractIntSetting?,
+    setting: AbstractSetting,
     titleId: Int,
     descriptionId: Int,
     val choicesId: Int,
-    val valuesId: Int,
-    val key: String? = null,
-    val defaultValue: Int? = null
+    val valuesId: Int
 ) : SettingsItem(setting, titleId, descriptionId) {
     override val type = TYPE_SINGLE_CHOICE
 
-    val selectedValue: Int
-        get() = if (setting != null) {
-            val setting = setting as AbstractIntSetting
-            setting.int
-        } else {
-            defaultValue!!
+    var selectedValue: Int
+        get() {
+            return when (setting) {
+                is AbstractIntSetting -> setting.int
+                else -> -1
+            }
+        }
+        set(value) {
+            when (setting) {
+                is AbstractIntSetting -> setting.setInt(value)
+            }
         }
-
-    /**
-     * Write a value to the backing int. If that int was previously null,
-     * initializes a new one and returns it, so it can be added to the Hashmap.
-     *
-     * @param selection New value of the int.
-     * @return the existing setting with the new value applied.
-     */
-    fun setSelectedValue(selection: Int): AbstractIntSetting {
-        val intSetting = setting as AbstractIntSetting
-        intSetting.setInt(selection)
-        return intSetting
-    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
index e71a29e354..c3b5df02c9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
@@ -4,75 +4,38 @@
 package org.yuzu.yuzu_emu.features.settings.model.view
 
 import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
-import kotlin.math.roundToInt
 import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
-import org.yuzu.yuzu_emu.utils.Log
+import kotlin.math.roundToInt
 
 class SliderSetting(
-    setting: AbstractSetting?,
+    setting: AbstractSetting,
     titleId: Int,
     descriptionId: Int,
     val min: Int,
     val max: Int,
-    val units: String,
-    val key: String? = null,
-    val defaultValue: Any? = null
+    val units: String
 ) : SettingsItem(setting, titleId, descriptionId) {
     override val type = TYPE_SLIDER
 
-    val selectedValue: Any
+    var selectedValue: Int
         get() {
-            val setting = setting ?: return defaultValue!!
             return when (setting) {
                 is AbstractByteSetting -> setting.byte.toInt()
                 is AbstractShortSetting -> setting.short.toInt()
                 is AbstractIntSetting -> setting.int
                 is AbstractFloatSetting -> setting.float.roundToInt()
-                else -> {
-                    Log.error("[SliderSetting] Error casting setting type.")
-                    -1
-                }
+                else -> -1
+            }
+        }
+        set(value) {
+            when (setting) {
+                is AbstractByteSetting -> setting.setByte(value.toByte())
+                is AbstractShortSetting -> setting.setShort(value.toShort())
+                is AbstractIntSetting -> setting.setInt(value)
+                is AbstractFloatSetting -> setting.setFloat(value.toFloat())
             }
         }
-
-    /**
-     * Write a value to the backing int. If that int was previously null,
-     * initializes a new one and returns it, so it can be added to the Hashmap.
-     *
-     * @param selection New value of the int.
-     * @return the existing setting with the new value applied.
-     */
-    fun setSelectedValue(selection: Int): AbstractIntSetting {
-        val intSetting = setting as AbstractIntSetting
-        intSetting.setInt(selection)
-        return intSetting
-    }
-
-    /**
-     * Write a value to the backing float. If that float was previously null,
-     * initializes a new one and returns it, so it can be added to the Hashmap.
-     *
-     * @param selection New value of the float.
-     * @return the existing setting with the new value applied.
-     */
-    fun setSelectedValue(selection: Float): AbstractFloatSetting {
-        val floatSetting = setting as AbstractFloatSetting
-        floatSetting.setFloat(selection)
-        return floatSetting
-    }
-
-    fun setSelectedValue(selection: Short): AbstractShortSetting {
-        val shortSetting = setting as AbstractShortSetting
-        shortSetting.setShort(selection)
-        return shortSetting
-    }
-
-    fun setSelectedValue(selection: Byte): AbstractByteSetting {
-        val byteSetting = setting as AbstractByteSetting
-        byteSetting.setByte(selection)
-        return byteSetting
-    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
index 2195641e3f..871dab4f3b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
@@ -3,57 +3,31 @@
 
 package org.yuzu.yuzu_emu.features.settings.model.view
 
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
 
 class StringSingleChoiceSetting(
-    setting: AbstractSetting?,
+    private val stringSetting: AbstractStringSetting,
     titleId: Int,
     descriptionId: Int,
     val choices: Array<String>,
-    val values: Array<String>?,
-    val key: String? = null,
-    private val defaultValue: String? = null
-) : SettingsItem(setting, titleId, descriptionId) {
+    val values: Array<String>
+) : SettingsItem(stringSetting, titleId, descriptionId) {
     override val type = TYPE_STRING_SINGLE_CHOICE
 
-    fun getValueAt(index: Int): String? {
-        if (values == null) return null
-        return if (index >= 0 && index < values.size) {
-            values[index]
-        } else {
-            ""
-        }
-    }
+    fun getValueAt(index: Int): String =
+        if (index >= 0 && index < values.size) values[index] else ""
+
+    var selectedValue: String
+        get() = stringSetting.string
+        set(value) = stringSetting.setString(value)
 
-    val selectedValue: String
-        get() = if (setting != null) {
-            val setting = setting as AbstractStringSetting
-            setting.string
-        } else {
-            defaultValue!!
-        }
     val selectValueIndex: Int
         get() {
-            val selectedValue = selectedValue
-            for (i in values!!.indices) {
+            for (i in values.indices) {
                 if (values[i] == selectedValue) {
                     return i
                 }
             }
             return -1
         }
-
-    /**
-     * Write a value to the backing int. If that int was previously null,
-     * initializes a new one and returns it, so it can be added to the Hashmap.
-     *
-     * @param selection New value of the int.
-     * @return the existing setting with the new value applied.
-     */
-    fun setSelectedValue(selection: String): AbstractStringSetting {
-        val stringSetting = setting as AbstractStringSetting
-        stringSetting.setString(selection)
-        return stringSetting
-    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
index 8a9d13a92e..91c273964a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
@@ -7,6 +7,6 @@ class SubmenuSetting(
     titleId: Int,
     descriptionId: Int,
     val menuKey: String
-) : SettingsItem(null, titleId, descriptionId) {
+) : SettingsItem(emptySetting, titleId, descriptionId) {
     override val type = TYPE_SUBMENU
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
index 4ed8070e5b..416967e645 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
@@ -10,53 +10,22 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
 class SwitchSetting(
     setting: AbstractSetting,
     titleId: Int,
-    descriptionId: Int,
-    val key: String? = null,
-    val defaultValue: Any? = null
+    descriptionId: Int
 ) : SettingsItem(setting, titleId, descriptionId) {
     override val type = TYPE_SWITCH
 
-    val isChecked: Boolean
+    var checked: Boolean
         get() {
-            if (setting == null) {
-                return defaultValue as Boolean
+            return when (setting) {
+                is AbstractIntSetting -> setting.int == 1
+                is AbstractBooleanSetting -> setting.boolean
+                else -> false
             }
-
-            // Try integer setting
-            try {
-                val setting = setting as AbstractIntSetting
-                return setting.int == 1
-            } catch (_: ClassCastException) {
-            }
-
-            // Try boolean setting
-            try {
-                val setting = setting as AbstractBooleanSetting
-                return setting.boolean
-            } catch (_: ClassCastException) {
-            }
-            return defaultValue as Boolean
         }
-
-    /**
-     * Write a value to the backing boolean. If that boolean was previously null,
-     * initializes a new one and returns it, so it can be added to the Hashmap.
-     *
-     * @param checked Pretty self explanatory.
-     * @return the existing setting with the new value applied.
-     */
-    fun setChecked(checked: Boolean): AbstractSetting {
-        // Try integer setting
-        try {
-            val setting = setting as AbstractIntSetting
-            setting.setInt(if (checked) 1 else 0)
-            return setting
-        } catch (_: ClassCastException) {
+        set(value) {
+            when (setting) {
+                is AbstractIntSetting -> setting.setInt(if (value) 1 else 0)
+                is AbstractBooleanSetting -> setting.setBoolean(value)
+            }
         }
-
-        // Try boolean setting
-        val setting = setting as AbstractBooleanSetting
-        setting.setBoolean(checked)
-        return setting
-    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index e2e8d8bece..27eaaa576e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -113,7 +113,7 @@ class SettingsAdapter(
     }
 
     fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
-        item.setChecked(checked)
+        item.checked = checked
         fragmentView.onSettingChanged()
     }
 
@@ -183,7 +183,7 @@ class SettingsAdapter(
             if (item.value != epochTime) {
                 fragmentView.onSettingChanged()
                 notifyItemChanged(clickedPosition)
-                item.setSelectedValue(epochTime)
+                item.value = epochTime
             }
             clickedItem = null
         }
@@ -244,7 +244,7 @@ class SettingsAdapter(
                 }
 
                 // Get the backing Setting, which may be null (if for example it was missing from the file)
-                scSetting.setSelectedValue(value)
+                scSetting.selectedValue = value
                 closeDialog()
             }
 
@@ -252,7 +252,7 @@ class SettingsAdapter(
                 val scSetting = clickedItem as StringSingleChoiceSetting
                 val value = scSetting.getValueAt(which)
                 if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
-                scSetting.setSelectedValue(value!!)
+                scSetting.selectedValue = value!!
                 closeDialog()
             }
 
@@ -264,21 +264,21 @@ class SettingsAdapter(
                 when (sliderSetting.setting) {
                     is ByteSetting -> {
                         val value = sliderProgress.toByte()
-                        sliderSetting.setSelectedValue(value)
+                        sliderSetting.selectedValue = value.toInt()
                     }
 
                     is ShortSetting -> {
                         val value = sliderProgress.toShort()
-                        sliderSetting.setSelectedValue(value)
+                        sliderSetting.selectedValue = value.toInt()
                     }
 
                     is FloatSetting -> {
                         val value = sliderProgress.toFloat()
-                        sliderSetting.setSelectedValue(value)
+                        sliderSetting.selectedValue = value.toInt()
                     }
 
                     else -> {
-                        sliderSetting.setSelectedValue(sliderProgress)
+                        sliderSetting.selectedValue = sliderProgress
                     }
                 }
                 closeDialog()
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 2bab9e5427..dddbf65bb7 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
@@ -75,47 +75,13 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
         settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings))
         sl.apply {
+            add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL))
+            add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM))
+            add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER))
+            add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO))
+            add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG))
             add(
-                SubmenuSetting(
-                    R.string.preferences_general,
-                    0,
-                    Settings.SECTION_GENERAL
-                )
-            )
-            add(
-                SubmenuSetting(
-                    R.string.preferences_system,
-                    0,
-                    Settings.SECTION_SYSTEM
-                )
-            )
-            add(
-                SubmenuSetting(
-                    R.string.preferences_graphics,
-                    0,
-                    Settings.SECTION_RENDERER
-                )
-            )
-            add(
-                SubmenuSetting(
-                    R.string.preferences_audio,
-                    0,
-                    Settings.SECTION_AUDIO
-                )
-            )
-            add(
-                SubmenuSetting(
-                    R.string.preferences_debug,
-                    0,
-                    Settings.SECTION_DEBUG
-                )
-            )
-            add(
-                RunnableSetting(
-                    R.string.reset_to_default,
-                    0,
-                    false
-                ) {
+                RunnableSetting(R.string.reset_to_default, 0, false) {
                     ResetSettingsDialogFragment().show(
                         settingsActivity.supportFragmentManager,
                         ResetSettingsDialogFragment.TAG
@@ -132,9 +98,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                 SwitchSetting(
                     BooleanSetting.RENDERER_USE_SPEED_LIMIT,
                     R.string.frame_limit_enable,
-                    R.string.frame_limit_enable_description,
-                    BooleanSetting.RENDERER_USE_SPEED_LIMIT.key,
-                    BooleanSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
+                    R.string.frame_limit_enable_description
                 )
             )
             add(
@@ -144,9 +108,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.frame_limit_slider_description,
                     1,
                     200,
-                    "%",
-                    ShortSetting.RENDERER_SPEED_LIMIT.key,
-                    ShortSetting.RENDERER_SPEED_LIMIT.defaultValue
+                    "%"
                 )
             )
             add(
@@ -155,18 +117,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.cpu_accuracy,
                     0,
                     R.array.cpuAccuracyNames,
-                    R.array.cpuAccuracyValues,
-                    IntSetting.CPU_ACCURACY.key,
-                    IntSetting.CPU_ACCURACY.defaultValue
+                    R.array.cpuAccuracyValues
                 )
             )
             add(
                 SwitchSetting(
                     BooleanSetting.PICTURE_IN_PICTURE,
                     R.string.picture_in_picture,
-                    R.string.picture_in_picture_description,
-                    BooleanSetting.PICTURE_IN_PICTURE.key,
-                    BooleanSetting.PICTURE_IN_PICTURE.defaultValue
+                    R.string.picture_in_picture_description
                 )
             )
         }
@@ -179,9 +137,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                 SwitchSetting(
                     BooleanSetting.USE_DOCKED_MODE,
                     R.string.use_docked_mode,
-                    R.string.use_docked_mode_description,
-                    BooleanSetting.USE_DOCKED_MODE.key,
-                    BooleanSetting.USE_DOCKED_MODE.defaultValue
+                    R.string.use_docked_mode_description
                 )
             )
             add(
@@ -190,9 +146,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.emulated_region,
                     0,
                     R.array.regionNames,
-                    R.array.regionValues,
-                    IntSetting.REGION_INDEX.key,
-                    IntSetting.REGION_INDEX.defaultValue
+                    R.array.regionValues
                 )
             )
             add(
@@ -201,29 +155,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.emulated_language,
                     0,
                     R.array.languageNames,
-                    R.array.languageValues,
-                    IntSetting.LANGUAGE_INDEX.key,
-                    IntSetting.LANGUAGE_INDEX.defaultValue
+                    R.array.languageValues
                 )
             )
             add(
                 SwitchSetting(
                     BooleanSetting.USE_CUSTOM_RTC,
                     R.string.use_custom_rtc,
-                    R.string.use_custom_rtc_description,
-                    BooleanSetting.USE_CUSTOM_RTC.key,
-                    BooleanSetting.USE_CUSTOM_RTC.defaultValue
-                )
-            )
-            add(
-                DateTimeSetting(
-                    LongSetting.CUSTOM_RTC,
-                    R.string.set_custom_rtc,
-                    0,
-                    LongSetting.CUSTOM_RTC.key,
-                    LongSetting.CUSTOM_RTC.defaultValue
+                    R.string.use_custom_rtc_description
                 )
             )
+            add(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
         }
     }
 
@@ -236,9 +178,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.renderer_accuracy,
                     0,
                     R.array.rendererAccuracyNames,
-                    R.array.rendererAccuracyValues,
-                    IntSetting.RENDERER_ACCURACY.key,
-                    IntSetting.RENDERER_ACCURACY.defaultValue
+                    R.array.rendererAccuracyValues
                 )
             )
             add(
@@ -247,9 +187,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.renderer_resolution,
                     0,
                     R.array.rendererResolutionNames,
-                    R.array.rendererResolutionValues,
-                    IntSetting.RENDERER_RESOLUTION.key,
-                    IntSetting.RENDERER_RESOLUTION.defaultValue
+                    R.array.rendererResolutionValues
                 )
             )
             add(
@@ -258,9 +196,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.renderer_vsync,
                     0,
                     R.array.rendererVSyncNames,
-                    R.array.rendererVSyncValues,
-                    IntSetting.RENDERER_VSYNC.key,
-                    IntSetting.RENDERER_VSYNC.defaultValue
+                    R.array.rendererVSyncValues
                 )
             )
             add(
@@ -269,9 +205,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.renderer_scaling_filter,
                     0,
                     R.array.rendererScalingFilterNames,
-                    R.array.rendererScalingFilterValues,
-                    IntSetting.RENDERER_SCALING_FILTER.key,
-                    IntSetting.RENDERER_SCALING_FILTER.defaultValue
+                    R.array.rendererScalingFilterValues
                 )
             )
             add(
@@ -280,9 +214,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.renderer_anti_aliasing,
                     0,
                     R.array.rendererAntiAliasingNames,
-                    R.array.rendererAntiAliasingValues,
-                    IntSetting.RENDERER_ANTI_ALIASING.key,
-                    IntSetting.RENDERER_ANTI_ALIASING.defaultValue
+                    R.array.rendererAntiAliasingValues
                 )
             )
             add(
@@ -291,9 +223,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.renderer_screen_layout,
                     0,
                     R.array.rendererScreenLayoutNames,
-                    R.array.rendererScreenLayoutValues,
-                    IntSetting.RENDERER_SCREEN_LAYOUT.key,
-                    IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue
+                    R.array.rendererScreenLayoutValues
                 )
             )
             add(
@@ -302,45 +232,35 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.renderer_aspect_ratio,
                     0,
                     R.array.rendererAspectRatioNames,
-                    R.array.rendererAspectRatioValues,
-                    IntSetting.RENDERER_ASPECT_RATIO.key,
-                    IntSetting.RENDERER_ASPECT_RATIO.defaultValue
+                    R.array.rendererAspectRatioValues
                 )
             )
             add(
                 SwitchSetting(
                     BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
                     R.string.use_disk_shader_cache,
-                    R.string.use_disk_shader_cache_description,
-                    BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
-                    BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
+                    R.string.use_disk_shader_cache_description
                 )
             )
             add(
                 SwitchSetting(
                     BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
                     R.string.renderer_force_max_clock,
-                    R.string.renderer_force_max_clock_description,
-                    BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key,
-                    BooleanSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
+                    R.string.renderer_force_max_clock_description
                 )
             )
             add(
                 SwitchSetting(
                     BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
                     R.string.renderer_asynchronous_shaders,
-                    R.string.renderer_asynchronous_shaders_description,
-                    BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
-                    BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
+                    R.string.renderer_asynchronous_shaders_description
                 )
             )
             add(
                 SwitchSetting(
                     BooleanSetting.RENDERER_REACTIVE_FLUSHING,
                     R.string.renderer_reactive_flushing,
-                    R.string.renderer_reactive_flushing_description,
-                    BooleanSetting.RENDERER_REACTIVE_FLUSHING.key,
-                    BooleanSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
+                    R.string.renderer_reactive_flushing_description
                 )
             )
         }
@@ -355,9 +275,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.audio_output_engine,
                     0,
                     R.array.outputEngineEntries,
-                    R.array.outputEngineValues,
-                    IntSetting.AUDIO_OUTPUT_ENGINE.key,
-                    IntSetting.AUDIO_OUTPUT_ENGINE.defaultValue
+                    R.array.outputEngineValues
                 )
             )
             add(
@@ -367,9 +285,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.audio_volume_description,
                     0,
                     100,
-                    "%",
-                    ByteSetting.AUDIO_VOLUME.key,
-                    ByteSetting.AUDIO_VOLUME.defaultValue
+                    "%"
                 )
             )
         }
@@ -392,7 +308,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                 override val key: String? = null
                 override val category = Settings.Category.UiGeneral
                 override val isRuntimeModifiable: Boolean = false
-                override val defaultValue: Any = 0
+                override val defaultValue: Int = 0
+                override fun reset() {
+                    preferences.edit()
+                        .putInt(Settings.PREF_THEME, defaultValue)
+                        .apply()
+                }
             }
 
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -431,7 +352,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                 override val key: String? = null
                 override val category = Settings.Category.UiGeneral
                 override val isRuntimeModifiable: Boolean = false
-                override val defaultValue: Any = -1
+                override val defaultValue: Int = -1
+                override fun reset() {
+                    preferences.edit()
+                        .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
+                        .apply()
+                }
             }
 
             add(
@@ -458,7 +384,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                 override val key: String? = null
                 override val category = Settings.Category.UiGeneral
                 override val isRuntimeModifiable: Boolean = false
-                override val defaultValue: Any = false
+                override val defaultValue: Boolean = false
+                override fun reset() {
+                    preferences.edit()
+                        .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
+                        .apply()
+                }
             }
 
             add(
@@ -481,18 +412,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     R.string.renderer_api,
                     0,
                     R.array.rendererApiNames,
-                    R.array.rendererApiValues,
-                    IntSetting.RENDERER_BACKEND.key,
-                    IntSetting.RENDERER_BACKEND.defaultValue
+                    R.array.rendererApiValues
                 )
             )
             add(
                 SwitchSetting(
                     BooleanSetting.RENDERER_DEBUG,
                     R.string.renderer_debug,
-                    R.string.renderer_debug_description,
-                    BooleanSetting.RENDERER_DEBUG.key,
-                    BooleanSetting.RENDERER_DEBUG.defaultValue
+                    R.string.renderer_debug_description
                 )
             )
 
@@ -501,9 +428,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                 SwitchSetting(
                     BooleanSetting.CPU_DEBUG_MODE,
                     R.string.cpu_debug_mode,
-                    R.string.cpu_debug_mode_description,
-                    BooleanSetting.CPU_DEBUG_MODE.key,
-                    BooleanSetting.CPU_DEBUG_MODE.defaultValue
+                    R.string.cpu_debug_mode_description
                 )
             )
 
@@ -520,15 +445,10 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                 override val key: String? = null
                 override val category = Settings.Category.Cpu
                 override val isRuntimeModifiable: Boolean = false
-                override val defaultValue: Any = true
+                override val defaultValue: Boolean = true
+                override fun reset() = setBoolean(defaultValue)
             }
-            add(
-                SwitchSetting(
-                    fastmem,
-                    R.string.fastmem,
-                    0
-                )
-            )
+            add(SwitchSetting(fastmem, R.string.fastmem, 0))
         }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
index eb25ea4fb7..68c0b24d6c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
@@ -46,7 +46,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
 
     override fun onLongClick(clicked: View): Boolean {
         if (setting.isEditable) {
-            return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+            return adapter.onLongClick(setting.setting, bindingAdapterPosition)
         }
         return false
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
index b42d955aa7..a582c425b7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
@@ -35,7 +35,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
                 }
             }
         } else if (item is StringSingleChoiceSetting) {
-            for (i in item.values!!.indices) {
+            for (i in item.values.indices) {
                 if (item.values[i] == item.selectedValue) {
                     binding.textSettingValue.text = item.choices[i]
                     break
@@ -66,7 +66,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
 
     override fun onLongClick(clicked: View): Boolean {
         if (setting.isEditable) {
-            return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+            return adapter.onLongClick(setting.setting, bindingAdapterPosition)
         }
         return false
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
index a23b5d1099..d94a46262c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
@@ -41,7 +41,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
 
     override fun onLongClick(clicked: View): Boolean {
         if (setting.isEditable) {
-            return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+            return adapter.onLongClick(setting.setting, bindingAdapterPosition)
         }
         return false
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index ef34bf5f44..200fbc4738 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
@@ -28,7 +28,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
         binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
             adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked)
         }
-        binding.switchWidget.isChecked = setting.isChecked
+        binding.switchWidget.isChecked = setting.checked
 
         setStyle(setting.isEditable, binding)
     }

From 95a939a49f798c2b4b04d6f8eae33c1d093d43b4 Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Tue, 22 Aug 2023 19:46:07 -0400
Subject: [PATCH 3/9] android: Migrate settings to navigation component

Consolidates all of the settings components to the fragment and activity with no interfaces and only the settings fragment presenter. This also includes new material animations and new viewmodel usage to prevent the fragment and activity directly interacting with one another.
---
 .../java/org/yuzu/yuzu_emu/YuzuApplication.kt |   2 +-
 .../yuzu_emu/activities/EmulationActivity.kt  |   4 +-
 .../features/settings/ui/SettingsActivity.kt  | 176 +++++++-----------
 .../settings/ui/SettingsActivityPresenter.kt  |  81 --------
 .../settings/ui/SettingsActivityView.kt       |  38 ----
 .../features/settings/ui/SettingsAdapter.kt   |  47 +++--
 .../features/settings/ui/SettingsFragment.kt  | 114 ++++++------
 .../settings/ui/SettingsFragmentPresenter.kt  |  68 +++----
 .../settings/ui/SettingsFragmentView.kt       |  42 -----
 .../yuzu_emu/fragments/EmulationFragment.kt   |  11 +-
 .../fragments/HomeSettingsFragment.kt         |  19 +-
 .../yuzu/yuzu_emu/model/SettingsViewModel.kt  |  47 +++++
 .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt |  14 +-
 .../yuzu_emu/utils/DirectoryInitialization.kt |  10 +-
 .../anim_pop_settings_fragment_out.xml        |  16 --
 .../anim-ldrtl/anim_settings_fragment_in.xml  |  16 --
 .../anim/anim_pop_settings_fragment_out.xml   |  16 --
 .../res/anim/anim_settings_fragment_in.xml    |  16 --
 .../res/anim/anim_settings_fragment_out.xml   |  10 -
 .../res/animator/menu_slide_in_from_start.xml |  20 --
 .../res/animator/menu_slide_out_to_start.xml  |  21 ---
 .../src/main/res/layout/activity_settings.xml |  52 ++----
 .../src/main/res/layout/fragment_settings.xml |  39 +++-
 .../app/src/main/res/menu/menu_settings.xml   |   2 -
 .../res/navigation/emulation_navigation.xml   |  17 ++
 .../main/res/navigation/home_navigation.xml   |  17 ++
 .../res/navigation/settings_navigation.xml    |  24 +++
 .../app/src/main/res/values/strings.xml       |   1 +
 28 files changed, 372 insertions(+), 568 deletions(-)
 delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
 delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
 delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
 delete mode 100644 src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml
 delete mode 100644 src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml
 delete mode 100644 src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml
 delete mode 100644 src/android/app/src/main/res/anim/anim_settings_fragment_in.xml
 delete mode 100644 src/android/app/src/main/res/anim/anim_settings_fragment_out.xml
 delete mode 100644 src/android/app/src/main/res/animator/menu_slide_in_from_start.xml
 delete mode 100644 src/android/app/src/main/res/animator/menu_slide_out_to_start.xml
 delete mode 100644 src/android/app/src/main/res/menu/menu_settings.xml
 create mode 100644 src/android/app/src/main/res/navigation/settings_navigation.xml

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
index 04ab6a220a..9561748cbb 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
@@ -46,7 +46,7 @@ class YuzuApplication : Application() {
         super.onCreate()
         application = this
         documentsTree = DocumentsTree()
-        DirectoryInitialization.start(applicationContext)
+        DirectoryInitialization.start()
         GpuDriverHelper.initializeDriverParameters(applicationContext)
         NativeLibrary.logDeviceInfo()
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 6f52a7a8da..dbd602a1d0 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -85,9 +85,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
 
         val navHostFragment =
             supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
-        val navController = navHostFragment.navController
-        navController
-            .setGraph(R.navigation.emulation_navigation, intent.extras)
+        navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
 
         isActivityRecreated = savedInstanceState != null
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index 733a53c8cf..7fd83f5f7a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -3,10 +3,7 @@
 
 package org.yuzu.yuzu_emu.features.settings.ui
 
-import android.content.Context
-import android.content.Intent
 import android.os.Bundle
-import android.view.Menu
 import android.view.View
 import android.view.ViewGroup.MarginLayoutParams
 import android.widget.Toast
@@ -16,20 +13,26 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.updatePadding
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.navArgs
 import com.google.android.material.color.MaterialColors
+import org.yuzu.yuzu_emu.NativeLibrary
 import java.io.IOException
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
 import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
+import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
+import org.yuzu.yuzu_emu.model.SettingsViewModel
 import org.yuzu.yuzu_emu.utils.*
 
-class SettingsActivity : AppCompatActivity(), SettingsActivityView {
-    private val presenter = SettingsActivityPresenter(this)
-
+class SettingsActivity : AppCompatActivity() {
     private lateinit var binding: ActivitySettingsBinding
 
+    private val args by navArgs<SettingsActivityArgs>()
+
+    private val settingsViewModel: SettingsViewModel by viewModels()
+
     override fun onCreate(savedInstanceState: Bundle?) {
         ThemeHelper.setTheme(this)
 
@@ -38,16 +41,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
         binding = ActivitySettingsBinding.inflate(layoutInflater)
         setContentView(binding.root)
 
+        settingsViewModel.game = args.game
+
+        val navHostFragment =
+            supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
+        navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras)
+
         WindowCompat.setDecorFitsSystemWindows(window, false)
 
-        val launcher = intent
-        val gameID = launcher.getStringExtra(ARG_GAME_ID)
-        val menuTag = launcher.getStringExtra(ARG_MENU_TAG)
-        presenter.onCreate(savedInstanceState, menuTag!!, gameID!!)
-
-        // Show "Back" button in the action bar for navigation
-        setSupportActionBar(binding.toolbarSettings)
-        supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+        if (savedInstanceState != null) {
+            settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
+        }
 
         if (InsetsHelper.getSystemGestureType(applicationContext) !=
             InsetsHelper.GESTURE_NAVIGATION
@@ -63,6 +67,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
             )
         }
 
+        settingsViewModel.shouldRecreate.observe(this) {
+            if (it) {
+                settingsViewModel.setShouldRecreate(false)
+                recreate()
+            }
+        }
+        settingsViewModel.shouldNavigateBack.observe(this) {
+            if (it) {
+                settingsViewModel.setShouldNavigateBack(false)
+                navigateBack()
+            }
+        }
+        settingsViewModel.shouldShowResetSettingsDialog.observe(this) {
+            if (it) {
+                settingsViewModel.setShouldShowResetSettingsDialog(false)
+                ResetSettingsDialogFragment().show(
+                    supportFragmentManager,
+                    ResetSettingsDialogFragment.TAG
+                )
+            }
+        }
+
         onBackPressedDispatcher.addCallback(
             this,
             object : OnBackPressedCallback(true) {
@@ -73,34 +99,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
         setInsets()
     }
 
-    override fun onSupportNavigateUp(): Boolean {
-        navigateBack()
-        return true
-    }
-
-    private fun navigateBack() {
-        if (supportFragmentManager.backStackEntryCount > 0) {
-            supportFragmentManager.popBackStack()
+    fun navigateBack() {
+        val navHostFragment =
+            supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
+        if (navHostFragment.childFragmentManager.backStackEntryCount > 0) {
+            navHostFragment.navController.popBackStack()
         } else {
             finish()
         }
     }
 
-    override fun onCreateOptionsMenu(menu: Menu): Boolean {
-        val inflater = menuInflater
-        inflater.inflate(R.menu.menu_settings, menu)
-        return true
-    }
-
     override fun onSaveInstanceState(outState: Bundle) {
         // Critical: If super method is not called, rotations will be busted.
         super.onSaveInstanceState(outState)
-        presenter.saveState(outState)
+        outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
     }
 
     override fun onStart() {
         super.onStart()
-        presenter.onStart()
+        // TODO: Load custom settings contextually
+        if (!DirectoryInitialization.areDirectoriesReady) {
+            DirectoryInitialization.start()
+        }
     }
 
     /**
@@ -110,65 +130,21 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
      */
     override fun onStop() {
         super.onStop()
-        presenter.onStop(isFinishing)
-    }
-
-    override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) {
-        if (!addToStack && settingsFragment != null) {
-            return
+        if (isFinishing && settingsViewModel.shouldSave) {
+            Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
+            Settings.saveSettings()
+            NativeLibrary.reloadSettings()
         }
-
-        val transaction = supportFragmentManager.beginTransaction()
-        if (addToStack) {
-            if (areSystemAnimationsEnabled()) {
-                transaction.setCustomAnimations(
-                    R.anim.anim_settings_fragment_in,
-                    R.anim.anim_settings_fragment_out,
-                    0,
-                    R.anim.anim_pop_settings_fragment_out
-                )
-            }
-            transaction.addToBackStack(null)
-        }
-        transaction.replace(
-            R.id.frame_content,
-            SettingsFragment.newInstance(menuTag, gameId),
-            FRAGMENT_TAG
-        )
-        transaction.commit()
     }
 
-    private fun areSystemAnimationsEnabled(): Boolean {
-        val duration = android.provider.Settings.Global.getFloat(
-            contentResolver,
-            android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
-            1f
-        )
-        val transition = android.provider.Settings.Global.getFloat(
-            contentResolver,
-            android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
-            1f
-        )
-        return duration != 0f && transition != 0f
-    }
-
-    override fun onSettingsFileLoaded() {
-        val fragment: SettingsFragmentView? = settingsFragment
-        fragment?.loadSettingsList()
-    }
-
-    override fun onSettingsFileNotFound() {
-        val fragment: SettingsFragmentView? = settingsFragment
-        fragment?.loadSettingsList()
-    }
-
-    override fun onSettingChanged() {
-        presenter.onSettingChanged()
+    override fun onDestroy() {
+        settingsViewModel.clear()
+        super.onDestroy()
     }
 
     fun onSettingsReset() {
         // Prevents saving to a non-existent settings file
-        presenter.onSettingsReset()
+        settingsViewModel.shouldSave = false
 
         // Delete settings file because the user may have changed values that do not exist in the UI
         val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
@@ -185,47 +161,21 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
         finish()
     }
 
-    fun setToolbarTitle(title: String) {
-        binding.toolbarSettingsLayout.title = title
-    }
-
-    private val settingsFragment: SettingsFragment?
-        get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
-
     private fun setInsets() {
         ViewCompat.setOnApplyWindowInsetsListener(
-            binding.frameContent
+            binding.navigationBarShade
         ) { view: View, windowInsets: WindowInsetsCompat ->
             val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
-            val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
-            view.updatePadding(
-                left = barInsets.left + cutoutInsets.left,
-                right = barInsets.right + cutoutInsets.right
-            )
 
-            val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
-            mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left
-            mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right
-            binding.appbarSettings.layoutParams = mlpAppBar
-
-            val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
+            val mlpShade = view.layoutParams as MarginLayoutParams
             mlpShade.height = barInsets.bottom
-            binding.navigationBarShade.layoutParams = mlpShade
+            view.layoutParams = mlpShade
 
             windowInsets
         }
     }
 
     companion object {
-        private const val ARG_MENU_TAG = "menu_tag"
-        private const val ARG_GAME_ID = "game_id"
-        private const val FRAGMENT_TAG = "settings"
-
-        fun launch(context: Context, menuTag: String?, gameId: String?) {
-            val settings = Intent(context, SettingsActivity::class.java)
-            settings.putExtra(ARG_MENU_TAG, menuTag)
-            settings.putExtra(ARG_GAME_ID, gameId)
-            context.startActivity(settings)
-        }
+        private const val KEY_SHOULD_SAVE = "should_save"
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
deleted file mode 100644
index fdbad32bf1..0000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.ui
-
-import android.content.Context
-import android.os.Bundle
-import java.io.File
-import org.yuzu.yuzu_emu.NativeLibrary
-import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-import org.yuzu.yuzu_emu.utils.DirectoryInitialization
-import org.yuzu.yuzu_emu.utils.Log
-
-class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
-    private var shouldSave = false
-    private lateinit var menuTag: String
-    private lateinit var gameId: String
-
-    fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
-        this.menuTag = menuTag
-        this.gameId = gameId
-        if (savedInstanceState != null) {
-            shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
-        }
-    }
-
-    fun onStart() {
-        prepareDirectoriesIfNeeded()
-    }
-
-    private fun loadSettingsUI() {
-        // TODO: Load custom settings contextually
-        activityView.showSettingsFragment(menuTag, false, gameId)
-        activityView.onSettingsFileLoaded()
-    }
-
-    private fun prepareDirectoriesIfNeeded() {
-        val configFile =
-            File(
-                "${DirectoryInitialization.userDirectory}/config/" +
-                    "${SettingsFile.FILE_NAME_CONFIG}.ini"
-            )
-        if (!configFile.exists()) {
-            Log.error(
-                "${DirectoryInitialization.userDirectory}/config/" +
-                    "${SettingsFile.FILE_NAME_CONFIG}.ini"
-            )
-            Log.error("yuzu config file could not be found!")
-        }
-
-        if (!DirectoryInitialization.areDirectoriesReady) {
-            DirectoryInitialization.start(activityView as Context)
-        }
-        loadSettingsUI()
-    }
-
-    fun onStop(finishing: Boolean) {
-        if (finishing && shouldSave) {
-            Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
-            Settings.saveSettings()
-            NativeLibrary.reloadSettings()
-        }
-    }
-
-    fun onSettingChanged() {
-        shouldSave = true
-    }
-
-    fun onSettingsReset() {
-        shouldSave = false
-    }
-
-    fun saveState(outState: Bundle) {
-        outState.putBoolean(KEY_SHOULD_SAVE, shouldSave)
-    }
-
-    companion object {
-        private const val KEY_SHOULD_SAVE = "should_save"
-    }
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
deleted file mode 100644
index 07a58b4eaa..0000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.ui
-
-/**
- * Abstraction for the Activity that manages SettingsFragments.
- */
-interface SettingsActivityView {
-    /**
-     * Show a new SettingsFragment.
-     *
-     * @param menuTag    Identifier for the settings group that should be displayed.
-     * @param addToStack Whether or not this fragment should replace a previous one.
-     */
-    fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String)
-
-    /**
-     * Called when a load operation completes.
-     */
-    fun onSettingsFileLoaded()
-
-    /**
-     * Called when a load operation fails.
-     */
-    fun onSettingsFileNotFound()
-
-    /**
-     * End the activity.
-     */
-    fun finish()
-
-    /**
-     * Called by a containing Fragment to tell the Activity that a setting was changed;
-     * unless this has been called, the Activity will not save to disk.
-     */
-    fun onSettingChanged()
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index 27eaaa576e..9883c2ec70 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -12,7 +12,8 @@ import android.view.LayoutInflater
 import android.view.ViewGroup
 import android.widget.TextView
 import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.ViewModelProvider
+import androidx.navigation.findNavController
 import androidx.recyclerview.widget.RecyclerView
 import com.google.android.material.datepicker.MaterialDatePicker
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -20,6 +21,7 @@ import com.google.android.material.slider.Slider
 import com.google.android.material.timepicker.MaterialTimePicker
 import com.google.android.material.timepicker.TimeFormat
 import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.SettingsNavigationDirections
 import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
 import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
 import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
@@ -30,18 +32,22 @@ import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
 import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
 import org.yuzu.yuzu_emu.features.settings.model.view.*
 import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
+import org.yuzu.yuzu_emu.model.SettingsViewModel
 
 class SettingsAdapter(
-    private val fragmentView: SettingsFragmentView,
+    private val fragment: SettingsFragment,
     private val context: Context
 ) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
-    private var settings: ArrayList<SettingsItem>? = null
+    private var settings = ArrayList<SettingsItem>()
     private var clickedItem: SettingsItem? = null
     private var clickedPosition: Int
     private var dialog: AlertDialog? = null
     private var sliderProgress = 0
     private var textSliderValue: TextView? = null
 
+    private val settingsViewModel: SettingsViewModel
+        get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
+
     private var defaultCancelListener =
         DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
 
@@ -91,30 +97,22 @@ class SettingsAdapter(
         holder.bind(getItem(position))
     }
 
-    private fun getItem(position: Int): SettingsItem {
-        return settings!![position]
-    }
+    private fun getItem(position: Int): SettingsItem = settings[position]
 
-    override fun getItemCount(): Int {
-        return if (settings != null) {
-            settings!!.size
-        } else {
-            0
-        }
-    }
+    override fun getItemCount(): Int = settings.size
 
     override fun getItemViewType(position: Int): Int {
         return getItem(position).type
     }
 
-    fun setSettingsList(settings: ArrayList<SettingsItem>?) {
+    fun setSettingsList(settings: ArrayList<SettingsItem>) {
         this.settings = settings
         notifyDataSetChanged()
     }
 
     fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
         item.checked = checked
-        fragmentView.onSettingChanged()
+        settingsViewModel.shouldSave = true
     }
 
     private fun onSingleChoiceClick(item: SingleChoiceSetting) {
@@ -155,7 +153,7 @@ class SettingsAdapter(
         calendar.timeZone = TimeZone.getTimeZone("UTC")
 
         var timeFormat: Int = TimeFormat.CLOCK_12H
-        if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) {
+        if (DateFormat.is24HourFormat(context)) {
             timeFormat = TimeFormat.CLOCK_24H
         }
 
@@ -172,7 +170,7 @@ class SettingsAdapter(
 
         datePicker.addOnPositiveButtonClickListener {
             timePicker.show(
-                (fragmentView.activityView as AppCompatActivity).supportFragmentManager,
+                fragment.childFragmentManager,
                 "TimePicker"
             )
         }
@@ -181,14 +179,14 @@ class SettingsAdapter(
             epochTime += timePicker.hour.toLong() * 60 * 60
             epochTime += timePicker.minute.toLong() * 60
             if (item.value != epochTime) {
-                fragmentView.onSettingChanged()
+                settingsViewModel.shouldSave = true
                 notifyItemChanged(clickedPosition)
                 item.value = epochTime
             }
             clickedItem = null
         }
         datePicker.show(
-            (fragmentView.activityView as AppCompatActivity).supportFragmentManager,
+            fragment.childFragmentManager,
             "DatePicker"
         )
     }
@@ -231,7 +229,8 @@ class SettingsAdapter(
     }
 
     fun onSubmenuClick(item: SubmenuSetting) {
-        fragmentView.loadSubMenu(item.menuKey)
+        val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
+        fragment.view?.findNavController()?.navigate(action)
     }
 
     override fun onClick(dialog: DialogInterface, which: Int) {
@@ -240,7 +239,7 @@ class SettingsAdapter(
                 val scSetting = clickedItem as SingleChoiceSetting
                 val value = getValueForSingleChoiceSelection(scSetting, which)
                 if (scSetting.selectedValue != value) {
-                    fragmentView.onSettingChanged()
+                    settingsViewModel.shouldSave = true
                 }
 
                 // Get the backing Setting, which may be null (if for example it was missing from the file)
@@ -251,7 +250,7 @@ class SettingsAdapter(
             is StringSingleChoiceSetting -> {
                 val scSetting = clickedItem as StringSingleChoiceSetting
                 val value = scSetting.getValueAt(which)
-                if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
+                if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
                 scSetting.selectedValue = value!!
                 closeDialog()
             }
@@ -259,7 +258,7 @@ class SettingsAdapter(
             is SliderSetting -> {
                 val sliderSetting = clickedItem as SliderSetting
                 if (sliderSetting.selectedValue != sliderProgress) {
-                    fragmentView.onSettingChanged()
+                    settingsViewModel.shouldSave = true
                 }
                 when (sliderSetting.setting) {
                     is ByteSetting -> {
@@ -294,7 +293,7 @@ class SettingsAdapter(
             .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
                 setting.reset()
                 notifyItemChanged(position)
-                fragmentView.onSettingChanged()
+                settingsViewModel.shouldSave = true
             }
             .setNegativeButton(android.R.string.cancel, null)
             .show()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index dc1bf6eb1d..de6aebd9df 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -3,39 +3,41 @@
 
 package org.yuzu.yuzu_emu.features.settings.ui
 
-import android.content.Context
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
 import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.fragment.navArgs
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.divider.MaterialDividerItemDecoration
+import com.google.android.material.transition.MaterialSharedAxis
+import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
-import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.model.SettingsViewModel
 
-class SettingsFragment : Fragment(), SettingsFragmentView {
-    override var activityView: SettingsActivityView? = null
-
-    private val fragmentPresenter = SettingsFragmentPresenter(this)
+class SettingsFragment : Fragment() {
+    private lateinit var presenter: SettingsFragmentPresenter
     private var settingsAdapter: SettingsAdapter? = null
 
     private var _binding: FragmentSettingsBinding? = null
     private val binding get() = _binding!!
 
-    override fun onAttach(context: Context) {
-        super.onAttach(context)
-        activityView = requireActivity() as SettingsActivityView
-    }
+    private val args by navArgs<SettingsFragmentArgs>()
+
+    private val settingsViewModel: SettingsViewModel by activityViewModels()
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG)
-        val gameId = requireArguments().getString(ARGUMENT_GAME_ID)
-        fragmentPresenter.onCreate(menuTag!!, gameId!!)
+        enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+        returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+        reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+        exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
     }
 
     override fun onCreateView(
@@ -48,7 +50,14 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        settingsAdapter = SettingsAdapter(this, requireActivity())
+        settingsAdapter = SettingsAdapter(this, requireContext())
+        presenter = SettingsFragmentPresenter(
+            settingsViewModel,
+            settingsAdapter!!,
+            args.menuTag,
+            args.game?.gameId ?: ""
+        )
+
         val dividerDecoration = MaterialDividerItemDecoration(
             requireContext(),
             LinearLayoutManager.VERTICAL
@@ -56,63 +65,52 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
         dividerDecoration.isLastItemDecorated = false
         binding.listSettings.apply {
             adapter = settingsAdapter
-            layoutManager = LinearLayoutManager(activity)
+            layoutManager = LinearLayoutManager(requireContext())
             addItemDecoration(dividerDecoration)
         }
-        fragmentPresenter.onViewCreated()
+
+        binding.toolbarSettings.setNavigationOnClickListener {
+            settingsViewModel.setShouldNavigateBack(true)
+        }
+
+        settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) {
+            if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it
+        }
+
+        presenter.onViewCreated()
 
         setInsets()
     }
 
     override fun onDetach() {
         super.onDetach()
-        activityView = null
-        if (settingsAdapter != null) {
-            settingsAdapter!!.closeDialog()
-        }
-    }
-
-    override fun showSettingsList(settingsList: ArrayList<SettingsItem>) {
-        settingsAdapter!!.setSettingsList(settingsList)
-    }
-
-    override fun loadSettingsList() {
-        fragmentPresenter.loadSettingsList()
-    }
-
-    override fun loadSubMenu(menuKey: String) {
-        activityView!!.showSettingsFragment(
-            menuKey,
-            true,
-            requireArguments().getString(ARGUMENT_GAME_ID)!!
-        )
-    }
-
-    override fun onSettingChanged() {
-        activityView!!.onSettingChanged()
+        settingsAdapter?.closeDialog()
     }
 
     private fun setInsets() {
         ViewCompat.setOnApplyWindowInsetsListener(
-            binding.listSettings
-        ) { view: View, windowInsets: WindowInsetsCompat ->
-            val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
-            view.updatePadding(bottom = insets.bottom)
+            binding.root
+        ) { _: View, windowInsets: WindowInsetsCompat ->
+            val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+            val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
+
+            val leftInsets = barInsets.left + cutoutInsets.left
+            val rightInsets = barInsets.right + cutoutInsets.right
+
+            val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
+            val mlpSettingsList = binding.listSettings.layoutParams as MarginLayoutParams
+            mlpSettingsList.leftMargin = sideMargin + leftInsets
+            mlpSettingsList.rightMargin = sideMargin + rightInsets
+            binding.listSettings.layoutParams = mlpSettingsList
+            binding.listSettings.updatePadding(
+                bottom = barInsets.bottom
+            )
+
+            val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
+            mlpAppBar.leftMargin = leftInsets
+            mlpAppBar.rightMargin = rightInsets
+            binding.appbarSettings.layoutParams = mlpAppBar
             windowInsets
         }
     }
-
-    companion object {
-        private const val ARGUMENT_MENU_TAG = "menu_tag"
-        private const val ARGUMENT_GAME_ID = "game_id"
-
-        fun newInstance(menuTag: String?, gameId: String?): Fragment {
-            val fragment = SettingsFragment()
-            val arguments = Bundle()
-            arguments.putString(ARGUMENT_MENU_TAG, menuTag)
-            arguments.putString(ARGUMENT_GAME_ID, gameId)
-            fragment.arguments = arguments
-            return fragment
-        }
-    }
 }
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 dddbf65bb7..ba45c317dd 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,6 +3,7 @@
 
 package org.yuzu.yuzu_emu.features.settings.ui
 
+import android.content.Context
 import android.content.SharedPreferences
 import android.os.Build
 import android.text.TextUtils
@@ -20,36 +21,36 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
 import org.yuzu.yuzu_emu.features.settings.model.view.*
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
-import org.yuzu.yuzu_emu.utils.ThemeHelper
+import org.yuzu.yuzu_emu.model.SettingsViewModel
 
-class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
-    private var menuTag: String? = null
-    private lateinit var gameId: String
-    private var settingsList: ArrayList<SettingsItem>? = null
+class SettingsFragmentPresenter(
+    private val settingsViewModel: SettingsViewModel,
+    private val adapter: SettingsAdapter,
+    private var menuTag: String,
+    private var gameId: String
+) {
+    private var settingsList = ArrayList<SettingsItem>()
 
-    private val settingsActivity get() = fragmentView.activityView as SettingsActivity
+    private val preferences: SharedPreferences
+        get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
 
-    private lateinit var preferences: SharedPreferences
-
-    fun onCreate(menuTag: String, gameId: String) {
-        this.gameId = gameId
-        this.menuTag = menuTag
-    }
+    private val context: Context get() = YuzuApplication.appContext
 
     fun onViewCreated() {
-        preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
         loadSettingsList()
     }
 
-    fun loadSettingsList() {
+    private fun loadSettingsList() {
         if (!TextUtils.isEmpty(gameId)) {
-            settingsActivity.setToolbarTitle("Game Settings: $gameId")
+            settingsViewModel.setToolbarTitle(
+                context.getString(
+                    R.string.advanced_settings_game,
+                    gameId
+                )
+            )
         }
+
         val sl = ArrayList<SettingsItem>()
-        if (menuTag == null) {
-            return
-        }
         when (menuTag) {
             SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
             Settings.SECTION_GENERAL -> addGeneralSettings(sl)
@@ -69,11 +70,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             }
         }
         settingsList = sl
-        fragmentView.showSettingsList(settingsList!!)
+        adapter.setSettingsList(settingsList)
     }
 
     private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
         sl.apply {
             add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL))
             add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM))
@@ -82,17 +83,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG))
             add(
                 RunnableSetting(R.string.reset_to_default, 0, false) {
-                    ResetSettingsDialogFragment().show(
-                        settingsActivity.supportFragmentManager,
-                        ResetSettingsDialogFragment.TAG
-                    )
+                    settingsViewModel.setShouldShowResetSettingsDialog(true)
                 }
             )
         }
     }
 
     private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
         sl.apply {
             add(
                 SwitchSetting(
@@ -131,7 +129,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     }
 
     private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
         sl.apply {
             add(
                 SwitchSetting(
@@ -170,7 +168,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     }
 
     private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
         sl.apply {
             add(
                 SingleChoiceSetting(
@@ -267,7 +265,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     }
 
     private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
         sl.apply {
             add(
                 SingleChoiceSetting(
@@ -292,7 +290,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     }
 
     private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
         sl.apply {
             val theme: AbstractIntSetting = object : AbstractIntSetting {
                 override val int: Int
@@ -302,7 +300,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     preferences.edit()
                         .putInt(Settings.PREF_THEME, value)
                         .apply()
-                    settingsActivity.recreate()
+                    settingsViewModel.setShouldRecreate(true)
                 }
 
                 override val key: String? = null
@@ -346,7 +344,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     preferences.edit()
                         .putInt(Settings.PREF_THEME_MODE, value)
                         .apply()
-                    ThemeHelper.setThemeMode(settingsActivity)
+                    settingsViewModel.setShouldRecreate(true)
                 }
 
                 override val key: String? = null
@@ -357,6 +355,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     preferences.edit()
                         .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
                         .apply()
+                    settingsViewModel.setShouldRecreate(true)
                 }
             }
 
@@ -378,7 +377,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     preferences.edit()
                         .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
                         .apply()
-                    settingsActivity.recreate()
+                    settingsViewModel.setShouldRecreate(true)
                 }
 
                 override val key: String? = null
@@ -389,6 +388,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
                     preferences.edit()
                         .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
                         .apply()
+                    settingsViewModel.setShouldRecreate(true)
                 }
             }
 
@@ -403,7 +403,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     }
 
     private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
         sl.apply {
             add(HeaderSetting(R.string.gpu))
             add(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
deleted file mode 100644
index a4d7a80aac..0000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.ui
-
-import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
-
-/**
- * Abstraction for a screen showing a list of settings. Instances of
- * this type of view will each display a layer of the setting hierarchy.
- */
-interface SettingsFragmentView {
-    /**
-     * Pass an ArrayList to the View so that it can be displayed on screen.
-     *
-     * @param settingsList The result of converting the HashMap to an ArrayList
-     */
-    fun showSettingsList(settingsList: ArrayList<SettingsItem>)
-
-    /**
-     * Instructs the Fragment to load the settings screen.
-     */
-    fun loadSettingsList()
-
-    /**
-     * @return The Fragment's containing activity.
-     */
-    val activityView: SettingsActivityView?
-
-    /**
-     * Tell the Fragment to tell the containing Activity to show a new
-     * Fragment containing a submenu of settings.
-     *
-     * @param menuKey Identifier for the settings group that should be shown.
-     */
-    fun loadSubMenu(menuKey: String)
-
-    /**
-     * Have the fragment tell the containing Activity that a setting was modified.
-     */
-    fun onSettingChanged()
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 09e93a017e..70df3af802 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -28,6 +28,7 @@ import androidx.fragment.app.Fragment
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.findNavController
 import androidx.navigation.fragment.navArgs
 import androidx.preference.PreferenceManager
 import androidx.window.layout.FoldingFeature
@@ -37,6 +38,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.android.material.slider.Slider
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
@@ -45,7 +47,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
 import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
 import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.overlay.InputOverlay
 import org.yuzu.yuzu_emu.utils.*
@@ -139,7 +140,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                 }
 
                 R.id.menu_settings -> {
-                    SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "")
+                    val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+                        null,
+                        SettingsFile.FILE_NAME_CONFIG
+                    )
+                    binding.root.findNavController().navigate(action)
                     true
                 }
 
@@ -211,7 +216,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
     override fun onResume() {
         super.onResume()
         if (!DirectoryInitialization.areDirectoriesReady) {
-            DirectoryInitialization.start(requireContext())
+            DirectoryInitialization.start()
         }
 
         updateScreenLayout()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index d5e7934915..cbbe14d220 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -25,17 +25,18 @@ import androidx.core.view.updatePadding
 import androidx.documentfile.provider.DocumentFile
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import androidx.navigation.findNavController
 import androidx.navigation.fragment.findNavController
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.android.material.transition.MaterialSharedAxis
 import org.yuzu.yuzu_emu.BuildConfig
+import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
 import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
 import org.yuzu.yuzu_emu.features.DocumentProvider
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.model.HomeSetting
 import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -74,7 +75,13 @@ class HomeSettingsFragment : Fragment() {
                     R.string.advanced_settings,
                     R.string.settings_description,
                     R.drawable.ic_settings,
-                    { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
+                    {
+                        val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+                            null,
+                            SettingsFile.FILE_NAME_CONFIG
+                        )
+                        binding.root.findNavController().navigate(action)
+                    }
                 )
             )
             add(
@@ -90,7 +97,13 @@ class HomeSettingsFragment : Fragment() {
                     R.string.preferences_theme,
                     R.string.theme_and_color_description,
                     R.drawable.ic_palette,
-                    { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
+                    {
+                        val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+                            null,
+                            Settings.SECTION_THEME
+                        )
+                        binding.root.findNavController().navigate(action)
+                    }
                 )
             )
             add(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
new file mode 100644
index 0000000000..1763341e20
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class SettingsViewModel : ViewModel() {
+    var game: Game? = null
+
+    var shouldSave = false
+
+    private val _toolbarTitle = MutableLiveData("")
+    val toolbarTitle: LiveData<String> get() = _toolbarTitle
+
+    private val _shouldRecreate = MutableLiveData(false)
+    val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate
+
+    private val _shouldNavigateBack = MutableLiveData(false)
+    val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack
+
+    private val _shouldShowResetSettingsDialog = MutableLiveData(false)
+    val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog
+
+    fun setToolbarTitle(value: String) {
+        _toolbarTitle.value = value
+    }
+
+    fun setShouldRecreate(value: Boolean) {
+        _shouldRecreate.value = value
+    }
+
+    fun setShouldNavigateBack(value: Boolean) {
+        _shouldNavigateBack.value = value
+    }
+
+    fun setShouldShowResetSettingsDialog(value: Boolean) {
+        _shouldShowResetSettingsDialog.value = value
+    }
+
+    fun clear() {
+        game = null
+        shouldSave = false
+    }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index d8dbf1f45f..7735452e51 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -33,13 +33,13 @@ import java.io.IOException
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
+import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.activities.EmulationActivity
 import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
 import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
 import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
@@ -105,11 +105,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
             when (it.itemId) {
                 R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
                 R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
-                R.id.homeSettingsFragment -> SettingsActivity.launch(
-                    this,
-                    SettingsFile.FILE_NAME_CONFIG,
-                    ""
-                )
+                R.id.homeSettingsFragment -> {
+                    val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+                        null,
+                        SettingsFile.FILE_NAME_CONFIG
+                    )
+                    navHostFragment.navController.navigate(action)
+                }
             }
         }
 
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 2ee63697eb..3c9f6bad0f 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,18 +3,18 @@
 
 package org.yuzu.yuzu_emu.utils
 
-import android.content.Context
 import java.io.IOException
 import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.YuzuApplication
 
 object DirectoryInitialization {
     private var userPath: String? = null
 
     var areDirectoriesReady: Boolean = false
 
-    fun start(context: Context) {
+    fun start() {
         if (!areDirectoriesReady) {
-            initializeInternalStorage(context)
+            initializeInternalStorage()
             NativeLibrary.initializeEmulation()
             areDirectoriesReady = true
         }
@@ -26,9 +26,9 @@ object DirectoryInitialization {
             return userPath
         }
 
-    private fun initializeInternalStorage(context: Context) {
+    private fun initializeInternalStorage() {
         try {
-            userPath = context.getExternalFilesDir(null)!!.canonicalPath
+            userPath = YuzuApplication.appContext.getExternalFilesDir(null)!!.canonicalPath
             NativeLibrary.setAppDirectory(userPath!!)
         } catch (e: IOException) {
             e.printStackTrace()
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml
deleted file mode 100644
index 9f49c133a0..0000000000
--- a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <alpha
-        android:duration="125"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromAlpha="1"
-        android:toAlpha="0" />
-
-    <translate
-        android:duration="125"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromXDelta="0"
-        android:toXDelta="-75" />
-
-</set>
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml
deleted file mode 100644
index 82fd719dba..0000000000
--- a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <alpha
-        android:duration="@android:integer/config_shortAnimTime"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromAlpha="0"
-        android:toAlpha="1" />
-
-    <translate
-        android:duration="@android:integer/config_shortAnimTime"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromXDelta="-200"
-        android:toXDelta="0" />
-
-</set>
diff --git a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml
deleted file mode 100644
index 5892128f18..0000000000
--- a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <alpha
-        android:duration="125"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromAlpha="1"
-        android:toAlpha="0" />
-
-    <translate
-        android:duration="125"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromXDelta="0"
-        android:toXDelta="75" />
-
-</set>
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml
deleted file mode 100644
index 98e0cf8bd6..0000000000
--- a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <alpha
-        android:duration="@android:integer/config_shortAnimTime"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromAlpha="0"
-        android:toAlpha="1" />
-
-    <translate
-        android:duration="@android:integer/config_shortAnimTime"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromXDelta="200"
-        android:toXDelta="0" />
-
-</set>
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml
deleted file mode 100644
index 77a40a4d1b..0000000000
--- a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <alpha
-        android:duration="@android:integer/config_shortAnimTime"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromAlpha="1"
-        android:toAlpha="0" />
-
-</set>
diff --git a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml b/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml
deleted file mode 100644
index 4612aee134..0000000000
--- a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <objectAnimator
-        android:propertyName="translationX"
-        android:valueType="floatType"
-        android:valueFrom="-1280dp"
-        android:valueTo="0"
-        android:interpolator="@android:interpolator/decelerate_quad"
-        android:duration="300"/>
-
-    <objectAnimator
-        android:propertyName="alpha"
-        android:valueType="floatType"
-        android:valueFrom="0"
-        android:valueTo="1"
-        android:interpolator="@android:interpolator/accelerate_quad"
-        android:duration="300"/>
-
-</set>
\ No newline at end of file
diff --git a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml b/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml
deleted file mode 100644
index c004789466..0000000000
--- a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <!-- This animation is used ONLY when a submenu is replaced. -->
-    <objectAnimator
-        android:propertyName="translationX"
-        android:valueType="floatType"
-        android:valueFrom="0"
-        android:valueTo="-1280dp"
-        android:interpolator="@android:interpolator/decelerate_quad"
-        android:duration="200"/>
-
-    <objectAnimator
-        android:propertyName="alpha"
-        android:valueType="floatType"
-        android:valueFrom="1"
-        android:valueTo="0"
-        android:interpolator="@android:interpolator/decelerate_quad"
-        android:duration="200"/>
-
-</set>
\ No newline at end of file
diff --git a/src/android/app/src/main/res/layout/activity_settings.xml b/src/android/app/src/main/res/layout/activity_settings.xml
index 14ae83b041..8a026a30ac 100644
--- a/src/android/app/src/main/res/layout/activity_settings.xml
+++ b/src/android/app/src/main/res/layout/activity_settings.xml
@@ -1,42 +1,24 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout
-    android:id="@+id/coordinator_main"
+<androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/constraint_settings"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="?attr/colorSurface">
 
-    <com.google.android.material.appbar.AppBarLayout
-        android:id="@+id/appbar_settings"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:fitsSystemWindows="true"
-        app:elevation="0dp">
-
-        <com.google.android.material.appbar.CollapsingToolbarLayout
-            style="?attr/collapsingToolbarLayoutMediumStyle"
-            android:id="@+id/toolbar_settings_layout"
-            android:layout_width="match_parent"
-            android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
-            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
-
-            <com.google.android.material.appbar.MaterialToolbar
-                android:id="@+id/toolbar_settings"
-                android:layout_width="match_parent"
-                android:layout_height="?attr/actionBarSize"
-                app:layout_collapseMode="pin" />
-
-        </com.google.android.material.appbar.CollapsingToolbarLayout>
-
-    </com.google.android.material.appbar.AppBarLayout>
-
-    <FrameLayout
-        android:id="@+id/frame_content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginHorizontal="12dp"
-        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+    <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/fragment_container"
+        android:name="androidx.navigation.fragment.NavHostFragment"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:defaultNavHost="true"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:layout="@layout/fragment_settings" />
 
     <View
         android:id="@+id/navigation_bar_shade"
@@ -45,6 +27,8 @@
         android:background="@android:color/transparent"
         android:clickable="false"
         android:focusable="false"
-        android:layout_gravity="bottom|center_horizontal" />
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
 
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_settings.xml b/src/android/app/src/main/res/layout/fragment_settings.xml
index 1677203476..ebedbf1ec4 100644
--- a/src/android/app/src/main/res/layout/fragment_settings.xml
+++ b/src/android/app/src/main/res/layout/fragment_settings.xml
@@ -1,14 +1,41 @@
 <?xml version="1.0" encoding="utf-8"?>
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/coordinator_main"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:background="?attr/colorSurface">
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/appbar_settings"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fitsSystemWindows="true"
+        app:elevation="0dp">
+
+        <com.google.android.material.appbar.CollapsingToolbarLayout
+            android:id="@+id/toolbar_settings_layout"
+            style="?attr/collapsingToolbarLayoutMediumStyle"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
+
+            <com.google.android.material.appbar.MaterialToolbar
+                android:id="@+id/toolbar_settings"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                app:layout_collapseMode="pin"
+                app:navigationIcon="@drawable/ic_back" />
+
+        </com.google.android.material.appbar.CollapsingToolbarLayout>
+
+    </com.google.android.material.appbar.AppBarLayout>
 
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/list_settings"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="?attr/colorSurface"
-        android:clipToPadding="false" />
+        android:clipToPadding="false"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
 
-</FrameLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/src/android/app/src/main/res/menu/menu_settings.xml b/src/android/app/src/main/res/menu/menu_settings.xml
deleted file mode 100644
index 1fe7aa6d4c..0000000000
--- a/src/android/app/src/main/res/menu/menu_settings.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<menu />
\ No newline at end of file
diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml
index 8208f4c2c4..2a3c23d554 100644
--- a/src/android/app/src/main/res/navigation/emulation_navigation.xml
+++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml
@@ -15,4 +15,21 @@
             app:argType="org.yuzu.yuzu_emu.model.Game" />
     </fragment>
 
+    <activity
+        android:id="@+id/settingsActivity"
+        android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
+        android:label="SettingsActivity">
+        <argument
+            android:name="game"
+            app:argType="org.yuzu.yuzu_emu.model.Game"
+            app:nullable="true" />
+        <argument
+            android:name="menuTag"
+            app:argType="string" />
+    </activity>
+
+    <action
+        android:id="@+id/action_global_settingsActivity"
+        app:destination="@id/settingsActivity" />
+
 </navigation>
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index fcebba7266..fb8e14448c 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -70,4 +70,21 @@
         app:destination="@id/emulationActivity"
         app:launchSingleTop="true" />
 
+    <activity
+        android:id="@+id/settingsActivity"
+        android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
+        android:label="SettingsActivity">
+        <argument
+            android:name="game"
+            app:argType="org.yuzu.yuzu_emu.model.Game"
+            app:nullable="true" />
+        <argument
+            android:name="menuTag"
+            app:argType="string" />
+    </activity>
+
+    <action
+        android:id="@+id/action_global_settingsActivity"
+        app:destination="@id/settingsActivity" />
+
 </navigation>
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml
new file mode 100644
index 0000000000..b36200c655
--- /dev/null
+++ b/src/android/app/src/main/res/navigation/settings_navigation.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/settings_navigation"
+    app:startDestination="@id/settingsFragment">
+
+    <fragment
+        android:id="@+id/settingsFragment"
+        android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsFragment"
+        android:label="SettingsFragment">
+        <argument
+            android:name="menuTag"
+            app:argType="string" />
+        <argument
+            android:name="game"
+            app:argType="org.yuzu.yuzu_emu.model.Game"
+            app:nullable="true" />
+    </fragment>
+
+    <action
+        android:id="@+id/action_global_settingsFragment"
+        app:destination="@id/settingsFragment" />
+
+</navigation>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index df76563fc2..6b782780a1 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -74,6 +74,7 @@
     <string name="install_gpu_driver">Install GPU driver</string>
     <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
     <string name="advanced_settings">Advanced settings</string>
+    <string name="advanced_settings_game">Advanced settings: %1$s</string>
     <string name="settings_description">Configure emulator settings</string>
     <string name="search_recently_played">Recently played</string>
     <string name="search_recently_added">Recently added</string>

From 369d06292fae1ac37dfb2863deb16978f29094ad Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Tue, 22 Aug 2023 19:49:02 -0400
Subject: [PATCH 4/9] android: Prevent infinite switch toggle loop

If something like a lifecycle event happens when this switch is toggled (Ex. whenever the black backgrounds switch is toggled), this could move the switch from the default position and trigger the checked changed listener and restart the loop. Here I just removed the listener at the start so we recycle the view properly still, set the checked state and then add the new listener.
---
 .../settings/ui/viewholder/SwitchSettingViewHolder.kt         | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index 200fbc4738..25b689d668 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
@@ -25,10 +25,12 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
             binding.textSettingDescription.text = ""
             binding.textSettingDescription.visibility = View.GONE
         }
+
+        binding.switchWidget.setOnCheckedChangeListener(null)
+        binding.switchWidget.isChecked = setting.checked
         binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
             adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked)
         }
-        binding.switchWidget.isChecked = setting.checked
 
         setStyle(setting.isEditable, binding)
     }

From d786d19880fbeddf4356d978ec858c4c4874935a Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Tue, 22 Aug 2023 23:32:10 -0400
Subject: [PATCH 5/9] android: Implement paired settings

Enables and disables editing on settings that rely on other boolean settings.
---
 .../settings/model/AbstractSetting.kt         |   7 +-
 .../settings/model/view/SettingsItem.kt       | 235 ++++++++++++++++
 .../features/settings/ui/SettingsAdapter.kt   |  34 ++-
 .../features/settings/ui/SettingsFragment.kt  |   7 +
 .../settings/ui/SettingsFragmentPresenter.kt  | 265 +++---------------
 .../ui/viewholder/SwitchSettingViewHolder.kt  |   4 +-
 .../yuzu/yuzu_emu/model/SettingsViewModel.kt  |   7 +
 .../org/yuzu/yuzu_emu/utils/NativeConfig.kt   |   2 +
 .../app/src/main/jni/native_config.cpp        |  15 +-
 9 files changed, 335 insertions(+), 241 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
index 724a2ecb84..73215c2473 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
@@ -6,14 +6,17 @@ package org.yuzu.yuzu_emu.features.settings.model
 import org.yuzu.yuzu_emu.utils.NativeConfig
 
 interface AbstractSetting {
-    val key: String?
+    val key: String
     val category: Settings.Category
     val defaultValue: Any
     val valueAsString: String
         get() = ""
 
     val isRuntimeModifiable: Boolean
-        get() = NativeConfig.getIsRuntimeModifiable(key!!)
+        get() = NativeConfig.getIsRuntimeModifiable(key)
+
+    val pairedSettingKey: String
+        get() = NativeConfig.getPairedSettingKey(key)
 
     fun reset()
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 3bdcc5bca2..b3b3fc2099 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -4,8 +4,15 @@
 package org.yuzu.yuzu_emu.features.settings.model.view
 
 import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
+import org.yuzu.yuzu_emu.features.settings.model.IntSetting
+import org.yuzu.yuzu_emu.features.settings.model.LongSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
 
 /**
  * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
@@ -37,11 +44,239 @@ abstract class SettingsItem(
         const val TYPE_DATETIME_SETTING = 6
         const val TYPE_RUNNABLE = 7
 
+        const val FASTMEM_COMBINED = "fastmem_combined"
+
         val emptySetting = object : AbstractSetting {
             override val key: String = ""
             override val category: Settings.Category = Settings.Category.Ui
             override val defaultValue: Any = false
             override fun reset() {}
         }
+
+        // Extension for putting SettingsItems into a hashmap without repeating yourself
+        fun HashMap<String, SettingsItem>.put(item: SettingsItem) {
+            put(item.setting.key, item)
+        }
+
+        // List of all general
+        val settingsItems = HashMap<String, SettingsItem>().apply {
+            put(
+                SwitchSetting(
+                    BooleanSetting.RENDERER_USE_SPEED_LIMIT,
+                    R.string.frame_limit_enable,
+                    R.string.frame_limit_enable_description
+                )
+            )
+            put(
+                SliderSetting(
+                    ShortSetting.RENDERER_SPEED_LIMIT,
+                    R.string.frame_limit_slider,
+                    R.string.frame_limit_slider_description,
+                    1,
+                    200,
+                    "%"
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.CPU_ACCURACY,
+                    R.string.cpu_accuracy,
+                    0,
+                    R.array.cpuAccuracyNames,
+                    R.array.cpuAccuracyValues
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.PICTURE_IN_PICTURE,
+                    R.string.picture_in_picture,
+                    R.string.picture_in_picture_description
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.USE_DOCKED_MODE,
+                    R.string.use_docked_mode,
+                    R.string.use_docked_mode_description
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.REGION_INDEX,
+                    R.string.emulated_region,
+                    0,
+                    R.array.regionNames,
+                    R.array.regionValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.LANGUAGE_INDEX,
+                    R.string.emulated_language,
+                    0,
+                    R.array.languageNames,
+                    R.array.languageValues
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.USE_CUSTOM_RTC,
+                    R.string.use_custom_rtc,
+                    R.string.use_custom_rtc_description
+                )
+            )
+            put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_ACCURACY,
+                    R.string.renderer_accuracy,
+                    0,
+                    R.array.rendererAccuracyNames,
+                    R.array.rendererAccuracyValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_RESOLUTION,
+                    R.string.renderer_resolution,
+                    0,
+                    R.array.rendererResolutionNames,
+                    R.array.rendererResolutionValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_VSYNC,
+                    R.string.renderer_vsync,
+                    0,
+                    R.array.rendererVSyncNames,
+                    R.array.rendererVSyncValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_SCALING_FILTER,
+                    R.string.renderer_scaling_filter,
+                    0,
+                    R.array.rendererScalingFilterNames,
+                    R.array.rendererScalingFilterValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_ANTI_ALIASING,
+                    R.string.renderer_anti_aliasing,
+                    0,
+                    R.array.rendererAntiAliasingNames,
+                    R.array.rendererAntiAliasingValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_SCREEN_LAYOUT,
+                    R.string.renderer_screen_layout,
+                    0,
+                    R.array.rendererScreenLayoutNames,
+                    R.array.rendererScreenLayoutValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_ASPECT_RATIO,
+                    R.string.renderer_aspect_ratio,
+                    0,
+                    R.array.rendererAspectRatioNames,
+                    R.array.rendererAspectRatioValues
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
+                    R.string.use_disk_shader_cache,
+                    R.string.use_disk_shader_cache_description
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
+                    R.string.renderer_force_max_clock,
+                    R.string.renderer_force_max_clock_description
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
+                    R.string.renderer_asynchronous_shaders,
+                    R.string.renderer_asynchronous_shaders_description
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.RENDERER_REACTIVE_FLUSHING,
+                    R.string.renderer_reactive_flushing,
+                    R.string.renderer_reactive_flushing_description
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.AUDIO_OUTPUT_ENGINE,
+                    R.string.audio_output_engine,
+                    0,
+                    R.array.outputEngineEntries,
+                    R.array.outputEngineValues
+                )
+            )
+            put(
+                SliderSetting(
+                    ByteSetting.AUDIO_VOLUME,
+                    R.string.audio_volume,
+                    R.string.audio_volume_description,
+                    0,
+                    100,
+                    "%"
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_BACKEND,
+                    R.string.renderer_api,
+                    0,
+                    R.array.rendererApiNames,
+                    R.array.rendererApiValues
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.RENDERER_DEBUG,
+                    R.string.renderer_debug,
+                    R.string.renderer_debug_description
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.CPU_DEBUG_MODE,
+                    R.string.cpu_debug_mode,
+                    R.string.cpu_debug_mode_description
+                )
+            )
+
+            val fastmem = object : AbstractBooleanSetting {
+                override val boolean: Boolean
+                    get() =
+                        BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
+
+                override fun setBoolean(value: Boolean) {
+                    BooleanSetting.FASTMEM.setBoolean(value)
+                    BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
+                }
+
+                override val key: String = FASTMEM_COMBINED
+                override val category = Settings.Category.Cpu
+                override val isRuntimeModifiable: Boolean = false
+                override val defaultValue: Boolean = true
+                override fun reset() = setBoolean(defaultValue)
+            }
+            put(SwitchSetting(fastmem, R.string.fastmem, 0))
+        }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index 9883c2ec70..ff1e64e0ad 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -14,7 +14,9 @@ import android.widget.TextView
 import androidx.appcompat.app.AlertDialog
 import androidx.lifecycle.ViewModelProvider
 import androidx.navigation.findNavController
-import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.AsyncDifferConfig
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
 import com.google.android.material.datepicker.MaterialDatePicker
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.android.material.slider.Slider
@@ -37,8 +39,8 @@ import org.yuzu.yuzu_emu.model.SettingsViewModel
 class SettingsAdapter(
     private val fragment: SettingsFragment,
     private val context: Context
-) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
-    private var settings = ArrayList<SettingsItem>()
+) : ListAdapter<SettingsItem, SettingViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
+    DialogInterface.OnClickListener {
     private var clickedItem: SettingsItem? = null
     private var clickedPosition: Int
     private var dialog: AlertDialog? = null
@@ -94,24 +96,18 @@ class SettingsAdapter(
     }
 
     override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
-        holder.bind(getItem(position))
+        holder.bind(currentList[position])
     }
 
-    private fun getItem(position: Int): SettingsItem = settings[position]
-
-    override fun getItemCount(): Int = settings.size
+    override fun getItemCount(): Int = currentList.size
 
     override fun getItemViewType(position: Int): Int {
-        return getItem(position).type
+        return currentList[position].type
     }
 
-    fun setSettingsList(settings: ArrayList<SettingsItem>) {
-        this.settings = settings
-        notifyDataSetChanged()
-    }
-
-    fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
+    fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
         item.checked = checked
+        settingsViewModel.setShouldReloadSettingsList(true)
         settingsViewModel.shouldSave = true
     }
 
@@ -338,4 +334,14 @@ class SettingsAdapter(
         }
         return -1
     }
+
+    private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
+        override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
+            return oldItem.setting.key == newItem.setting.key
+        }
+
+        override fun areContentsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
+            return oldItem.setting.key == newItem.setting.key
+        }
+    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index de6aebd9df..5890b06425 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -77,6 +77,13 @@ class SettingsFragment : Fragment() {
             if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it
         }
 
+        settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
+            if (it) {
+                settingsViewModel.setShouldReloadSettingsList(false)
+                presenter.loadSettingsList()
+            }
+        }
+
         presenter.onViewCreated()
 
         setInsets()
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 ba45c317dd..22a529b1ba 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
@@ -22,6 +22,7 @@ import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
 import org.yuzu.yuzu_emu.features.settings.model.view.*
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.utils.NativeConfig
 
 class SettingsFragmentPresenter(
     private val settingsViewModel: SettingsViewModel,
@@ -36,11 +37,22 @@ class SettingsFragmentPresenter(
 
     private val context: Context get() = YuzuApplication.appContext
 
+    // Extension for populating settings list based on paired settings
+    fun ArrayList<SettingsItem>.add(key: String) {
+        val item = SettingsItem.settingsItems[key]!!
+        val pairedSettingKey = item.setting.pairedSettingKey
+        if (pairedSettingKey.isNotEmpty()) {
+            val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
+            if (!pairedSettingValue) return
+        }
+        add(item)
+    }
+
     fun onViewCreated() {
         loadSettingsList()
     }
 
-    private fun loadSettingsList() {
+    fun loadSettingsList() {
         if (!TextUtils.isEmpty(gameId)) {
             settingsViewModel.setToolbarTitle(
                 context.getString(
@@ -70,7 +82,7 @@ class SettingsFragmentPresenter(
             }
         }
         settingsList = sl
-        adapter.setSettingsList(settingsList)
+        adapter.submitList(settingsList)
     }
 
     private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
@@ -92,200 +104,46 @@ class SettingsFragmentPresenter(
     private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
         settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
         sl.apply {
-            add(
-                SwitchSetting(
-                    BooleanSetting.RENDERER_USE_SPEED_LIMIT,
-                    R.string.frame_limit_enable,
-                    R.string.frame_limit_enable_description
-                )
-            )
-            add(
-                SliderSetting(
-                    ShortSetting.RENDERER_SPEED_LIMIT,
-                    R.string.frame_limit_slider,
-                    R.string.frame_limit_slider_description,
-                    1,
-                    200,
-                    "%"
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.CPU_ACCURACY,
-                    R.string.cpu_accuracy,
-                    0,
-                    R.array.cpuAccuracyNames,
-                    R.array.cpuAccuracyValues
-                )
-            )
-            add(
-                SwitchSetting(
-                    BooleanSetting.PICTURE_IN_PICTURE,
-                    R.string.picture_in_picture,
-                    R.string.picture_in_picture_description
-                )
-            )
+            add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
+            add(ShortSetting.RENDERER_SPEED_LIMIT.key)
+            add(IntSetting.CPU_ACCURACY.key)
+            add(BooleanSetting.PICTURE_IN_PICTURE.key)
         }
     }
 
     private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
         settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
         sl.apply {
-            add(
-                SwitchSetting(
-                    BooleanSetting.USE_DOCKED_MODE,
-                    R.string.use_docked_mode,
-                    R.string.use_docked_mode_description
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.REGION_INDEX,
-                    R.string.emulated_region,
-                    0,
-                    R.array.regionNames,
-                    R.array.regionValues
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.LANGUAGE_INDEX,
-                    R.string.emulated_language,
-                    0,
-                    R.array.languageNames,
-                    R.array.languageValues
-                )
-            )
-            add(
-                SwitchSetting(
-                    BooleanSetting.USE_CUSTOM_RTC,
-                    R.string.use_custom_rtc,
-                    R.string.use_custom_rtc_description
-                )
-            )
-            add(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
+            add(BooleanSetting.USE_DOCKED_MODE.key)
+            add(IntSetting.REGION_INDEX.key)
+            add(IntSetting.LANGUAGE_INDEX.key)
+            add(BooleanSetting.USE_CUSTOM_RTC.key)
+            add(LongSetting.CUSTOM_RTC.key)
         }
     }
 
     private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
         settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
         sl.apply {
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_ACCURACY,
-                    R.string.renderer_accuracy,
-                    0,
-                    R.array.rendererAccuracyNames,
-                    R.array.rendererAccuracyValues
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_RESOLUTION,
-                    R.string.renderer_resolution,
-                    0,
-                    R.array.rendererResolutionNames,
-                    R.array.rendererResolutionValues
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_VSYNC,
-                    R.string.renderer_vsync,
-                    0,
-                    R.array.rendererVSyncNames,
-                    R.array.rendererVSyncValues
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_SCALING_FILTER,
-                    R.string.renderer_scaling_filter,
-                    0,
-                    R.array.rendererScalingFilterNames,
-                    R.array.rendererScalingFilterValues
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_ANTI_ALIASING,
-                    R.string.renderer_anti_aliasing,
-                    0,
-                    R.array.rendererAntiAliasingNames,
-                    R.array.rendererAntiAliasingValues
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_SCREEN_LAYOUT,
-                    R.string.renderer_screen_layout,
-                    0,
-                    R.array.rendererScreenLayoutNames,
-                    R.array.rendererScreenLayoutValues
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_ASPECT_RATIO,
-                    R.string.renderer_aspect_ratio,
-                    0,
-                    R.array.rendererAspectRatioNames,
-                    R.array.rendererAspectRatioValues
-                )
-            )
-            add(
-                SwitchSetting(
-                    BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
-                    R.string.use_disk_shader_cache,
-                    R.string.use_disk_shader_cache_description
-                )
-            )
-            add(
-                SwitchSetting(
-                    BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
-                    R.string.renderer_force_max_clock,
-                    R.string.renderer_force_max_clock_description
-                )
-            )
-            add(
-                SwitchSetting(
-                    BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
-                    R.string.renderer_asynchronous_shaders,
-                    R.string.renderer_asynchronous_shaders_description
-                )
-            )
-            add(
-                SwitchSetting(
-                    BooleanSetting.RENDERER_REACTIVE_FLUSHING,
-                    R.string.renderer_reactive_flushing,
-                    R.string.renderer_reactive_flushing_description
-                )
-            )
+            add(IntSetting.RENDERER_ACCURACY.key)
+            add(IntSetting.RENDERER_RESOLUTION.key)
+            add(IntSetting.RENDERER_VSYNC.key)
+            add(IntSetting.RENDERER_SCALING_FILTER.key)
+            add(IntSetting.RENDERER_ANTI_ALIASING.key)
+            add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
+            add(IntSetting.RENDERER_ASPECT_RATIO.key)
+            add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
+            add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
+            add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
+            add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
         }
     }
 
     private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
         settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
         sl.apply {
-            add(
-                SingleChoiceSetting(
-                    IntSetting.AUDIO_OUTPUT_ENGINE,
-                    R.string.audio_output_engine,
-                    0,
-                    R.array.outputEngineEntries,
-                    R.array.outputEngineValues
-                )
-            )
-            add(
-                SliderSetting(
-                    ByteSetting.AUDIO_VOLUME,
-                    R.string.audio_volume,
-                    R.string.audio_volume_description,
-                    0,
-                    100,
-                    "%"
-                )
-            )
+            add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
+            add(ByteSetting.AUDIO_VOLUME.key)
         }
     }
 
@@ -303,7 +161,7 @@ class SettingsFragmentPresenter(
                     settingsViewModel.setShouldRecreate(true)
                 }
 
-                override val key: String? = null
+                override val key: String = Settings.PREF_THEME
                 override val category = Settings.Category.UiGeneral
                 override val isRuntimeModifiable: Boolean = false
                 override val defaultValue: Int = 0
@@ -347,7 +205,7 @@ class SettingsFragmentPresenter(
                     settingsViewModel.setShouldRecreate(true)
                 }
 
-                override val key: String? = null
+                override val key: String = Settings.PREF_THEME_MODE
                 override val category = Settings.Category.UiGeneral
                 override val isRuntimeModifiable: Boolean = false
                 override val defaultValue: Int = -1
@@ -380,7 +238,7 @@ class SettingsFragmentPresenter(
                     settingsViewModel.setShouldRecreate(true)
                 }
 
-                override val key: String? = null
+                override val key: String = Settings.PREF_BLACK_BACKGROUNDS
                 override val category = Settings.Category.UiGeneral
                 override val isRuntimeModifiable: Boolean = false
                 override val defaultValue: Boolean = false
@@ -406,49 +264,12 @@ class SettingsFragmentPresenter(
         settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
         sl.apply {
             add(HeaderSetting(R.string.gpu))
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_BACKEND,
-                    R.string.renderer_api,
-                    0,
-                    R.array.rendererApiNames,
-                    R.array.rendererApiValues
-                )
-            )
-            add(
-                SwitchSetting(
-                    BooleanSetting.RENDERER_DEBUG,
-                    R.string.renderer_debug,
-                    R.string.renderer_debug_description
-                )
-            )
+            add(IntSetting.RENDERER_BACKEND.key)
+            add(BooleanSetting.RENDERER_DEBUG.key)
 
             add(HeaderSetting(R.string.cpu))
-            add(
-                SwitchSetting(
-                    BooleanSetting.CPU_DEBUG_MODE,
-                    R.string.cpu_debug_mode,
-                    R.string.cpu_debug_mode_description
-                )
-            )
-
-            val fastmem = object : AbstractBooleanSetting {
-                override val boolean: Boolean
-                    get() =
-                        BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
-
-                override fun setBoolean(value: Boolean) {
-                    BooleanSetting.FASTMEM.setBoolean(value)
-                    BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
-                }
-
-                override val key: String? = null
-                override val category = Settings.Category.Cpu
-                override val isRuntimeModifiable: Boolean = false
-                override val defaultValue: Boolean = true
-                override fun reset() = setBoolean(defaultValue)
-            }
-            add(SwitchSetting(fastmem, R.string.fastmem, 0))
+            add(BooleanSetting.CPU_DEBUG_MODE.key)
+            add(SettingsItem.FASTMEM_COMBINED)
         }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index 25b689d668..0a37d36245 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
@@ -29,7 +29,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
         binding.switchWidget.setOnCheckedChangeListener(null)
         binding.switchWidget.isChecked = setting.checked
         binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
-            adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked)
+            adapter.onBooleanClick(item, binding.switchWidget.isChecked)
         }
 
         setStyle(setting.isEditable, binding)
@@ -43,7 +43,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
 
     override fun onLongClick(clicked: View): Boolean {
         if (setting.isEditable) {
-            return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+            return adapter.onLongClick(setting.setting, bindingAdapterPosition)
         }
         return false
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
index 1763341e20..6f22762933 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -24,6 +24,9 @@ class SettingsViewModel : ViewModel() {
     private val _shouldShowResetSettingsDialog = MutableLiveData(false)
     val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog
 
+    private val _shouldReloadSettingsList = MutableLiveData(false)
+    val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
+
     fun setToolbarTitle(value: String) {
         _toolbarTitle.value = value
     }
@@ -40,6 +43,10 @@ class SettingsViewModel : ViewModel() {
         _shouldShowResetSettingsDialog.value = value
     }
 
+    fun setShouldReloadSettingsList(value: Boolean) {
+        _shouldReloadSettingsList.value = value
+    }
+
     fun clear() {
         game = null
         shouldSave = false
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
index d4d981f9ef..9425f8b991 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
@@ -28,4 +28,6 @@ object NativeConfig {
     external fun getIsRuntimeModifiable(key: String): Boolean
 
     external fun getConfigHeader(category: Int): String
+
+    external fun getPairedSettingKey(key: String): String
 }
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
index 6123b3d08d..8a704960cd 100644
--- a/src/android/app/src/main/jni/native_config.cpp
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -216,9 +216,22 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEn
 }
 
 jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj,
-                                                                         jint jcategory) {
+                                                                   jint jcategory) {
     auto category = static_cast<Settings::Category>(jcategory);
     return ToJString(env, Settings::TranslateCategory(category));
 }
 
+jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* env, jobject obj,
+                                                                       jstring jkey) {
+    auto setting = getSetting<std::string>(env, jkey);
+    if (setting == nullptr) {
+        return ToJString(env, "");
+    }
+    if (setting->PairedSetting() == nullptr) {
+        return ToJString(env, "");
+    }
+
+    return ToJString(env, setting->PairedSetting()->GetLabel());
+}
+
 } // extern "C"

From fd5c7b21ddc0e40df049773d9106e349072352f3 Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Thu, 24 Aug 2023 16:11:08 -0400
Subject: [PATCH 6/9] android: Add search for settings

---
 .../features/settings/ui/SettingsAdapter.kt   |   3 +-
 .../features/settings/ui/SettingsFragment.kt  |  34 ++++
 .../fragments/SettingsSearchFragment.kt       | 189 ++++++++++++++++++
 .../yuzu/yuzu_emu/model/SettingsViewModel.kt  |   7 +
 .../res/layout/fragment_settings_search.xml   | 120 +++++++++++
 .../app/src/main/res/menu/menu_settings.xml   |  11 +
 .../res/navigation/settings_navigation.xml    |   8 +
 .../app/src/main/res/values/strings.xml       |   1 +
 8 files changed, 372 insertions(+), 1 deletion(-)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
 create mode 100644 src/android/app/src/main/res/layout/fragment_settings_search.xml
 create mode 100644 src/android/app/src/main/res/menu/menu_settings.xml

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index ff1e64e0ad..f5eba1222c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -12,6 +12,7 @@ import android.view.LayoutInflater
 import android.view.ViewGroup
 import android.widget.TextView
 import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.Fragment
 import androidx.lifecycle.ViewModelProvider
 import androidx.navigation.findNavController
 import androidx.recyclerview.widget.AsyncDifferConfig
@@ -37,7 +38,7 @@ import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
 import org.yuzu.yuzu_emu.model.SettingsViewModel
 
 class SettingsAdapter(
-    private val fragment: SettingsFragment,
+    private val fragment: Fragment,
     private val context: Context
 ) : ListAdapter<SettingsItem, SettingViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
     DialogInterface.OnClickListener {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index 5890b06425..0ea587a880 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -13,12 +13,14 @@ import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import androidx.navigation.findNavController
 import androidx.navigation.fragment.navArgs
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.divider.MaterialDividerItemDecoration
 import com.google.android.material.transition.MaterialSharedAxis
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
+import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.model.SettingsViewModel
 
 class SettingsFragment : Fragment() {
@@ -84,11 +86,43 @@ class SettingsFragment : Fragment() {
             }
         }
 
+        settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) {
+            if (it) {
+                reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+                exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+            } else {
+                reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+                exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+            }
+        }
+
+        if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
+            binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
+            binding.toolbarSettings.setOnMenuItemClickListener {
+                when (it.itemId) {
+                    R.id.action_search -> {
+                        reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+                        exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+                        view.findNavController()
+                            .navigate(R.id.action_settingsFragment_to_settingsSearchFragment)
+                        true
+                    }
+
+                    else -> false
+                }
+            }
+        }
+
         presenter.onViewCreated()
 
         setInsets()
     }
 
+    override fun onResume() {
+        super.onResume()
+        settingsViewModel.setIsUsingSearch(false)
+    }
+
     override fun onDetach() {
         super.onDetach()
         settingsAdapter?.closeDialog()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
new file mode 100644
index 0000000000..4f93db4adf
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
@@ -0,0 +1,189 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updatePadding
+import androidx.core.widget.doOnTextChanged
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.divider.MaterialDividerItemDecoration
+import com.google.android.material.transition.MaterialSharedAxis
+import info.debatty.java.stringsimilarity.Cosine
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+class SettingsSearchFragment : Fragment() {
+    private var _binding: FragmentSettingsSearchBinding? = null
+    private val binding get() = _binding!!
+
+    private var settingsAdapter: SettingsAdapter? = null
+
+    private val settingsViewModel: SettingsViewModel by activityViewModels()
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+        returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        _binding = FragmentSettingsSearchBinding.inflate(layoutInflater)
+        return binding.root
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        settingsViewModel.setIsUsingSearch(true)
+
+        if (savedInstanceState != null) {
+            binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
+        }
+
+        settingsAdapter = SettingsAdapter(this, requireContext())
+
+        val dividerDecoration = MaterialDividerItemDecoration(
+            requireContext(),
+            LinearLayoutManager.VERTICAL
+        )
+        dividerDecoration.isLastItemDecorated = false
+        binding.settingsList.apply {
+            adapter = settingsAdapter
+            layoutManager = LinearLayoutManager(requireContext())
+            addItemDecoration(dividerDecoration)
+        }
+
+        focusSearch()
+
+        binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) }
+        binding.searchBackground.setOnClickListener { focusSearch() }
+        binding.clearButton.setOnClickListener { binding.searchText.setText("") }
+        binding.searchText.doOnTextChanged { _, _, _, _ ->
+            search()
+            binding.settingsList.smoothScrollToPosition(0)
+        }
+        settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
+            if (it) {
+                settingsViewModel.setShouldReloadSettingsList(false)
+                search()
+            }
+        }
+
+        search()
+
+        setInsets()
+    }
+
+    override fun onDetach() {
+        super.onDetach()
+        settingsAdapter?.closeDialog()
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
+    }
+
+    private fun search() {
+        val searchTerm = binding.searchText.text.toString().lowercase()
+        binding.clearButton.visibility =
+            if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
+        if (searchTerm.isEmpty()) {
+            binding.noResultsView.visibility = View.VISIBLE
+            settingsAdapter?.submitList(emptyList())
+            return
+        }
+
+        val baseList = SettingsItem.settingsItems
+        val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
+        val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
+            val title = getString(item.value.nameId).lowercase()
+            val similarity = similarityAlgorithm.similarity(searchTerm, title)
+            if (similarity > 0.08) {
+                Pair(similarity, item)
+            } else {
+                null
+            }
+        }.sortedByDescending { it.first }.mapNotNull {
+            val item = it.second.value
+            val pairedSettingKey = item.setting.pairedSettingKey
+            val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) {
+                val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
+                if (pairedSettingValue) it.second.value else null
+            } else {
+                it.second.value
+            }
+            optionalSetting
+        }
+        settingsAdapter?.submitList(sortedList)
+        binding.noResultsView.visibility =
+            if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
+    }
+
+    private fun focusSearch() {
+        binding.searchText.requestFocus()
+        val imm = requireActivity()
+            .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
+        imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
+    }
+
+    private fun setInsets() =
+        ViewCompat.setOnApplyWindowInsetsListener(
+            binding.root
+        ) { _: View, windowInsets: WindowInsetsCompat ->
+            val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
+            val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
+            val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip)
+
+            val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+            val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
+
+            val leftInsets = barInsets.left + cutoutInsets.left
+            val rightInsets = barInsets.right + cutoutInsets.right
+
+            binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing)
+            binding.frameSearch.updatePadding(
+                left = leftInsets + sideMargin,
+                top = barInsets.top + topMargin,
+                right = rightInsets + sideMargin
+            )
+            binding.noResultsView.updatePadding(
+                left = leftInsets,
+                right = rightInsets,
+                bottom = barInsets.bottom
+            )
+
+            val mlpSettingsList = binding.settingsList.layoutParams as ViewGroup.MarginLayoutParams
+            mlpSettingsList.leftMargin = leftInsets + sideMargin
+            mlpSettingsList.rightMargin = rightInsets + sideMargin
+            binding.settingsList.layoutParams = mlpSettingsList
+
+            val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
+            mlpDivider.leftMargin = leftInsets + sideMargin
+            mlpDivider.rightMargin = rightInsets + sideMargin
+            binding.divider.layoutParams = mlpDivider
+
+            windowInsets
+        }
+
+    companion object {
+        const val SEARCH_TEXT = "SearchText"
+    }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
index 6f22762933..a0cb7225f1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -27,6 +27,9 @@ class SettingsViewModel : ViewModel() {
     private val _shouldReloadSettingsList = MutableLiveData(false)
     val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
 
+    private val _isUsingSearch = MutableLiveData(false)
+    val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
+
     fun setToolbarTitle(value: String) {
         _toolbarTitle.value = value
     }
@@ -47,6 +50,10 @@ class SettingsViewModel : ViewModel() {
         _shouldReloadSettingsList.value = value
     }
 
+    fun setIsUsingSearch(value: Boolean) {
+        _isUsingSearch.value = value
+    }
+
     fun clear() {
         game = null
         shouldSave = false
diff --git a/src/android/app/src/main/res/layout/fragment_settings_search.xml b/src/android/app/src/main/res/layout/fragment_settings_search.xml
new file mode 100644
index 0000000000..c779ed2fcd
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_settings_search.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <RelativeLayout
+        android:id="@+id/relativeLayout"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/divider">
+
+        <LinearLayout
+            android:id="@+id/no_results_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:orientation="vertical">
+
+            <ImageView
+                android:id="@+id/icon_no_results"
+                android:layout_width="match_parent"
+                android:layout_height="80dp"
+                android:src="@drawable/ic_search" />
+
+            <com.google.android.material.textview.MaterialTextView
+                android:id="@+id/notice_text"
+                style="@style/TextAppearance.Material3.TitleLarge"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:paddingTop="8dp"
+                android:text="@string/search_settings"
+                tools:visibility="visible" />
+
+        </LinearLayout>
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/settings_list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:clipToPadding="false" />
+
+    </RelativeLayout>
+
+    <FrameLayout
+        android:id="@+id/frame_search"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:clipToPadding="false"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <com.google.android.material.card.MaterialCardView
+            android:id="@+id/search_background"
+            style="?attr/materialCardViewFilledStyle"
+            android:layout_width="match_parent"
+            android:layout_height="56dp"
+            app:cardCornerRadius="28dp">
+
+            <LinearLayout
+                android:id="@+id/search_container"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_marginEnd="56dp"
+                android:orientation="horizontal">
+
+                <Button
+                    android:id="@+id/back_button"
+                    style="?attr/materialIconButtonFilledTonalStyle"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    android:layout_marginStart="8dp"
+                    app:backgroundTint="@android:color/transparent"
+                    app:icon="@drawable/ic_back" />
+
+                <EditText
+                    android:id="@+id/search_text"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:background="@android:color/transparent"
+                    android:hint="@string/search_settings"
+                    android:imeOptions="flagNoFullscreen"
+                    android:inputType="text"
+                    android:maxLines="1" />
+
+            </LinearLayout>
+
+            <Button
+                android:id="@+id/clear_button"
+                style="?attr/materialIconButtonFilledTonalStyle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical|end"
+                android:layout_marginEnd="8dp"
+                android:visibility="invisible"
+                app:backgroundTint="@android:color/transparent"
+                app:icon="@drawable/ic_clear"
+                tools:visibility="visible" />
+
+        </com.google.android.material.card.MaterialCardView>
+
+    </FrameLayout>
+
+    <com.google.android.material.divider.MaterialDivider
+        android:id="@+id/divider"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/frame_search" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/menu/menu_settings.xml b/src/android/app/src/main/res/menu/menu_settings.xml
new file mode 100644
index 0000000000..21501a471f
--- /dev/null
+++ b/src/android/app/src/main/res/menu/menu_settings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/action_search"
+        android:icon="@drawable/ic_search"
+        android:title="@string/home_search"
+        app:showAsAction="always" />
+
+</menu>
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml
index b36200c655..88e1b45877 100644
--- a/src/android/app/src/main/res/navigation/settings_navigation.xml
+++ b/src/android/app/src/main/res/navigation/settings_navigation.xml
@@ -15,10 +15,18 @@
             android:name="game"
             app:argType="org.yuzu.yuzu_emu.model.Game"
             app:nullable="true" />
+        <action
+            android:id="@+id/action_settingsFragment_to_settingsSearchFragment"
+            app:destination="@id/settingsSearchFragment" />
     </fragment>
 
     <action
         android:id="@+id/action_global_settingsFragment"
         app:destination="@id/settingsFragment" />
 
+    <fragment
+        android:id="@+id/settingsSearchFragment"
+        android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment"
+        android:label="SettingsSearchFragment" />
+
 </navigation>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 6b782780a1..d43891cecf 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -43,6 +43,7 @@
     <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
     <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
     <string name="home_search_games">Search games</string>
+    <string name="search_settings">Search settings</string>
     <string name="games_dir_selected">Games directory selected</string>
     <string name="install_prod_keys">Install prod.keys</string>
     <string name="install_prod_keys_description">Required to decrypt retail games</string>

From 45280a03420d4cdc2fb491b21d409f4101997bab Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Fri, 25 Aug 2023 21:27:13 -0400
Subject: [PATCH 7/9] android: Proper state restoration on settings dialogs

All dialog code (except for the Date/Time ones) has been extracted out into a generic settings dialog fragment that handles everything through a viewmodel. State for each dialog will now be retained and dialogs will stay shown through configuration changes.

I won't be changing the current state of the date and time dialog fragments until Google decides to make their classes non-final or if/when we migrate to Jetpack Compose.
---
 .../features/settings/ui/SettingsAdapter.kt   | 227 ++++-------------
 .../features/settings/ui/SettingsFragment.kt  |   5 -
 .../ui/viewholder/DateTimeViewHolder.kt       |   2 +-
 .../ui/viewholder/SingleChoiceViewHolder.kt   |   2 +-
 .../ui/viewholder/SliderViewHolder.kt         |   2 +-
 .../ui/viewholder/SwitchSettingViewHolder.kt  |   2 +-
 .../fragments/SettingsDialogFragment.kt       | 235 ++++++++++++++++++
 .../fragments/SettingsSearchFragment.kt       |   5 -
 .../yuzu/yuzu_emu/model/SettingsViewModel.kt  |  37 ++-
 9 files changed, 319 insertions(+), 198 deletions(-)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index f5eba1222c..a7a029fc12 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -4,58 +4,54 @@
 package org.yuzu.yuzu_emu.features.settings.ui
 
 import android.content.Context
-import android.content.DialogInterface
 import android.icu.util.Calendar
 import android.icu.util.TimeZone
 import android.text.format.DateFormat
 import android.view.LayoutInflater
 import android.view.ViewGroup
-import android.widget.TextView
-import androidx.appcompat.app.AlertDialog
 import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
 import androidx.navigation.findNavController
 import androidx.recyclerview.widget.AsyncDifferConfig
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.ListAdapter
 import com.google.android.material.datepicker.MaterialDatePicker
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.slider.Slider
 import com.google.android.material.timepicker.MaterialTimePicker
 import com.google.android.material.timepicker.TimeFormat
+import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.SettingsNavigationDirections
-import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
 import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
 import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
 import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
-import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
-import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
 import org.yuzu.yuzu_emu.features.settings.model.view.*
 import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
+import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment
 import org.yuzu.yuzu_emu.model.SettingsViewModel
 
 class SettingsAdapter(
     private val fragment: Fragment,
     private val context: Context
-) : ListAdapter<SettingsItem, SettingViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
-    DialogInterface.OnClickListener {
-    private var clickedItem: SettingsItem? = null
-    private var clickedPosition: Int
-    private var dialog: AlertDialog? = null
-    private var sliderProgress = 0
-    private var textSliderValue: TextView? = null
-
+) : ListAdapter<SettingsItem, SettingViewHolder>(
+    AsyncDifferConfig.Builder(DiffCallback()).build()
+) {
     private val settingsViewModel: SettingsViewModel
         get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
 
-    private var defaultCancelListener =
-        DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
-
     init {
-        clickedPosition = -1
+        fragment.viewLifecycleOwner.lifecycleScope.launch {
+            fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                settingsViewModel.adapterItemChanged.collect {
+                    if (it != -1) {
+                        notifyItemChanged(it)
+                        settingsViewModel.setAdapterItemChanged(-1)
+                    }
+                }
+            }
+        }
     }
 
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
@@ -112,36 +108,25 @@ class SettingsAdapter(
         settingsViewModel.shouldSave = true
     }
 
-    private fun onSingleChoiceClick(item: SingleChoiceSetting) {
-        clickedItem = item
-        val value = getSelectionForSingleChoiceValue(item)
-        dialog = MaterialAlertDialogBuilder(context)
-            .setTitle(item.nameId)
-            .setSingleChoiceItems(item.choicesId, value, this)
-            .show()
-    }
-
     fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
-        clickedPosition = position
-        onSingleChoiceClick(item)
-    }
-
-    private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
-        clickedItem = item
-        dialog = MaterialAlertDialogBuilder(context)
-            .setTitle(item.nameId)
-            .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
-            .show()
+        SettingsDialogFragment.newInstance(
+            settingsViewModel,
+            item,
+            SettingsItem.TYPE_SINGLE_CHOICE,
+            position
+        ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
     }
 
     fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
-        clickedPosition = position
-        onStringSingleChoiceClick(item)
+        SettingsDialogFragment.newInstance(
+            settingsViewModel,
+            item,
+            SettingsItem.TYPE_STRING_SINGLE_CHOICE,
+            position
+        ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
     }
 
     fun onDateTimeClick(item: DateTimeSetting, position: Int) {
-        clickedItem = item
-        clickedPosition = position
         val storedTime = item.value * 1000
 
         // Helper to extract hour and minute from epoch time
@@ -177,10 +162,9 @@ class SettingsAdapter(
             epochTime += timePicker.minute.toLong() * 60
             if (item.value != epochTime) {
                 settingsViewModel.shouldSave = true
-                notifyItemChanged(clickedPosition)
+                notifyItemChanged(position)
                 item.value = epochTime
             }
-            clickedItem = null
         }
         datePicker.show(
             fragment.childFragmentManager,
@@ -189,40 +173,12 @@ class SettingsAdapter(
     }
 
     fun onSliderClick(item: SliderSetting, position: Int) {
-        clickedItem = item
-        clickedPosition = position
-        sliderProgress = item.selectedValue as Int
-
-        val inflater = LayoutInflater.from(context)
-        val sliderBinding = DialogSliderBinding.inflate(inflater)
-
-        textSliderValue = sliderBinding.textValue
-        textSliderValue!!.text = String.format(
-            context.getString(R.string.value_with_units),
-            sliderProgress.toString(),
-            item.units
-        )
-
-        sliderBinding.slider.apply {
-            valueFrom = item.min.toFloat()
-            valueTo = item.max.toFloat()
-            value = sliderProgress.toFloat()
-            addOnChangeListener { _: Slider, value: Float, _: Boolean ->
-                sliderProgress = value.toInt()
-                textSliderValue!!.text = String.format(
-                    context.getString(R.string.value_with_units),
-                    sliderProgress.toString(),
-                    item.units
-                )
-            }
-        }
-
-        dialog = MaterialAlertDialogBuilder(context)
-            .setTitle(item.nameId)
-            .setView(sliderBinding.root)
-            .setPositiveButton(android.R.string.ok, this)
-            .setNegativeButton(android.R.string.cancel, defaultCancelListener)
-            .show()
+        SettingsDialogFragment.newInstance(
+            settingsViewModel,
+            item,
+            SettingsItem.TYPE_SLIDER,
+            position
+        ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
     }
 
     fun onSubmenuClick(item: SubmenuSetting) {
@@ -230,112 +186,17 @@ class SettingsAdapter(
         fragment.view?.findNavController()?.navigate(action)
     }
 
-    override fun onClick(dialog: DialogInterface, which: Int) {
-        when (clickedItem) {
-            is SingleChoiceSetting -> {
-                val scSetting = clickedItem as SingleChoiceSetting
-                val value = getValueForSingleChoiceSelection(scSetting, which)
-                if (scSetting.selectedValue != value) {
-                    settingsViewModel.shouldSave = true
-                }
-
-                // Get the backing Setting, which may be null (if for example it was missing from the file)
-                scSetting.selectedValue = value
-                closeDialog()
-            }
-
-            is StringSingleChoiceSetting -> {
-                val scSetting = clickedItem as StringSingleChoiceSetting
-                val value = scSetting.getValueAt(which)
-                if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
-                scSetting.selectedValue = value!!
-                closeDialog()
-            }
-
-            is SliderSetting -> {
-                val sliderSetting = clickedItem as SliderSetting
-                if (sliderSetting.selectedValue != sliderProgress) {
-                    settingsViewModel.shouldSave = true
-                }
-                when (sliderSetting.setting) {
-                    is ByteSetting -> {
-                        val value = sliderProgress.toByte()
-                        sliderSetting.selectedValue = value.toInt()
-                    }
-
-                    is ShortSetting -> {
-                        val value = sliderProgress.toShort()
-                        sliderSetting.selectedValue = value.toInt()
-                    }
-
-                    is FloatSetting -> {
-                        val value = sliderProgress.toFloat()
-                        sliderSetting.selectedValue = value.toInt()
-                    }
-
-                    else -> {
-                        sliderSetting.selectedValue = sliderProgress
-                    }
-                }
-                closeDialog()
-            }
-        }
-        clickedItem = null
-        sliderProgress = -1
-    }
-
-    fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
-        MaterialAlertDialogBuilder(context)
-            .setMessage(R.string.reset_setting_confirmation)
-            .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
-                setting.reset()
-                notifyItemChanged(position)
-                settingsViewModel.shouldSave = true
-            }
-            .setNegativeButton(android.R.string.cancel, null)
-            .show()
+    fun onLongClick(item: SettingsItem, position: Int): Boolean {
+        SettingsDialogFragment.newInstance(
+            settingsViewModel,
+            item,
+            SettingsDialogFragment.TYPE_RESET_SETTING,
+            position
+        ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
 
         return true
     }
 
-    fun closeDialog() {
-        if (dialog != null) {
-            if (clickedPosition != -1) {
-                notifyItemChanged(clickedPosition)
-                clickedPosition = -1
-            }
-            dialog!!.dismiss()
-            dialog = null
-        }
-    }
-
-    private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
-        val valuesId = item.valuesId
-        return if (valuesId > 0) {
-            val valuesArray = context.resources.getIntArray(valuesId)
-            valuesArray[which]
-        } else {
-            which
-        }
-    }
-
-    private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
-        val value = item.selectedValue
-        val valuesId = item.valuesId
-        if (valuesId > 0) {
-            val valuesArray = context.resources.getIntArray(valuesId)
-            for (index in valuesArray.indices) {
-                val current = valuesArray[index]
-                if (current == value) {
-                    return index
-                }
-            }
-        } else {
-            return value
-        }
-        return -1
-    }
-
     private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
         override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
             return oldItem.setting.key == newItem.setting.key
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index 0ea587a880..bc319714c7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -123,11 +123,6 @@ class SettingsFragment : Fragment() {
         settingsViewModel.setIsUsingSearch(false)
     }
 
-    override fun onDetach() {
-        super.onDetach()
-        settingsAdapter?.closeDialog()
-    }
-
     private fun setInsets() {
         ViewCompat.setOnApplyWindowInsetsListener(
             binding.root
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
index 68c0b24d6c..525f013f8b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
@@ -46,7 +46,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
 
     override fun onLongClick(clicked: View): Boolean {
         if (setting.isEditable) {
-            return adapter.onLongClick(setting.setting, bindingAdapterPosition)
+            return adapter.onLongClick(setting, bindingAdapterPosition)
         }
         return false
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
index a582c425b7..80d1b22c1a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
@@ -66,7 +66,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
 
     override fun onLongClick(clicked: View): Boolean {
         if (setting.isEditable) {
-            return adapter.onLongClick(setting.setting, bindingAdapterPosition)
+            return adapter.onLongClick(setting, bindingAdapterPosition)
         }
         return false
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
index d94a46262c..b83c901006 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
@@ -41,7 +41,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
 
     override fun onLongClick(clicked: View): Boolean {
         if (setting.isEditable) {
-            return adapter.onLongClick(setting.setting, bindingAdapterPosition)
+            return adapter.onLongClick(setting, bindingAdapterPosition)
         }
         return false
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index 0a37d36245..57fdeaa208 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
@@ -43,7 +43,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
 
     override fun onLongClick(clicked: View): Boolean {
         if (setting.isEditable) {
-            return adapter.onLongClick(setting.setting, bindingAdapterPosition)
+            return adapter.onLongClick(setting, bindingAdapterPosition)
         }
         return false
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
new file mode 100644
index 0000000000..d18ec69749
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
@@ -0,0 +1,235 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.slider.Slider
+import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
+import org.yuzu.yuzu_emu.model.SettingsViewModel
+
+class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
+    private var type = 0
+    private var position = 0
+
+    private var defaultCancelListener =
+        DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
+
+    private val settingsViewModel: SettingsViewModel by activityViewModels()
+
+    private lateinit var sliderBinding: DialogSliderBinding
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        type = requireArguments().getInt(TYPE)
+        position = requireArguments().getInt(POSITION)
+
+        if (settingsViewModel.clickedItem == null) dismiss()
+    }
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        return when (type) {
+            TYPE_RESET_SETTING -> {
+                MaterialAlertDialogBuilder(requireContext())
+                    .setMessage(R.string.reset_setting_confirmation)
+                    .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
+                        settingsViewModel.clickedItem!!.setting.reset()
+                        settingsViewModel.setAdapterItemChanged(position)
+                        settingsViewModel.shouldSave = true
+                    }
+                    .setNegativeButton(android.R.string.cancel, null)
+                    .create()
+            }
+
+            SettingsItem.TYPE_SINGLE_CHOICE -> {
+                val item = settingsViewModel.clickedItem as SingleChoiceSetting
+                val value = getSelectionForSingleChoiceValue(item)
+                MaterialAlertDialogBuilder(requireContext())
+                    .setTitle(item.nameId)
+                    .setSingleChoiceItems(item.choicesId, value, this)
+                    .create()
+            }
+
+            SettingsItem.TYPE_SLIDER -> {
+                sliderBinding = DialogSliderBinding.inflate(layoutInflater)
+                val item = settingsViewModel.clickedItem as SliderSetting
+
+                settingsViewModel.setSliderTextValue(item.selectedValue.toFloat(), item.units)
+                sliderBinding.slider.apply {
+                    valueFrom = item.min.toFloat()
+                    valueTo = item.max.toFloat()
+                    value = settingsViewModel.sliderProgress.value.toFloat()
+                    addOnChangeListener { _: Slider, value: Float, _: Boolean ->
+                        settingsViewModel.setSliderTextValue(value, item.units)
+                    }
+                }
+
+                MaterialAlertDialogBuilder(requireContext())
+                    .setTitle(item.nameId)
+                    .setView(sliderBinding.root)
+                    .setPositiveButton(android.R.string.ok, this)
+                    .setNegativeButton(android.R.string.cancel, defaultCancelListener)
+                    .create()
+            }
+
+            SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
+                val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
+                MaterialAlertDialogBuilder(requireContext())
+                    .setTitle(item.nameId)
+                    .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
+                    .create()
+            }
+
+            else -> super.onCreateDialog(savedInstanceState)
+        }
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return when (type) {
+            SettingsItem.TYPE_SLIDER -> sliderBinding.root
+            else -> super.onCreateView(inflater, container, savedInstanceState)
+        }
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        when (type) {
+            SettingsItem.TYPE_SLIDER -> {
+                viewLifecycleOwner.lifecycleScope.launch {
+                    repeatOnLifecycle(Lifecycle.State.CREATED) {
+                        settingsViewModel.sliderTextValue.collect {
+                            sliderBinding.textValue.text = it
+                        }
+                    }
+                    repeatOnLifecycle(Lifecycle.State.CREATED) {
+                        settingsViewModel.sliderProgress.collect {
+                            sliderBinding.slider.value = it.toFloat()
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    override fun onClick(dialog: DialogInterface, which: Int) {
+        when (settingsViewModel.clickedItem) {
+            is SingleChoiceSetting -> {
+                val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
+                val value = getValueForSingleChoiceSelection(scSetting, which)
+                if (scSetting.selectedValue != value) {
+                    settingsViewModel.shouldSave = true
+                }
+                scSetting.selectedValue = value
+            }
+
+            is StringSingleChoiceSetting -> {
+                val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
+                val value = scSetting.getValueAt(which)
+                if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
+                scSetting.selectedValue = value
+            }
+
+            is SliderSetting -> {
+                val sliderSetting = settingsViewModel.clickedItem as SliderSetting
+                if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) {
+                    settingsViewModel.shouldSave = true
+                }
+                sliderSetting.selectedValue = settingsViewModel.sliderProgress.value
+            }
+        }
+        closeDialog()
+    }
+
+    private fun closeDialog() {
+        settingsViewModel.setAdapterItemChanged(position)
+        settingsViewModel.clickedItem = null
+        settingsViewModel.setSliderProgress(-1f)
+        dismiss()
+    }
+
+    private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
+        val valuesId = item.valuesId
+        return if (valuesId > 0) {
+            val valuesArray = requireContext().resources.getIntArray(valuesId)
+            valuesArray[which]
+        } else {
+            which
+        }
+    }
+
+    private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
+        val value = item.selectedValue
+        val valuesId = item.valuesId
+        if (valuesId > 0) {
+            val valuesArray = requireContext().resources.getIntArray(valuesId)
+            for (index in valuesArray.indices) {
+                val current = valuesArray[index]
+                if (current == value) {
+                    return index
+                }
+            }
+        } else {
+            return value
+        }
+        return -1
+    }
+
+    companion object {
+        const val TAG = "SettingsDialogFragment"
+
+        const val TYPE_RESET_SETTING = -1
+
+        const val TITLE = "Title"
+        const val TYPE = "Type"
+        const val POSITION = "Position"
+
+        fun newInstance(
+            settingsViewModel: SettingsViewModel,
+            clickedItem: SettingsItem,
+            type: Int,
+            position: Int
+        ): SettingsDialogFragment {
+            when (type) {
+                SettingsItem.TYPE_HEADER,
+                SettingsItem.TYPE_SWITCH,
+                SettingsItem.TYPE_SUBMENU,
+                SettingsItem.TYPE_DATETIME_SETTING,
+                SettingsItem.TYPE_RUNNABLE ->
+                    throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!")
+
+                SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress(
+                    (clickedItem as SliderSetting).selectedValue.toFloat()
+                )
+            }
+            settingsViewModel.clickedItem = clickedItem
+
+            val args = Bundle()
+            args.putInt(TYPE, type)
+            args.putInt(POSITION, position)
+            val fragment = SettingsDialogFragment()
+            fragment.arguments = args
+            return fragment
+        }
+    }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
index 4f93db4adf..55b6a0367b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
@@ -91,11 +91,6 @@ class SettingsSearchFragment : Fragment() {
         setInsets()
     }
 
-    override fun onDetach() {
-        super.onDetach()
-        settingsAdapter?.closeDialog()
-    }
-
     override fun onSaveInstanceState(outState: Bundle) {
         super.onSaveInstanceState(outState)
         outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
index a0cb7225f1..d16d15fa6a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -5,13 +5,19 @@ package org.yuzu.yuzu_emu.model
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
 
-class SettingsViewModel : ViewModel() {
+class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
     var game: Game? = null
 
     var shouldSave = false
 
+    var clickedItem: SettingsItem? = null
+
     private val _toolbarTitle = MutableLiveData("")
     val toolbarTitle: LiveData<String> get() = _toolbarTitle
 
@@ -30,6 +36,12 @@ class SettingsViewModel : ViewModel() {
     private val _isUsingSearch = MutableLiveData(false)
     val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
 
+    val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1)
+
+    val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "")
+
+    val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
+
     fun setToolbarTitle(value: String) {
         _toolbarTitle.value = value
     }
@@ -54,8 +66,31 @@ class SettingsViewModel : ViewModel() {
         _isUsingSearch.value = value
     }
 
+    fun setSliderTextValue(value: Float, units: String) {
+        savedStateHandle[KEY_SLIDER_PROGRESS] = value
+        savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format(
+            YuzuApplication.appContext.getString(R.string.value_with_units),
+            value.toInt().toString(),
+            units
+        )
+    }
+
+    fun setSliderProgress(value: Float) {
+        savedStateHandle[KEY_SLIDER_PROGRESS] = value
+    }
+
+    fun setAdapterItemChanged(value: Int) {
+        savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value
+    }
+
     fun clear() {
         game = null
         shouldSave = false
     }
+
+    companion object {
+        const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
+        const val KEY_SLIDER_PROGRESS = "SliderProgress"
+        const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
+    }
 }

From 21ad5f5cc515efd80223b548218588b78ae91a53 Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Fri, 25 Aug 2023 21:58:52 -0400
Subject: [PATCH 8/9] android: Add optional androidDefault property to settings

Certain settings have specific defaults for Android only. This lets us reflect them in the Kotlin side with very little code.
---
 .../features/settings/model/AbstractSetting.kt        |  2 ++
 .../features/settings/model/BooleanSetting.kt         | 11 +++++++----
 .../yuzu_emu/features/settings/model/IntSetting.kt    |  9 ++++++---
 3 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
index 73215c2473..8b6d29fe5b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
@@ -9,6 +9,8 @@ interface AbstractSetting {
     val key: String
     val category: Settings.Category
     val defaultValue: Any
+    val androidDefault: Any?
+        get() = null
     val valueAsString: String
         get() = ""
 
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 f7528642e9..e0c0538c78 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
@@ -7,17 +7,18 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
 
 enum class BooleanSetting(
     override val key: String,
-    override val category: Settings.Category
+    override val category: Settings.Category,
+    override val androidDefault: Boolean? = null
 ) : AbstractBooleanSetting {
     CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
     FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
     FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
     RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core),
-    USE_DOCKED_MODE("use_docked_mode", Settings.Category.System),
+    USE_DOCKED_MODE("use_docked_mode", Settings.Category.System, false),
     RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer),
     RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer),
     RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer),
-    RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer),
+    RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer, false),
     RENDERER_DEBUG("debug", Settings.Category.Renderer),
     PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android),
     USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System);
@@ -27,7 +28,9 @@ enum class BooleanSetting(
 
     override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value)
 
-    override val defaultValue: Boolean by lazy { NativeConfig.getBoolean(key, true) }
+    override val defaultValue: Boolean by lazy {
+        androidDefault ?: NativeConfig.getBoolean(key, true)
+    }
 
     override val valueAsString: String
         get() = if (boolean) "1" else "0"
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 a647572071..1513621243 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
@@ -7,13 +7,14 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
 
 enum class IntSetting(
     override val key: String,
-    override val category: Settings.Category
+    override val category: Settings.Category,
+    override val androidDefault: Int? = null
 ) : AbstractIntSetting {
     CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
     REGION_INDEX("region_index", Settings.Category.System),
     LANGUAGE_INDEX("language_index", Settings.Category.System),
     RENDERER_BACKEND("backend", Settings.Category.Renderer),
-    RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer),
+    RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer, 0),
     RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer),
     RENDERER_VSYNC("use_vsync", Settings.Category.Renderer),
     RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer),
@@ -27,7 +28,9 @@ enum class IntSetting(
 
     override fun setInt(value: Int) = NativeConfig.setInt(key, value)
 
-    override val defaultValue: Int by lazy { NativeConfig.getInt(key, true) }
+    override val defaultValue: Int by lazy {
+        androidDefault ?: NativeConfig.getInt(key, true)
+    }
 
     override val valueAsString: String
         get() = int.toString()

From 4701eea6462dc0ed688815a8446c5122de2eeb22 Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Tue, 29 Aug 2023 20:21:29 -0400
Subject: [PATCH 9/9] android: Don't reload settings when stopping settings
 activity

---
 .../org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt  | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index 7fd83f5f7a..908c01265c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -16,7 +16,6 @@ import androidx.core.view.WindowInsetsCompat
 import androidx.navigation.fragment.NavHostFragment
 import androidx.navigation.navArgs
 import com.google.android.material.color.MaterialColors
-import org.yuzu.yuzu_emu.NativeLibrary
 import java.io.IOException
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@@ -133,7 +132,6 @@ class SettingsActivity : AppCompatActivity() {
         if (isFinishing && settingsViewModel.shouldSave) {
             Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
             Settings.saveSettings()
-            NativeLibrary.reloadSettings()
         }
     }