summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Charles Lombardo2023-08-25 21:27:13 -0400
committerGravatar Charles Lombardo2023-08-29 19:42:42 -0400
commit45280a03420d4cdc2fb491b21d409f4101997bab (patch)
treeca7b5c3b54f6accaed77a1514e3a6448b3cc7de2
parentandroid: Add search for settings (diff)
downloadyuzu-45280a03420d4cdc2fb491b21d409f4101997bab.tar.gz
yuzu-45280a03420d4cdc2fb491b21d409f4101997bab.tar.xz
yuzu-45280a03420d4cdc2fb491b21d409f4101997bab.zip
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.
Diffstat (limited to '')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt227
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt235
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt37
9 files changed, 319 insertions, 198 deletions
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 f5eba1222..a7a029fc1 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 @@
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.content.Context 6import android.content.Context
7import android.content.DialogInterface
8import android.icu.util.Calendar 7import android.icu.util.Calendar
9import android.icu.util.TimeZone 8import android.icu.util.TimeZone
10import android.text.format.DateFormat 9import android.text.format.DateFormat
11import android.view.LayoutInflater 10import android.view.LayoutInflater
12import android.view.ViewGroup 11import android.view.ViewGroup
13import android.widget.TextView
14import androidx.appcompat.app.AlertDialog
15import androidx.fragment.app.Fragment 12import androidx.fragment.app.Fragment
13import androidx.lifecycle.Lifecycle
16import androidx.lifecycle.ViewModelProvider 14import androidx.lifecycle.ViewModelProvider
15import androidx.lifecycle.lifecycleScope
16import androidx.lifecycle.repeatOnLifecycle
17import androidx.navigation.findNavController 17import androidx.navigation.findNavController
18import androidx.recyclerview.widget.AsyncDifferConfig 18import androidx.recyclerview.widget.AsyncDifferConfig
19import androidx.recyclerview.widget.DiffUtil 19import androidx.recyclerview.widget.DiffUtil
20import androidx.recyclerview.widget.ListAdapter 20import androidx.recyclerview.widget.ListAdapter
21import com.google.android.material.datepicker.MaterialDatePicker 21import com.google.android.material.datepicker.MaterialDatePicker
22import com.google.android.material.dialog.MaterialAlertDialogBuilder
23import com.google.android.material.slider.Slider
24import com.google.android.material.timepicker.MaterialTimePicker 22import com.google.android.material.timepicker.MaterialTimePicker
25import com.google.android.material.timepicker.TimeFormat 23import com.google.android.material.timepicker.TimeFormat
24import kotlinx.coroutines.launch
26import org.yuzu.yuzu_emu.R 25import org.yuzu.yuzu_emu.R
27import org.yuzu.yuzu_emu.SettingsNavigationDirections 26import org.yuzu.yuzu_emu.SettingsNavigationDirections
28import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
29import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding 27import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
30import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding 28import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
31import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding 29import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
32import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
33import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
34import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
35import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
36import org.yuzu.yuzu_emu.features.settings.model.view.* 30import org.yuzu.yuzu_emu.features.settings.model.view.*
37import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* 31import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
32import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment
38import org.yuzu.yuzu_emu.model.SettingsViewModel 33import org.yuzu.yuzu_emu.model.SettingsViewModel
39 34
40class SettingsAdapter( 35class SettingsAdapter(
41 private val fragment: Fragment, 36 private val fragment: Fragment,
42 private val context: Context 37 private val context: Context
43) : ListAdapter<SettingsItem, SettingViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()), 38) : ListAdapter<SettingsItem, SettingViewHolder>(
44 DialogInterface.OnClickListener { 39 AsyncDifferConfig.Builder(DiffCallback()).build()
45 private var clickedItem: SettingsItem? = null 40) {
46 private var clickedPosition: Int
47 private var dialog: AlertDialog? = null
48 private var sliderProgress = 0
49 private var textSliderValue: TextView? = null
50
51 private val settingsViewModel: SettingsViewModel 41 private val settingsViewModel: SettingsViewModel
52 get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java] 42 get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
53 43
54 private var defaultCancelListener =
55 DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
56
57 init { 44 init {
58 clickedPosition = -1 45 fragment.viewLifecycleOwner.lifecycleScope.launch {
46 fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
47 settingsViewModel.adapterItemChanged.collect {
48 if (it != -1) {
49 notifyItemChanged(it)
50 settingsViewModel.setAdapterItemChanged(-1)
51 }
52 }
53 }
54 }
59 } 55 }
60 56
61 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder { 57 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
@@ -112,36 +108,25 @@ class SettingsAdapter(
112 settingsViewModel.shouldSave = true 108 settingsViewModel.shouldSave = true
113 } 109 }
114 110
115 private fun onSingleChoiceClick(item: SingleChoiceSetting) {
116 clickedItem = item
117 val value = getSelectionForSingleChoiceValue(item)
118 dialog = MaterialAlertDialogBuilder(context)
119 .setTitle(item.nameId)
120 .setSingleChoiceItems(item.choicesId, value, this)
121 .show()
122 }
123
124 fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) { 111 fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
125 clickedPosition = position 112 SettingsDialogFragment.newInstance(
126 onSingleChoiceClick(item) 113 settingsViewModel,
127 } 114 item,
128 115 SettingsItem.TYPE_SINGLE_CHOICE,
129 private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) { 116 position
130 clickedItem = item 117 ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
131 dialog = MaterialAlertDialogBuilder(context)
132 .setTitle(item.nameId)
133 .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
134 .show()
135 } 118 }
136 119
137 fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) { 120 fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
138 clickedPosition = position 121 SettingsDialogFragment.newInstance(
139 onStringSingleChoiceClick(item) 122 settingsViewModel,
123 item,
124 SettingsItem.TYPE_STRING_SINGLE_CHOICE,
125 position
126 ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
140 } 127 }
141 128
142 fun onDateTimeClick(item: DateTimeSetting, position: Int) { 129 fun onDateTimeClick(item: DateTimeSetting, position: Int) {
143 clickedItem = item
144 clickedPosition = position
145 val storedTime = item.value * 1000 130 val storedTime = item.value * 1000
146 131
147 // Helper to extract hour and minute from epoch time 132 // Helper to extract hour and minute from epoch time
@@ -177,10 +162,9 @@ class SettingsAdapter(
177 epochTime += timePicker.minute.toLong() * 60 162 epochTime += timePicker.minute.toLong() * 60
178 if (item.value != epochTime) { 163 if (item.value != epochTime) {
179 settingsViewModel.shouldSave = true 164 settingsViewModel.shouldSave = true
180 notifyItemChanged(clickedPosition) 165 notifyItemChanged(position)
181 item.value = epochTime 166 item.value = epochTime
182 } 167 }
183 clickedItem = null
184 } 168 }
185 datePicker.show( 169 datePicker.show(
186 fragment.childFragmentManager, 170 fragment.childFragmentManager,
@@ -189,40 +173,12 @@ class SettingsAdapter(
189 } 173 }
190 174
191 fun onSliderClick(item: SliderSetting, position: Int) { 175 fun onSliderClick(item: SliderSetting, position: Int) {
192 clickedItem = item 176 SettingsDialogFragment.newInstance(
193 clickedPosition = position 177 settingsViewModel,
194 sliderProgress = item.selectedValue as Int 178 item,
195 179 SettingsItem.TYPE_SLIDER,
196 val inflater = LayoutInflater.from(context) 180 position
197 val sliderBinding = DialogSliderBinding.inflate(inflater) 181 ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
198
199 textSliderValue = sliderBinding.textValue
200 textSliderValue!!.text = String.format(
201 context.getString(R.string.value_with_units),
202 sliderProgress.toString(),
203 item.units
204 )
205
206 sliderBinding.slider.apply {
207 valueFrom = item.min.toFloat()
208 valueTo = item.max.toFloat()
209 value = sliderProgress.toFloat()
210 addOnChangeListener { _: Slider, value: Float, _: Boolean ->
211 sliderProgress = value.toInt()
212 textSliderValue!!.text = String.format(
213 context.getString(R.string.value_with_units),
214 sliderProgress.toString(),
215 item.units
216 )
217 }
218 }
219
220 dialog = MaterialAlertDialogBuilder(context)
221 .setTitle(item.nameId)
222 .setView(sliderBinding.root)
223 .setPositiveButton(android.R.string.ok, this)
224 .setNegativeButton(android.R.string.cancel, defaultCancelListener)
225 .show()
226 } 182 }
227 183
228 fun onSubmenuClick(item: SubmenuSetting) { 184 fun onSubmenuClick(item: SubmenuSetting) {
@@ -230,112 +186,17 @@ class SettingsAdapter(
230 fragment.view?.findNavController()?.navigate(action) 186 fragment.view?.findNavController()?.navigate(action)
231 } 187 }
232 188
233 override fun onClick(dialog: DialogInterface, which: Int) { 189 fun onLongClick(item: SettingsItem, position: Int): Boolean {
234 when (clickedItem) { 190 SettingsDialogFragment.newInstance(
235 is SingleChoiceSetting -> { 191 settingsViewModel,
236 val scSetting = clickedItem as SingleChoiceSetting 192 item,
237 val value = getValueForSingleChoiceSelection(scSetting, which) 193 SettingsDialogFragment.TYPE_RESET_SETTING,
238 if (scSetting.selectedValue != value) { 194 position
239 settingsViewModel.shouldSave = true 195 ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
240 }
241
242 // Get the backing Setting, which may be null (if for example it was missing from the file)
243 scSetting.selectedValue = value
244 closeDialog()
245 }
246
247 is StringSingleChoiceSetting -> {
248 val scSetting = clickedItem as StringSingleChoiceSetting
249 val value = scSetting.getValueAt(which)
250 if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
251 scSetting.selectedValue = value!!
252 closeDialog()
253 }
254
255 is SliderSetting -> {
256 val sliderSetting = clickedItem as SliderSetting
257 if (sliderSetting.selectedValue != sliderProgress) {
258 settingsViewModel.shouldSave = true
259 }
260 when (sliderSetting.setting) {
261 is ByteSetting -> {
262 val value = sliderProgress.toByte()
263 sliderSetting.selectedValue = value.toInt()
264 }
265
266 is ShortSetting -> {
267 val value = sliderProgress.toShort()
268 sliderSetting.selectedValue = value.toInt()
269 }
270
271 is FloatSetting -> {
272 val value = sliderProgress.toFloat()
273 sliderSetting.selectedValue = value.toInt()
274 }
275
276 else -> {
277 sliderSetting.selectedValue = sliderProgress
278 }
279 }
280 closeDialog()
281 }
282 }
283 clickedItem = null
284 sliderProgress = -1
285 }
286
287 fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
288 MaterialAlertDialogBuilder(context)
289 .setMessage(R.string.reset_setting_confirmation)
290 .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
291 setting.reset()
292 notifyItemChanged(position)
293 settingsViewModel.shouldSave = true
294 }
295 .setNegativeButton(android.R.string.cancel, null)
296 .show()
297 196
298 return true 197 return true
299 } 198 }
300 199
301 fun closeDialog() {
302 if (dialog != null) {
303 if (clickedPosition != -1) {
304 notifyItemChanged(clickedPosition)
305 clickedPosition = -1
306 }
307 dialog!!.dismiss()
308 dialog = null
309 }
310 }
311
312 private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
313 val valuesId = item.valuesId
314 return if (valuesId > 0) {
315 val valuesArray = context.resources.getIntArray(valuesId)
316 valuesArray[which]
317 } else {
318 which
319 }
320 }
321
322 private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
323 val value = item.selectedValue
324 val valuesId = item.valuesId
325 if (valuesId > 0) {
326 val valuesArray = context.resources.getIntArray(valuesId)
327 for (index in valuesArray.indices) {
328 val current = valuesArray[index]
329 if (current == value) {
330 return index
331 }
332 }
333 } else {
334 return value
335 }
336 return -1
337 }
338
339 private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() { 200 private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
340 override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean { 201 override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
341 return oldItem.setting.key == newItem.setting.key 202 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 0ea587a88..bc319714c 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() {
123 settingsViewModel.setIsUsingSearch(false) 123 settingsViewModel.setIsUsingSearch(false)
124 } 124 }
125 125
126 override fun onDetach() {
127 super.onDetach()
128 settingsAdapter?.closeDialog()
129 }
130
131 private fun setInsets() { 126 private fun setInsets() {
132 ViewCompat.setOnApplyWindowInsetsListener( 127 ViewCompat.setOnApplyWindowInsetsListener(
133 binding.root 128 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 68c0b24d6..525f013f8 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
46 46
47 override fun onLongClick(clicked: View): Boolean { 47 override fun onLongClick(clicked: View): Boolean {
48 if (setting.isEditable) { 48 if (setting.isEditable) {
49 return adapter.onLongClick(setting.setting, bindingAdapterPosition) 49 return adapter.onLongClick(setting, bindingAdapterPosition)
50 } 50 }
51 return false 51 return false
52 } 52 }
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 a582c425b..80d1b22c1 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
66 66
67 override fun onLongClick(clicked: View): Boolean { 67 override fun onLongClick(clicked: View): Boolean {
68 if (setting.isEditable) { 68 if (setting.isEditable) {
69 return adapter.onLongClick(setting.setting, bindingAdapterPosition) 69 return adapter.onLongClick(setting, bindingAdapterPosition)
70 } 70 }
71 return false 71 return false
72 } 72 }
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 d94a46262..b83c90100 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
41 41
42 override fun onLongClick(clicked: View): Boolean { 42 override fun onLongClick(clicked: View): Boolean {
43 if (setting.isEditable) { 43 if (setting.isEditable) {
44 return adapter.onLongClick(setting.setting, bindingAdapterPosition) 44 return adapter.onLongClick(setting, bindingAdapterPosition)
45 } 45 }
46 return false 46 return false
47 } 47 }
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 0a37d3624..57fdeaa20 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
43 43
44 override fun onLongClick(clicked: View): Boolean { 44 override fun onLongClick(clicked: View): Boolean {
45 if (setting.isEditable) { 45 if (setting.isEditable) {
46 return adapter.onLongClick(setting.setting, bindingAdapterPosition) 46 return adapter.onLongClick(setting, bindingAdapterPosition)
47 } 47 }
48 return false 48 return false
49 } 49 }
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 000000000..d18ec6974
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
@@ -0,0 +1,235 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.fragments
5
6import android.app.Dialog
7import android.content.DialogInterface
8import android.os.Bundle
9import android.view.LayoutInflater
10import android.view.View
11import android.view.ViewGroup
12import androidx.fragment.app.DialogFragment
13import androidx.fragment.app.activityViewModels
14import androidx.lifecycle.Lifecycle
15import androidx.lifecycle.lifecycleScope
16import androidx.lifecycle.repeatOnLifecycle
17import com.google.android.material.dialog.MaterialAlertDialogBuilder
18import com.google.android.material.slider.Slider
19import kotlinx.coroutines.launch
20import org.yuzu.yuzu_emu.R
21import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
22import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
23import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
24import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
25import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
26import org.yuzu.yuzu_emu.model.SettingsViewModel
27
28class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
29 private var type = 0
30 private var position = 0
31
32 private var defaultCancelListener =
33 DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
34
35 private val settingsViewModel: SettingsViewModel by activityViewModels()
36
37 private lateinit var sliderBinding: DialogSliderBinding
38
39 override fun onCreate(savedInstanceState: Bundle?) {
40 super.onCreate(savedInstanceState)
41 type = requireArguments().getInt(TYPE)
42 position = requireArguments().getInt(POSITION)
43
44 if (settingsViewModel.clickedItem == null) dismiss()
45 }
46
47 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
48 return when (type) {
49 TYPE_RESET_SETTING -> {
50 MaterialAlertDialogBuilder(requireContext())
51 .setMessage(R.string.reset_setting_confirmation)
52 .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
53 settingsViewModel.clickedItem!!.setting.reset()
54 settingsViewModel.setAdapterItemChanged(position)
55 settingsViewModel.shouldSave = true
56 }
57 .setNegativeButton(android.R.string.cancel, null)
58 .create()
59 }
60
61 SettingsItem.TYPE_SINGLE_CHOICE -> {
62 val item = settingsViewModel.clickedItem as SingleChoiceSetting
63 val value = getSelectionForSingleChoiceValue(item)
64 MaterialAlertDialogBuilder(requireContext())
65 .setTitle(item.nameId)
66 .setSingleChoiceItems(item.choicesId, value, this)
67 .create()
68 }
69
70 SettingsItem.TYPE_SLIDER -> {
71 sliderBinding = DialogSliderBinding.inflate(layoutInflater)
72 val item = settingsViewModel.clickedItem as SliderSetting
73
74 settingsViewModel.setSliderTextValue(item.selectedValue.toFloat(), item.units)
75 sliderBinding.slider.apply {
76 valueFrom = item.min.toFloat()
77 valueTo = item.max.toFloat()
78 value = settingsViewModel.sliderProgress.value.toFloat()
79 addOnChangeListener { _: Slider, value: Float, _: Boolean ->
80 settingsViewModel.setSliderTextValue(value, item.units)
81 }
82 }
83
84 MaterialAlertDialogBuilder(requireContext())
85 .setTitle(item.nameId)
86 .setView(sliderBinding.root)
87 .setPositiveButton(android.R.string.ok, this)
88 .setNegativeButton(android.R.string.cancel, defaultCancelListener)
89 .create()
90 }
91
92 SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
93 val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
94 MaterialAlertDialogBuilder(requireContext())
95 .setTitle(item.nameId)
96 .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
97 .create()
98 }
99
100 else -> super.onCreateDialog(savedInstanceState)
101 }
102 }
103
104 override fun onCreateView(
105 inflater: LayoutInflater,
106 container: ViewGroup?,
107 savedInstanceState: Bundle?
108 ): View? {
109 return when (type) {
110 SettingsItem.TYPE_SLIDER -> sliderBinding.root
111 else -> super.onCreateView(inflater, container, savedInstanceState)
112 }
113 }
114
115 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
116 super.onViewCreated(view, savedInstanceState)
117 when (type) {
118 SettingsItem.TYPE_SLIDER -> {
119 viewLifecycleOwner.lifecycleScope.launch {
120 repeatOnLifecycle(Lifecycle.State.CREATED) {
121 settingsViewModel.sliderTextValue.collect {
122 sliderBinding.textValue.text = it
123 }
124 }
125 repeatOnLifecycle(Lifecycle.State.CREATED) {
126 settingsViewModel.sliderProgress.collect {
127 sliderBinding.slider.value = it.toFloat()
128 }
129 }
130 }
131 }
132 }
133 }
134
135 override fun onClick(dialog: DialogInterface, which: Int) {
136 when (settingsViewModel.clickedItem) {
137 is SingleChoiceSetting -> {
138 val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
139 val value = getValueForSingleChoiceSelection(scSetting, which)
140 if (scSetting.selectedValue != value) {
141 settingsViewModel.shouldSave = true
142 }
143 scSetting.selectedValue = value
144 }
145
146 is StringSingleChoiceSetting -> {
147 val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
148 val value = scSetting.getValueAt(which)
149 if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
150 scSetting.selectedValue = value
151 }
152
153 is SliderSetting -> {
154 val sliderSetting = settingsViewModel.clickedItem as SliderSetting
155 if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) {
156 settingsViewModel.shouldSave = true
157 }
158 sliderSetting.selectedValue = settingsViewModel.sliderProgress.value
159 }
160 }
161 closeDialog()
162 }
163
164 private fun closeDialog() {
165 settingsViewModel.setAdapterItemChanged(position)
166 settingsViewModel.clickedItem = null
167 settingsViewModel.setSliderProgress(-1f)
168 dismiss()
169 }
170
171 private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
172 val valuesId = item.valuesId
173 return if (valuesId > 0) {
174 val valuesArray = requireContext().resources.getIntArray(valuesId)
175 valuesArray[which]
176 } else {
177 which
178 }
179 }
180
181 private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
182 val value = item.selectedValue
183 val valuesId = item.valuesId
184 if (valuesId > 0) {
185 val valuesArray = requireContext().resources.getIntArray(valuesId)
186 for (index in valuesArray.indices) {
187 val current = valuesArray[index]
188 if (current == value) {
189 return index
190 }
191 }
192 } else {
193 return value
194 }
195 return -1
196 }
197
198 companion object {
199 const val TAG = "SettingsDialogFragment"
200
201 const val TYPE_RESET_SETTING = -1
202
203 const val TITLE = "Title"
204 const val TYPE = "Type"
205 const val POSITION = "Position"
206
207 fun newInstance(
208 settingsViewModel: SettingsViewModel,
209 clickedItem: SettingsItem,
210 type: Int,
211 position: Int
212 ): SettingsDialogFragment {
213 when (type) {
214 SettingsItem.TYPE_HEADER,
215 SettingsItem.TYPE_SWITCH,
216 SettingsItem.TYPE_SUBMENU,
217 SettingsItem.TYPE_DATETIME_SETTING,
218 SettingsItem.TYPE_RUNNABLE ->
219 throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!")
220
221 SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress(
222 (clickedItem as SliderSetting).selectedValue.toFloat()
223 )
224 }
225 settingsViewModel.clickedItem = clickedItem
226
227 val args = Bundle()
228 args.putInt(TYPE, type)
229 args.putInt(POSITION, position)
230 val fragment = SettingsDialogFragment()
231 fragment.arguments = args
232 return fragment
233 }
234 }
235}
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 4f93db4ad..55b6a0367 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() {
91 setInsets() 91 setInsets()
92 } 92 }
93 93
94 override fun onDetach() {
95 super.onDetach()
96 settingsAdapter?.closeDialog()
97 }
98
99 override fun onSaveInstanceState(outState: Bundle) { 94 override fun onSaveInstanceState(outState: Bundle) {
100 super.onSaveInstanceState(outState) 95 super.onSaveInstanceState(outState)
101 outState.putString(SEARCH_TEXT, binding.searchText.text.toString()) 96 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 a0cb7225f..d16d15fa6 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
5 5
6import androidx.lifecycle.LiveData 6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData 7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.SavedStateHandle
8import androidx.lifecycle.ViewModel 9import androidx.lifecycle.ViewModel
10import org.yuzu.yuzu_emu.R
11import org.yuzu.yuzu_emu.YuzuApplication
12import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
9 13
10class SettingsViewModel : ViewModel() { 14class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
11 var game: Game? = null 15 var game: Game? = null
12 16
13 var shouldSave = false 17 var shouldSave = false
14 18
19 var clickedItem: SettingsItem? = null
20
15 private val _toolbarTitle = MutableLiveData("") 21 private val _toolbarTitle = MutableLiveData("")
16 val toolbarTitle: LiveData<String> get() = _toolbarTitle 22 val toolbarTitle: LiveData<String> get() = _toolbarTitle
17 23
@@ -30,6 +36,12 @@ class SettingsViewModel : ViewModel() {
30 private val _isUsingSearch = MutableLiveData(false) 36 private val _isUsingSearch = MutableLiveData(false)
31 val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch 37 val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
32 38
39 val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1)
40
41 val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "")
42
43 val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
44
33 fun setToolbarTitle(value: String) { 45 fun setToolbarTitle(value: String) {
34 _toolbarTitle.value = value 46 _toolbarTitle.value = value
35 } 47 }
@@ -54,8 +66,31 @@ class SettingsViewModel : ViewModel() {
54 _isUsingSearch.value = value 66 _isUsingSearch.value = value
55 } 67 }
56 68
69 fun setSliderTextValue(value: Float, units: String) {
70 savedStateHandle[KEY_SLIDER_PROGRESS] = value
71 savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format(
72 YuzuApplication.appContext.getString(R.string.value_with_units),
73 value.toInt().toString(),
74 units
75 )
76 }
77
78 fun setSliderProgress(value: Float) {
79 savedStateHandle[KEY_SLIDER_PROGRESS] = value
80 }
81
82 fun setAdapterItemChanged(value: Int) {
83 savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value
84 }
85
57 fun clear() { 86 fun clear() {
58 game = null 87 game = null
59 shouldSave = false 88 shouldSave = false
60 } 89 }
90
91 companion object {
92 const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
93 const val KEY_SLIDER_PROGRESS = "SliderProgress"
94 const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
95 }
61} 96}