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.
This commit is contained in:
Producdevity 2025-07-20 18:11:55 +02:00 committed by crueter
parent 4051ace2e6
commit 67d7a3343b
3 changed files with 143 additions and 22 deletions

View file

@ -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.
*/

View file

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

View file

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