Migrate crash reporter UI to Jetpack Compose
This commit is contained in:
@@ -2,6 +2,7 @@ package de.mm20.launcher2.ui.component.preferences
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -23,7 +24,8 @@ import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
fun PreferenceScreen(
|
||||
title: String,
|
||||
floatingActionButton: @Composable () -> Unit = {},
|
||||
content: LazyListScope.() -> Unit
|
||||
topBarActions: @Composable RowScope.() -> Unit = {},
|
||||
content: LazyListScope.() -> Unit,
|
||||
) {
|
||||
val navController = LocalNavController.current
|
||||
val systemUiController = rememberSystemUiController()
|
||||
@@ -50,6 +52,7 @@ fun PreferenceScreen(
|
||||
Icon(imageVector = Icons.Rounded.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
},
|
||||
actions = topBarActions
|
||||
)
|
||||
}) {
|
||||
LazyColumn(
|
||||
|
||||
@@ -6,10 +6,7 @@ import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.navigation.navArgument
|
||||
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||
import com.google.accompanist.navigation.animation.composable
|
||||
@@ -30,6 +27,8 @@ import de.mm20.launcher2.ui.settings.buildinfo.BuildInfoSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.calendarwidget.CalendarWidgetSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.cards.CardsSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.clockwidget.ClockWidgetSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.crashreporter.CrashReportScreen
|
||||
import de.mm20.launcher2.ui.settings.crashreporter.CrashReporterScreen
|
||||
import de.mm20.launcher2.ui.settings.debug.DebugSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.easteregg.EasterEggSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.filesearch.FileSearchSettingsScreen
|
||||
@@ -55,6 +54,12 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
setContent {
|
||||
val navController = rememberAnimatedNavController()
|
||||
|
||||
LaunchedEffect(intent) {
|
||||
intent.getStringExtra("de.mm20.launcher2.settings.ROUTE")
|
||||
?.let { navController.navigate(it) }
|
||||
}
|
||||
|
||||
val cardStyle by remember {
|
||||
dataStore.data.map { it.cards }.distinctUntilChanged()
|
||||
}.collectAsState(
|
||||
@@ -127,6 +132,17 @@ class SettingsActivity : BaseActivity() {
|
||||
composable("settings/debug") {
|
||||
DebugSettingsScreen()
|
||||
}
|
||||
composable("settings/debug/crashreporter") {
|
||||
CrashReporterScreen()
|
||||
}
|
||||
composable("settings/debug/crashreporter/report?fileName={fileName}",
|
||||
arguments = listOf(navArgument("fileName") {
|
||||
nullable = false
|
||||
})
|
||||
) {
|
||||
val fileName = it.arguments?.getString("fileName")
|
||||
CrashReportScreen(fileName!!)
|
||||
}
|
||||
composable(
|
||||
"settings/license?library={libraryName}",
|
||||
arguments = listOf(navArgument("libraryName") {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package de.mm20.launcher2.ui.settings.crashreporter
|
||||
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.BugReport
|
||||
import androidx.compose.material.icons.rounded.Share
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.crashreporter.CrashReportType
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
|
||||
@Composable
|
||||
fun CrashReportScreen(fileName: String) {
|
||||
val viewModel: CrashReportScreenVM = viewModel()
|
||||
val context = LocalContext.current
|
||||
val crashReport by remember(fileName) { viewModel.getCrashReport(fileName) }.observeAsState()
|
||||
PreferenceScreen(
|
||||
title = when (crashReport?.type) {
|
||||
CrashReportType.Exception -> "Exception"
|
||||
CrashReportType.Crash -> "Crash"
|
||||
null -> ""
|
||||
},
|
||||
topBarActions = {
|
||||
IconButton(onClick = { crashReport?.let { viewModel.shareCrashReport(context, it) } }) {
|
||||
Icon(imageVector = Icons.Rounded.Share, contentDescription = null)
|
||||
}
|
||||
if (crashReport?.type == CrashReportType.Crash) {
|
||||
IconButton(onClick = { crashReport?.let { viewModel.createIssue(context, it) } }) {
|
||||
Icon(imageVector = Icons.Rounded.BugReport, contentDescription = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
item {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
color = if (crashReport?.type == CrashReportType.Crash) {
|
||||
MaterialTheme.colorScheme.errorContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.primaryContainer
|
||||
},
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(
|
||||
rememberScrollState()
|
||||
),
|
||||
) {
|
||||
crashReport?.stacktrace?.let {
|
||||
Text(
|
||||
text = it,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp)
|
||||
) {
|
||||
Text(text = "Device Information", style = MaterialTheme.typography.titleMedium)
|
||||
val deviceInformation = remember { viewModel.getDeviceInformation(context) }
|
||||
Text(
|
||||
text = deviceInformation,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package de.mm20.launcher2.ui.settings.crashreporter
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.liveData
|
||||
import de.mm20.launcher2.crashreporter.CrashReport
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import java.io.File
|
||||
import java.net.URLEncoder
|
||||
|
||||
class CrashReportScreenVM : ViewModel() {
|
||||
fun getCrashReport(fileName: String) = liveData<CrashReport?> {
|
||||
emit(CrashReporter.getCrashReport(fileName))
|
||||
}
|
||||
|
||||
fun getDeviceInformation(context: Context): String {
|
||||
return CrashReporter.getDeviceInformation(context)
|
||||
}
|
||||
|
||||
fun createIssue(context: Context, crashReport: CrashReport) {
|
||||
val stacktrace = crashReport.stacktrace?.lines()?.let {
|
||||
if (it.size > 15) it.subList(0, 15)
|
||||
.joinToString("\n") + "\n[${it.size - 15} lines truncated]"
|
||||
else it.joinToString("\n")
|
||||
} ?: ""
|
||||
val body =
|
||||
"## Description\n\n" +
|
||||
"*Please provide as many information about the crash as possible (What did you do before the crash happened? Steps to reproduce?)*\n\n" +
|
||||
"## Strack trace\n\n" +
|
||||
"```\n" +
|
||||
"${stacktrace}\n" +
|
||||
"```\n\n" +
|
||||
"## Device info\n" +
|
||||
"${getDeviceInformation(context).replace("\n", "<br>")}\n"
|
||||
val url = "https://github.com/MM2-0/Kvaesitso/issues/new?labels=crash+report&body=${
|
||||
URLEncoder.encode(
|
||||
body,
|
||||
"utf8"
|
||||
)
|
||||
}"
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW).apply {
|
||||
data = Uri.parse(url)
|
||||
})
|
||||
}
|
||||
|
||||
fun shareCrashReport(context: Context, crashReport: CrashReport) {
|
||||
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
context.applicationContext.packageName + ".fileprovider",
|
||||
File(crashReport.filePath)
|
||||
)
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.type = "*/*"
|
||||
intent.putExtra(Intent.EXTRA_TEXT, CrashReporter.getDeviceInformation(context))
|
||||
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
context.startActivity(Intent.createChooser(intent, "Share via"))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package de.mm20.launcher2.ui.settings.crashreporter
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Error
|
||||
import androidx.compose.material.icons.rounded.ErrorOutline
|
||||
import androidx.compose.material.icons.rounded.Warning
|
||||
import androidx.compose.material.icons.rounded.WarningAmber
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.crashreporter.CrashReportType
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
import java.net.URLEncoder
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CrashReporterScreen() {
|
||||
val viewModel: CrashReporterScreenVM = viewModel()
|
||||
val navController = LocalNavController.current
|
||||
val reports by viewModel.reports.observeAsState()
|
||||
val showExceptions by viewModel.showExceptions.observeAsState(true)
|
||||
val showCrashes by viewModel.showCrashes.observeAsState(true)
|
||||
PreferenceScreen(title = stringResource(R.string.preference_crash_reporter)) {
|
||||
reports?.let {
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
IconToggleButton(checked = showExceptions, onCheckedChange = { value ->
|
||||
viewModel.setShowExceptions(value)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = if (showExceptions) Icons.Rounded.Warning else Icons.Rounded.WarningAmber,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.alpha(if (showExceptions) 1f else 0.5f)
|
||||
)
|
||||
}
|
||||
IconToggleButton(checked = showCrashes, onCheckedChange = { value ->
|
||||
viewModel.setShowCrashes(value)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = if (showCrashes) Icons.Rounded.Error else Icons.Rounded.ErrorOutline,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.alpha(if (showCrashes) 1f else 0.5f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
items(it) {
|
||||
OutlinedCard(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 4.dp, horizontal = 8.dp)
|
||||
,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
navController?.navigate("settings/debug/crashreporter/report?fileName=${it.filePath}")
|
||||
}
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = DateUtils.formatDateTime(
|
||||
LocalContext.current,
|
||||
it.time.time,
|
||||
DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE
|
||||
),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides if (it.type == CrashReportType.Exception) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
imageVector = if (it.type == CrashReportType.Exception) Icons.Rounded.Warning else Icons.Rounded.Error,
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
text = if (it.type == CrashReportType.Exception) "Exception" else "Crash",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = it.summary,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} ?: item {
|
||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package de.mm20.launcher2.ui.settings.crashreporter
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.liveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.crashreporter.CrashReport
|
||||
import de.mm20.launcher2.crashreporter.CrashReportType
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class CrashReporterScreenVM: ViewModel() {
|
||||
fun setShowCrashes(showCrashes: Boolean) {
|
||||
this.showCrashes.value = showCrashes
|
||||
updateReports()
|
||||
}
|
||||
|
||||
fun setShowExceptions(showExceptions: Boolean) {
|
||||
this.showExceptions.value = showExceptions
|
||||
updateReports()
|
||||
}
|
||||
|
||||
private fun updateReports() {
|
||||
val exceptions = showExceptions.value == true
|
||||
val crashes = showCrashes.value == true
|
||||
reports.value = _reports?.filter {
|
||||
it.type == CrashReportType.Exception && exceptions ||
|
||||
it.type == CrashReportType.Crash && crashes
|
||||
}
|
||||
}
|
||||
|
||||
val showExceptions = MutableLiveData(true)
|
||||
val showCrashes = MutableLiveData(true)
|
||||
|
||||
val reports = MutableLiveData<List<CrashReport>?>(null)
|
||||
private var _reports: List<CrashReport>? = null
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
_reports = CrashReporter.getCrashReports()
|
||||
reports.value = _reports
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
@@ -19,6 +20,7 @@ import java.io.File
|
||||
fun DebugSettingsScreen() {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val navController = LocalNavController.current
|
||||
PreferenceScreen(
|
||||
stringResource(R.string.preference_screen_debug)
|
||||
) {
|
||||
@@ -27,7 +29,7 @@ fun DebugSettingsScreen() {
|
||||
title = stringResource(R.string.preference_crash_reporter),
|
||||
summary = stringResource(R.string.preference_crash_reporter_summary),
|
||||
onClick = {
|
||||
context.startActivity(CrashReporter.getLaunchIntent())
|
||||
navController?.navigate("settings/debug/crashreporter")
|
||||
})
|
||||
|
||||
Preference(
|
||||
|
||||
Reference in New Issue
Block a user