From b200916844353dd1c931c7bf6059d003e8f9fac6 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Sat, 5 Sep 2026 13:45:34 +0200 Subject: [PATCH] feat: show the group's key in full, with a copy button The shared-key screen showed `thresholdPublicKey.take(16)` followed by an ellipsis. A 16-character prefix is enough to recognise a key you already know and not enough for the one thing this key is for. Members compare it out of band to confirm every device finished the ceremony on the same key. That is the check that catches a device which quietly ended up elsewhere -- and it cannot be done against a prefix, or from a screen the value cannot be copied off. Both halves of that were missing. The whole 66-character key now renders, wrapping rather than ellipsised, in a monospaced face so a character-by-character comparison lines up instead of drifting under proportional spacing. A FilledIconButton beside it copies the key via LocalClipboardManager, the same way ShareProfileScreen and the image viewers already do it. The "Key: " prefix became a label above so the key gets the full width. No copied-confirmation toast, matching ShareProfileScreen: Android shows its own clipboard notice on 13+, and a snackbar here would double up. Co-Authored-By: Claude Opus 5 --- .../compose/ui/composable/DkgRitualScreen.kt | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt index 8f4b0f62..c32eb08f 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.ErrorOutline import androidx.compose.material.icons.filled.Key import androidx.compose.material.icons.filled.RadioButtonUnchecked @@ -43,6 +44,9 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import press.mantra.compose.database.model.ChatRoom @@ -393,10 +397,38 @@ private fun RitualProgress( } session.thresholdPublicKey?.let { thresholdPublicKey -> - Text( - text = "Key: ${thresholdPublicKey.take(16)}…", - style = MaterialTheme.typography.bodyMedium - ) + val clipboardManager = LocalClipboardManager.current + + Text(text = "Key", style = MaterialTheme.typography.labelMedium) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // In full, and wrapping rather than ellipsised. This is the + // group's key: it is what members compare out of band to + // check every device ended the ceremony on the same one, and + // a truncated key cannot be compared. Monospaced so the + // comparison is character by character. + Text( + text = thresholdPublicKey, + style = MaterialTheme.typography.bodySmall, + fontFamily = FontFamily.Monospace, + modifier = Modifier.weight(1f) + ) + + FilledIconButton( + onClick = { + clipboardManager.setText(AnnotatedString(thresholdPublicKey)) + } + ) { + Icon( + imageVector = Icons.Default.ContentCopy, + contentDescription = "Copy the group's key" + ) + } + } } Text(