diff --git a/app/src/main/java/com/geeksville/mesh/ui/NodeItem.kt b/app/src/main/java/com/geeksville/mesh/ui/NodeItem.kt index 4fdc64d7d..7d8fc1a14 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NodeItem.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeItem.kt @@ -170,6 +170,7 @@ fun NodeItem( NodeKeyStatusIcon( hasPKC = thatNode.hasPKC, mismatchKey = thatNode.mismatchKey, + publicKey = thatNode.user.publicKey, modifier = Modifier.size(32.dp) ) Text( diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/NodeKeyStatusIcon.kt b/app/src/main/java/com/geeksville/mesh/ui/components/NodeKeyStatusIcon.kt index 233bbc777..10b814aa2 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/NodeKeyStatusIcon.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/NodeKeyStatusIcon.kt @@ -17,8 +17,25 @@ package com.geeksville.mesh.ui.components +import android.util.Base64 +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.ContentAlpha import androidx.compose.material.Icon import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.TextButton import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyOff import androidx.compose.material.icons.filled.Lock @@ -27,17 +44,83 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.rememberVectorPainter -import androidx.compose.ui.res.painterResource +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog import com.geeksville.mesh.R +import com.geeksville.mesh.model.Channel +import com.geeksville.mesh.ui.theme.AppTheme +import com.google.protobuf.ByteString + +@Composable +private fun KeyStatusDialog( + @StringRes title: Int, + @StringRes text: Int, + key: ByteString?, + onDismiss: () -> Unit = {} +) = Dialog( + onDismissRequest = onDismiss, +) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = MaterialTheme.colors.background + ) { + LazyColumn( + contentPadding = PaddingValues(horizontal = 24.dp, vertical = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { + Text( + text = stringResource(id = title), + color = MaterialTheme.colors.onBackground.copy(alpha = ContentAlpha.high), + textAlign = TextAlign.Center, + ) + Spacer(Modifier.height(16.dp)) + Text( + text = stringResource(id = text), + color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium), + textAlign = TextAlign.Center, + ) + Spacer(Modifier.height(16.dp)) + if (key != null && title == R.string.encryption_pkc) { + val keyString = Base64.encodeToString(key.toByteArray(), Base64.NO_WRAP) + SelectionContainer { + Text( + text = "Public Key: $keyString", + textAlign = TextAlign.Center, + ) + } + Spacer(Modifier.height(16.dp)) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + TextButton( + onClick = onDismiss, + colors = ButtonDefaults.textButtonColors( + contentColor = MaterialTheme.colors.onSurface, + ), + ) { Text(text = stringResource(id = R.string.close)) } + } + } + } + } +} @Composable fun NodeKeyStatusIcon( hasPKC: Boolean, mismatchKey: Boolean, + publicKey: ByteString? = null, modifier: Modifier = Modifier, ) { var showEncryptionDialog by remember { mutableStateOf(false) } @@ -47,13 +130,13 @@ fun NodeKeyStatusIcon( hasPKC -> R.string.encryption_pkc to R.string.encryption_pkc_text else -> R.string.encryption_psk to R.string.encryption_psk_text } - SimpleAlertDialog(title, text) { showEncryptionDialog = false } + KeyStatusDialog(title, text, publicKey) { showEncryptionDialog = false } } val (icon, tint) = when { - mismatchKey -> rememberVectorPainter(Icons.Default.KeyOff) to Color.Red - hasPKC -> rememberVectorPainter(Icons.Default.Lock) to Color(color = 0xFF30C047) - else -> painterResource(R.drawable.ic_lock_open_right_24) to Color(color = 0xFFFEC30A) + mismatchKey -> Icons.Default.KeyOff to Color.Red + hasPKC -> Icons.Default.Lock to Color(color = 0xFF30C047) + else -> ImageVector.vectorResource(R.drawable.ic_lock_open_right_24) to Color(color = 0xFFFEC30A) } IconButton( @@ -61,7 +144,7 @@ fun NodeKeyStatusIcon( modifier = modifier, ) { Icon( - painter = icon, + imageVector = icon, contentDescription = stringResource( id = when { mismatchKey -> R.string.encryption_error @@ -73,3 +156,39 @@ fun NodeKeyStatusIcon( ) } } + +@PreviewLightDark +@Composable +private fun KeyStatusDialogErrorPreview() { + AppTheme { + KeyStatusDialog( + title = R.string.encryption_error, + text = R.string.encryption_error_text, + key = null, + ) + } +} + +@PreviewLightDark +@Composable +private fun KeyStatusDialogPkcPreview() { + AppTheme { + KeyStatusDialog( + title = R.string.encryption_pkc, + text = R.string.encryption_pkc_text, + key = Channel.getRandomKey(), + ) + } +} + +@PreviewLightDark +@Composable +private fun KeyStatusDialogPskPreview() { + AppTheme { + KeyStatusDialog( + title = R.string.encryption_psk, + text = R.string.encryption_psk_text, + key = null, + ) + } +}