From 295ffd4d478e93cc1e0c4937403e47e44298beda Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Thu, 23 Mar 2023 03:46:04 -0400 Subject: [PATCH] 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. --- .../features/settings/model/Settings.kt | 3 + .../settings/model/SettingsViewModel.kt | 2 +- .../features/settings/ui/SettingsActivity.kt | 12 ++-- .../settings/ui/SettingsActivityPresenter.kt | 4 +- .../settings/ui/SettingsActivityView.kt | 12 ++-- .../features/settings/ui/SettingsAdapter.kt | 9 +-- .../features/settings/ui/SettingsFragment.kt | 26 +++----- .../settings/ui/SettingsFragmentPresenter.kt | 59 ++++++------------- .../settings/ui/SettingsFragmentView.kt | 17 +----- 9 files changed, 51 insertions(+), 93 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index b59815770f..bbe0097d62 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt @@ -13,6 +13,8 @@ import java.util.* class Settings { private var gameId: String? = null + var isLoaded = false + /** * A HashMap, 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) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt index 0e33a85bb3..7141604d55 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt @@ -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() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index 1683f511f1..60858ecd66 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt @@ -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) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt index 60df9d5b58..2a86e44632 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt @@ -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() { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt index f19ca0e30c..2b6dd2fce5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt @@ -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() 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 1f81f6e623..cdbdc78a00 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt @@ -12,6 +12,7 @@ import android.view.LayoutInflater import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.app.AlertDialog +import androidx.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?) { + fun setSettingsList(settings: ArrayList?) { 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) { 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 f522a82b7d..a9cfdc21f8 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 @@ -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) { - 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() { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 8eaa0a0fab..e2b1326f8f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -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? = 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 putSetting(setting: 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() { + fun onViewCreated() { loadSettingsList() } - fun setSettings(settings: Settings) { - if (settingsList == null) { - this.settings = settings - loadSettingsList() - } else { - fragmentView.fragmentActivity.setTitle(R.string.preferences_settings) - fragmentView.showSettingsList(settingsList!!) - } + fun putSetting(setting: Setting) { + settings.getSection(setting.section)!!.putSetting(setting) } - private fun loadSettingsList() { + fun loadSettingsList() { if (!TextUtils.isEmpty(gameId)) { - fragmentView.fragmentActivity.title = "Game Settings: $gameId" + settingsActivity.title = "Game Settings: $gameId" } val sl = ArrayList() if (menuTag == null) { @@ -77,7 +56,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } private fun addConfigSettings(sl: ArrayList) { - 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) { - 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) { - 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) { - 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) { - 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( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt index 2d9700fcaf..9a14c57951 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt @@ -3,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) /** - * 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