-
-
Notifications
You must be signed in to change notification settings - Fork 192
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui): add crash report activity to handle uncaught exceptions (#576)
- Loading branch information
1 parent
802b149
commit 3165aac
Showing
5 changed files
with
246 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
229 changes: 229 additions & 0 deletions
229
app/src/main/java/me/ash/reader/infrastructure/android/CrashReportActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
package me.ash.reader.infrastructure.android | ||
|
||
import android.os.Build | ||
import android.os.Bundle | ||
import androidx.activity.ComponentActivity | ||
import androidx.activity.compose.setContent | ||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.size | ||
import androidx.compose.foundation.layout.width | ||
import androidx.compose.foundation.rememberScrollState | ||
import androidx.compose.foundation.text.ClickableText | ||
import androidx.compose.foundation.verticalScroll | ||
import androidx.compose.material.icons.Icons | ||
import androidx.compose.material.icons.outlined.BugReport | ||
import androidx.compose.material.icons.rounded.ContentCopy | ||
import androidx.compose.material3.Button | ||
import androidx.compose.material3.ButtonDefaults | ||
import androidx.compose.material3.Icon | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Scaffold | ||
import androidx.compose.material3.Surface | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.platform.LocalClipboardManager | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.text.AnnotatedString | ||
import androidx.compose.ui.text.ExperimentalTextApi | ||
import androidx.compose.ui.text.SpanStyle | ||
import androidx.compose.ui.text.UrlAnnotation | ||
import androidx.compose.ui.text.buildAnnotatedString | ||
import androidx.compose.ui.text.font.FontFamily | ||
import androidx.compose.ui.text.style.TextDecoration | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.unit.dp | ||
import androidx.core.view.ViewCompat | ||
import androidx.core.view.WindowCompat | ||
import me.ash.reader.R | ||
import me.ash.reader.infrastructure.preference.LocalDarkTheme | ||
import me.ash.reader.infrastructure.preference.LocalOpenLink | ||
import me.ash.reader.infrastructure.preference.LocalOpenLinkSpecificBrowser | ||
import me.ash.reader.infrastructure.preference.SettingsProvider | ||
import me.ash.reader.ui.ext.getCurrentVersion | ||
import me.ash.reader.ui.ext.openURL | ||
import me.ash.reader.ui.theme.AppTheme | ||
|
||
class CrashReportActivity : ComponentActivity() { | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
WindowCompat.setDecorFitsSystemWindows(window, false) | ||
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { v, insets -> | ||
v.setPadding(0, 0, 0, 0) | ||
insets | ||
} | ||
val errorMessage: String = intent.getStringExtra(ERROR_REPORT_KEY).toString() | ||
|
||
setContent { | ||
SettingsProvider { | ||
AppTheme(useDarkTheme = LocalDarkTheme.current.isDarkTheme()) { | ||
val clipboardManager = LocalClipboardManager.current | ||
val appVersion = getCurrentVersion().toString() | ||
val deviceModel = "${Build.MANUFACTURER} ${Build.MODEL}" | ||
val androidVersion = | ||
"Android ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})" | ||
|
||
val errorReport = | ||
"Version: $appVersion\nDevice: $deviceModel\nSystem: $androidVersion\n\nStack trace: \n\n$errorMessage" | ||
|
||
CrashReportPage(text = errorReport) { | ||
clipboardManager.setText(AnnotatedString(errorReport)) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
override fun onDestroy() { | ||
super.onDestroy() | ||
if (isFinishing) finishAffinity() | ||
} | ||
|
||
companion object { | ||
const val ERROR_REPORT_KEY = "error_stack_trace" | ||
} | ||
|
||
} | ||
|
||
|
||
@OptIn(ExperimentalTextApi::class) | ||
@Composable | ||
@Preview(apiLevel = 33) | ||
fun CrashReportPage( | ||
text: String = "Version: 0.9.11\n" + | ||
"Model: Google Pixel 6 Pro\n" + "System: Android 13 (API 33)\n\n" + | ||
"Stack trace: \n" + | ||
"\nFATAL EXCEPTION: main\n" + | ||
"Process: me.ash.reader, PID: 29184\n" + | ||
"java.lang.IllegalArgumentException: performMeasureAndLayout called during measure layout\n" + | ||
"\tat androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.kt:133)\n" + | ||
"\tat androidx.compose.ui.platform.AndroidComposeView.measureAndLayout(AndroidComposeView.android.kt:34)\n" + | ||
"\tat androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.android.kt:15)\n" + | ||
"\tat android.view.View.draw(View.java:24193)\n" + | ||
"\tat android.view.View.updateDisplayListIfDirty(View.java:23056)\n" + | ||
"\tat android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4550)\n", | ||
onClick: () -> Unit = {} | ||
) { | ||
val context = LocalContext.current | ||
val openLinkPreference = LocalOpenLink.current | ||
val openLinkSpecificBrowserPreference = LocalOpenLinkSpecificBrowser.current | ||
|
||
Scaffold( | ||
modifier = Modifier.fillMaxSize(), | ||
containerColor = MaterialTheme.colorScheme.surfaceContainer, | ||
bottomBar = {}) { | ||
Column( | ||
modifier = Modifier | ||
.padding(it) | ||
.verticalScroll(rememberScrollState()) | ||
) { | ||
Icon( | ||
imageVector = Icons.Outlined.BugReport, | ||
contentDescription = null, | ||
modifier = Modifier | ||
.padding(top = 24.dp) | ||
.padding(horizontal = 16.dp) | ||
.size(64.dp), | ||
tint = MaterialTheme.colorScheme.surfaceTint | ||
) | ||
|
||
Text( | ||
text = stringResource(R.string.unexpected_error_title), | ||
style = MaterialTheme.typography.headlineLarge, | ||
modifier = Modifier | ||
.padding(start = 16.dp, end = 16.dp, top = 12.dp) | ||
) | ||
Spacer(modifier = Modifier.height(24.dp)) | ||
|
||
|
||
val hyperLinkText = stringResource(R.string.submit_bug_report) | ||
val msg = stringResource(R.string.unexpected_error_msg).format(hyperLinkText) | ||
|
||
val annotatedString = buildAnnotatedString { | ||
append(msg.format(hyperLinkText)) | ||
val startIndex = msg.indexOf(hyperLinkText) | ||
val endIndex = startIndex + hyperLinkText.length | ||
addUrlAnnotation( | ||
UrlAnnotation("https://github.com/Ashinch/ReadYou/issues/new?assignees=&labels=bug&projects=&template=bug_report.md&title="), | ||
start = startIndex, | ||
end = endIndex | ||
) | ||
addStyle( | ||
SpanStyle( | ||
color = MaterialTheme.colorScheme.primary, | ||
textDecoration = TextDecoration.Underline, | ||
), start = startIndex, | ||
end = endIndex | ||
) | ||
} | ||
|
||
ClickableText( | ||
text = annotatedString, | ||
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onSurface), | ||
modifier = Modifier.padding(horizontal = 16.dp), | ||
onClick = { index -> | ||
annotatedString.getUrlAnnotations(index, index).firstOrNull()?.let { range -> | ||
context.openURL( | ||
url = range.item.url, | ||
openLink = openLinkPreference, | ||
specificBrowser = openLinkSpecificBrowserPreference | ||
) | ||
} | ||
} | ||
) | ||
|
||
Row( | ||
modifier = Modifier | ||
.padding(vertical = 16.dp, horizontal = 16.dp) | ||
.fillMaxWidth(), | ||
horizontalArrangement = Arrangement.End | ||
) { | ||
Spacer(modifier = Modifier.width(12.dp)) | ||
Button( | ||
onClick = onClick, | ||
modifier = Modifier | ||
.padding(), | ||
contentPadding = ButtonDefaults.ButtonWithIconContentPadding | ||
) { | ||
Icon( | ||
imageVector = Icons.Rounded.ContentCopy, | ||
contentDescription = null, | ||
modifier = Modifier.size(ButtonDefaults.IconSize) | ||
) | ||
Spacer(modifier = Modifier.width(8.dp)) | ||
Text(text = stringResource(R.string.copy_error_report)) | ||
} | ||
} | ||
|
||
|
||
Surface( | ||
shape = MaterialTheme.shapes.large, | ||
color = MaterialTheme.colorScheme.surface, | ||
modifier = Modifier.padding(12.dp) | ||
) { | ||
Column { | ||
Text( | ||
text = text, | ||
style = MaterialTheme.typography.bodyMedium.copy( | ||
fontFamily = FontFamily.Monospace | ||
), | ||
color = MaterialTheme.colorScheme.onSurfaceVariant, | ||
modifier = Modifier | ||
.padding(16.dp) | ||
.fillMaxWidth() | ||
) | ||
} | ||
|
||
} | ||
|
||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters