From 1a11aaa651fded8931dd27dc592975b4072e3834 Mon Sep 17 00:00:00 2001 From: lynxnb Date: Thu, 23 Feb 2023 00:48:28 +0100 Subject: [PATCH] Add per-game settings configuration functionality --- app/src/main/java/emu/skyline/AppDialog.kt | 8 ++ app/src/main/java/emu/skyline/MainActivity.kt | 3 +- .../skyline/preference/GpuDriverActivity.kt | 23 ++++- .../skyline/preference/GpuDriverPreference.kt | 11 ++- .../preference/ResetSettingsPreference.kt | 37 ++++++++ .../skyline/settings/GameSettingsFragment.kt | 93 ++++++++++++++++++ .../emu/skyline/settings/SettingsActivity.kt | 4 + app/src/main/res/layout/app_dialog.xml | 95 +++++++++++-------- app/src/main/res/values/strings.xml | 8 ++ .../main/res/xml/custom_game_preferences.xml | 17 ++++ 10 files changed, 251 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/emu/skyline/preference/ResetSettingsPreference.kt create mode 100644 app/src/main/java/emu/skyline/settings/GameSettingsFragment.kt create mode 100644 app/src/main/res/xml/custom_game_preferences.xml diff --git a/app/src/main/java/emu/skyline/AppDialog.kt b/app/src/main/java/emu/skyline/AppDialog.kt index f5be9556..e5d0283b 100644 --- a/app/src/main/java/emu/skyline/AppDialog.kt +++ b/app/src/main/java/emu/skyline/AppDialog.kt @@ -23,6 +23,7 @@ import com.google.android.material.snackbar.Snackbar import emu.skyline.data.AppItem import emu.skyline.databinding.AppDialogBinding import emu.skyline.loader.LoaderResult +import emu.skyline.settings.SettingsActivity /** * This dialog is used to show extra game metadata and provide extra options such as pinning the game to the home screen @@ -75,6 +76,13 @@ class AppDialog : BottomSheetDialogFragment() { startActivity(Intent(activity, EmulationActivity::class.java).apply { data = item.uri }) } + binding.gameSettings.isEnabled = item.loaderResult == LoaderResult.Success + binding.gameSettings.setOnClickListener { + startActivity(Intent(activity, SettingsActivity::class.java).apply { + putExtras(requireArguments()) + }) + } + val shortcutManager = requireActivity().getSystemService(ShortcutManager::class.java) binding.gamePin.isEnabled = shortcutManager.isRequestPinShortcutSupported diff --git a/app/src/main/java/emu/skyline/MainActivity.kt b/app/src/main/java/emu/skyline/MainActivity.kt index b23aebc5..a1bc4dd8 100644 --- a/app/src/main/java/emu/skyline/MainActivity.kt +++ b/app/src/main/java/emu/skyline/MainActivity.kt @@ -333,8 +333,7 @@ class MainActivity : AppCompatActivity() { super.onResume() // Try to return to normal GPU clocks upon resuming back to main activity, to avoid GPU being stuck at max clocks after a crash - if (EmulationSettings.global.forceMaxGpuClocks) - GpuDriverHelper.forceMaxGpuClocks(false) + GpuDriverHelper.forceMaxGpuClocks(false) var layoutTypeChanged = false for (appViewItem in adapter.allItems.filterIsInstance(AppViewItem::class.java)) { diff --git a/app/src/main/java/emu/skyline/preference/GpuDriverActivity.kt b/app/src/main/java/emu/skyline/preference/GpuDriverActivity.kt index 003cb580..ca37cfa1 100644 --- a/app/src/main/java/emu/skyline/preference/GpuDriverActivity.kt +++ b/app/src/main/java/emu/skyline/preference/GpuDriverActivity.kt @@ -22,6 +22,7 @@ import emu.skyline.adapter.GenericListItem import emu.skyline.adapter.GpuDriverViewItem import emu.skyline.adapter.SelectableGenericAdapter import emu.skyline.adapter.SpacingItemDecoration +import emu.skyline.data.AppItem import emu.skyline.databinding.GpuDriverActivityBinding import emu.skyline.settings.EmulationSettings import emu.skyline.utils.GpuDriverHelper @@ -38,6 +39,8 @@ import kotlinx.coroutines.launch class GpuDriverActivity : AppCompatActivity() { private val binding by lazy { GpuDriverActivityBinding.inflate(layoutInflater) } + private val item by lazy { intent.extras?.getSerializable("item") as AppItem? } + private val adapter = SelectableGenericAdapter(0) lateinit var emulationSettings : EmulationSettings @@ -80,7 +83,8 @@ class GpuDriverActivity : AppCompatActivity() { GpuDriverHelper.getInstalledDrivers(this).onEachIndexed { index, (file, metadata) -> items.add(GpuDriverViewItem(metadata).apply { - onDelete = { position, wasChecked -> + // Enable the delete button when configuring global settings only + onDelete = if (emulationSettings.isGlobal) { position, wasChecked -> // If the driver was selected, select the system driver as the active one if (wasChecked) emulationSettings.gpuDriver = EmulationSettings.SYSTEM_GPU_DRIVER @@ -103,7 +107,7 @@ class GpuDriverActivity : AppCompatActivity() { } } }).show() - } + } else null onClick = { emulationSettings.gpuDriver = metadata.label @@ -127,8 +131,18 @@ class GpuDriverActivity : AppCompatActivity() { WindowInsetsHelper.addMargin(binding.addDriverButton, bottom = true) setSupportActionBar(binding.titlebar.toolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.title = getString(R.string.gpu_driver_config) + supportActionBar?.apply { + setDisplayHomeAsUpEnabled(true) + title = getString(R.string.gpu_driver_config) + subtitle = item?.title + } + + emulationSettings = if (item == null) { + EmulationSettings.global + } else { + val appItem = item as AppItem + EmulationSettings.forTitleId(appItem.titleId ?: appItem.key()) + } val layoutManager = LinearLayoutManager(this) binding.driverList.layoutManager = layoutManager @@ -173,7 +187,6 @@ class GpuDriverActivity : AppCompatActivity() { installCallback.launch(intent) } - emulationSettings = EmulationSettings.global populateAdapter() } diff --git a/app/src/main/java/emu/skyline/preference/GpuDriverPreference.kt b/app/src/main/java/emu/skyline/preference/GpuDriverPreference.kt index e907f594..2d9f19e3 100644 --- a/app/src/main/java/emu/skyline/preference/GpuDriverPreference.kt +++ b/app/src/main/java/emu/skyline/preference/GpuDriverPreference.kt @@ -13,6 +13,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.preference.Preference import androidx.preference.Preference.SummaryProvider import androidx.preference.R +import emu.skyline.data.AppItem import emu.skyline.settings.EmulationSettings import emu.skyline.utils.GpuDriverHelper import emu.skyline.R as SkylineR @@ -25,6 +26,12 @@ class GpuDriverPreference @JvmOverloads constructor(context : Context, attrs : A notifyChanged() } + /** + * The app item being configured, used to load the correct settings in [GpuDriverActivity] + * This is populated by [emu.skyline.settings.GameSettingsFragment] + */ + var item : AppItem? = null + init { val supportsCustomDriverLoading = GpuDriverHelper.supportsCustomDriverLoading() if (supportsCustomDriverLoading) { @@ -48,5 +55,7 @@ class GpuDriverPreference @JvmOverloads constructor(context : Context, attrs : A /** * This launches [GpuDriverActivity] on click to manage driver packages */ - override fun onClick() = driverCallback.launch(Intent(context, GpuDriverActivity::class.java)) + override fun onClick() = driverCallback.launch(Intent(context, GpuDriverActivity::class.java).apply { + putExtra("item", item) + }) } diff --git a/app/src/main/java/emu/skyline/preference/ResetSettingsPreference.kt b/app/src/main/java/emu/skyline/preference/ResetSettingsPreference.kt new file mode 100644 index 00000000..ac23d305 --- /dev/null +++ b/app/src/main/java/emu/skyline/preference/ResetSettingsPreference.kt @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/) + */ + +package emu.skyline.preference + +import android.app.Activity +import android.content.Context +import android.util.AttributeSet +import androidx.preference.Preference +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import emu.skyline.R + +/** + * Clears all values in the current shared preferences, showing a dialog to confirm the action + * This preference recreates the activity to update the UI after modifying shared preferences + */ +class ResetSettingsPreference @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = androidx.preference.R.attr.preferenceStyle) : Preference(context, attrs, defStyleAttr) { + init { + setOnPreferenceClickListener { + MaterialAlertDialogBuilder(context) + .setTitle(title) + .setMessage(R.string.reset_settings_warning) + .setPositiveButton(android.R.string.ok) { _, _ -> + // Clear all shared preferences + sharedPreferences?.apply { edit().clear().apply() } + // Recreate the activity to update the UI after modifying shared preferences + (context as? Activity)?.recreate() + } + .setNegativeButton(android.R.string.cancel, null) + .show() + + true + } + } +} diff --git a/app/src/main/java/emu/skyline/settings/GameSettingsFragment.kt b/app/src/main/java/emu/skyline/settings/GameSettingsFragment.kt new file mode 100644 index 00000000..af67d6b6 --- /dev/null +++ b/app/src/main/java/emu/skyline/settings/GameSettingsFragment.kt @@ -0,0 +1,93 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/) + */ + +package emu.skyline.settings + +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.preference.* +import emu.skyline.BuildConfig +import emu.skyline.R +import emu.skyline.data.AppItem +import emu.skyline.preference.GpuDriverPreference +import emu.skyline.preference.IntegerListPreference +import emu.skyline.utils.GpuDriverHelper +import emu.skyline.utils.WindowInsetsHelper + +/** + * This fragment is used to display custom game preferences + */ +class GameSettingsFragment : PreferenceFragmentCompat() { + companion object { + private const val DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG" + } + + private val item by lazy { requireArguments().getSerializable("item")!! as AppItem } + + override fun onViewCreated(view : View, savedInstanceState : Bundle?) { + super.onViewCreated(view, savedInstanceState) + val recyclerView = view.findViewById(R.id.recycler_view) + WindowInsetsHelper.setPadding(recyclerView, bottom = true) + + (activity as AppCompatActivity).supportActionBar?.subtitle = item.title + } + + /** + * This constructs the preferences from XML preference resources + */ + override fun onCreatePreferences(savedInstanceState : Bundle?, rootKey : String?) { + preferenceManager.sharedPreferencesName = EmulationSettings.prefNameForTitle(item.titleId ?: item.key()) + addPreferencesFromResource(R.xml.custom_game_preferences) + addPreferencesFromResource(R.xml.emulation_preferences) + + // Toggle emulation settings enabled state based on use_custom_settings state + listOf( + findPreference("category_system"), + findPreference("category_presentation"), + findPreference("category_gpu"), + findPreference("category_hacks"), + findPreference("category_audio"), + findPreference("category_debug") + ).forEach { it?.dependency = "use_custom_settings" } + + // Uncheck `disable_frame_throttling` if `force_triple_buffering` gets disabled + val disableFrameThrottlingPref = findPreference("disable_frame_throttling")!! + findPreference("force_triple_buffering")?.setOnPreferenceChangeListener { _, newValue -> + if (newValue == false) + disableFrameThrottlingPref.isChecked = false + true + } + + // Only show debug settings in debug builds + @Suppress("SENSELESS_COMPARISON") + if (BuildConfig.BUILD_TYPE != "release") + findPreference("category_debug")?.isVisible = true + + if (!GpuDriverHelper.supportsForceMaxGpuClocks()) { + val forceMaxGpuClocksPref = findPreference("force_max_gpu_clocks")!! + forceMaxGpuClocksPref.isSelectable = false + forceMaxGpuClocksPref.isChecked = false + forceMaxGpuClocksPref.summary = context!!.getString(R.string.force_max_gpu_clocks_desc_unsupported) + } + + findPreference("gpu_driver")?.item = item + } + + override fun onDisplayPreferenceDialog(preference : Preference) { + if (preference is IntegerListPreference) { + // Check if dialog is already showing + if (parentFragmentManager.findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) + return + + val dialogFragment = IntegerListPreference.IntegerListPreferenceDialogFragmentCompat.newInstance(preference.getKey()) + @Suppress("DEPRECATION") + dialogFragment.setTargetFragment(this, 0) // androidx.preference.PreferenceDialogFragmentCompat depends on the target fragment being set correctly even though it's deprecated + dialogFragment.show(parentFragmentManager, DIALOG_FRAGMENT_TAG) + } else { + super.onDisplayPreferenceDialog(preference) + } + } +} diff --git a/app/src/main/java/emu/skyline/settings/SettingsActivity.kt b/app/src/main/java/emu/skyline/settings/SettingsActivity.kt index 14a245ad..868a9b90 100644 --- a/app/src/main/java/emu/skyline/settings/SettingsActivity.kt +++ b/app/src/main/java/emu/skyline/settings/SettingsActivity.kt @@ -25,8 +25,12 @@ class SettingsActivity : AppCompatActivity() { /** * The instance of [PreferenceFragmentCompat] that is shown inside [R.id.settings] + * Retrieves extras from the intent if any and instantiates the appropriate fragment */ private val preferenceFragment by lazy { + if (intent.hasExtra("item")) + GameSettingsFragment().apply { arguments = intent.extras } + else GlobalSettingsFragment() } diff --git a/app/src/main/res/layout/app_dialog.xml b/app/src/main/res/layout/app_dialog.xml index d6485813..7efb1fd3 100644 --- a/app/src/main/res/layout/app_dialog.xml +++ b/app/src/main/res/layout/app_dialog.xml @@ -17,7 +17,7 @@ android:layout_height="wrap_content" android:nextFocusRight="@id/game_play" android:paddingHorizontal="16dp" - android:paddingBottom="16dp"> + android:paddingBottom="8dp"> @@ -86,43 +85,59 @@ app:layout_constraintTop_toBottomOf="@id/game_version" tools:text="Nintendo" /> - - -