Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
249007d
Add Rust diagnostics reporting
praveenperera Jul 6, 2026
e044ed7
Add mobile diagnostics review flow
praveenperera Jul 6, 2026
df1be93
Regenerate diagnostics bindings
praveenperera Jul 6, 2026
2afe451
Improve diagnostics redaction flow
praveenperera Jul 7, 2026
9a69889
Read persisted logs in capture snapshots
praveenperera Jul 8, 2026
923e1c5
Refactor Android diagnostics sheet
praveenperera Jul 8, 2026
44b2b6c
Remove iOS diagnostics row subtitle
praveenperera Jul 8, 2026
3572d98
Harden diagnostics failure capture
praveenperera Jul 8, 2026
44e4cce
Format diagnostics report sizes
praveenperera Jul 8, 2026
38e7591
Keep diagnostics spinner responsive
praveenperera Jul 8, 2026
c9e43d4
Persist app diagnostics logs
praveenperera Jul 8, 2026
c077f1c
Improve diagnostics upload feedback
praveenperera Jul 8, 2026
5be2254
Persist submitted diagnostics history
praveenperera Jul 8, 2026
f44fe97
Show submitted diagnostics history
praveenperera Jul 8, 2026
38b8ce8
Harden diagnostics submission
praveenperera Jul 9, 2026
e8ac3f0
Show diagnostics history load failures
praveenperera Jul 9, 2026
785fca0
Report diagnostics log write failures
praveenperera Jul 9, 2026
9fc28e0
Handle Swift diagnostics clear failures
praveenperera Jul 9, 2026
a68c939
Drop old log writer before reattach
praveenperera Jul 9, 2026
43e93d2
Include diagnostics build metadata
praveenperera Jul 9, 2026
1256615
Avoid retrying ambiguous upload responses
praveenperera Jul 9, 2026
a0b9389
Redact formatted seed phrase variants
praveenperera Jul 9, 2026
50f5b86
Harden diagnostics privacy safeguards
praveenperera Jul 10, 2026
18a7938
Clear Android logcat with diagnostics reset
praveenperera Jul 10, 2026
14ef915
Refactor diagnostics error handling and tests
praveenperera Jul 10, 2026
10749f3
Redact ambiguous txid hex runs
praveenperera Jul 10, 2026
7c227a1
Fix diagnostics UI state handling
praveenperera Jul 10, 2026
6a95c20
Harden diagnostics log storage
praveenperera Jul 10, 2026
b4562d6
Clarify diagnostics client token
praveenperera Jul 10, 2026
5217788
Correct coin control trace label
praveenperera Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.bitcoinppl.cove.Auth
import org.bitcoinppl.cove.BuildConfig
import org.bitcoinppl.cove.cloudbackup.AndroidCloudStorageAccess
import org.bitcoinppl.cove.cloudbackup.clearCloudBackupDriveAccountBinding
Expand Down Expand Up @@ -72,7 +74,18 @@ fun AboutSettingsScreen(
var showWipeCloudDialog by remember { mutableStateOf(false) }
var wipeCloudResult by remember { mutableStateOf<WipeCloudResult?>(null) }
var showResetLocalStateDialog by remember { mutableStateOf(false) }
var showSendDiagnostics by remember { mutableStateOf(false) }
var showSubmittedDiagnostics by remember { mutableStateOf(false) }
var sendDiagnosticsSubmitting by remember { mutableStateOf(false) }
var resetLocalStateMessage by remember { mutableStateOf<String?>(null) }
val isInDecoyMode = Auth.isInDecoyMode()
var submittedDiagnosticsLoadState by remember {
mutableStateOf<SubmittedDiagnosticsLoadState>(
SubmittedDiagnosticsLoadState.Loaded(emptyList()),
)
}
var submittedDiagnosticsRefreshId by remember { mutableIntStateOf(0) }
var submittedDiagnosticsRefreshJob by remember { mutableStateOf<Job?>(null) }
var isBetaEnabled by remember {
mutableStateOf(
Database().globalFlag().getBoolConfig(GlobalFlagKey.BETA_FEATURES_ENABLED)
Expand All @@ -87,6 +100,40 @@ fun AboutSettingsScreen(
}
}

fun refreshSubmittedDiagnostics() {
submittedDiagnosticsRefreshId++
val refreshId = submittedDiagnosticsRefreshId
submittedDiagnosticsRefreshJob?.cancel()

if (Auth.isInDecoyMode()) {
submittedDiagnosticsLoadState = SubmittedDiagnosticsLoadState.Loaded(emptyList())
return
}

submittedDiagnosticsRefreshJob = coroutineScope.launch {
val loadState = loadSubmittedDiagnosticsRecords()

if (refreshId != submittedDiagnosticsRefreshId) {
return@launch
}

submittedDiagnosticsLoadState =
if (Auth.isInDecoyMode()) {
SubmittedDiagnosticsLoadState.Loaded(emptyList())
} else {
loadState
}
}
}

LaunchedEffect(isInDecoyMode) {
if (isInDecoyMode) {
showSubmittedDiagnostics = false
}

refreshSubmittedDiagnostics()
}

AboutSettingsContent(
version = BuildConfig.VERSION_NAME,
buildNumber = BuildConfig.VERSION_CODE.toString(),
Expand All @@ -107,6 +154,18 @@ fun AboutSettingsScreen(
}
context.startActivity(intent)
},
showSendDiagnostics = !isInDecoyMode,
onSendDiagnosticsClick = {
if (!isInDecoyMode) showSendDiagnostics = true
},
showSubmittedDiagnostics =
!isInDecoyMode && submittedDiagnosticsLoadState.shouldShowAboutEntry,
submittedDiagnosticsSummary = submittedDiagnosticsSummary(submittedDiagnosticsLoadState),
onSubmittedDiagnosticsClick = {
if (!isInDecoyMode && submittedDiagnosticsLoadState.shouldShowAboutEntry) {
showSubmittedDiagnostics = true
}
},
onWipeCloudBackupClick = { showWipeCloudDialog = true },
onResetLocalBackupStateClick = { showResetLocalStateDialog = true },
modifier = modifier,
Expand Down Expand Up @@ -253,6 +312,41 @@ fun AboutSettingsScreen(
},
)
}

