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
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.provider.DocumentsContract
import android.text.format.Formatter.formatShortFileSize
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
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.Refresh
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
@@ -63,8 +67,8 @@ import java.net.UnknownHostException
data class LoadFailureInfo(
val title: LaunchUtils.LauncherError,
val description: String,
val extendedMessage: String?
val description: String? = null,
val extendedMessage: String? = null
)
class MainActivity : ComponentActivity() {
@@ -83,6 +87,12 @@ class MainActivity : ComponentActivity() {
val returnError = intent.extras?.getSerializable(LaunchUtils.LAUNCHER_KEY_RETURN_ERROR)
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 {
val themeOption by PreferenceUtils.useIntPreference(PreferenceUtils.Key.THEME)
val theme = Theme.fromInt(themeOption)
@@ -95,10 +105,6 @@ class MainActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
val loadFailureInfo = if (returnError != null && returnMessage != null) {
LoadFailureInfo(returnError, returnMessage, returnExtendedMessage)
} else { null }
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_FAILED -> stringResource(R.string.load_failed_link_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) {
@@ -436,6 +443,10 @@ fun ErrorInfoBody(failureReason: LaunchUtils.LauncherError, modifier: Modifier =
stringResource(R.string.load_failed_recommendation_update),
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) {
@@ -454,10 +465,11 @@ fun ErrorInfoTitle(failureReason: LaunchUtils.LauncherError) {
LaunchUtils.LauncherError.LINKER_NEEDS_32BIT,
LaunchUtils.LauncherError.LINKER_FAILED -> stringResource(R.string.load_failed_link_error)
LaunchUtils.LauncherError.GENERIC -> stringResource(R.string.load_failed_generic_error)
LaunchUtils.LauncherError.CRASHED -> stringResource(R.string.load_failed_crashed)
}
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
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)
@Composable
fun ErrorInfoSheet(failureInfo: LoadFailureInfo, onDismiss: () -> Unit) {
@@ -483,19 +528,21 @@ fun ErrorInfoSheet(failureInfo: LoadFailureInfo, onDismiss: () -> Unit) {
ErrorInfoBody(failureInfo.title)
Column(modifier = Modifier.padding(vertical = 8.dp)) {
Text(
stringResource(R.string.launcher_error_details),
style = Typography.headlineSmall
)
if (failureInfo.description != null) {
Column(modifier = Modifier.padding(vertical = 8.dp)) {
Text(
stringResource(R.string.launcher_error_details),
style = Typography.headlineSmall
)
Spacer(Modifier.size(8.dp))
Spacer(Modifier.size(8.dp))
Text(
failureInfo.description,
fontFamily = FontFamily.Monospace,
style = Typography.bodyMedium
)
Text(
failureInfo.description,
fontFamily = FontFamily.Monospace,
style = Typography.bodyMedium
)
}
}
val clipboardManager = LocalClipboardManager.current
@@ -509,28 +556,45 @@ fun ErrorInfoSheet(failureInfo: LoadFailureInfo, onDismiss: () -> Unit) {
12.dp, alignment = Alignment.CenterHorizontally
)
) {
Button(onClick = {
val launchIntent = Intent(context, ApplicationLogsActivity::class.java)
context.startActivity(launchIntent)
}) {
Icon(
painterResource(R.drawable.icon_description),
contentDescription = null
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.launcher_error_view_logs))
}
if (failureInfo.description != null) {
Button(onClick = { onShowLogs(context) }) {
Icon(
painterResource(R.drawable.icon_description),
contentDescription = null
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.launcher_error_view_logs))
}
FilledTonalButton(onClick = {
val message = failureInfo.extendedMessage ?: failureInfo.description
clipboardManager.setText(AnnotatedString(message))
}) {
Icon(
painterResource(R.drawable.icon_content_copy),
contentDescription = null
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.launcher_error_copy))
FilledTonalButton(onClick = {
val message = failureInfo.extendedMessage ?: failureInfo.description
clipboardManager.setText(AnnotatedString(message))
}) {
Icon(
painterResource(R.drawable.icon_content_copy),
contentDescription = null
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
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() }
var showErrorInfo by remember { mutableStateOf(false) }
val hasError = loadFailureInfo != null
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(
message = context.getString(R.string.launcher_failed_to_load),
message = message,
actionLabel = context.getString(R.string.launcher_error_more),
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 {
LINKER_NEEDS_64BIT,
LINKER_NEEDS_32BIT,
LINKER_FAILED,
GENERIC
GENERIC,
CRASHED
}
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_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_crashed">Geode crashed.</string>
<string name="launcher_error_more">Learn more</string>
<string name="launcher_error_details">Error details</string>
<string name="launcher_error_view_logs">View logs</string>
<string name="launcher_error_copy_error_alt">Copy</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 -->
<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_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_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_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_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_safe_mode">Hold the Launch button to launch in safe mode.</string>
<string name="message_box_cancel">Cancel</string>
<string name="message_box_accept">OK</string>