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.Column
8import androidx.compose.foundation.layout.Row
9import androidx.compose.foundation.layout.Spacer
10import androidx.compose.foundation.layout.fillMaxSize
11import androidx.compose.foundation.layout.fillMaxWidth
12import androidx.compose.foundation.layout.padding
13import androidx.compose.foundation.layout.size
14import androidx.compose.foundation.lazy.LazyColumn
15import androidx.compose.foundation.lazy.items
16import androidx.compose.material.icons.Icons
17import androidx.compose.material.icons.automirrored.filled.ArrowBack
18import androidx.compose.material.icons.filled.BugReport
19import androidx.compose.material.icons.filled.DeleteSweep
20import androidx.compose.material.icons.filled.Share
21import androidx.compose.material3.AlertDialog
22import androidx.compose.material3.ExperimentalMaterial3Api
23import androidx.compose.material3.HorizontalDivider
24import androidx.compose.material3.Icon
25import androidx.compose.material3.IconButton
26import androidx.compose.material3.MaterialTheme
27import androidx.compose.material3.Scaffold
28import androidx.compose.material3.Text
29import androidx.compose.material3.TextButton
30import androidx.compose.material3.TopAppBar
31import androidx.compose.runtime.Composable
32import androidx.compose.runtime.LaunchedEffect
33import androidx.compose.runtime.getValue
34import androidx.compose.runtime.mutableStateOf
35import androidx.compose.runtime.remember
36import androidx.compose.runtime.rememberCoroutineScope
37import androidx.compose.runtime.saveable.rememberSaveable
38import androidx.compose.runtime.setValue
39import kotlinx.coroutines.Dispatchers
40import kotlinx.coroutines.launch
41import kotlinx.coroutines.withContext
42import androidx.compose.ui.Alignment
43import androidx.compose.ui.Modifier
44import androidx.compose.ui.platform.LocalContext
45import androidx.compose.ui.res.stringResource
46import androidx.compose.ui.semantics.Role
47import androidx.compose.ui.semantics.heading
48import androidx.compose.ui.semantics.role
49import androidx.compose.ui.semantics.semantics
50import androidx.compose.ui.unit.dp
51import androidx.lifecycle.compose.collectAsStateWithLifecycle
52import soy.iko.opencode.R
53import soy.iko.opencode.data.repo.CrashLogger
54import soy.iko.opencode.ui.components.rememberRelativeTime
56@OptIn(ExperimentalMaterial3Api::class)
58fun DiagnosticsScreen(onBack: () -> Unit) {
59 val context = LocalContext.current
60 val logger = remember { CrashLogger.get(context) }
61 val reports by logger.reports.collectAsStateWithLifecycle()
62 var viewing by rememberSaveable { mutableStateOf<String?>(null) }
63 val shareScope = rememberCoroutineScope()
64 val shareLabel = stringResource(R.string.share)
69 title = { Text(stringResource(R.string.diagnostics)) },
71 IconButton(onClick = onBack) {
72 Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
76 if (reports.isNotEmpty()) {
77 IconButton(onClick = {
78 shareScope.launch(Dispatchers.IO) {
79 runCatching { logger.clearAll() }
80 .onFailure { Log.w("Diagnostics", "Failed to clear crash reports", it) }
83 Icon(Icons.Filled.DeleteSweep, contentDescription = stringResource(R.string.clear_all))
90 Column(modifier = Modifier.fillMaxSize().padding(padding)) {
92 stringResource(R.string.diagnostics_desc),
93 style = MaterialTheme.typography.bodySmall,
94 color = MaterialTheme.colorScheme.onSurfaceVariant,
95 modifier = Modifier.padding(16.dp),
98 stringResource(R.string.crash_reports),
99 style = MaterialTheme.typography.titleSmall,
100 color = MaterialTheme.colorScheme.primary,
102 .padding(start = 16.dp, bottom = 8.dp)
103 .semantics { heading() },
105 if (reports.isEmpty()) {
107 stringResource(R.string.no_crash_reports),
108 style = MaterialTheme.typography.bodyMedium,
109 color = MaterialTheme.colorScheme.onSurfaceVariant,
110 modifier = Modifier.padding(24.dp),
113 LazyColumn(modifier = Modifier.fillMaxSize()) {
114 items(reports, key = { it.fileName }) { report ->
118 .clickable(role = Role.Button) { viewing = report.fileName }
119 .padding(horizontal = 16.dp, vertical = 12.dp),
120 verticalAlignment = Alignment.CenterVertically,
121 horizontalArrangement = Arrangement.spacedBy(12.dp),
124 Icons.Filled.BugReport,
125 contentDescription = null,
126 tint = MaterialTheme.colorScheme.error,
128 Column(modifier = Modifier.weight(1f)) {
129 Text(report.preview, style = MaterialTheme.typography.bodyMedium, maxLines = 1)
131 rememberRelativeTime(report.timestamp),
132 style = MaterialTheme.typography.bodySmall,
133 color = MaterialTheme.colorScheme.onSurfaceVariant,
136 IconButton(onClick = {
138 val content = withContext(Dispatchers.IO) {
139 logger.readReport(report.fileName).orEmpty()
141 val send = Intent(Intent.ACTION_SEND).apply {
143 putExtra(Intent.EXTRA_SUBJECT, shareLabel)
144 putExtra(Intent.EXTRA_TEXT, content)
146 runCatching { context.startActivity(Intent.createChooser(send, shareLabel)) }
147 .onFailure { Log.w("Diagnostics", "Failed to share crash report", it) }
150 Icon(Icons.Filled.Share, contentDescription = stringResource(R.string.share))
160 val reportName = viewing
161 if (reportName != null) {
162 var reportContent by remember(reportName) { mutableStateOf<String?>(null) }
163 LaunchedEffect(reportName) {
164 reportContent = withContext(Dispatchers.IO) { logger.readReport(reportName) }
166 val content = reportContent
168 onDismissRequest = { viewing = null },
170 TextButton(onClick = { viewing = null }) { Text(stringResource(R.string.back)) }
174 val shareLabel2 = stringResource(R.string.share)
175 TextButton(onClick = {
176 val send = Intent(Intent.ACTION_SEND).apply {
178 putExtra(Intent.EXTRA_SUBJECT, shareLabel2)
179 putExtra(Intent.EXTRA_TEXT, content.orEmpty())
181 runCatching { context.startActivity(Intent.createChooser(send, shareLabel2)) }
182 .onFailure { Log.w("Diagnostics", "Failed to share crash report", it) }
183 }) { Text(stringResource(R.string.share)) }
184 Spacer(Modifier.size(8.dp))
185 TextButton(onClick = {
186 shareScope.launch(Dispatchers.IO) {
187 runCatching { logger.deleteReport(reportName) }
188 .onFailure { Log.w("Diagnostics", "Failed to delete crash report", it) }
191 }) { Text(stringResource(R.string.delete)) }
194 title = { Text(stringResource(R.string.crash_report)) },
198 style = MaterialTheme.typography.bodySmall,
199 modifier = Modifier.fillMaxWidth(),
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