android: Persist settings across configuration changes

Mostly things get refactored here to remove previous assumptions made about how the activity/fragment lifecycles would operate. The important change for persistence is removing the assumption that the user will be at the first settings fragment on recreation when deciding whether or not to reload settings. Now we check a flag in Settings to know if we loaded the settings within this lifecycle.
This commit is contained in:
Charles Lombardo 2023-03-23 03:46:04 -04:00 committed by bunnei
parent aaefe8a0e0
commit 295ffd4d47
9 changed files with 51 additions and 93 deletions

View File

@ -13,6 +13,8 @@ import java.util.*
class Settings {
private var gameId: String? = null
var isLoaded = false
/**
* A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
* when getting a key not already in the map
@ -43,6 +45,7 @@ class Settings {
if (!TextUtils.isEmpty(gameId)) {
loadCustomGameSettings(gameId!!, view)
}
isLoaded = true
}
private fun loadYuzuSettings(view: SettingsActivityView) {

View File

@ -3,5 +3,5 @@ package org.yuzu.yuzu_emu.features.settings.model
import androidx.lifecycle.ViewModel
class SettingsViewModel : ViewModel() {
var settings = Settings()
val settings = Settings()
}

View File

@ -35,11 +35,7 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
private val settingsViewModel: SettingsViewModel by viewModels()
override var settings: Settings
get() = settingsViewModel.settings
set(settings) {
settingsViewModel.settings = settings
}
override val settings: Settings get() = settingsViewModel.settings
override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this)
@ -179,14 +175,14 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
).show()
}
override fun onSettingsFileLoaded(settings: Settings) {
override fun onSettingsFileLoaded() {
val fragment: SettingsFragmentView? = settingsFragment
fragment?.onSettingsFileLoaded(settings)
fragment?.loadSettingsList()
}
override fun onSettingsFileNotFound() {
val fragment: SettingsFragmentView? = settingsFragment
fragment?.loadDefaultSettings()
fragment?.loadSettingsList()
}
override fun showToastMessage(message: String, is_long: Boolean) {

View File

@ -36,7 +36,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
}
private fun loadSettingsUI() {
if (settings.isEmpty) {
if (!settings.isLoaded) {
if (!TextUtils.isEmpty(gameId)) {
settings.loadSettings(gameId, activityView)
} else {
@ -44,7 +44,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
}
}
activityView.showSettingsFragment(menuTag, false, gameId)
activityView.onSettingsFileLoaded(settings)
activityView.onSettingsFileLoaded()
}
private fun prepareDirectoriesIfNeeded() {

View File

@ -24,19 +24,17 @@ interface SettingsActivityView {
* loaded from disk, so that each Fragment doesn't need to perform its own
* read operation.
*
* @return A possibly null HashMap of Settings.
* @return A HashMap of Settings.
*/
var settings: Settings
val settings: Settings
/**
* Called when an asynchronous load operation completes.
*
* @param settings The (possibly null) result of the ini load operation.
* Called when a load operation completes.
*/
fun onSettingsFileLoaded(settings: Settings)
fun onSettingsFileLoaded()
/**
* Called when an asynchronous load operation fails.
* Called when a load operation fails.
*/
fun onSettingsFileNotFound()

View File

@ -12,6 +12,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -93,7 +94,7 @@ class SettingsAdapter(
return getItem(position).type
}
fun setSettings(settings: ArrayList<SettingsItem>?) {
fun setSettingsList(settings: ArrayList<SettingsItem>?) {
this.settings = settings
notifyDataSetChanged()
}
@ -144,7 +145,7 @@ class SettingsAdapter(
calendar.timeZone = TimeZone.getTimeZone("UTC")
var timeFormat: Int = TimeFormat.CLOCK_12H
if (DateFormat.is24HourFormat(fragmentView.fragmentActivity)) {
if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) {
timeFormat = TimeFormat.CLOCK_24H
}
@ -161,7 +162,7 @@ class SettingsAdapter(
datePicker.addOnPositiveButtonClickListener {
timePicker.show(
fragmentView.fragmentActivity.supportFragmentManager,
(fragmentView.activityView as AppCompatActivity).supportFragmentManager,
"TimePicker"
)
}
@ -177,7 +178,7 @@ class SettingsAdapter(
item.setSelectedValue(rtcString)
clickedItem = null
}
datePicker.show(fragmentView.fragmentActivity.supportFragmentManager, "DatePicker")
datePicker.show((fragmentView.activityView as AppCompatActivity).supportFragmentManager, "DatePicker")
}
fun onSliderClick(item: SliderSetting, position: Int) {

View File

@ -12,7 +12,6 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
@ -21,10 +20,9 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
class SettingsFragment : Fragment(), SettingsFragmentView {
override lateinit var fragmentActivity: FragmentActivity
override var activityView: SettingsActivityView? = null
private val presenter = SettingsFragmentPresenter(this)
private var activityView: SettingsActivityView? = null
private val fragmentPresenter = SettingsFragmentPresenter(this)
private var settingsAdapter: SettingsAdapter? = null
private var _binding: FragmentSettingsBinding? = null
@ -32,15 +30,14 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
override fun onAttach(context: Context) {
super.onAttach(context)
activityView = context as SettingsActivityView
fragmentActivity = requireActivity()
activityView = requireActivity() as SettingsActivityView
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG)
val gameId = requireArguments().getString(ARGUMENT_GAME_ID)
presenter.onCreate(menuTag!!, gameId!!)
fragmentPresenter.onCreate(menuTag!!, gameId!!)
}
override fun onCreateView(
@ -61,8 +58,7 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
layoutManager = LinearLayoutManager(activity)
addItemDecoration(dividerDecoration)
}
val activity = activity as SettingsActivityView?
presenter.onViewCreated(activity!!.settings)
fragmentPresenter.onViewCreated()
setInsets()
}
@ -75,16 +71,12 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
}
}
override fun onSettingsFileLoaded(settings: Settings) {
presenter.setSettings(settings)
}
override fun showSettingsList(settingsList: ArrayList<SettingsItem>) {
settingsAdapter!!.setSettings(settingsList)
settingsAdapter!!.setSettingsList(settingsList)
}
override fun loadDefaultSettings() {
presenter.loadDefaultSettings()
override fun loadSettingsList() {
fragmentPresenter.loadSettingsList()
}
override fun loadSubMenu(menuKey: String) {
@ -100,7 +92,7 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
}
override fun putSetting(setting: Setting) {
presenter.putSetting(setting)
fragmentPresenter.putSetting(setting)
}
override fun onSettingChanged() {

View File

@ -4,58 +4,37 @@
package org.yuzu.yuzu_emu.features.settings.ui
import android.text.TextUtils
import androidx.appcompat.app.AppCompatActivity
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.features.settings.model.Setting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
private var menuTag: String? = null
private lateinit var gameId: String
private var settings: Settings? = null
private var settingsList: ArrayList<SettingsItem>? = null
private val settingsActivity get() = fragmentView.activityView as AppCompatActivity
private val settings get() = fragmentView.activityView!!.settings
fun onCreate(menuTag: String, gameId: String) {
this.gameId = gameId
this.menuTag = menuTag
}
fun onViewCreated(settings: Settings) {
setSettings(settings)
fun onViewCreated() {
loadSettingsList()
}
fun putSetting(setting: Setting) {
settings!!.getSection(setting.section)!!.putSetting(setting)
settings.getSection(setting.section)!!.putSetting(setting)
}
private fun asStringSetting(setting: Setting?): StringSetting? {
if (setting == null) {
return null
}
val stringSetting = StringSetting(setting.key, setting.section, setting.valueAsString)
putSetting(stringSetting)
return stringSetting
}
fun loadDefaultSettings() {
loadSettingsList()
}
fun setSettings(settings: Settings) {
if (settingsList == null) {
this.settings = settings
loadSettingsList()
} else {
fragmentView.fragmentActivity.setTitle(R.string.preferences_settings)
fragmentView.showSettingsList(settingsList!!)
}
}
private fun loadSettingsList() {
fun loadSettingsList() {
if (!TextUtils.isEmpty(gameId)) {
fragmentView.fragmentActivity.title = "Game Settings: $gameId"
settingsActivity.title = "Game Settings: $gameId"
}
val sl = ArrayList<SettingsItem>()
if (menuTag == null) {
@ -77,7 +56,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
fragmentView.fragmentActivity.setTitle(R.string.preferences_settings)
settingsActivity.setTitle(R.string.preferences_settings)
sl.apply {
add(
SubmenuSetting(
@ -119,12 +98,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
fragmentView.fragmentActivity.setTitle(R.string.preferences_general)
val rendererSection = settings!!.getSection(Settings.SECTION_RENDERER)
settingsActivity.setTitle(R.string.preferences_general)
val rendererSection = settings.getSection(Settings.SECTION_RENDERER)
val frameLimitEnable =
rendererSection!!.getSetting(SettingsFile.KEY_RENDERER_USE_SPEED_LIMIT)
val frameLimitValue = rendererSection.getSetting(SettingsFile.KEY_RENDERER_SPEED_LIMIT)
val cpuSection = settings!!.getSection(Settings.SECTION_CPU)
val cpuSection = settings.getSection(Settings.SECTION_CPU)
val cpuAccuracy = cpuSection!!.getSetting(SettingsFile.KEY_CPU_ACCURACY)
sl.apply {
add(
@ -166,8 +145,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
fragmentView.fragmentActivity.setTitle(R.string.preferences_system)
val systemSection = settings!!.getSection(Settings.SECTION_SYSTEM)
settingsActivity.setTitle(R.string.preferences_system)
val systemSection = settings.getSection(Settings.SECTION_SYSTEM)
val dockedMode = systemSection!!.getSetting(SettingsFile.KEY_USE_DOCKED_MODE)
val region = systemSection.getSetting(SettingsFile.KEY_REGION_INDEX)
val language = systemSection.getSetting(SettingsFile.KEY_LANGUAGE_INDEX)
@ -210,8 +189,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
fragmentView.fragmentActivity.setTitle(R.string.preferences_graphics)
val rendererSection = settings!!.getSection(Settings.SECTION_RENDERER)
settingsActivity.setTitle(R.string.preferences_graphics)
val rendererSection = settings.getSection(Settings.SECTION_RENDERER)
val rendererBackend = rendererSection!!.getSetting(SettingsFile.KEY_RENDERER_BACKEND)
val rendererAccuracy = rendererSection.getSetting(SettingsFile.KEY_RENDERER_ACCURACY)
val rendererResolution = rendererSection.getSetting(SettingsFile.KEY_RENDERER_RESOLUTION)
@ -305,8 +284,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
fragmentView.fragmentActivity.setTitle(R.string.preferences_audio)
val audioSection = settings!!.getSection(Settings.SECTION_AUDIO)
settingsActivity.setTitle(R.string.preferences_audio)
val audioSection = settings.getSection(Settings.SECTION_AUDIO)
val audioVolume = audioSection!!.getSetting(SettingsFile.KEY_AUDIO_VOLUME)
sl.add(
SliderSetting(

View File

@ -3,9 +3,7 @@
package org.yuzu.yuzu_emu.features.settings.ui
import androidx.fragment.app.FragmentActivity
import org.yuzu.yuzu_emu.features.settings.model.Setting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
/**
@ -13,14 +11,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
* this type of view will each display a layer of the setting hierarchy.
*/
interface SettingsFragmentView {
/**
* Called by the containing Activity to notify the Fragment that an
* asynchronous load operation completed.
*
* @param settings The (possibly null) result of the ini load operation.
*/
fun onSettingsFileLoaded(settings: Settings)
/**
* Pass an ArrayList to the View so that it can be displayed on screen.
*
@ -29,15 +19,14 @@ interface SettingsFragmentView {
fun showSettingsList(settingsList: ArrayList<SettingsItem>)
/**
* Called by the containing Activity when an asynchronous load operation fails.
* Instructs the Fragment to load the settings screen with defaults selected.
* Instructs the Fragment to load the settings screen.
*/
fun loadDefaultSettings()
fun loadSettingsList()
/**
* @return The Fragment's containing activity.
*/
val fragmentActivity: FragmentActivity
val activityView: SettingsActivityView?
/**
* Tell the Fragment to tell the containing Activity to show a new