summaryrefslogtreecommitdiffstats
path: root/src/android/app
diff options
context:
space:
mode:
Diffstat (limited to 'src/android/app')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt52
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt30
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt61
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt29
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt91
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt7
-rw-r--r--src/android/app/src/main/jni/emu_window/emu_window.cpp8
-rw-r--r--src/android/app/src/main/jni/emu_window/emu_window.h4
-rw-r--r--src/android/app/src/main/jni/native.cpp11
-rw-r--r--src/android/app/src/main/jni/native.h6
-rw-r--r--src/android/app/src/main/jni/uisettings.h2
-rw-r--r--src/android/app/src/main/res/drawable/ic_audio.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_code.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_graphics.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_system_settings.xml9
-rw-r--r--src/android/app/src/main/res/layout-w600dp/fragment_about.xml233
-rw-r--r--src/android/app/src/main/res/layout/card_home_option.xml4
-rw-r--r--src/android/app/src/main/res/layout/fragment_about.xml10
-rw-r--r--src/android/app/src/main/res/layout/fragment_emulation.xml11
-rw-r--r--src/android/app/src/main/res/layout/fragment_home_settings.xml9
-rw-r--r--src/android/app/src/main/res/layout/fragment_search.xml1
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting.xml72
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting_switch.xml8
-rw-r--r--src/android/app/src/main/res/layout/list_item_settings_header.xml3
-rw-r--r--src/android/app/src/main/res/values/arrays.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml7
38 files changed, 525 insertions, 255 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 054e4b755..f41d7bdbf 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -373,8 +373,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ val isEmulationActive = emulationViewModel.emulationStarted.value &&
+ !emulationViewModel.isEmulationStopping.value
pictureInPictureParamsBuilder.setAutoEnterEnabled(
- BooleanSetting.PICTURE_IN_PICTURE.boolean
+ BooleanSetting.PICTURE_IN_PICTURE.boolean && isEmulationActive
)
}
setPictureInPictureParams(pictureInPictureParamsBuilder.build())
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index 0c82cdba8..2ef638559 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -22,12 +22,16 @@ import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
@@ -92,28 +96,34 @@ class GameAdapter(private val activity: AppCompatActivity) :
data = Uri.parse(holder.game.path)
}
- val layerDrawable = ResourcesCompat.getDrawable(
- YuzuApplication.appContext.resources,
- R.drawable.shortcut,
- null
- ) as LayerDrawable
- layerDrawable.setDrawableByLayerId(
- R.id.shortcut_foreground,
- GameIconUtils.getGameIcon(holder.game).toDrawable(YuzuApplication.appContext.resources)
- )
- val inset = YuzuApplication.appContext.resources
- .getDimensionPixelSize(R.dimen.icon_inset)
- layerDrawable.setLayerInset(1, inset, inset, inset, inset)
- val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
- .setShortLabel(holder.game.title)
- .setIcon(
- IconCompat.createWithAdaptiveBitmap(
- layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
+ activity.lifecycleScope.launch {
+ withContext(Dispatchers.IO) {
+ val layerDrawable = ResourcesCompat.getDrawable(
+ YuzuApplication.appContext.resources,
+ R.drawable.shortcut,
+ null
+ ) as LayerDrawable
+ layerDrawable.setDrawableByLayerId(
+ R.id.shortcut_foreground,
+ GameIconUtils.getGameIcon(activity, holder.game)
+ .toDrawable(YuzuApplication.appContext.resources)
)
- )
- .setIntent(openIntent)
- .build()
- ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
+ val inset = YuzuApplication.appContext.resources
+ .getDimensionPixelSize(R.dimen.icon_inset)
+ layerDrawable.setLayerInset(1, inset, inset, inset, inset)
+ val shortcut =
+ ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
+ .setShortLabel(holder.game.title)
+ .setIcon(
+ IconCompat.createWithAdaptiveBitmap(
+ layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
+ )
+ )
+ .setIntent(openIntent)
+ .build()
+ ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
+ }
+ }
val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game)
view.findNavController().navigate(action)
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 08e2a973d..2bf0e1b0d 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
@@ -82,7 +82,6 @@ object Settings {
enum class MenuTag(val titleId: Int) {
SECTION_ROOT(R.string.advanced_settings),
- SECTION_GENERAL(R.string.preferences_general),
SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics),
SECTION_AUDIO(R.string.preferences_audio),
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
index 522cc49df..425160024 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
@@ -3,10 +3,13 @@
package org.yuzu.yuzu_emu.features.settings.model.view
+import androidx.annotation.DrawableRes
+
class RunnableSetting(
titleId: Int,
descriptionId: Int,
val isRuntimeRunnable: Boolean,
+ @DrawableRes val iconId: Int = 0,
val runnable: () -> Unit
) : SettingsItem(emptySetting, titleId, descriptionId) {
override val type = TYPE_RUNNABLE
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index b3b3fc209..6aba69dbe 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -73,7 +73,7 @@ abstract class SettingsItem(
R.string.frame_limit_slider,
R.string.frame_limit_slider_description,
1,
- 200,
+ 400,
"%"
)
)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
index b343e527e..94953b18a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
@@ -3,11 +3,14 @@
package org.yuzu.yuzu_emu.features.settings.model.view
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.features.settings.model.Settings
class SubmenuSetting(
- titleId: Int,
- descriptionId: Int,
+ @StringRes titleId: Int,
+ @StringRes descriptionId: Int,
+ @DrawableRes val iconId: Int,
val menuKey: Settings.MenuTag
) : SettingsItem(emptySetting, titleId, descriptionId) {
override val type = TYPE_SUBMENU
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 70d8ec14b..769baf744 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
@@ -20,7 +20,6 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
-import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@@ -68,15 +67,9 @@ class SettingsFragment : Fragment() {
)
binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
- val dividerDecoration = MaterialDividerItemDecoration(
- requireContext(),
- LinearLayoutManager.VERTICAL
- )
- dividerDecoration.isLastItemDecorated = false
binding.listSettings.apply {
adapter = settingsAdapter
layoutManager = LinearLayoutManager(requireContext())
- addItemDecoration(dividerDecoration)
}
binding.toolbarSettings.setNavigationOnClickListener {
@@ -94,17 +87,6 @@ class SettingsFragment : Fragment() {
}
}
}
- launch {
- settingsViewModel.isUsingSearch.collectLatest {
- if (it) {
- reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
- exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
- } else {
- reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
- exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
- }
- }
- }
}
if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
@@ -112,8 +94,6 @@ class SettingsFragment : Fragment() {
binding.toolbarSettings.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_search -> {
- reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
- exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
view.findNavController()
.navigate(R.id.action_settingsFragment_to_settingsSearchFragment)
true
@@ -129,11 +109,6 @@ class SettingsFragment : Fragment() {
setInsets()
}
- override fun onResume() {
- super.onResume()
- settingsViewModel.setIsUsingSearch(false)
- }
-
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
@@ -144,10 +119,9 @@ class SettingsFragment : Fragment() {
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
- val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
val mlpSettingsList = binding.listSettings.layoutParams as MarginLayoutParams
- mlpSettingsList.leftMargin = sideMargin + leftInsets
- mlpSettingsList.rightMargin = sideMargin + rightInsets
+ mlpSettingsList.leftMargin = leftInsets
+ mlpSettingsList.rightMargin = rightInsets
binding.listSettings.layoutParams = mlpSettingsList
binding.listSettings.updatePadding(
bottom = barInsets.bottom
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 766414a6c..8b71e32f3 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
@@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.features.settings.ui
-import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.widget.Toast
@@ -32,8 +31,6 @@ class SettingsFragmentPresenter(
private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
- private val context: Context get() = YuzuApplication.appContext
-
// Extension for populating settings list based on paired settings
fun ArrayList<SettingsItem>.add(key: String) {
val item = SettingsItem.settingsItems[key]!!
@@ -53,7 +50,6 @@ class SettingsFragmentPresenter(
val sl = ArrayList<SettingsItem>()
when (menuTag) {
Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
- Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl)
Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
@@ -75,30 +71,53 @@ class SettingsFragmentPresenter(
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
sl.apply {
- add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL))
- add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM))
- add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER))
- add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO))
- add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG))
add(
- RunnableSetting(R.string.reset_to_default, 0, false) {
- settingsViewModel.setShouldShowResetSettingsDialog(true)
- }
+ SubmenuSetting(
+ R.string.preferences_system,
+ R.string.preferences_system_description,
+ R.drawable.ic_system_settings,
+ Settings.MenuTag.SECTION_SYSTEM
+ )
+ )
+ add(
+ SubmenuSetting(
+ R.string.preferences_graphics,
+ R.string.preferences_graphics_description,
+ R.drawable.ic_graphics,
+ Settings.MenuTag.SECTION_RENDERER
+ )
+ )
+ add(
+ SubmenuSetting(
+ R.string.preferences_audio,
+ R.string.preferences_audio_description,
+ R.drawable.ic_audio,
+ Settings.MenuTag.SECTION_AUDIO
+ )
+ )
+ add(
+ SubmenuSetting(
+ R.string.preferences_debug,
+ R.string.preferences_debug_description,
+ R.drawable.ic_code,
+ Settings.MenuTag.SECTION_DEBUG
+ )
+ )
+ add(
+ RunnableSetting(
+ R.string.reset_to_default,
+ R.string.reset_to_default_description,
+ false,
+ R.drawable.ic_restore
+ ) { settingsViewModel.setShouldShowResetSettingsDialog(true) }
)
}
}
- private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
+ private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
sl.apply {
add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
add(ShortSetting.RENDERER_SPEED_LIMIT.key)
- add(IntSetting.CPU_ACCURACY.key)
- add(BooleanSetting.PICTURE_IN_PICTURE.key)
- }
- }
-
- private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
- sl.apply {
add(BooleanSetting.USE_DOCKED_MODE.key)
add(IntSetting.REGION_INDEX.key)
add(IntSetting.LANGUAGE_INDEX.key)
@@ -116,6 +135,7 @@ class SettingsFragmentPresenter(
add(IntSetting.RENDERER_ANTI_ALIASING.key)
add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
add(IntSetting.RENDERER_ASPECT_RATIO.key)
+ add(BooleanSetting.PICTURE_IN_PICTURE.key)
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
@@ -249,6 +269,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.RENDERER_DEBUG.key)
add(HeaderSetting(R.string.cpu))
+ add(IntSetting.CPU_ACCURACY.key)
add(BooleanSetting.CPU_DEBUG_MODE.key)
add(SettingsItem.FASTMEM_COMBINED)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
index 83a2e94f1..036195624 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
+import androidx.core.content.res.ResourcesCompat
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.RunnableSetting
@@ -16,6 +17,19 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
override fun bind(item: SettingsItem) {
setting = item as RunnableSetting
+ if (item.iconId != 0) {
+ binding.icon.visibility = View.VISIBLE
+ binding.icon.setImageDrawable(
+ ResourcesCompat.getDrawable(
+ binding.icon.resources,
+ item.iconId,
+ binding.icon.context.theme
+ )
+ )
+ } else {
+ binding.icon.visibility = View.GONE
+ }
+
binding.textSettingName.setText(item.nameId)
if (item.descriptionId != 0) {
binding.textSettingDescription.setText(item.descriptionId)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
index 1cf581a9d..8100c65dd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
+import androidx.core.content.res.ResourcesCompat
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SubmenuSetting
@@ -15,6 +16,19 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
override fun bind(item: SettingsItem) {
this.item = item as SubmenuSetting
+ if (item.iconId != 0) {
+ binding.icon.visibility = View.VISIBLE
+ binding.icon.setImageDrawable(
+ ResourcesCompat.getDrawable(
+ binding.icon.resources,
+ item.iconId,
+ binding.icon.context.theme
+ )
+ )
+ } else {
+ binding.icon.visibility = View.GONE
+ }
+
binding.textSettingName.setText(item.nameId)
if (item.descriptionId != 0) {
binding.textSettingDescription.setText(item.descriptionId)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
index 2ff827c6b..a1620fbb7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt
@@ -114,10 +114,10 @@ class AboutFragment : Fragment() {
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
- val mlpAppBar = binding.appbarAbout.layoutParams as MarginLayoutParams
- mlpAppBar.leftMargin = leftInsets
- mlpAppBar.rightMargin = rightInsets
- binding.appbarAbout.layoutParams = mlpAppBar
+ val mlpToolbar = binding.toolbarAbout.layoutParams as MarginLayoutParams
+ mlpToolbar.leftMargin = leftInsets
+ mlpToolbar.rightMargin = rightInsets
+ binding.toolbarAbout.layoutParams = mlpToolbar
val mlpScrollAbout = binding.scrollAbout.layoutParams as MarginLayoutParams
mlpScrollAbout.leftMargin = leftInsets
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index c456c0592..c32fa0d7e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -10,7 +10,6 @@ import android.content.DialogInterface
import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.res.Configuration
-import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.os.Handler
@@ -155,7 +154,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
binding.surfaceEmulation.holder.addCallback(this)
- binding.showFpsText.setTextColor(Color.YELLOW)
binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
binding.drawerLayout.addDrawerListener(object : DrawerListener {
@@ -414,12 +412,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val FRAMETIME = 2
val SPEED = 3
perfStatsUpdater = {
- if (emulationViewModel.emulationStarted.value == true) {
+ if (emulationViewModel.emulationStarted.value) {
val perfStats = NativeLibrary.getPerfStats()
- if (perfStats[FPS] > 0 && _binding != null) {
+ if (_binding != null) {
binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
}
- perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100)
+ perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)
}
}
perfStatsUpdateHandler.post(perfStatsUpdater!!)
@@ -464,7 +462,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
// Restrict emulation and overlays to the top of the screen
binding.emulationContainer.layoutParams.height = it.bounds.top
- binding.overlayContainer.layoutParams.height = it.bounds.top
// Restrict input and menu drawer to the bottom of the screen
binding.inputContainer.layoutParams.height = it.bounds.bottom
binding.inGameMenu.layoutParams.height = it.bounds.bottom
@@ -478,7 +475,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (!isFolding) {
binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
- binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
isInFoldableLayout = false
updateOrientation()
@@ -486,7 +482,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
binding.emulationContainer.requestLayout()
binding.inputContainer.requestLayout()
- binding.overlayContainer.requestLayout()
binding.inGameMenu.requestLayout()
}
@@ -712,24 +707,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
v.setPadding(left, cutInsets.top, right, 0)
-
- // Ensure FPS text doesn't get cut off by rounded display corners
- val sidePadding = resources.getDimensionPixelSize(R.dimen.spacing_xtralarge)
- if (cutInsets.left == 0) {
- binding.showFpsText.setPadding(
- sidePadding,
- cutInsets.top,
- cutInsets.right,
- cutInsets.bottom
- )
- } else {
- binding.showFpsText.setPadding(
- cutInsets.left,
- cutInsets.top,
- cutInsets.right,
- cutInsets.bottom
- )
- }
windowInsets
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
index ec116ab62..6940fc757 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt
@@ -21,6 +21,8 @@ import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.Installable
import org.yuzu.yuzu_emu.ui.main.MainActivity
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
class InstallableFragment : Fragment() {
private var _binding: FragmentInstallablesBinding? = null
@@ -78,7 +80,15 @@ class InstallableFragment : Fragment() {
R.string.manage_save_data,
R.string.import_export_saves_description,
install = { mainActivity.importSaves.launch(arrayOf("application/zip")) },
- export = { mainActivity.exportSave() }
+ export = {
+ mainActivity.exportSaves.launch(
+ "yuzu saves - ${
+ LocalDateTime.now().format(
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
+ )
+ }.zip"
+ )
+ }
)
} else {
Installable(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
index 9d0594c6e..f95d545bf 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
@@ -40,8 +40,10 @@ class SettingsSearchFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
- returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+ enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+ returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
}
override fun onCreateView(
@@ -55,7 +57,6 @@ class SettingsSearchFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- settingsViewModel.setIsUsingSearch(true)
if (savedInstanceState != null) {
binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
index de84b2adb..2fa3ab31b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
@@ -18,8 +18,8 @@ class Game(
val version: String = "",
val isHomebrew: Boolean = false
) : Parcelable {
- val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime"
- val keyLastPlayedTime get() = "${programId}_LastPlayed"
+ val keyAddedToLibraryTime get() = "${path}_AddedToLibraryTime"
+ val keyLastPlayedTime get() = "${path}_LastPlayed"
override fun equals(other: Any?): Boolean {
if (other !is Game) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
index 53fa7a8de..6f947674e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -29,9 +29,6 @@ class SettingsViewModel : ViewModel() {
val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
private val _shouldReloadSettingsList = MutableStateFlow(false)
- val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch
- private val _isUsingSearch = MutableStateFlow(false)
-
val sliderProgress: StateFlow<Int> get() = _sliderProgress
private val _sliderProgress = MutableStateFlow(-1)
@@ -57,10 +54,6 @@ class SettingsViewModel : ViewModel() {
_shouldReloadSettingsList.value = value
}
- fun setIsUsingSearch(value: Boolean) {
- _isUsingSearch.value = value
- }
-
fun setSliderTextValue(value: Float, units: String) {
_sliderProgress.value = value.toInt()
_sliderTextValue.value = String.format(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 211b7cf69..ace5dddea 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.ui.main
import android.content.Intent
import android.net.Uri
import android.os.Bundle
-import android.provider.DocumentsContract
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import android.view.WindowManager
@@ -20,7 +19,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
-import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
@@ -41,7 +39,6 @@ import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
-import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
@@ -53,9 +50,6 @@ import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.*
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
-import java.io.FileOutputStream
-import java.time.LocalDateTime
-import java.time.format.DateTimeFormatter
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
@@ -73,7 +67,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
// Get first subfolder in saves folder (should be the user folder)
val savesFolderRoot get() = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: ""
- private var lastZipCreated: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
@@ -657,74 +650,30 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
/**
- * Zips the save files located in the given folder path and creates a new zip file with the current date and time.
- * @return true if the zip file is successfully created, false otherwise.
- */
- private fun zipSave(): Boolean {
- try {
- val tempFolder = File(getPublicFilesDir().canonicalPath, "temp")
- tempFolder.mkdirs()
- val saveFolder = File(savesFolderRoot)
- val outputZipFile = File(
- tempFolder,
- "yuzu saves - ${
- LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
- }.zip"
- )
- outputZipFile.createNewFile()
- val result = FileUtil.zipFromInternalStorage(
- saveFolder,
- savesFolderRoot,
- BufferedOutputStream(FileOutputStream(outputZipFile))
- )
- if (result == TaskState.Failed) {
- return false
- }
- lastZipCreated = outputZipFile
- } catch (e: Exception) {
- return false
- }
- return true
- }
-
- /**
* Exports the save file located in the given folder path by creating a zip file and sharing it via intent.
*/
- fun exportSave() {
- CoroutineScope(Dispatchers.IO).launch {
- val wasZipCreated = zipSave()
- val lastZipFile = lastZipCreated
- if (!wasZipCreated || lastZipFile == null) {
- withContext(Dispatchers.Main) {
- Toast.makeText(
- this@MainActivity,
- getString(R.string.export_save_failed),
- Toast.LENGTH_LONG
- ).show()
- }
- return@launch
- }
+ val exportSaves = registerForActivityResult(
+ ActivityResultContracts.CreateDocument("application/zip")
+ ) { result ->
+ if (result == null) {
+ return@registerForActivityResult
+ }
- withContext(Dispatchers.Main) {
- val file = DocumentFile.fromSingleUri(
- this@MainActivity,
- DocumentsContract.buildDocumentUri(
- DocumentProvider.AUTHORITY,
- "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
- )
- )!!
- val intent = Intent(Intent.ACTION_SEND)
- .setDataAndType(file.uri, "application/zip")
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- .putExtra(Intent.EXTRA_STREAM, file.uri)
- startForResultExportSave.launch(
- Intent.createChooser(
- intent,
- getString(R.string.share_save_file)
- )
- )
+ IndeterminateProgressDialogFragment.newInstance(
+ this,
+ R.string.save_files_exporting,
+ false
+ ) {
+ val zipResult = FileUtil.zipFromInternalStorage(
+ File(savesFolderRoot),
+ savesFolderRoot,
+ BufferedOutputStream(contentResolver.openOutputStream(result))
+ )
+ return@newInstance when (zipResult) {
+ TaskState.Completed -> getString(R.string.export_success)
+ TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed)
}
- }
+ }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
private val startForResultExportSave =
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
index 654d62f52..2e9b0beb8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
@@ -8,9 +8,9 @@ import android.graphics.BitmapFactory
import android.widget.ImageView
import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable
+import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil.decode.DataSource
-import coil.executeBlocking
import coil.fetch.DrawableResult
import coil.fetch.FetchResult
import coil.fetch.Fetcher
@@ -76,12 +76,13 @@ object GameIconUtils {
imageLoader.enqueue(request)
}
- fun getGameIcon(game: Game): Bitmap {
+ suspend fun getGameIcon(lifecycleOwner: LifecycleOwner, game: Game): Bitmap {
val request = ImageRequest.Builder(YuzuApplication.appContext)
.data(game)
+ .lifecycle(lifecycleOwner)
.error(R.drawable.default_icon)
.build()
- return imageLoader.executeBlocking(request)
+ return imageLoader.execute(request)
.drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888)
}
}
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp
index a7e414b81..c4f631924 100644
--- a/src/android/app/src/main/jni/emu_window/emu_window.cpp
+++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp
@@ -9,6 +9,7 @@
#include "input_common/drivers/virtual_gamepad.h"
#include "input_common/main.h"
#include "jni/emu_window/emu_window.h"
+#include "jni/native.h"
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
m_window_width = ANativeWindow_getWidth(surface);
@@ -57,6 +58,13 @@ void EmuWindow_Android::OnRemoveNfcTag() {
m_input_subsystem->GetVirtualAmiibo()->CloseAmiibo();
}
+void EmuWindow_Android::OnFrameDisplayed() {
+ if (!m_first_frame) {
+ EmulationSession::GetInstance().OnEmulationStarted();
+ m_first_frame = true;
+ }
+}
+
EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsystem,
ANativeWindow* surface,
std::shared_ptr<Common::DynamicLibrary> driver_library)
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h
index b38087f73..a34a0e479 100644
--- a/src/android/app/src/main/jni/emu_window/emu_window.h
+++ b/src/android/app/src/main/jni/emu_window/emu_window.h
@@ -45,7 +45,7 @@ public:
float gyro_z, float accel_x, float accel_y, float accel_z);
void OnReadNfcTag(std::span<u8> data);
void OnRemoveNfcTag();
- void OnFrameDisplayed() override {}
+ void OnFrameDisplayed() override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
return {std::make_unique<GraphicsContext_Android>(m_driver_library)};
@@ -61,4 +61,6 @@ private:
float m_window_height{};
std::shared_ptr<Common::DynamicLibrary> m_driver_library;
+
+ bool m_first_frame = false;
};
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 46438906e..64663b084 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -199,8 +199,8 @@ bool EmulationSession::IsPaused() const {
return m_is_running && m_is_paused;
}
-const Core::PerfStatsResults& EmulationSession::PerfStats() const {
- std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
+const Core::PerfStatsResults& EmulationSession::PerfStats() {
+ m_perf_stats = m_system.GetAndResetPerfStats();
return m_perf_stats;
}
@@ -372,8 +372,6 @@ void EmulationSession::RunEmulation() {
m_system.InitializeDebugger();
}
- OnEmulationStarted();
-
while (true) {
{
[[maybe_unused]] std::unique_lock lock(m_mutex);
@@ -383,11 +381,6 @@ void EmulationSession::RunEmulation() {
break;
}
}
- {
- // Refresh performance stats.
- std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
- m_perf_stats = m_system.GetAndResetPerfStats();
- }
}
}
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
index 3b9596459..78ef96802 100644
--- a/src/android/app/src/main/jni/native.h
+++ b/src/android/app/src/main/jni/native.h
@@ -41,7 +41,7 @@ public:
void RunEmulation();
void ShutdownEmulation();
- const Core::PerfStatsResults& PerfStats() const;
+ const Core::PerfStatsResults& PerfStats();
void ConfigureFilesystemProvider(const std::string& filepath);
void InitializeSystem(bool reload);
Core::SystemResultStatus InitializeEmulation(const std::string& filepath);
@@ -52,9 +52,10 @@ public:
void OnGamepadDisconnectEvent([[maybe_unused]] int index);
SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard();
+ static void OnEmulationStarted();
+
private:
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
- static void OnEmulationStarted();
static void OnEmulationStopped(Core::SystemResultStatus result);
private:
@@ -80,6 +81,5 @@ private:
// Synchronization
std::condition_variable_any m_cv;
- mutable std::mutex m_perf_stats_mutex;
mutable std::mutex m_mutex;
};
diff --git a/src/android/app/src/main/jni/uisettings.h b/src/android/app/src/main/jni/uisettings.h
index 494654af7..37bc33918 100644
--- a/src/android/app/src/main/jni/uisettings.h
+++ b/src/android/app/src/main/jni/uisettings.h
@@ -13,7 +13,7 @@ struct Values {
Settings::Linkage linkage;
// Android
- Settings::Setting<bool> picture_in_picture{linkage, true, "picture_in_picture",
+ Settings::Setting<bool> picture_in_picture{linkage, false, "picture_in_picture",
Settings::Category::Android};
Settings::Setting<s32> screen_layout{linkage,
5,
diff --git a/src/android/app/src/main/res/drawable/ic_audio.xml b/src/android/app/src/main/res/drawable/ic_audio.xml
new file mode 100644
index 000000000..e306c3b0c
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_audio.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_code.xml b/src/android/app/src/main/res/drawable/ic_code.xml
new file mode 100644
index 000000000..26f83b39b
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_code.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M320,720 L80,480l240,-240 57,57 -184,184 183,183 -56,56ZM640,720 L583,663 767,479 584,296 640,240 880,480 640,720Z"/>
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_graphics.xml b/src/android/app/src/main/res/drawable/ic_graphics.xml
new file mode 100644
index 000000000..2fdb5a4d6
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_graphics.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M160,840q-33,0 -56.5,-23.5T80,760v-560q0,-33 23.5,-56.5T160,120h560q33,0 56.5,23.5T800,200v80h80v80h-80v80h80v80h-80v80h80v80h-80v80q0,33 -23.5,56.5T720,840L160,840ZM160,760h560v-560L160,200v560ZM240,680h200v-160L240,520v160ZM480,400h160v-120L480,280v120ZM240,480h200v-200L240,280v200ZM480,680h160v-240L480,440v240ZM160,200v560,-560Z"/>
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_system_settings.xml b/src/android/app/src/main/res/drawable/ic_system_settings.xml
new file mode 100644
index 000000000..7701a2bab
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_system_settings.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="?attr/colorControlNormal"
+ android:pathData="M320,960q-17,0 -28.5,-11.5T280,920q0,-17 11.5,-28.5T320,880q17,0 28.5,11.5T360,920q0,17 -11.5,28.5T320,960ZM480,960q-17,0 -28.5,-11.5T440,920q0,-17 11.5,-28.5T480,880q17,0 28.5,11.5T520,920q0,17 -11.5,28.5T480,960ZM640,960q-17,0 -28.5,-11.5T600,920q0,-17 11.5,-28.5T640,880q17,0 28.5,11.5T680,920q0,17 -11.5,28.5T640,960ZM320,800q-33,0 -56.5,-23.5T240,720v-640q0,-33 23.5,-56.5T320,0h320q33,0 56.5,23.5T720,80v640q0,33 -23.5,56.5T640,800L320,800ZM320,720h320v-40L320,680v40ZM320,600h320v-400L320,200v400ZM320,120h320v-40L320,80v40ZM320,120v-40,40ZM320,720v-40,40Z"/>
+</vector>
diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_about.xml b/src/android/app/src/main/res/layout-w600dp/fragment_about.xml
new file mode 100644
index 000000000..a26ffbc73
--- /dev/null
+++ b/src/android/app/src/main/res/layout-w600dp/fragment_about.xml
@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/coordinator_about"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/colorSurface">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/appbar_about"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true">
+
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/toolbar_about"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:navigationIcon="@drawable/ic_back"
+ app:title="@string/about" />
+
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <androidx.core.widget.NestedScrollView
+ android:id="@+id/scroll_about"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fadeScrollbars="false"
+ android:scrollbars="vertical"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+ <LinearLayout
+ android:id="@+id/content_about"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/image_logo"
+ android:layout_width="200dp"
+ android:layout_height="200dp"
+ android:layout_gravity="center_horizontal"
+ android:padding="20dp"
+ android:src="@drawable/ic_yuzu_title" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="16dp">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:text="@string/about"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="6dp"
+ android:text="@string/about_app_description"
+ android:textAlignment="viewStart" />
+
+ </LinearLayout>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="20dp" />
+
+ <LinearLayout
+ android:id="@+id/button_contributors"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:orientation="vertical"
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="16dp">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:text="@string/contributors"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="6dp"
+ android:text="@string/contributors_description"
+ android:textAlignment="viewStart" />
+
+ </LinearLayout>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="20dp" />
+
+ <LinearLayout
+ android:id="@+id/button_licenses"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:orientation="vertical"
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="16dp">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:text="@string/licenses"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="6dp"
+ android:text="@string/licenses_description"
+ android:textAlignment="viewStart" />
+
+ </LinearLayout>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="20dp" />
+
+ <LinearLayout
+ android:id="@+id/button_build_hash"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:orientation="vertical"
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="16dp">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:text="@string/build"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_build_hash"
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="6dp"
+ android:textAlignment="viewStart"
+ tools:text="abc123" />
+
+ </LinearLayout>
+
+ <com.google.android.material.divider.MaterialDivider
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="20dp" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="40dp"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="16dp"
+ android:gravity="center_horizontal"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/button_discord"
+ style="?attr/materialIconButtonStyle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ app:icon="@drawable/ic_discord"
+ app:iconGravity="textEnd"
+ app:iconSize="24dp"
+ app:iconTint="?attr/colorOnSurface" />
+
+ <Button
+ android:id="@+id/button_website"
+ style="?attr/materialIconButtonStyle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ app:icon="@drawable/ic_website"
+ app:iconGravity="textEnd"
+ app:iconSize="24dp"
+ app:iconTint="?attr/colorOnSurface" />
+
+ <Button
+ android:id="@+id/button_github"
+ style="?attr/materialIconButtonStyle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ app:icon="@drawable/ic_github"
+ app:iconGravity="textEnd"
+ app:iconSize="24dp"
+ app:iconTint="?attr/colorOnSurface" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </androidx.core.widget.NestedScrollView>
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/src/android/app/src/main/res/layout/card_home_option.xml b/src/android/app/src/main/res/layout/card_home_option.xml
index 6e8a232f9..cb667c928 100644
--- a/src/android/app/src/main/res/layout/card_home_option.xml
+++ b/src/android/app/src/main/res/layout/card_home_option.xml
@@ -6,8 +6,8 @@
android:id="@+id/option_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginVertical="12dp"
- android:layout_marginHorizontal="16dp"
+ android:layout_marginBottom="24dp"
+ android:layout_marginHorizontal="12dp"
android:background="?attr/selectableItemBackground"
android:backgroundTint="?attr/colorSurfaceVariant"
android:clickable="true"
diff --git a/src/android/app/src/main/res/layout/fragment_about.xml b/src/android/app/src/main/res/layout/fragment_about.xml
index 3e1d98451..a24f5230e 100644
--- a/src/android/app/src/main/res/layout/fragment_about.xml
+++ b/src/android/app/src/main/res/layout/fragment_about.xml
@@ -38,17 +38,17 @@
<ImageView
android:id="@+id/image_logo"
- android:layout_width="250dp"
- android:layout_height="250dp"
- android:layout_marginTop="20dp"
+ android:layout_width="150dp"
+ android:layout_height="150dp"
+ android:layout_marginTop="24dp"
+ android:layout_marginBottom="28dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/ic_yuzu_title" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginHorizontal="20dp"
- android:layout_marginTop="28dp" />
+ android:layout_marginHorizontal="20dp" />
<LinearLayout
android:layout_width="match_parent"
diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml
index 750ce094a..5252adf54 100644
--- a/src/android/app/src/main/res/layout/fragment_emulation.xml
+++ b/src/android/app/src/main/res/layout/fragment_emulation.xml
@@ -134,18 +134,21 @@
<FrameLayout
android:id="@+id/overlay_container"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
- <TextView
+ <com.google.android.material.textview.MaterialTextView
android:id="@+id/show_fps_text"
+ style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:clickable="false"
android:focusable="false"
- android:shadowColor="@android:color/black"
+ android:paddingHorizontal="20dp"
android:textColor="@android:color/white"
- android:textSize="12sp"
+ android:shadowColor="@android:color/black"
+ android:shadowRadius="3"
tools:ignore="RtlHardcoded" />
</FrameLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_home_settings.xml b/src/android/app/src/main/res/layout/fragment_home_settings.xml
index 1cb421dcb..d84093ba3 100644
--- a/src/android/app/src/main/res/layout/fragment_home_settings.xml
+++ b/src/android/app/src/main/res/layout/fragment_home_settings.xml
@@ -14,13 +14,14 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:background="?attr/colorSurface">
+ android:background="?attr/colorSurface"
+ android:paddingHorizontal="8dp">
<ImageView
android:id="@+id/logo_image"
- android:layout_width="128dp"
- android:layout_height="128dp"
- android:layout_margin="64dp"
+ android:layout_width="96dp"
+ android:layout_height="96dp"
+ android:layout_marginVertical="32dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/ic_yuzu_full" />
diff --git a/src/android/app/src/main/res/layout/fragment_search.xml b/src/android/app/src/main/res/layout/fragment_search.xml
index b8d54d947..efdfd7047 100644
--- a/src/android/app/src/main/res/layout/fragment_search.xml
+++ b/src/android/app/src/main/res/layout/fragment_search.xml
@@ -127,6 +127,7 @@
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingVertical="4dp"
+ app:checkedChip="@id/chip_recently_played"
app:chipSpacingHorizontal="12dp"
app:singleLine="true"
app:singleSelection="true">
diff --git a/src/android/app/src/main/res/layout/list_item_setting.xml b/src/android/app/src/main/res/layout/list_item_setting.xml
index f1037a740..544280e75 100644
--- a/src/android/app/src/main/res/layout/list_item_setting.xml
+++ b/src/android/app/src/main/res/layout/list_item_setting.xml
@@ -10,41 +10,59 @@
android:focusable="true"
android:gravity="center_vertical"
android:minHeight="72dp"
- android:padding="@dimen/spacing_large">
+ android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:orientation="horizontal">
- <com.google.android.material.textview.MaterialTextView
- android:id="@+id/text_setting_name"
- style="@style/TextAppearance.Material3.HeadlineMedium"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAlignment="viewStart"
- android:textSize="16sp"
- app:lineHeight="22dp"
- tools:text="Setting Name" />
-
- <com.google.android.material.textview.MaterialTextView
- android:id="@+id/text_setting_description"
- style="@style/TextAppearance.Material3.BodySmall"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/spacing_small"
- android:textAlignment="viewStart"
- tools:text="@string/app_disclaimer" />
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="24dp"
+ android:layout_gravity="center_vertical"
+ android:visibility="gone"
+ app:tint="?attr/colorOnSurface" />
- <com.google.android.material.textview.MaterialTextView
- android:id="@+id/text_setting_value"
- style="@style/TextAppearance.Material3.LabelMedium"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/spacing_small"
- android:textAlignment="viewStart"
- android:textStyle="bold"
- tools:text="1x" />
+ android:orientation="vertical">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_name"
+ style="@style/TextAppearance.Material3.HeadlineMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:textSize="17sp"
+ app:lineHeight="22dp"
+ tools:text="Setting Name" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_description"
+ style="@style/TextAppearance.Material3.BodySmall"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacing_small"
+ android:textAlignment="viewStart"
+ tools:text="@string/app_disclaimer" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_value"
+ style="@style/TextAppearance.Material3.LabelMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacing_small"
+ android:textAlignment="viewStart"
+ android:textStyle="bold"
+ android:textSize="13sp"
+ tools:text="1x" />
+
+ </LinearLayout>
</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/list_item_setting_switch.xml b/src/android/app/src/main/res/layout/list_item_setting_switch.xml
index a5767adee..a8f5aff78 100644
--- a/src/android/app/src/main/res/layout/list_item_setting_switch.xml
+++ b/src/android/app/src/main/res/layout/list_item_setting_switch.xml
@@ -8,9 +8,7 @@
android:clickable="true"
android:focusable="true"
android:minHeight="72dp"
- android:paddingVertical="@dimen/spacing_large"
- android:paddingStart="@dimen/spacing_large"
- android:paddingEnd="24dp">
+ android:padding="16dp">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_widget"
@@ -24,7 +22,7 @@
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerVertical="true"
- android:layout_marginEnd="@dimen/spacing_large"
+ android:layout_marginEnd="24dp"
android:layout_toStartOf="@+id/switch_widget"
android:gravity="center_vertical"
android:orientation="vertical">
@@ -35,7 +33,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
- android:textSize="16sp"
+ android:textSize="17sp"
app:lineHeight="28dp"
tools:text="@string/frame_limit_enable" />
diff --git a/src/android/app/src/main/res/layout/list_item_settings_header.xml b/src/android/app/src/main/res/layout/list_item_settings_header.xml
index cf85bc0da..21276b19e 100644
--- a/src/android/app/src/main/res/layout/list_item_settings_header.xml
+++ b/src/android/app/src/main/res/layout/list_item_settings_header.xml
@@ -7,7 +7,8 @@
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingHorizontal="@dimen/spacing_large"
- android:paddingVertical="16dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="8dp"
android:textAlignment="viewStart"
android:textColor="?attr/colorPrimary"
android:textStyle="bold"
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index dc10159c9..51bcc49a3 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -2,7 +2,6 @@
<resources>
<string-array name="regionNames">
- <item>@string/auto</item>
<item>@string/region_australia</item>
<item>@string/region_china</item>
<item>@string/region_europe</item>
@@ -13,7 +12,6 @@
</string-array>
<integer-array name="regionValues">
- <item>-1</item>
<item>3</item>
<item>4</item>
<item>2</item>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index c551a6106..471af8795 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -91,6 +91,7 @@
<string name="manage_save_data">Manage save data</string>
<string name="manage_save_data_description">Save data found. Please select an option below.</string>
<string name="import_export_saves_description">Import or export save files</string>
+ <string name="save_files_exporting">Exporting save files…</string>
<string name="save_file_imported_success">Imported successfully</string>
<string name="save_file_invalid_zip_structure">Invalid save directory structure</string>
<string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string>
@@ -240,6 +241,7 @@
<string name="shutting_down">Shutting down…</string>
<string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
<string name="reset_to_default">Reset to default</string>
+ <string name="reset_to_default_description">Resets all advanced settings</string>
<string name="reset_all_settings">Reset all settings?</string>
<string name="reset_all_settings_description">All advanced settings will be reset to their default configuration. This can not be undone.</string>
<string name="settings_reset">Settings reset</string>
@@ -255,6 +257,7 @@
<string name="cancelling">Cancelling</string>
<string name="install">Install</string>
<string name="delete">Delete</string>
+ <string name="export_success">Exported successfully</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string>
@@ -271,10 +274,14 @@
<string name="preferences_settings">Settings</string>
<string name="preferences_general">General</string>
<string name="preferences_system">System</string>
+ <string name="preferences_system_description">Docked mode, region, language</string>
<string name="preferences_graphics">Graphics</string>
+ <string name="preferences_graphics_description">Accuracy level, resolution, shader cache</string>
<string name="preferences_audio">Audio</string>
+ <string name="preferences_audio_description">Output engine, volume</string>
<string name="preferences_theme">Theme and color</string>
<string name="preferences_debug">Debug</string>
+ <string name="preferences_debug_description">CPU/GPU debugging, graphics API, fastmem</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">Your ROM is encrypted</string>