Add per-game settings configuration functionality

This commit is contained in:
lynxnb 2023-02-23 00:48:28 +01:00 committed by Niccolò Betto
parent 0467614dc0
commit 1a11aaa651
10 changed files with 251 additions and 48 deletions

View file

@ -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

View file

@ -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)) {

View file

@ -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()
}

View file

@ -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)
})
}

View file

@ -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
}
}
}

View file

@ -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<View>(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<Preference?>(
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<CheckBoxPreference>("disable_frame_throttling")!!
findPreference<CheckBoxPreference>("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<Preference>("category_debug")?.isVisible = true
if (!GpuDriverHelper.supportsForceMaxGpuClocks()) {
val forceMaxGpuClocksPref = findPreference<CheckBoxPreference>("force_max_gpu_clocks")!!
forceMaxGpuClocksPref.isSelectable = false
forceMaxGpuClocksPref.isChecked = false
forceMaxGpuClocksPref.summary = context!!.getString(R.string.force_max_gpu_clocks_desc_unsupported)
}
findPreference<GpuDriverPreference>("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)
}
}
}

View file

@ -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()
}

View file

@ -17,7 +17,7 @@
android:layout_height="wrap_content"
android:nextFocusRight="@id/game_play"
android:paddingHorizontal="16dp"
android:paddingBottom="16dp">
android:paddingBottom="8dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/game_icon"
@ -36,9 +36,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintHeight_min="140dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_min="140dp"
app:layout_constraintStart_toEndOf="@id/game_icon"
app:layout_constraintTop_toTopOf="parent">
@ -86,43 +85,59 @@
app:layout_constraintTop_toBottomOf="@id/game_version"
tools:text="Nintendo" />
<com.google.android.flexbox.FlexboxLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
app:flexWrap="nowrap"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/game_author"
app:layout_constraintVertical_bias="1">
<Button
android:id="@+id/game_play"
style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/play"
android:focusedByDefault="true"
android:text="@string/play"
android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_play"
app:iconTint="?attr/colorAccent"
app:layout_flexGrow="1"
app:layout_maxWidth="180dp" />
<Button
android:id="@+id/game_pin"
style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/pin"
android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_add_home"
app:iconGravity="textStart"
android:padding="0dp"
app:iconPadding="0dp" />
</com.google.android.flexbox.FlexboxLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.flexbox.FlexboxLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:flexWrap="nowrap"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/game_icon"
app:layout_constraintVertical_bias="1">
<Button
android:id="@+id/game_play"
style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/play"
android:focusedByDefault="true"
android:text="@string/play"
android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_play"
app:iconTint="?attr/colorAccent"
app:layout_flexGrow="1"
app:layout_maxWidth="140dp" />
<Button
android:id="@+id/game_settings"
style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/settings"
android:padding="0dp"
android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_settings"
app:iconGravity="textStart"
app:iconPadding="0dp" />
<Button
android:id="@+id/game_pin"
style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:contentDescription="@string/pin"
android:padding="0dp"
android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_add_home"
app:iconGravity="textStart"
app:iconPadding="0dp" />
</com.google.android.flexbox.FlexboxLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View file

@ -43,6 +43,14 @@
<string name="select_action">Always Show Game Information</string>
<string name="select_action_desc_on">Game information will be shown on clicking a game</string>
<string name="select_action_desc_off">Game information will only be shown on long-clicking a game</string>
<!-- Settings - Game -->
<string name="game">Game</string>
<string name="use_custom_settings">Enable Custom Settings</string>
<string name="use_custom_settings_desc_on">Custom settings are enabled for this game</string>
<string name="use_custom_settings_desc_off">Custom settings are disabled for this game</string>
<string name="reset_custom_settings">Reset Custom Settings</string>
<string name="reset_custom_settings_desc">Reset custom settings to the default values</string>
<string name="reset_settings_warning">Are you sure you want to reset all settings to the default values? <b>Current settings will be lost</b></string>
<!-- Settings - System -->
<string name="system">System</string>
<string name="use_docked">Use Docked Mode</string>

View file

@ -0,0 +1,17 @@
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:key="category_game"
android:title="@string/game">
<CheckBoxPreference
android:defaultValue="false"
android:summaryOff="@string/use_custom_settings_desc_off"
android:summaryOn="@string/use_custom_settings_desc_on"
app:key="use_custom_settings"
app:title="@string/use_custom_settings" />
<emu.skyline.preference.ResetSettingsPreference
android:summary="@string/reset_custom_settings_desc"
app:key="reset_custom_settings"
app:title="@string/reset_custom_settings" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>