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.
This commit is contained in:
parent
fd5c7b21dd
commit
45280a0342
|
@ -4,58 +4,54 @@
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
package org.yuzu.yuzu_emu.features.settings.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.icu.util.Calendar
|
import android.icu.util.Calendar
|
||||||
import android.icu.util.TimeZone
|
import android.icu.util.TimeZone
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.google.android.material.datepicker.MaterialDatePicker
|
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.MaterialTimePicker
|
||||||
import com.google.android.material.timepicker.TimeFormat
|
import com.google.android.material.timepicker.TimeFormat
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.SettingsNavigationDirections
|
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.ListItemSettingBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
|
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.model.view.*
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
|
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
|
||||||
|
import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment
|
||||||
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||||
|
|
||||||
class SettingsAdapter(
|
class SettingsAdapter(
|
||||||
private val fragment: Fragment,
|
private val fragment: Fragment,
|
||||||
private val context: Context
|
private val context: Context
|
||||||
) : ListAdapter<SettingsItem, SettingViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
) : ListAdapter<SettingsItem, SettingViewHolder>(
|
||||||
DialogInterface.OnClickListener {
|
AsyncDifferConfig.Builder(DiffCallback()).build()
|
||||||
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
|
private val settingsViewModel: SettingsViewModel
|
||||||
get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
|
get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
|
||||||
|
|
||||||
private var defaultCancelListener =
|
|
||||||
DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
|
|
||||||
|
|
||||||
init {
|
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 {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
|
||||||
|
@ -112,36 +108,25 @@ class SettingsAdapter(
|
||||||
settingsViewModel.shouldSave = true
|
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) {
|
fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
|
||||||
clickedPosition = position
|
SettingsDialogFragment.newInstance(
|
||||||
onSingleChoiceClick(item)
|
settingsViewModel,
|
||||||
}
|
item,
|
||||||
|
SettingsItem.TYPE_SINGLE_CHOICE,
|
||||||
private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
|
position
|
||||||
clickedItem = item
|
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
||||||
dialog = MaterialAlertDialogBuilder(context)
|
|
||||||
.setTitle(item.nameId)
|
|
||||||
.setSingleChoiceItems(item.choices, item.selectValueIndex, this)
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
|
fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
|
||||||
clickedPosition = position
|
SettingsDialogFragment.newInstance(
|
||||||
onStringSingleChoiceClick(item)
|
settingsViewModel,
|
||||||
|
item,
|
||||||
|
SettingsItem.TYPE_STRING_SINGLE_CHOICE,
|
||||||
|
position
|
||||||
|
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
|
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
|
||||||
clickedItem = item
|
|
||||||
clickedPosition = position
|
|
||||||
val storedTime = item.value * 1000
|
val storedTime = item.value * 1000
|
||||||
|
|
||||||
// Helper to extract hour and minute from epoch time
|
// Helper to extract hour and minute from epoch time
|
||||||
|
@ -177,10 +162,9 @@ class SettingsAdapter(
|
||||||
epochTime += timePicker.minute.toLong() * 60
|
epochTime += timePicker.minute.toLong() * 60
|
||||||
if (item.value != epochTime) {
|
if (item.value != epochTime) {
|
||||||
settingsViewModel.shouldSave = true
|
settingsViewModel.shouldSave = true
|
||||||
notifyItemChanged(clickedPosition)
|
notifyItemChanged(position)
|
||||||
item.value = epochTime
|
item.value = epochTime
|
||||||
}
|
}
|
||||||
clickedItem = null
|
|
||||||
}
|
}
|
||||||
datePicker.show(
|
datePicker.show(
|
||||||
fragment.childFragmentManager,
|
fragment.childFragmentManager,
|
||||||
|
@ -189,40 +173,12 @@ class SettingsAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSliderClick(item: SliderSetting, position: Int) {
|
fun onSliderClick(item: SliderSetting, position: Int) {
|
||||||
clickedItem = item
|
SettingsDialogFragment.newInstance(
|
||||||
clickedPosition = position
|
settingsViewModel,
|
||||||
sliderProgress = item.selectedValue as Int
|
item,
|
||||||
|
SettingsItem.TYPE_SLIDER,
|
||||||
val inflater = LayoutInflater.from(context)
|
position
|
||||||
val sliderBinding = DialogSliderBinding.inflate(inflater)
|
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSubmenuClick(item: SubmenuSetting) {
|
fun onSubmenuClick(item: SubmenuSetting) {
|
||||||
|
@ -230,112 +186,17 @@ class SettingsAdapter(
|
||||||
fragment.view?.findNavController()?.navigate(action)
|
fragment.view?.findNavController()?.navigate(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
fun onLongClick(item: SettingsItem, position: Int): Boolean {
|
||||||
when (clickedItem) {
|
SettingsDialogFragment.newInstance(
|
||||||
is SingleChoiceSetting -> {
|
settingsViewModel,
|
||||||
val scSetting = clickedItem as SingleChoiceSetting
|
item,
|
||||||
val value = getValueForSingleChoiceSelection(scSetting, which)
|
SettingsDialogFragment.TYPE_RESET_SETTING,
|
||||||
if (scSetting.selectedValue != value) {
|
position
|
||||||
settingsViewModel.shouldSave = true
|
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
|
|
||||||
return true
|
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>() {
|
private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
|
||||||
override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
|
override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
|
||||||
return oldItem.setting.key == newItem.setting.key
|
return oldItem.setting.key == newItem.setting.key
|
||||||
|
|
|
@ -123,11 +123,6 @@ class SettingsFragment : Fragment() {
|
||||||
settingsViewModel.setIsUsingSearch(false)
|
settingsViewModel.setIsUsingSearch(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetach() {
|
|
||||||
super.onDetach()
|
|
||||||
settingsAdapter?.closeDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setInsets() {
|
private fun setInsets() {
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(
|
ViewCompat.setOnApplyWindowInsetsListener(
|
||||||
binding.root
|
binding.root
|
||||||
|
|
|
@ -46,7 +46,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
override fun onLongClick(clicked: View): Boolean {
|
||||||
if (setting.isEditable) {
|
if (setting.isEditable) {
|
||||||
return adapter.onLongClick(setting.setting, bindingAdapterPosition)
|
return adapter.onLongClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
override fun onLongClick(clicked: View): Boolean {
|
||||||
if (setting.isEditable) {
|
if (setting.isEditable) {
|
||||||
return adapter.onLongClick(setting.setting, bindingAdapterPosition)
|
return adapter.onLongClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
override fun onLongClick(clicked: View): Boolean {
|
||||||
if (setting.isEditable) {
|
if (setting.isEditable) {
|
||||||
return adapter.onLongClick(setting.setting, bindingAdapterPosition)
|
return adapter.onLongClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
override fun onLongClick(clicked: View): Boolean {
|
||||||
if (setting.isEditable) {
|
if (setting.isEditable) {
|
||||||
return adapter.onLongClick(setting.setting, bindingAdapterPosition)
|
return adapter.onLongClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -91,11 +91,6 @@ class SettingsSearchFragment : Fragment() {
|
||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetach() {
|
|
||||||
super.onDetach()
|
|
||||||
settingsAdapter?.closeDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
|
outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
|
||||||
|
|
|
@ -5,13 +5,19 @@ package org.yuzu.yuzu_emu.model
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
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 game: Game? = null
|
||||||
|
|
||||||
var shouldSave = false
|
var shouldSave = false
|
||||||
|
|
||||||
|
var clickedItem: SettingsItem? = null
|
||||||
|
|
||||||
private val _toolbarTitle = MutableLiveData("")
|
private val _toolbarTitle = MutableLiveData("")
|
||||||
val toolbarTitle: LiveData<String> get() = _toolbarTitle
|
val toolbarTitle: LiveData<String> get() = _toolbarTitle
|
||||||
|
|
||||||
|
@ -30,6 +36,12 @@ class SettingsViewModel : ViewModel() {
|
||||||
private val _isUsingSearch = MutableLiveData(false)
|
private val _isUsingSearch = MutableLiveData(false)
|
||||||
val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
|
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) {
|
fun setToolbarTitle(value: String) {
|
||||||
_toolbarTitle.value = value
|
_toolbarTitle.value = value
|
||||||
}
|
}
|
||||||
|
@ -54,8 +66,31 @@ class SettingsViewModel : ViewModel() {
|
||||||
_isUsingSearch.value = value
|
_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() {
|
fun clear() {
|
||||||
game = null
|
game = null
|
||||||
shouldSave = false
|
shouldSave = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
|
||||||
|
const val KEY_SLIDER_PROGRESS = "SliderProgress"
|
||||||
|
const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue