From c77cf1d0e81a078b17cd454a024198ef148a5e54 Mon Sep 17 00:00:00 2001 From: Producdevity Date: Sun, 20 Jul 2025 18:11:55 +0200 Subject: [PATCH] Add user feedback with Toast messages for custom settings and driver handling * Introduced Toast messages across `CustomSettingsHandler`, `DriverResolver`, and `EmulationFragment` to improve user interaction and feedback for key operations. * Enhanced error handling and confirmation dialogs, including options to launch with default settings when custom settings fail. --- .../yuzu_emu/fragments/EmulationFragment.kt | 151 +++++++++++++++--- .../yuzu_emu/utils/CustomSettingsHandler.kt | 11 +- .../org/yuzu/yuzu_emu/utils/DriverResolver.kt | 3 + 3 files changed, 143 insertions(+), 22 deletions(-) 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 a4c86b94c7..2834c36add 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 @@ -151,7 +151,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } - if (!isCustomSettingsIntent) finishGameSetup() + // For non-EmuReady intents, finish game setup immediately + // EmuReady intents handle setup asynchronously in handleEmuReadyIntent() + if (!isCustomSettingsIntent) { + finishGameSetup() + } } /** @@ -173,18 +177,52 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { ).show() requireActivity().finish() return + } catch (e: Exception) { + Log.error("[EmulationFragment] Error during game setup: ${e.message}") + Toast.makeText( + requireContext(), + "Setup error: ${e.message?.take(30) ?: "Unknown"}", + Toast.LENGTH_SHORT + ).show() + requireActivity().finish() + return } // Handle configuration loading - if (isCustomSettingsIntent) { - // Custom settings already applied by CustomSettingsHandler - Log.info("[EmulationFragment] Using custom settings from intent") - } else if (args.custom || intentGame != null) { - // Always load custom settings when launching a game from an intent - SettingsFile.loadCustomConfig(game) - NativeConfig.unloadPerGameConfig() - } else { - NativeConfig.reloadGlobalConfig() + try { + if (isCustomSettingsIntent) { + // Custom settings already applied by CustomSettingsHandler + Log.info("[EmulationFragment] Using custom settings from intent") + } else if (args.custom) { + // Load custom settings when explicitly requested via args + SettingsFile.loadCustomConfig(game) + NativeConfig.unloadPerGameConfig() + Log.info("[EmulationFragment] Loading custom settings for ${game.title}") + } else if (intentGame != null) { + // For intent games, check if custom settings exist and load them, otherwise use global + val customConfigFile = SettingsFile.getCustomSettingsFile(game) + if (customConfigFile.exists()) { + Log.info("[EmulationFragment] Found existing custom settings for ${game.title}, loading them") + SettingsFile.loadCustomConfig(game) + NativeConfig.unloadPerGameConfig() + } else { + Log.info("[EmulationFragment] No custom settings found for ${game.title}, using global settings") + NativeConfig.reloadGlobalConfig() + } + } else { + // Default case - use global settings + Log.info("[EmulationFragment] Using global settings") + NativeConfig.reloadGlobalConfig() + } + } catch (e: Exception) { + Log.error("[EmulationFragment] Error loading configuration: ${e.message}") + Log.info("[EmulationFragment] Falling back to global settings") + try { + NativeConfig.reloadGlobalConfig() + } catch (fallbackException: Exception) { + Log.error("[EmulationFragment] Critical error: could not load global config: ${fallbackException.message}") + throw fallbackException + } } // Install the selected driver asynchronously as the game starts @@ -209,12 +247,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { CoroutineScope(Dispatchers.Main).launch { try { // Find the game first to get the title for confirmation + Toast.makeText(requireContext(), "Searching for game...", Toast.LENGTH_SHORT).show() val foundGame = CustomSettingsHandler.findGameByTitleId(titleId, requireContext()) if (foundGame == null) { Log.error("[EmulationFragment] Game not found for title ID: $titleId") Toast.makeText( requireContext(), - "Game not found in library for title ID: $titleId", + "Game not found: $titleId", Toast.LENGTH_LONG ).show() requireActivity().finish() @@ -241,28 +280,75 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { if (intentGame == null) { Log.error("[EmulationFragment] Custom settings processing failed for title ID: $titleId") + // Ask user if they want to launch with default settings Toast.makeText( requireContext(), - "Failed to apply custom settings. This could be due to:\n• User cancelled configuration overwrite\n• Driver installation failed\n• Missing required drivers", - Toast.LENGTH_LONG + "Custom settings failed", + Toast.LENGTH_SHORT ).show() - requireActivity().finish() - return@launch - } - isCustomSettingsIntent = true + val launchWithDefault = askUserToLaunchWithDefaultSettings( + foundGame.title, + "This could be due to:\n• User cancelled configuration overwrite\n• Driver installation failed\n• Missing required drivers" + ) + + if (launchWithDefault) { + Log.info("[EmulationFragment] User chose to launch with default settings") + Toast.makeText( + requireContext(), + "Launching with default settings", + Toast.LENGTH_SHORT + ).show() + intentGame = foundGame + isCustomSettingsIntent = false + } else { + Log.info("[EmulationFragment] User cancelled launch after custom settings failure") + Toast.makeText( + requireContext(), + "Launch cancelled", + Toast.LENGTH_SHORT + ).show() + requireActivity().finish() + return@launch + } + } else { + Toast.makeText( + requireContext(), + "Custom settings applied", + Toast.LENGTH_SHORT + ).show() + isCustomSettingsIntent = true + } } else { // Handle title-only launch (no custom settings) + Log.info("[EmulationFragment] Launching game with default settings") + Toast.makeText( + requireContext(), + "Launching ${foundGame.title}", + Toast.LENGTH_SHORT + ).show() intentGame = foundGame + isCustomSettingsIntent = false } - finishGameSetup() + // Ensure we have a valid game before finishing setup + if (intentGame != null) { + finishGameSetup() + } else { + Log.error("[EmulationFragment] No valid game found after processing intent") + Toast.makeText( + requireContext(), + "Failed to initialize game", + Toast.LENGTH_SHORT + ).show() + requireActivity().finish() + } } catch (e: Exception) { Log.error("[EmulationFragment] Error processing EmuReady intent: ${e.message}") Toast.makeText( requireContext(), - "Error processing EmuReady request: ${e.message}", + "Error: ${e.message?.take(50) ?: "Unknown error"}", Toast.LENGTH_LONG ).show() requireActivity().finish() @@ -272,7 +358,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { Log.error("[EmulationFragment] EmuReady intent missing title_id") Toast.makeText( requireContext(), - "Invalid EmuReady request: missing title ID", + "Invalid request: missing title ID", Toast.LENGTH_SHORT ).show() requireActivity().finish() @@ -306,6 +392,31 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } + /** + * Ask user if they want to launch with default settings when custom settings fail + */ + private suspend fun askUserToLaunchWithDefaultSettings(gameTitle: String, errorMessage: String): Boolean { + return suspendCoroutine { continuation -> + requireActivity().runOnUiThread { + MaterialAlertDialogBuilder(requireContext()) + .setTitle("Custom Settings Failed") + .setMessage( + "Failed to apply custom settings for \"$gameTitle\":\n\n" + + "$errorMessage\n\n" + + "Would you like to launch the game with default settings instead?" + ) + .setPositiveButton("Launch with Default Settings") { _, _ -> + continuation.resume(true) + } + .setNegativeButton("Cancel") { _, _ -> + continuation.resume(false) + } + .setCancelable(false) + .show() + } + } + } + /** * Initialize the UI and start emulation in here. */ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt index cb0b505ff9..2048ff4008 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt @@ -91,9 +91,11 @@ object CustomSettingsHandler { val configFile = getConfigFile(titleId) if (configFile.exists() && activity != null) { Log.info("[CustomSettingsHandler] Config file already exists, asking user for confirmation") + Toast.makeText(activity, "Config exists, asking to overwrite", Toast.LENGTH_SHORT).show() val shouldOverwrite = askUserToOverwriteConfig(activity, game.title) if (!shouldOverwrite) { Log.info("[CustomSettingsHandler] User chose not to overwrite existing config") + Toast.makeText(activity, "Overwrite cancelled", Toast.LENGTH_SHORT).show() return null } } @@ -103,9 +105,11 @@ object CustomSettingsHandler { val driverPath = DriverResolver.extractDriverPath(customSettings) if (driverPath != null) { Log.info("[CustomSettingsHandler] Custom settings specify driver: $driverPath") + Toast.makeText(activity, "Checking driver: ${driverPath.split("/").lastOrNull()?.take(20) ?: "driver"}", Toast.LENGTH_SHORT).show() val driverExists = DriverResolver.ensureDriverExists(driverPath, activity, driverViewModel) if (!driverExists) { Log.error("[CustomSettingsHandler] Required driver not available: $driverPath") + Toast.makeText(activity, "Driver unavailable", Toast.LENGTH_SHORT).show() // Don't write config if driver installation failed return null } @@ -115,6 +119,7 @@ object CustomSettingsHandler { // Only write the config file after all checks pass if (!writeConfigFile(titleId, customSettings)) { Log.error("[CustomSettingsHandler] Failed to write config file") + Toast.makeText(activity, "Config write failed", Toast.LENGTH_SHORT).show() return null } @@ -122,9 +127,11 @@ object CustomSettingsHandler { try { NativeConfig.initializePerGameConfig(game.programId, configFile.nameWithoutExtension) Log.info("[CustomSettingsHandler] Successfully applied custom settings") + Toast.makeText(activity, "Custom settings applied", Toast.LENGTH_SHORT).show() return game } catch (e: Exception) { Log.error("[CustomSettingsHandler] Failed to apply custom settings: ${e.message}") + Toast.makeText(activity, "Config apply failed", Toast.LENGTH_SHORT).show() return null } } @@ -161,10 +168,10 @@ object CustomSettingsHandler { } if (foundGame != null) { Log.info("[CustomSettingsHandler] Found game: ${foundGame.title} at ${foundGame.path}") - Toast.makeText(context, "Found game: ${foundGame.title}", Toast.LENGTH_SHORT).show() + Toast.makeText(context, "Found: ${foundGame.title}", Toast.LENGTH_SHORT).show() } else { Log.warning("[CustomSettingsHandler] No game found for title ID: $titleId") - Toast.makeText(context, "Game not found for title ID: $titleId", Toast.LENGTH_SHORT).show() + Toast.makeText(context, "Game not found: $titleId", Toast.LENGTH_SHORT).show() } return foundGame } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt index 386a84e7d9..bcd5204240 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DriverResolver.kt @@ -6,6 +6,7 @@ package org.yuzu.yuzu_emu.utils +import android.widget.Toast import androidx.core.net.toUri import androidx.fragment.app.FragmentActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -283,6 +284,7 @@ object DriverResolver { ): Boolean { return try { Log.info("[DriverResolver] Downloading driver: ${artifact.name}") + Toast.makeText(activity, "Downloading driver...", Toast.LENGTH_SHORT).show() val cacheDir = activity.externalCacheDir ?: throw IOException("Cache directory not available") @@ -326,6 +328,7 @@ object DriverResolver { if (GpuDriverHelper.copyDriverToInternalStorage(file.toUri())) { driverViewModel.onDriverAdded(Pair(driverPath, driverData)) Log.info("[DriverResolver] Successfully installed driver: ${driverData.name}") + Toast.makeText(activity, "Driver installed", Toast.LENGTH_SHORT).show() true } else { throw IOException("Failed to install driver")