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] 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"
+    }
 }