From 6c7e284f64dd4f2f02014dd6488924a0343a3292 Mon Sep 17 00:00:00 2001 From: Abandoned Cart Date: Thu, 15 Jun 2023 22:36:03 -0400 Subject: [PATCH 1/2] android: Add support for concurrent installs --- .../fragments/InstallDialogFragment.kt | 62 +++++++++ .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 118 +++++++++++++----- .../app/src/main/res/values/strings.xml | 14 ++- 3 files changed, 154 insertions(+), 40 deletions(-) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallDialogFragment.kt diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallDialogFragment.kt new file mode 100644 index 000000000..d8850f941 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallDialogFragment.kt @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.fragments + +import android.app.Dialog +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.yuzu.yuzu_emu.R + +class InstallDialogFragment : DialogFragment() { + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val titleId = requireArguments().getInt(TITLE) + val description = requireArguments().getString(DESCRIPTION) + val helpLinkId = requireArguments().getInt(HELP_LINK) + + val dialog = MaterialAlertDialogBuilder(requireContext()) + .setPositiveButton(R.string.close, null) + .setTitle(titleId) + .setMessage(description) + + if (helpLinkId != 0) { + dialog.setNeutralButton(R.string.learn_more) { _, _ -> + openLink(getString(helpLinkId)) + } + } + + return dialog.show() + } + + private fun openLink(link: String) { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) + startActivity(intent) + } + + companion object { + const val TAG = "MessageDialogFragment" + + private const val TITLE = "Title" + private const val DESCRIPTION = "Description" + private const val HELP_LINK = "Link" + + fun newInstance( + titleId: Int, + description: String, + helpLinkId: Int = 0 + ): InstallDialogFragment { + val dialog = InstallDialogFragment() + val bundle = Bundle() + bundle.apply { + putInt(TITLE, titleId) + putString(DESCRIPTION, description) + putInt(HELP_LINK, helpLinkId) + } + dialog.arguments = bundle + return dialog + } + } +} 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 cc1d87f1b..5257d7b36 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 @@ -4,6 +4,7 @@ package org.yuzu.yuzu_emu.ui.main import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.View import android.view.ViewGroup.MarginLayoutParams @@ -42,6 +43,7 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment +import org.yuzu.yuzu_emu.fragments.InstallDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.HomeViewModel @@ -481,62 +483,110 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } } - val installGameUpdate = - registerForActivityResult(ActivityResultContracts.OpenDocument()) { - if (it == null) { - return@registerForActivityResult - } - + val installGameUpdate = registerForActivityResult( + ActivityResultContracts.OpenMultipleDocuments() + ) { documents: List -> + if (documents.isNotEmpty()) { IndeterminateProgressDialogFragment.newInstance( this@MainActivity, R.string.install_game_content ) { - val result = NativeLibrary.installFileToNand(it.toString()) + var installSuccess = 0 + var installOverwrite = 0 + var errorBaseGame = 0 + var errorExtension = 0 + var errorOther = 0 + var errorTotal = 0 lifecycleScope.launch { - withContext(Dispatchers.Main) { - when (result) { + documents.forEach { + when (NativeLibrary.installFileToNand(it.toString())) { NativeLibrary.InstallFileToNandResult.Success -> { - Toast.makeText( - applicationContext, - R.string.install_game_content_success, - Toast.LENGTH_SHORT - ).show() + installSuccess += 1 } NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> { - Toast.makeText( - applicationContext, - R.string.install_game_content_success_overwrite, - Toast.LENGTH_SHORT - ).show() + installOverwrite += 1 } NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> { - MessageDialogFragment.newInstance( - R.string.install_game_content_failure, - R.string.install_game_content_failure_base - ).show(supportFragmentManager, MessageDialogFragment.TAG) + errorBaseGame += 1 } NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> { - MessageDialogFragment.newInstance( - R.string.install_game_content_failure, - R.string.install_game_content_failure_file_extension, - R.string.install_game_content_help_link - ).show(supportFragmentManager, MessageDialogFragment.TAG) + errorExtension += 1 } else -> { - MessageDialogFragment.newInstance( - R.string.install_game_content_failure, - R.string.install_game_content_failure_description, - R.string.install_game_content_help_link - ).show(supportFragmentManager, MessageDialogFragment.TAG) + errorOther += 1 } } } + withContext(Dispatchers.Main) { + val separator = System.getProperty("line.separator") ?: "\n" + val installResult = StringBuilder() + if (installSuccess > 0) { + installResult.append( + getString( + R.string.install_game_content_success_install, + installSuccess + ) + ) + installResult.append(separator) + } + if (installOverwrite > 0) { + installResult.append( + getString( + R.string.install_game_content_success_overwrite, + installOverwrite + ) + ) + installResult.append(separator) + } + errorTotal = errorBaseGame + errorExtension + errorOther + if (errorTotal > 0) { + installResult.append(separator) + installResult.append( + getString( + R.string.install_game_content_failed_count, + + ) + ) + installResult.append(separator) + if (errorBaseGame > 0) { + installResult.append(separator) + installResult.append( + getString(R.string.install_game_content_failure_base) + ) + installResult.append(separator) + } + if (errorExtension > 0) { + installResult.append(separator) + installResult.append( + getString(R.string.install_game_content_failure_file_extension) + ) + installResult.append(separator) + } + if (errorOther > 0) { + installResult.append( + getString(R.string.install_game_content_failure_description) + ) + installResult.append(separator) + } + InstallDialogFragment.newInstance( + R.string.install_game_content_failure, + installResult.toString().trim(), + R.string.install_game_content_help_link + ).show(supportFragmentManager, MessageDialogFragment.TAG) + } else { + InstallDialogFragment.newInstance( + R.string.install_game_content_success, + installResult.toString().trim(), + ).show(supportFragmentManager, MessageDialogFragment.TAG) + } + } } - return@newInstance result + return@newInstance installSuccess + installOverwrite + errorTotal }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) } + } } diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index cc1d8c39d..75eca30a1 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -104,12 +104,14 @@ No log file found Install game content Install game updates or DLC - Error installing file to NAND - Game content installation failed. Please ensure content is valid and that the prod.keys file is installed. - Installation of base games isn\'t permitted in order to avoid possible conflicts. Please select an update or DLC instead. - The selected file type is not supported. Only NSP and XCI content is supported for this action. Please verify the game content is valid. - Game content installed successfully - Game content was overwritten successfully + Error installing file(s) to NAND + Please ensure content(s) are valid and that the prod.keys file is installed. + Installation of base games isn\'t permitted in order to avoid possible conflicts. + Only NSP and XCI content is supported. Please verify the game content(s) are valid. + %1$d installation error(s) + Game content(s) installed successfully + %1$d installed successfully + %1$d overwritten successfully https://yuzu-emu.org/help/quickstart/#dumping-installed-updates From 1a85d8804a044d689e53b2497be01b65c76c34d2 Mon Sep 17 00:00:00 2001 From: Abandoned Cart Date: Fri, 16 Jun 2023 07:50:47 -0400 Subject: [PATCH 2/2] android: Generalize string message dialog --- ...logFragment.kt => LongMessageDialogFragment.kt} | 8 ++++---- .../java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) rename src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/{InstallDialogFragment.kt => LongMessageDialogFragment.kt} (89%) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt similarity index 89% rename from src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallDialogFragment.kt rename to src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt index d8850f941..b29b627e9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt @@ -11,7 +11,7 @@ import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.yuzu.yuzu_emu.R -class InstallDialogFragment : DialogFragment() { +class LongMessageDialogFragment : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val titleId = requireArguments().getInt(TITLE) val description = requireArguments().getString(DESCRIPTION) @@ -37,7 +37,7 @@ class InstallDialogFragment : DialogFragment() { } companion object { - const val TAG = "MessageDialogFragment" + const val TAG = "LongMessageDialogFragment" private const val TITLE = "Title" private const val DESCRIPTION = "Description" @@ -47,8 +47,8 @@ class InstallDialogFragment : DialogFragment() { titleId: Int, description: String, helpLinkId: Int = 0 - ): InstallDialogFragment { - val dialog = InstallDialogFragment() + ): LongMessageDialogFragment { + val dialog = LongMessageDialogFragment() val bundle = Bundle() bundle.apply { putInt(TITLE, titleId) 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 5257d7b36..3086cfad3 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 @@ -43,7 +43,7 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment -import org.yuzu.yuzu_emu.fragments.InstallDialogFragment +import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.HomeViewModel @@ -548,7 +548,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { installResult.append( getString( R.string.install_game_content_failed_count, - + errorTotal ) ) installResult.append(separator) @@ -572,16 +572,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider { ) installResult.append(separator) } - InstallDialogFragment.newInstance( + LongMessageDialogFragment.newInstance( R.string.install_game_content_failure, installResult.toString().trim(), R.string.install_game_content_help_link - ).show(supportFragmentManager, MessageDialogFragment.TAG) + ).show(supportFragmentManager, LongMessageDialogFragment.TAG) } else { - InstallDialogFragment.newInstance( + LongMessageDialogFragment.newInstance( R.string.install_game_content_success, - installResult.toString().trim(), - ).show(supportFragmentManager, MessageDialogFragment.TAG) + installResult.toString().trim() + ).show(supportFragmentManager, LongMessageDialogFragment.TAG) } } }