1package soy.iko.opencode.ui.settings
3import android.content.Intent
5import androidx.compose.foundation.clickable
6import androidx.compose.foundation.layout.Arrangement
7import androidx.compose.foundation.layout.Box
8import androidx.compose.foundation.layout.Column
9import androidx.compose.foundation.layout.Row
10import androidx.compose.foundation.layout.Spacer
11import androidx.compose.foundation.layout.fillMaxSize
12import androidx.compose.foundation.layout.fillMaxWidth
13import androidx.compose.foundation.layout.padding
14import androidx.compose.foundation.layout.size
15import androidx.compose.foundation.lazy.LazyColumn
16import androidx.compose.foundation.lazy.items
17import androidx.compose.material.icons.Icons
18import androidx.compose.material.icons.automirrored.filled.ArrowBack
19import androidx.compose.material.icons.filled.BugReport
20import androidx.compose.material.icons.filled.DeleteSweep
21import androidx.compose.material.icons.filled.Share
22import androidx.compose.material3.AlertDialog
23import androidx.compose.material3.CircularProgressIndicator
24import androidx.compose.material3.ExperimentalMaterial3Api
25import androidx.compose.material3.HorizontalDivider
26import androidx.compose.material3.Icon
27import androidx.compose.material3.IconButton
28import androidx.compose.material3.MaterialTheme
29import androidx.compose.material3.Scaffold
30import androidx.compose.material3.Text
31import androidx.compose.material3.TextButton
32import androidx.compose.material3.TopAppBar
33import androidx.compose.runtime.Composable
34import androidx.compose.runtime.LaunchedEffect
35import androidx.compose.runtime.getValue
36import androidx.compose.runtime.mutableStateOf
37import androidx.compose.runtime.remember
38import androidx.compose.runtime.rememberCoroutineScope
39import androidx.compose.runtime.saveable.rememberSaveable
40import androidx.compose.runtime.setValue
41import kotlinx.coroutines.Dispatchers
42import kotlinx.coroutines.launch
43import kotlinx.coroutines.withContext
44import soy.iko.opencode.util.runCatchingCancellable
45import androidx.compose.ui.Alignment
46import androidx.compose.ui.Modifier
47import androidx.compose.ui.platform.LocalContext
48import androidx.compose.ui.res.stringResource
49import androidx.compose.ui.semantics.Role
50import androidx.compose.ui.semantics.contentDescription
51import androidx.compose.ui.semantics.heading
52import androidx.compose.ui.semantics.role
53import androidx.compose.ui.semantics.semantics
54import androidx.compose.ui.unit.dp
55import androidx.lifecycle.compose.collectAsStateWithLifecycle
56import soy.iko.opencode.R
57import soy.iko.opencode.data.repo.CrashLogger
58import soy.iko.opencode.ui.components.rememberRelativeTime
60@OptIn(ExperimentalMaterial3Api::class)
62fun DiagnosticsScreen(onBack: () -> Unit) {
63 val context = LocalContext.current
64 val logger = remember { CrashLogger.get(context) }
65 val reports by logger.reports.collectAsStateWithLifecycle()
66 var viewing by rememberSaveable { mutableStateOf<String?>(null) }
67 val shareScope = rememberCoroutineScope()
68 val shareLabel = stringResource(R.string.share)
73 title = { Text(stringResource(R.string.diagnostics)) },
75 IconButton(onClick = onBack) {
76 Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
80 if (reports.isNotEmpty()) {
81 IconButton(onClick = {
84 Icon(Icons.Filled.DeleteSweep, contentDescription = stringResource(R.string.clear_all))
91 Column(modifier = Modifier.fillMaxSize().padding(padding)) {
93 stringResource(R.string.diagnostics_desc),
94 style = MaterialTheme.typography.bodySmall,
95 color = MaterialTheme.colorScheme.onSurfaceVariant,
96 modifier = Modifier.padding(16.dp),
99 stringResource(R.string.crash_reports),
100 style = MaterialTheme.typography.titleSmall,
101 color = MaterialTheme.colorScheme.primary,
103 .padding(start = 16.dp, bottom = 8.dp)
104 .semantics { heading() },
106 if (reports.isEmpty()) {
108 stringResource(R.string.no_crash_reports),
109 style = MaterialTheme.typography.bodyMedium,
110 color = MaterialTheme.colorScheme.onSurfaceVariant,
111 modifier = Modifier.padding(24.dp),
114 LazyColumn(modifier = Modifier.fillMaxSize()) {
115 items(reports, key = { it.fileName }) { report ->
119 .clickable(role = Role.Button) { viewing = report.fileName }
120 .padding(horizontal = 16.dp, vertical = 12.dp),
121 verticalAlignment = Alignment.CenterVertically,
122 horizontalArrangement = Arrangement.spacedBy(12.dp),
125 Icons.Filled.BugReport,
126 contentDescription = null,
127 tint = MaterialTheme.colorScheme.error,
129 Column(modifier = Modifier.weight(1f)) {
130 Text(report.preview, style = MaterialTheme.typography.bodyMedium, maxLines = 1)
132 rememberRelativeTime(report.timestamp),
133 style = MaterialTheme.typography.bodySmall,
134 color = MaterialTheme.colorScheme.onSurfaceVariant,
137 IconButton(onClick = {
139 val content = withContext(Dispatchers.IO) {
140 logger.readReport(report.fileName).orEmpty()
142 val send = Intent(Intent.ACTION_SEND).apply {
144 putExtra(Intent.EXTRA_SUBJECT, shareLabel)
145 putExtra(Intent.EXTRA_TEXT, content)
147 runCatchingCancellable { context.startActivity(Intent.createChooser(send, shareLabel)) }
148 .onFailure { Log.w("Diagnostics", "Failed to share crash report", it) }
151 Icon(Icons.Filled.Share, contentDescription = stringResource(R.string.share))
161 val reportName = viewing
162 if (reportName != null) {
163 var reportContent by remember(reportName) { mutableStateOf<String?>(null) }
164 LaunchedEffect(reportName) {
165 reportContent = withContext(Dispatchers.IO) {
166 runCatching { logger.readReport(reportName) }.getOrNull()
169 val content = reportContent
171 onDismissRequest = { viewing = null },
173 TextButton(onClick = { viewing = null }) { Text(stringResource(R.string.back)) }
177 val shareLabel2 = stringResource(R.string.share)
178 TextButton(onClick = {
179 val send = Intent(Intent.ACTION_SEND).apply {
181 putExtra(Intent.EXTRA_SUBJECT, shareLabel2)
182 putExtra(Intent.EXTRA_TEXT, content.orEmpty())
184 runCatchingCancellable { context.startActivity(Intent.createChooser(send, shareLabel2)) }
185 .onFailure { Log.w("Diagnostics", "Failed to share crash report", it) }
186 }) { Text(stringResource(R.string.share)) }
187 Spacer(Modifier.size(8.dp))
188 TextButton(onClick = {
189 logger.deleteReport(reportName)
191 }) { Text(stringResource(R.string.delete)) }
194 title = { Text(stringResource(R.string.crash_report)) },
196 if (content != null) {
199 style = MaterialTheme.typography.bodySmall,
200 modifier = Modifier.fillMaxWidth(),
203 val loadingLabel = stringResource(R.string.loading)
205 modifier = Modifier.fillMaxWidth().padding(vertical = 24.dp),
206 contentAlignment = Alignment.Center,
208 CircularProgressIndicator(Modifier.semantics { contentDescription = loadingLabel })
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list