From ca05b768dfbe54edede2d3728006a559d6db299e Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Wed, 18 Dec 2024 14:43:23 +0100 Subject: [PATCH 1/8] Added QR code private key export dialog --- .../amethyst/ui/navigation/DrawerContent.kt | 2 +- .../ui/screen/loggedIn/AccountBackupDialog.kt | 73 ++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 34e8fa0b9..1823a8fae 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -539,7 +539,7 @@ fun ListContent( MediaServersListView({ editMediaServers = false }, accountViewModel = accountViewModel, nav = nav) } if (backupDialogOpen) { - AccountBackupDialog(accountViewModel, onClose = { backupDialogOpen = false }) + AccountBackupDialog(accountViewModel, nav, onClose = { backupDialogOpen = false }) } if (conectOrbotDialogOpen) { ConnectTorDialog( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt index 027713819..59c508e36 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt @@ -35,6 +35,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll @@ -71,6 +72,7 @@ import androidx.compose.ui.platform.LocalAutofill import androidx.compose.ui.platform.LocalAutofillTree import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType @@ -90,11 +92,16 @@ import com.halilibo.richtext.ui.material3.RichText import com.halilibo.richtext.ui.resolveDefaults import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.ui.navigation.EmptyNav +import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.note.authenticate +import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding +import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.crypto.CryptoUtils @@ -107,13 +114,14 @@ import kotlinx.coroutines.launch @Composable fun AccountBackupDialog( accountViewModel: AccountViewModel, + nav: INav, onClose: () -> Unit, ) { Dialog( onDismissRequest = onClose, properties = DialogProperties(usePlatformDefaultWidth = false), ) { - DialogContents(accountViewModel, onClose) + DialogContents(accountViewModel, nav, onClose) } } @@ -123,6 +131,7 @@ fun DialogContentsPreview() { ThemeComparisonRow { DialogContents( mockAccountViewModel(), + EmptyNav, {}, ) } @@ -132,6 +141,7 @@ fun DialogContentsPreview() { @Composable private fun DialogContents( accountViewModel: AccountViewModel, + nav: INav, onClose: () -> Unit, ) { Surface( @@ -185,6 +195,30 @@ private fun DialogContents( NSecCopyButton(accountViewModel) + // store the dialog open or close state + var dialogOpen by remember { mutableStateOf(false) } + IconButton( + onClick = { + dialogOpen = true + nav.closeDrawer() + }, + ) { + Icon( + painter = painterResource(R.drawable.ic_qrcode), + contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.primary, + ) + } + + if (dialogOpen) { + ShowKeyQRDialog( + accountViewModel.account.settings.keyPair.privKey + ?.toNsec(), + onClose = { dialogOpen = false }, + ) + } + Spacer(modifier = Modifier.height(30.dp)) val content = stringRes(R.string.account_backup_tips3_md) @@ -448,3 +482,40 @@ private fun encryptCopyNSec( } } } + +@Composable +private fun ShowKeyQRDialog( + qrCode: String?, + onClose: () -> Unit, +) { + Dialog( + onDismissRequest = onClose, + properties = DialogProperties(usePlatformDefaultWidth = false), + ) { + Surface { + Column { + Row( + modifier = Modifier.padding(10.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + BackButton(onPress = onClose) + } + + Column( + modifier = Modifier.fillMaxSize().padding(horizontal = 10.dp), + verticalArrangement = Arrangement.SpaceAround, + ) { + Column { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().padding(horizontal = Size10dp), + ) { + QrCodeDrawer(qrCode ?: "error") + } + } + } + } + } + } +} From bd26fcfafd8f1c4eb63f23cb2bdb4e466267d272 Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Wed, 18 Dec 2024 14:54:09 +0100 Subject: [PATCH 2/8] Clean up layout --- .../ui/screen/loggedIn/AccountBackupDialog.kt | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt index 59c508e36..88e725b3e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt @@ -101,7 +101,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding -import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.crypto.CryptoUtils @@ -493,27 +492,30 @@ private fun ShowKeyQRDialog( properties = DialogProperties(usePlatformDefaultWidth = false), ) { Surface { - Column { + Column( + modifier = + Modifier + .fillMaxSize() + .padding(10.dp), + ) { + // Back button at the top Row( - modifier = Modifier.padding(10.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { BackButton(onPress = onClose) } + // QR Code content Column( - modifier = Modifier.fillMaxSize().padding(horizontal = 10.dp), - verticalArrangement = Arrangement.SpaceAround, + modifier = + Modifier + .fillMaxSize() + .padding(vertical = 10.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, ) { - Column { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth().padding(horizontal = Size10dp), - ) { - QrCodeDrawer(qrCode ?: "error") - } - } + QrCodeDrawer(qrCode ?: "error") } } } From 12d646e8a9d7286bede26d6dfdfd89cb2b409c3a Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Wed, 18 Dec 2024 15:02:38 +0100 Subject: [PATCH 3/8] Move QR code next to button --- .../ui/screen/loggedIn/AccountBackupDialog.kt | 61 ++++++++++++------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt index 88e725b3e..e266ea882 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt @@ -192,30 +192,14 @@ private fun DialogContents( Spacer(modifier = Modifier.height(20.dp)) - NSecCopyButton(accountViewModel) + Row { + Column { + NSecCopyButton(accountViewModel) + } - // store the dialog open or close state - var dialogOpen by remember { mutableStateOf(false) } - IconButton( - onClick = { - dialogOpen = true - nav.closeDrawer() - }, - ) { - Icon( - painter = painterResource(R.drawable.ic_qrcode), - contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), - modifier = Modifier.size(24.dp), - tint = MaterialTheme.colorScheme.primary, - ) - } - - if (dialogOpen) { - ShowKeyQRDialog( - accountViewModel.account.settings.keyPair.privKey - ?.toNsec(), - onClose = { dialogOpen = false }, - ) + Column { + QrCodeButton(nav, accountViewModel) + } } Spacer(modifier = Modifier.height(30.dp)) @@ -321,6 +305,37 @@ private fun DialogContents( } } +@Composable +private fun QrCodeButton( + nav: INav, + accountViewModel: AccountViewModel, +) { + // store the dialog open or close state + var dialogOpen by remember { mutableStateOf(false) } + + IconButton( + onClick = { + dialogOpen = true + nav.closeDrawer() + }, + ) { + Icon( + painter = painterResource(R.drawable.ic_qrcode), + contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.primary, + ) + } + + if (dialogOpen) { + ShowKeyQRDialog( + accountViewModel.account.settings.keyPair.privKey + ?.toNsec(), + onClose = { dialogOpen = false }, + ) + } +} + @Composable private fun NSecCopyButton(accountViewModel: AccountViewModel) { val clipboardManager = LocalClipboardManager.current From af5f136e6ca0bae7d10eb2c1cfe625f7ce443d81 Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Wed, 18 Dec 2024 20:18:23 +0100 Subject: [PATCH 4/8] added keyguard to qr code key export --- .../amethyst/ui/navigation/DrawerContent.kt | 2 +- .../ui/screen/loggedIn/AccountBackupDialog.kt | 32 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 1823a8fae..34e8fa0b9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -539,7 +539,7 @@ fun ListContent( MediaServersListView({ editMediaServers = false }, accountViewModel = accountViewModel, nav = nav) } if (backupDialogOpen) { - AccountBackupDialog(accountViewModel, nav, onClose = { backupDialogOpen = false }) + AccountBackupDialog(accountViewModel, onClose = { backupDialogOpen = false }) } if (conectOrbotDialogOpen) { ConnectTorDialog( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt index e266ea882..6a32c8bd9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt @@ -92,8 +92,6 @@ import com.halilibo.richtext.ui.material3.RichText import com.halilibo.richtext.ui.resolveDefaults import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account -import com.vitorpamplona.amethyst.ui.navigation.EmptyNav -import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.note.authenticate import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton @@ -113,14 +111,13 @@ import kotlinx.coroutines.launch @Composable fun AccountBackupDialog( accountViewModel: AccountViewModel, - nav: INav, onClose: () -> Unit, ) { Dialog( onDismissRequest = onClose, properties = DialogProperties(usePlatformDefaultWidth = false), ) { - DialogContents(accountViewModel, nav, onClose) + DialogContents(accountViewModel, onClose) } } @@ -130,7 +127,6 @@ fun DialogContentsPreview() { ThemeComparisonRow { DialogContents( mockAccountViewModel(), - EmptyNav, {}, ) } @@ -140,7 +136,6 @@ fun DialogContentsPreview() { @Composable private fun DialogContents( accountViewModel: AccountViewModel, - nav: INav, onClose: () -> Unit, ) { Surface( @@ -198,7 +193,7 @@ private fun DialogContents( } Column { - QrCodeButton(nav, accountViewModel) + QrCodeButton(accountViewModel) } } @@ -306,17 +301,28 @@ private fun DialogContents( } @Composable -private fun QrCodeButton( - nav: INav, - accountViewModel: AccountViewModel, -) { +private fun QrCodeButton(accountViewModel: AccountViewModel) { + val context = LocalContext.current + // store the dialog open or close state var dialogOpen by remember { mutableStateOf(false) } + val keyguardLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + if (result.resultCode == Activity.RESULT_OK) { + dialogOpen = true + } + } + IconButton( onClick = { - dialogOpen = true - nav.closeDrawer() + authenticate( + title = stringRes(context, R.string.copy_my_secret_key), + context = context, + keyguardLauncher = keyguardLauncher, + onApproved = { dialogOpen = true }, + onError = { title, message -> accountViewModel.toast(title, message) }, + ) }, ) { Icon( From eec82120709c795c55b6c16bd2602d6bccf13168 Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Wed, 18 Dec 2024 20:50:38 +0100 Subject: [PATCH 5/8] added keyguard to qr code encrypted key export --- .../ui/screen/loggedIn/AccountBackupDialog.kt | 104 ++++++++++++++---- 1 file changed, 80 insertions(+), 24 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt index 6a32c8bd9..0a2090483 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt @@ -342,6 +342,54 @@ private fun QrCodeButton(accountViewModel: AccountViewModel) { } } +@Composable +private fun QrCodeButtonEncrypted( + accountViewModel: AccountViewModel, + password: MutableState, +) { + val context = LocalContext.current + + // store the dialog open or close state + var dialogOpen by remember { mutableStateOf(false) } + + val keyguardLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + if (result.resultCode == Activity.RESULT_OK) { + dialogOpen = true + } + } + + IconButton( + enabled = password.value.text.isNotBlank(), + onClick = { + authenticate( + title = stringRes(context, R.string.copy_my_secret_key), + context = context, + keyguardLauncher = keyguardLauncher, + onApproved = { dialogOpen = true }, + onError = { title, message -> accountViewModel.toast(title, message) }, + ) + }, + ) { + Icon( + painter = painterResource(R.drawable.ic_qrcode), + contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), + modifier = Modifier.size(24.dp), + ) + } + + if (dialogOpen) { + val key = + accountViewModel.account.settings.keyPair.privKey + ?.toHexKey() + ?.let { CryptoUtils.encryptNIP49(it, password.value.text) } + ShowKeyQRDialog( + key, + onClose = { dialogOpen = false }, + ) + } +} + @Composable private fun NSecCopyButton(accountViewModel: AccountViewModel) { val clipboardManager = LocalClipboardManager.current @@ -403,30 +451,38 @@ private fun EncryptNSecCopyButton( } } - OutlinedButton( - modifier = Modifier.padding(horizontal = 3.dp), - onClick = { - authenticate( - title = stringRes(context, R.string.copy_my_secret_key), - context = context, - keyguardLauncher = keyguardLauncher, - onApproved = { encryptCopyNSec(password, context, scope, accountViewModel, clipboardManager) }, - onError = { title, message -> accountViewModel.toast(title, message) }, - ) - }, - shape = ButtonBorder, - contentPadding = ButtonPadding, - enabled = password.value.text.isNotBlank(), - ) { - Icon( - imageVector = Icons.Default.Key, - contentDescription = - stringRes(R.string.copies_the_nsec_id_your_password_to_the_clipboard_for_backup), - modifier = Modifier.padding(end = 5.dp), - ) - Text( - stringRes(id = R.string.encrypt_and_copy_my_secret_key), - ) + Row { + Column { + OutlinedButton( + modifier = Modifier.padding(horizontal = 3.dp), + onClick = { + authenticate( + title = stringRes(context, R.string.copy_my_secret_key), + context = context, + keyguardLauncher = keyguardLauncher, + onApproved = { encryptCopyNSec(password, context, scope, accountViewModel, clipboardManager) }, + onError = { title, message -> accountViewModel.toast(title, message) }, + ) + }, + shape = ButtonBorder, + contentPadding = ButtonPadding, + enabled = password.value.text.isNotBlank(), + ) { + Icon( + imageVector = Icons.Default.Key, + contentDescription = + stringRes(R.string.copies_the_nsec_id_your_password_to_the_clipboard_for_backup), + modifier = Modifier.padding(end = 5.dp), + ) + Text( + stringRes(id = R.string.encrypt_and_copy_my_secret_key), + ) + } + } + + Column { + QrCodeButtonEncrypted(accountViewModel, password) + } } } From b273e6dd4f61dbb0aac0ef8d0e09d7a9e7806fbb Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Wed, 18 Dec 2024 22:52:01 +0100 Subject: [PATCH 6/8] rearrange code for easier diff --- .../ui/screen/loggedIn/AccountBackupDialog.kt | 180 +++++++++--------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt index 0a2090483..40ec2ca58 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt @@ -300,96 +300,6 @@ private fun DialogContents( } } -@Composable -private fun QrCodeButton(accountViewModel: AccountViewModel) { - val context = LocalContext.current - - // store the dialog open or close state - var dialogOpen by remember { mutableStateOf(false) } - - val keyguardLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> - if (result.resultCode == Activity.RESULT_OK) { - dialogOpen = true - } - } - - IconButton( - onClick = { - authenticate( - title = stringRes(context, R.string.copy_my_secret_key), - context = context, - keyguardLauncher = keyguardLauncher, - onApproved = { dialogOpen = true }, - onError = { title, message -> accountViewModel.toast(title, message) }, - ) - }, - ) { - Icon( - painter = painterResource(R.drawable.ic_qrcode), - contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), - modifier = Modifier.size(24.dp), - tint = MaterialTheme.colorScheme.primary, - ) - } - - if (dialogOpen) { - ShowKeyQRDialog( - accountViewModel.account.settings.keyPair.privKey - ?.toNsec(), - onClose = { dialogOpen = false }, - ) - } -} - -@Composable -private fun QrCodeButtonEncrypted( - accountViewModel: AccountViewModel, - password: MutableState, -) { - val context = LocalContext.current - - // store the dialog open or close state - var dialogOpen by remember { mutableStateOf(false) } - - val keyguardLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> - if (result.resultCode == Activity.RESULT_OK) { - dialogOpen = true - } - } - - IconButton( - enabled = password.value.text.isNotBlank(), - onClick = { - authenticate( - title = stringRes(context, R.string.copy_my_secret_key), - context = context, - keyguardLauncher = keyguardLauncher, - onApproved = { dialogOpen = true }, - onError = { title, message -> accountViewModel.toast(title, message) }, - ) - }, - ) { - Icon( - painter = painterResource(R.drawable.ic_qrcode), - contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), - modifier = Modifier.size(24.dp), - ) - } - - if (dialogOpen) { - val key = - accountViewModel.account.settings.keyPair.privKey - ?.toHexKey() - ?.let { CryptoUtils.encryptNIP49(it, password.value.text) } - ShowKeyQRDialog( - key, - onClose = { dialogOpen = false }, - ) - } -} - @Composable private fun NSecCopyButton(accountViewModel: AccountViewModel) { val clipboardManager = LocalClipboardManager.current @@ -559,6 +469,96 @@ private fun encryptCopyNSec( } } +@Composable +private fun QrCodeButton(accountViewModel: AccountViewModel) { + val context = LocalContext.current + + // store the dialog open or close state + var dialogOpen by remember { mutableStateOf(false) } + + val keyguardLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + if (result.resultCode == Activity.RESULT_OK) { + dialogOpen = true + } + } + + IconButton( + onClick = { + authenticate( + title = stringRes(context, R.string.copy_my_secret_key), + context = context, + keyguardLauncher = keyguardLauncher, + onApproved = { dialogOpen = true }, + onError = { title, message -> accountViewModel.toast(title, message) }, + ) + }, + ) { + Icon( + painter = painterResource(R.drawable.ic_qrcode), + contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.primary, + ) + } + + if (dialogOpen) { + ShowKeyQRDialog( + accountViewModel.account.settings.keyPair.privKey + ?.toNsec(), + onClose = { dialogOpen = false }, + ) + } +} + +@Composable +private fun QrCodeButtonEncrypted( + accountViewModel: AccountViewModel, + password: MutableState, +) { + val context = LocalContext.current + + // store the dialog open or close state + var dialogOpen by remember { mutableStateOf(false) } + + val keyguardLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + if (result.resultCode == Activity.RESULT_OK) { + dialogOpen = true + } + } + + IconButton( + enabled = password.value.text.isNotBlank(), + onClick = { + authenticate( + title = stringRes(context, R.string.copy_my_secret_key), + context = context, + keyguardLauncher = keyguardLauncher, + onApproved = { dialogOpen = true }, + onError = { title, message -> accountViewModel.toast(title, message) }, + ) + }, + ) { + Icon( + painter = painterResource(R.drawable.ic_qrcode), + contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), + modifier = Modifier.size(24.dp), + ) + } + + if (dialogOpen) { + val key = + accountViewModel.account.settings.keyPair.privKey + ?.toHexKey() + ?.let { CryptoUtils.encryptNIP49(it, password.value.text) } + ShowKeyQRDialog( + key, + onClose = { dialogOpen = false }, + ) + } +} + @Composable private fun ShowKeyQRDialog( qrCode: String?, From 4f4da12aa7e229ff045bba12830ec07e9a120d7c Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Wed, 18 Dec 2024 23:06:37 +0100 Subject: [PATCH 7/8] refactor QrCodeButton methods to avoid duplications --- .../ui/screen/loggedIn/AccountBackupDialog.kt | 84 ++++++++----------- 1 file changed, 34 insertions(+), 50 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt index 40ec2ca58..503f54aaf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt @@ -100,6 +100,7 @@ import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow +import com.vitorpamplona.amethyst.ui.theme.grayText import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.encoders.toHexKey @@ -470,51 +471,10 @@ private fun encryptCopyNSec( } @Composable -private fun QrCodeButton(accountViewModel: AccountViewModel) { - val context = LocalContext.current - - // store the dialog open or close state - var dialogOpen by remember { mutableStateOf(false) } - - val keyguardLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> - if (result.resultCode == Activity.RESULT_OK) { - dialogOpen = true - } - } - - IconButton( - onClick = { - authenticate( - title = stringRes(context, R.string.copy_my_secret_key), - context = context, - keyguardLauncher = keyguardLauncher, - onApproved = { dialogOpen = true }, - onError = { title, message -> accountViewModel.toast(title, message) }, - ) - }, - ) { - Icon( - painter = painterResource(R.drawable.ic_qrcode), - contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), - modifier = Modifier.size(24.dp), - tint = MaterialTheme.colorScheme.primary, - ) - } - - if (dialogOpen) { - ShowKeyQRDialog( - accountViewModel.account.settings.keyPair.privKey - ?.toNsec(), - onClose = { dialogOpen = false }, - ) - } -} - -@Composable -private fun QrCodeButtonEncrypted( +private fun QrCodeButtonBase( accountViewModel: AccountViewModel, - password: MutableState, + isEnabled: Boolean = true, + onDialogShow: () -> String?, ) { val context = LocalContext.current @@ -529,7 +489,7 @@ private fun QrCodeButtonEncrypted( } IconButton( - enabled = password.value.text.isNotBlank(), + enabled = isEnabled, onClick = { authenticate( title = stringRes(context, R.string.copy_my_secret_key), @@ -544,21 +504,45 @@ private fun QrCodeButtonEncrypted( painter = painterResource(R.drawable.ic_qrcode), contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), modifier = Modifier.size(24.dp), + tint = if (isEnabled) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.grayText, ) } if (dialogOpen) { - val key = - accountViewModel.account.settings.keyPair.privKey - ?.toHexKey() - ?.let { CryptoUtils.encryptNIP49(it, password.value.text) } ShowKeyQRDialog( - key, + onDialogShow(), onClose = { dialogOpen = false }, ) } } +@Composable +private fun QrCodeButton(accountViewModel: AccountViewModel) { + QrCodeButtonBase( + accountViewModel = accountViewModel, + onDialogShow = { + accountViewModel.account.settings.keyPair.privKey + ?.toNsec() + }, + ) +} + +@Composable +private fun QrCodeButtonEncrypted( + accountViewModel: AccountViewModel, + password: MutableState, +) { + QrCodeButtonBase( + accountViewModel = accountViewModel, + isEnabled = password.value.text.isNotBlank(), + onDialogShow = { + accountViewModel.account.settings.keyPair.privKey + ?.toHexKey() + ?.let { CryptoUtils.encryptNIP49(it, password.value.text) } + }, + ) +} + @Composable private fun ShowKeyQRDialog( qrCode: String?, From 9f1b5a3c1aa67579ca0e27cf8049ca60735a8556 Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Wed, 18 Dec 2024 23:23:57 +0100 Subject: [PATCH 8/8] added content description to QR code icons --- .../amethyst/ui/screen/loggedIn/AccountBackupDialog.kt | 10 ++++++---- amethyst/src/main/res/values/strings.xml | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt index 503f54aaf..ef32ca148 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt @@ -128,8 +128,7 @@ fun DialogContentsPreview() { ThemeComparisonRow { DialogContents( mockAccountViewModel(), - {}, - ) + ) {} } } @@ -251,7 +250,7 @@ private fun DialogContents( }, keyboardOptions = KeyboardOptions( - autoCorrect = false, + autoCorrectEnabled = false, keyboardType = KeyboardType.Password, imeAction = ImeAction.Go, ), @@ -474,6 +473,7 @@ private fun encryptCopyNSec( private fun QrCodeButtonBase( accountViewModel: AccountViewModel, isEnabled: Boolean = true, + contentDescription: Int, onDialogShow: () -> String?, ) { val context = LocalContext.current @@ -502,7 +502,7 @@ private fun QrCodeButtonBase( ) { Icon( painter = painterResource(R.drawable.ic_qrcode), - contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code), + contentDescription = stringRes(id = contentDescription), modifier = Modifier.size(24.dp), tint = if (isEnabled) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.grayText, ) @@ -520,6 +520,7 @@ private fun QrCodeButtonBase( private fun QrCodeButton(accountViewModel: AccountViewModel) { QrCodeButtonBase( accountViewModel = accountViewModel, + contentDescription = R.string.show_private_key_qr_code, onDialogShow = { accountViewModel.account.settings.keyPair.privKey ?.toNsec() @@ -535,6 +536,7 @@ private fun QrCodeButtonEncrypted( QrCodeButtonBase( accountViewModel = accountViewModel, isEnabled = password.value.text.isNotBlank(), + contentDescription = R.string.show_encrypted_private_key_qr_code, onDialogShow = { accountViewModel.account.settings.keyPair.privKey ?.toHexKey() diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 139e40195..6c6ddaf51 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -153,6 +153,8 @@ Lightning Address Copies the Nsec ID (your password) to the clipboard for backup Copy Secret Key to the Clipboard + Show private key QR code + Show encrypted private key QR code Copies the public key to the clipboard for sharing Copy Public Key (NPub) to the Clipboard Send a Direct Message