val dismissSendDiagnostics = {
if (!sendDiagnosticsSubmitting) {
showSendDiagnostics = false
refreshSubmittedDiagnostics()
}
}

if (showSendDiagnostics && !isInDecoyMode) {
FullScreenSettingsModal(
onDismiss = dismissSendDiagnostics,
dismissEnabled = !sendDiagnosticsSubmitting,
) {
SendDiagnosticsSheet(
onDismiss = dismissSendDiagnostics,
onSubmittingChange = { sendDiagnosticsSubmitting = it },
)
}
}

val dismissSubmittedDiagnostics = {
showSubmittedDiagnostics = false
refreshSubmittedDiagnostics()
}

if (showSubmittedDiagnostics && !isInDecoyMode) {
FullScreenSettingsModal(
onDismiss = dismissSubmittedDiagnostics,
) {
SubmittedDiagnosticsScreen(
onDismiss = dismissSubmittedDiagnostics,
onRecordsChanged = { refreshSubmittedDiagnostics() },
)
}
}
}

@OptIn(ExperimentalMaterial3Api::class)
Expand All @@ -266,6 +360,11 @@ internal fun AboutSettingsContent(
onBack: () -> Unit,
onBuildNumberClick: () -> Unit,
onFeedbackClick: () -> Unit,
showSendDiagnostics: Boolean,
onSendDiagnosticsClick: () -> Unit,
showSubmittedDiagnostics: Boolean,
submittedDiagnosticsSummary: String,
onSubmittedDiagnosticsClick: () -> Unit,
onWipeCloudBackupClick: () -> Unit,
onResetLocalBackupStateClick: () -> Unit,
modifier: Modifier = Modifier,
Expand Down Expand Up @@ -326,6 +425,22 @@ internal fun AboutSettingsContent(
valueStyle = MaterialTheme.typography.bodySmall,
onClick = onFeedbackClick,
)
if (showSendDiagnostics) {
MaterialDivider()
AboutRow(
label = "Send Diagnostics",
onClick = onSendDiagnosticsClick,
)
}
if (showSubmittedDiagnostics) {
MaterialDivider()
AboutRow(
label = "Submitted Diagnostics",
value = submittedDiagnosticsSummary,
valueStyle = MaterialTheme.typography.bodySmall,
onClick = onSubmittedDiagnosticsClick,
)
}
}
}

Expand Down Expand Up @@ -388,12 +503,39 @@ internal fun AboutSettingsPreviewContent() {
onBack = { },
onBuildNumberClick = { },
onFeedbackClick = { },
showSendDiagnostics = true,
onSendDiagnosticsClick = { },
showSubmittedDiagnostics = true,
submittedDiagnosticsSummary = "3 reports",
onSubmittedDiagnosticsClick = { },
onWipeCloudBackupClick = { },
onResetLocalBackupStateClick = { },
)
}
}

private fun diagnosticsReportCountText(count: Int): String =
if (count == 1) {
"1 report"
} else {
"$count reports"
}

private val SubmittedDiagnosticsLoadState.shouldShowAboutEntry: Boolean
get() =
when (this) {
SubmittedDiagnosticsLoadState.Loading -> false
is SubmittedDiagnosticsLoadState.Loaded -> records.isNotEmpty()
is SubmittedDiagnosticsLoadState.Failed -> true
}

private fun submittedDiagnosticsSummary(state: SubmittedDiagnosticsLoadState): String =
when (state) {
SubmittedDiagnosticsLoadState.Loading -> "Loading"
is SubmittedDiagnosticsLoadState.Loaded -> diagnosticsReportCountText(state.records.size)
is SubmittedDiagnosticsLoadState.Failed -> "Unavailable"
}

@Composable
private fun DebugRow(
title: String,
Expand Down Expand Up @@ -434,7 +576,7 @@ private suspend fun debugWipeCloudBackup(context: Context): WipeCloudResult {
@Composable
private fun AboutRow(
label: String,
value: String,
value: String? = null,
onClick: (() -> Unit)? = null,
valueStyle: androidx.compose.ui.text.TextStyle = MaterialTheme.typography.bodyLarge,
) {
Expand All @@ -452,10 +594,12 @@ private fun AboutRow(
text = label,
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = value,
style = valueStyle,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
value?.let {
Text(
text = it,
style = valueStyle,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
}
Loading
Loading