add crash info popup

This commit is contained in:
qimiko committed 2024-02-13 21:20:12 -07:00
1 parent 83557c3ebe
commit 7bc7b898a2
3 files changed
+149 -45

No files matched your search

@@ -1,9 +1,12 @@
package com.geode.launcher package com.geode.launcher
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.provider.DocumentsContract
import android.text.format.Formatter.formatShortFileSize import android.text.format.Formatter.formatShortFileSize
import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
@@ -17,6 +20,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.Warning import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -63,8 +67,8 @@ import java.net.UnknownHostException
data class LoadFailureInfo( data class LoadFailureInfo(
val title: LaunchUtils.LauncherError, val title: LaunchUtils.LauncherError,
val description: String, val description: String? = null,
val extendedMessage: String? val extendedMessage: String? = null
) )
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@@ -83,6 +87,12 @@ class MainActivity : ComponentActivity() {
val returnError = intent.extras?.getSerializable(LaunchUtils.LAUNCHER_KEY_RETURN_ERROR) val returnError = intent.extras?.getSerializable(LaunchUtils.LAUNCHER_KEY_RETURN_ERROR)
as? LaunchUtils.LauncherError as? LaunchUtils.LauncherError
val loadFailureInfo = if (returnError != null && returnMessage != null) {
LoadFailureInfo(returnError, returnMessage, returnExtendedMessage)
} else if (LaunchUtils.lastSessionCrashed(this)) {
LoadFailureInfo(LaunchUtils.LauncherError.CRASHED)
} else { null }
setContent { setContent {
val themeOption by PreferenceUtils.useIntPreference(PreferenceUtils.Key.THEME) val themeOption by PreferenceUtils.useIntPreference(PreferenceUtils.Key.THEME)
val theme = Theme.fromInt(themeOption) val theme = Theme.fromInt(themeOption)
@@ -95,10 +105,6 @@ class MainActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
val loadFailureInfo = if (returnError != null && returnMessage != null) {
LoadFailureInfo(returnError, returnMessage, returnExtendedMessage)
} else { null }
MainScreen(gdInstalled, geodeInstalled, loadFailureInfo) MainScreen(gdInstalled, geodeInstalled, loadFailureInfo)
} }
} }
@@ -416,6 +422,7 @@ fun ErrorInfoBody(failureReason: LaunchUtils.LauncherError, modifier: Modifier =
LaunchUtils.LauncherError.LINKER_NEEDS_32BIT -> stringResource(R.string.load_failed_abi_error) LaunchUtils.LauncherError.LINKER_NEEDS_32BIT -> stringResource(R.string.load_failed_abi_error)
LaunchUtils.LauncherError.LINKER_FAILED -> stringResource(R.string.load_failed_link_error_description) LaunchUtils.LauncherError.LINKER_FAILED -> stringResource(R.string.load_failed_link_error_description)
LaunchUtils.LauncherError.GENERIC -> stringResource(R.string.load_failed_generic_error_description) LaunchUtils.LauncherError.GENERIC -> stringResource(R.string.load_failed_generic_error_description)
LaunchUtils.LauncherError.CRASHED -> stringResource(R.string.load_failed_crashed_description)
} }
val recommendations = when (failureReason) { val recommendations = when (failureReason) {
@@ -436,6 +443,10 @@ fun ErrorInfoBody(failureReason: LaunchUtils.LauncherError, modifier: Modifier =
stringResource(R.string.load_failed_recommendation_update), stringResource(R.string.load_failed_recommendation_update),
stringResource(R.string.load_failed_recommendation_report) stringResource(R.string.load_failed_recommendation_report)
) )
LaunchUtils.LauncherError.CRASHED -> listOf(
stringResource(R.string.load_failed_recommendation_safe_mode),
stringResource(R.string.load_failed_recommendation_report)
)
} }
Column(modifier = modifier) { Column(modifier = modifier) {
@@ -454,10 +465,11 @@ fun ErrorInfoTitle(failureReason: LaunchUtils.LauncherError) {
LaunchUtils.LauncherError.LINKER_NEEDS_32BIT, LaunchUtils.LauncherError.LINKER_NEEDS_32BIT,
LaunchUtils.LauncherError.LINKER_FAILED -> stringResource(R.string.load_failed_link_error) LaunchUtils.LauncherError.LINKER_FAILED -> stringResource(R.string.load_failed_link_error)
LaunchUtils.LauncherError.GENERIC -> stringResource(R.string.load_failed_generic_error) LaunchUtils.LauncherError.GENERIC -> stringResource(R.string.load_failed_generic_error)
LaunchUtils.LauncherError.CRASHED -> stringResource(R.string.load_failed_crashed)
} }
Row( Row(
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
@@ -470,6 +482,39 @@ fun ErrorInfoTitle(failureReason: LaunchUtils.LauncherError) {
} }
fun onShareCrash(context: Context) {
val lastCrash = LaunchUtils.getLastCrash(context)
if (lastCrash == null) {
Toast.makeText(context, R.string.launcher_error_export_missing, Toast.LENGTH_SHORT).show()
return
}
val baseDirectory = LaunchUtils.getBaseDirectory(context)
val providerLocation = lastCrash.toRelativeString(baseDirectory)
val documentsPath = "${UserDirectoryProvider.ROOT}${providerLocation}"
val uri = DocumentsContract.buildDocumentUri("${context.packageName}.user", documentsPath)
println("shareCrash: $uri")
val shareIntent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_STREAM, uri)
type = "application/octet-stream"
}
try {
context.startActivity(shareIntent)
} catch (_: ActivityNotFoundException) {
Toast.makeText(context, R.string.no_activity_found, Toast.LENGTH_SHORT).show()
}
}
fun onShowLogs(context: Context) {
val launchIntent = Intent(context, ApplicationLogsActivity::class.java)
context.startActivity(launchIntent)
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ErrorInfoSheet(failureInfo: LoadFailureInfo, onDismiss: () -> Unit) { fun ErrorInfoSheet(failureInfo: LoadFailureInfo, onDismiss: () -> Unit) {
@@ -483,19 +528,21 @@ fun ErrorInfoSheet(failureInfo: LoadFailureInfo, onDismiss: () -> Unit) {
ErrorInfoBody(failureInfo.title) ErrorInfoBody(failureInfo.title)
Column(modifier = Modifier.padding(vertical = 8.dp)) { if (failureInfo.description != null) {
Text( Column(modifier = Modifier.padding(vertical = 8.dp)) {
stringResource(R.string.launcher_error_details), Text(
style = Typography.headlineSmall stringResource(R.string.launcher_error_details),
) style = Typography.headlineSmall
)
Spacer(Modifier.size(8.dp)) Spacer(Modifier.size(8.dp))
Text( Text(
failureInfo.description, failureInfo.description,
fontFamily = FontFamily.Monospace, fontFamily = FontFamily.Monospace,
style = Typography.bodyMedium style = Typography.bodyMedium
) )
}
} }
val clipboardManager = LocalClipboardManager.current val clipboardManager = LocalClipboardManager.current
@@ -509,28 +556,45 @@ fun ErrorInfoSheet(failureInfo: LoadFailureInfo, onDismiss: () -> Unit) {
12.dp, alignment = Alignment.CenterHorizontally 12.dp, alignment = Alignment.CenterHorizontally
) )
) { ) {
Button(onClick = { if (failureInfo.description != null) {
val launchIntent = Intent(context, ApplicationLogsActivity::class.java) Button(onClick = { onShowLogs(context) }) {
context.startActivity(launchIntent) Icon(
}) { painterResource(R.drawable.icon_description),
Icon( contentDescription = null
painterResource(R.drawable.icon_description), )
contentDescription = null Spacer(Modifier.size(ButtonDefaults.IconSpacing))
) Text(stringResource(R.string.launcher_error_view_logs))
Spacer(Modifier.size(ButtonDefaults.IconSpacing)) }
Text(stringResource(R.string.launcher_error_view_logs))
}
FilledTonalButton(onClick = { FilledTonalButton(onClick = {
val message = failureInfo.extendedMessage ?: failureInfo.description val message = failureInfo.extendedMessage ?: failureInfo.description
clipboardManager.setText(AnnotatedString(message)) clipboardManager.setText(AnnotatedString(message))
}) { }) {
Icon( Icon(
painterResource(R.drawable.icon_content_copy), painterResource(R.drawable.icon_content_copy),
contentDescription = null contentDescription = null
) )
Spacer(Modifier.size(ButtonDefaults.IconSpacing)) Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.launcher_error_copy)) Text(stringResource(R.string.launcher_error_copy))
}
} else {
Button(onClick = { onShareCrash(context) }) {
Icon(
Icons.Filled.Share,
contentDescription = null
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.launcher_error_export_crash))
}
FilledTonalButton(onClick = { onShowLogs(context) }) {
Icon(
painterResource(R.drawable.icon_description),
contentDescription = null
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.launcher_error_view_logs))
}
} }
} }
@@ -590,15 +654,20 @@ fun MainScreen(
} }
} }
val hasError = loadFailureInfo != null
var showErrorInfo by remember { mutableStateOf(false) }
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
var showErrorInfo by remember { mutableStateOf(false) }
val hasError = loadFailureInfo != null
LaunchedEffect(hasError) { LaunchedEffect(hasError) {
if (hasError) { if (loadFailureInfo != null) {
val message = if (loadFailureInfo.title == LaunchUtils.LauncherError.CRASHED) {
context.getString(R.string.launcher_crashed)
} else {
context.getString(R.string.launcher_failed_to_load)
}
val res = snackbarHostState.showSnackbar( val res = snackbarHostState.showSnackbar(
message = context.getString(R.string.launcher_failed_to_load), message = message,
actionLabel = context.getString(R.string.launcher_error_more), actionLabel = context.getString(R.string.launcher_error_more),
duration = SnackbarDuration.Indefinite, duration = SnackbarDuration.Indefinite,
) )
@@ -156,11 +156,40 @@ object LaunchUtils {
} }
} }
private const val CRASH_INDICATOR_NAME = "lastSessionDidCrash"
private fun getCrashDirectory(context: Context): File {
val base = getBaseDirectory(context)
return File(base, "game/geode/crashlogs/")
}
fun getLastCrash(context: Context): File? {
val crashDirectory = getCrashDirectory(context)
if (!crashDirectory.exists()) {
return null
}
val children = crashDirectory.listFiles {
// ignore indicator files (including old file)
_, name -> name != CRASH_INDICATOR_NAME && name != "last-pid"
}
return children?.maxByOrNull { it.lastModified() }
}
fun lastSessionCrashed(context: Context): Boolean {
val base = getCrashDirectory(context)
val crashIndicatorFile = File(base, CRASH_INDICATOR_NAME)
return crashIndicatorFile.exists()
}
enum class LauncherError { enum class LauncherError {
LINKER_NEEDS_64BIT, LINKER_NEEDS_64BIT,
LINKER_NEEDS_32BIT, LINKER_NEEDS_32BIT,
LINKER_FAILED, LINKER_FAILED,
GENERIC GENERIC,
CRASHED
} }
const val LAUNCHER_KEY_RETURN_ERROR = "return_error" const val LAUNCHER_KEY_RETURN_ERROR = "return_error"
+6
View File
@@ -33,11 +33,14 @@
<string name="safe_mode_enable_title">Enable safe mode?</string> <string name="safe_mode_enable_title">Enable safe mode?</string>
<string name="safe_mode_enable_description">Safe mode disables loading of all mods, allowing one to remove problematic mods or diagnose crashes. If you\'re not experiencing issues with Geode, you should probably not be using safe mode.</string> <string name="safe_mode_enable_description">Safe mode disables loading of all mods, allowing one to remove problematic mods or diagnose crashes. If you\'re not experiencing issues with Geode, you should probably not be using safe mode.</string>
<string name="launcher_failed_to_load">Geode failed to load.</string> <string name="launcher_failed_to_load">Geode failed to load.</string>
<string name="launcher_crashed">Geode crashed.</string>
<string name="launcher_error_more">Learn more</string> <string name="launcher_error_more">Learn more</string>
<string name="launcher_error_details">Error details</string> <string name="launcher_error_details">Error details</string>
<string name="launcher_error_view_logs">View logs</string> <string name="launcher_error_view_logs">View logs</string>
<string name="launcher_error_copy_error_alt">Copy</string> <string name="launcher_error_copy_error_alt">Copy</string>
<string name="launcher_error_copy">Copy details</string> <string name="launcher_error_copy">Copy details</string>
<string name="launcher_error_export_crash">Share crash</string>
<string name="launcher_error_export_missing">No crash dump was found!</string>
<!-- Updater strings --> <!-- Updater strings -->
<string name="release_fetch_failed">Update failed.\n%1$s</string> <string name="release_fetch_failed">Update failed.\n%1$s</string>
@@ -58,11 +61,14 @@
<string name="load_failed_abi_error">The architecture of the launcher and Geometry Dash do not match. Try to:</string> <string name="load_failed_abi_error">The architecture of the launcher and Geometry Dash do not match. Try to:</string>
<string name="load_failed_generic_error">Failed to load Geode</string> <string name="load_failed_generic_error">Failed to load Geode</string>
<string name="load_failed_generic_error_description">Uncaught error during game load. Try to:</string> <string name="load_failed_generic_error_description">Uncaught error during game load. Try to:</string>
<string name="load_failed_crashed">Geode crashed</string>
<string name="load_failed_crashed_description">A crash was reported during the last session. Try to:</string>
<string name="load_failed_recommendation_reinstall">Reinstall Geometry Dash from the Play Store.</string> <string name="load_failed_recommendation_reinstall">Reinstall Geometry Dash from the Play Store.</string>
<string name="load_failed_recommendation_switch_32bit_abi">Install the 32-bit version of the launcher.</string> <string name="load_failed_recommendation_switch_32bit_abi">Install the 32-bit version of the launcher.</string>
<string name="load_failed_recommendation_switch_64bit_abi">Install the non 32-bit version of the launcher.</string> <string name="load_failed_recommendation_switch_64bit_abi">Install the non 32-bit version of the launcher.</string>
<string name="load_failed_recommendation_update">Verify that the latest versions of the launcher and Geometry Dash are installed.</string> <string name="load_failed_recommendation_update">Verify that the latest versions of the launcher and Geometry Dash are installed.</string>
<string name="load_failed_recommendation_report">Report this issue to the developers.</string> <string name="load_failed_recommendation_report">Report this issue to the developers.</string>
<string name="load_failed_recommendation_safe_mode">Hold the Launch button to launch in safe mode.</string>
<string name="message_box_cancel">Cancel</string> <string name="message_box_cancel">Cancel</string>
<string name="message_box_accept">OK</string> <string name="message_box_accept">OK</string>