diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt index 7366e2c778..73a96bf17c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt @@ -83,6 +83,17 @@ class GamePropertiesAdapter( } else { binding.details.setVisible(false) } + + if (submenuProperty.secondaryAction != null) { + binding.buttonSecondaryAction.setVisible(submenuProperty.secondaryAction.isShown) + binding.buttonSecondaryAction.setIconResource(submenuProperty.secondaryAction.iconId) + binding.buttonSecondaryAction.contentDescription = binding.buttonSecondaryAction.context.getString(submenuProperty.secondaryAction.descriptionId) + binding.buttonSecondaryAction.setOnClickListener { + submenuProperty.secondaryAction.action.invoke() + } + } else { + binding.buttonSecondaryAction.setVisible(false) + } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt index f55edb418e..af4edfa1e0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt @@ -3,9 +3,11 @@ package org.yuzu.yuzu_emu.fragments +import android.content.Intent import android.content.pm.ShortcutInfo import android.content.pm.ShortcutManager import android.os.Bundle +import android.provider.DocumentsContract import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -14,6 +16,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding +import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope @@ -29,12 +32,14 @@ import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.adapters.GamePropertiesAdapter import org.yuzu.yuzu_emu.databinding.FragmentGamePropertiesBinding +import org.yuzu.yuzu_emu.features.DocumentProvider import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.model.DriverViewModel import org.yuzu.yuzu_emu.model.GameProperty import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.InstallableProperty +import org.yuzu.yuzu_emu.model.SubMenuProperSecondaryAction import org.yuzu.yuzu_emu.model.SubmenuProperty import org.yuzu.yuzu_emu.model.TaskState import org.yuzu.yuzu_emu.utils.DirectoryInitialization @@ -137,25 +142,44 @@ class GamePropertiesFragment : Fragment() { SubmenuProperty( R.string.info, R.string.info_description, - R.drawable.ic_info_outline - ) { - val action = GamePropertiesFragmentDirections - .actionPerGamePropertiesFragmentToGameInfoFragment(args.game) - binding.root.findNavController().navigate(action) - } + R.drawable.ic_info_outline, + action = { + val action = GamePropertiesFragmentDirections + .actionPerGamePropertiesFragmentToGameInfoFragment(args.game) + binding.root.findNavController().navigate(action) + } + ) ) add( SubmenuProperty( R.string.preferences_settings, R.string.per_game_settings_description, - R.drawable.ic_settings - ) { - val action = HomeNavigationDirections.actionGlobalSettingsActivity( - args.game, - Settings.MenuTag.SECTION_ROOT + R.drawable.ic_settings, + action = { + val action = HomeNavigationDirections.actionGlobalSettingsActivity( + args.game, + Settings.MenuTag.SECTION_ROOT + ) + binding.root.findNavController().navigate(action) + }, + secondaryAction = SubMenuProperSecondaryAction( + isShown = File( + DirectoryInitialization.userDirectory + + "/config/custom/" + args.game.settingsName + ".ini" + ).exists(), + descriptionId = R.string.share_game_settings, + iconId = R.drawable.ic_share, + action = { + val configFile = File( + DirectoryInitialization.userDirectory + + "/config/custom/" + args.game.settingsName + ".ini" + ) + if (configFile.exists()) { + shareConfigFile(configFile) + } + } ) - binding.root.findNavController().navigate(action) - } + ) ) if (GpuDriverHelper.supportsCustomDriverLoading()) { @@ -164,12 +188,13 @@ class GamePropertiesFragment : Fragment() { R.string.gpu_driver_manager, R.string.install_gpu_driver_description, R.drawable.ic_build, - detailsFlow = driverViewModel.selectedDriverTitle - ) { - val action = GamePropertiesFragmentDirections - .actionPerGamePropertiesFragmentToDriverManagerFragment(args.game) - binding.root.findNavController().navigate(action) - } + detailsFlow = driverViewModel.selectedDriverTitle, + action = { + val action = GamePropertiesFragmentDirections + .actionPerGamePropertiesFragmentToDriverManagerFragment(args.game) + binding.root.findNavController().navigate(action) + } + ) ) } @@ -178,12 +203,13 @@ class GamePropertiesFragment : Fragment() { SubmenuProperty( R.string.add_ons, R.string.add_ons_description, - R.drawable.ic_edit - ) { - val action = GamePropertiesFragmentDirections - .actionPerGamePropertiesFragmentToAddonsFragment(args.game) - binding.root.findNavController().navigate(action) - } + R.drawable.ic_edit, + action = { + val action = GamePropertiesFragmentDirections + .actionPerGamePropertiesFragmentToAddonsFragment(args.game) + binding.root.findNavController().navigate(action) + } + ) ) add( InstallableProperty( @@ -245,7 +271,7 @@ class GamePropertiesFragment : Fragment() { R.string.clear_shader_cache, R.string.clear_shader_cache_description, R.drawable.ic_delete, - { + details = { if (shaderCacheDir.exists()) { val bytes = shaderCacheDir.walkTopDown().filter { it.isFile } .map { it.length() }.sum() @@ -253,23 +279,24 @@ class GamePropertiesFragment : Fragment() { } else { MemoryUtil.bytesToSizeUnit(0f) } + }, + action = { + MessageDialogFragment.newInstance( + requireActivity(), + titleId = R.string.clear_shader_cache, + descriptionId = R.string.clear_shader_cache_warning_description, + positiveAction = { + shaderCacheDir.deleteRecursively() + Toast.makeText( + YuzuApplication.appContext, + R.string.cleared_shaders_successfully, + Toast.LENGTH_SHORT + ).show() + homeViewModel.reloadPropertiesList(true) + } + ).show(parentFragmentManager, MessageDialogFragment.TAG) } - ) { - MessageDialogFragment.newInstance( - requireActivity(), - titleId = R.string.clear_shader_cache, - descriptionId = R.string.clear_shader_cache_warning_description, - positiveAction = { - shaderCacheDir.deleteRecursively() - Toast.makeText( - YuzuApplication.appContext, - R.string.cleared_shaders_successfully, - Toast.LENGTH_SHORT - ).show() - homeViewModel.reloadPropertiesList(true) - } - ).show(parentFragmentManager, MessageDialogFragment.TAG) - } + ) ) } } @@ -284,6 +311,7 @@ class GamePropertiesFragment : Fragment() { override fun onResume() { super.onResume() driverViewModel.updateDriverNameForGame(args.game) + reloadList() } private fun setInsets() = @@ -420,4 +448,30 @@ class GamePropertiesFragment : Fragment() { } }.show(parentFragmentManager, ProgressDialogFragment.TAG) } + + private fun shareConfigFile(configFile: File) { + val file = DocumentFile.fromSingleUri( + requireContext(), + DocumentsContract.buildDocumentUri( + DocumentProvider.AUTHORITY, + "${DocumentProvider.ROOT_ID}/${configFile}" + ) + )!! + + val intent = Intent(Intent.ACTION_SEND) + .setDataAndType(file.uri, FileUtil.TEXT_PLAIN) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + if (file.exists()) { + intent.putExtra(Intent.EXTRA_STREAM, file.uri) + startActivity(Intent.createChooser(intent, getText(R.string.share_game_settings))) + } else { + Toast.makeText( + requireContext(), + getText(R.string.share_config_failed), + Toast.LENGTH_SHORT + ).show() + } + + } + } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameProperties.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameProperties.kt index 0135a95beb..0b3c1f59f8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameProperties.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameProperties.kt @@ -24,9 +24,17 @@ data class SubmenuProperty( override val iconId: Int, val details: (() -> String)? = null, val detailsFlow: StateFlow? = null, - val action: () -> Unit + val action: () -> Unit, + val secondaryAction: SubMenuProperSecondaryAction? = null ) : GameProperty +data class SubMenuProperSecondaryAction( + val isShown : Boolean, + val descriptionId: Int, + val iconId: Int, + val action: () -> Unit +) + data class InstallableProperty( override val titleId: Int, override val descriptionId: Int, diff --git a/src/android/app/src/main/res/layout/card_simple_outlined.xml b/src/android/app/src/main/res/layout/card_simple_outlined.xml index e29df6a2de..4f64b3fdd9 100644 --- a/src/android/app/src/main/res/layout/card_simple_outlined.xml +++ b/src/android/app/src/main/res/layout/card_simple_outlined.xml @@ -67,6 +67,17 @@ + + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 2c7923d5a3..98a1ca6415 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -752,6 +752,9 @@ Updates and DLC Mods and cheats Important addon notice + Share Game Settings + Failed to share configuration file + In order to install mods and cheats, you must select a folder that contains a cheats/, romfs/, or exefs/ directory. We can\'t verify if these will be compatible with your game so be careful! Invalid directory