From b4ca226eb78bd7dc55b85ecc3bdd53ca8d0cf3c1 Mon Sep 17 00:00:00 2001 From: kari-ts <135075563+kari-ts@users.noreply.github.com> Date: Thu, 29 Aug 2024 12:37:10 -0700 Subject: [PATCH] android: make clipboard values clickable and focusable (#483) also, use Column isntead of LazyColumn since the Tailnet lock view is a short list and doesn't require lazy rendering Fixes tailscale/corp#21737 Signed-off-by: kari-ts --- .../ipn/ui/util/ClipboardValueView.kt | 11 +- .../ipn/ui/view/TailnetLockSetupView.kt | 139 +++++++++--------- 2 files changed, 78 insertions(+), 72 deletions(-) diff --git a/android/src/main/java/com/tailscale/ipn/ui/util/ClipboardValueView.kt b/android/src/main/java/com/tailscale/ipn/ui/util/ClipboardValueView.kt index 79a5c34..90e7b79 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/util/ClipboardValueView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/util/ClipboardValueView.kt @@ -4,6 +4,7 @@ package com.tailscale.ipn.ui.util import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width @@ -20,17 +21,15 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp import com.tailscale.ipn.R import com.tailscale.ipn.ui.theme.titledListItem -import com.tailscale.ipn.ui.util.AndroidTVUtil.isAndroidTV @Composable fun ClipboardValueView(value: String, title: String? = null, subtitle: String? = null) { val localClipboardManager = LocalClipboardManager.current val modifier = - if (isAndroidTV()) { - Modifier - } else { - Modifier.clickable { localClipboardManager.setText(AnnotatedString(value)) } - } + Modifier.focusable() + .clickable { + localClipboardManager.setText(AnnotatedString(value)) + } ListItem( colors = MaterialTheme.colorScheme.titledListItem, diff --git a/android/src/main/java/com/tailscale/ipn/ui/view/TailnetLockSetupView.kt b/android/src/main/java/com/tailscale/ipn/ui/view/TailnetLockSetupView.kt index a4ff740..b1a4074 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/view/TailnetLockSetupView.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/view/TailnetLockSetupView.kt @@ -42,89 +42,96 @@ fun TailnetLockSetupView( backToSettings: BackNavigation, model: TailnetLockSetupViewModel = viewModel(factory = TailnetLockSetupViewModelFactory()) ) { - val statusItems by model.statusItems.collectAsState() - val nodeKey by model.nodeKey.collectAsState() - val tailnetLockKey by model.tailnetLockKey.collectAsState() - val tailnetLockTlPubKey = tailnetLockKey.replace("nlpub", "tlpub") - - Scaffold(topBar = { Header(R.string.tailnet_lock, onBack = backToSettings) }) { innerPadding -> - LoadingIndicator.Wrap { - LazyColumn(modifier = Modifier.padding(innerPadding)) { - item(key = "header") { ExplainerView() } - - items(items = statusItems, key = { "status_${it.title}" }) { statusItem -> - Lists.ItemDivider() - - ListItem( - leadingContent = { - Icon( - painter = painterResource(id = statusItem.icon), - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant) - }, - headlineContent = { Text(stringResource(statusItem.title)) }) - } - - item(key = "nodeKey") { - Lists.SectionDivider() - - ClipboardValueView( - value = nodeKey, - title = stringResource(R.string.node_key), - subtitle = stringResource(R.string.node_key_explainer)) - } - - item(key = "tailnetLockKey") { - Lists.SectionDivider() - - ClipboardValueView( - value = tailnetLockTlPubKey, - title = stringResource(R.string.tailnet_lock_key), - subtitle = stringResource(R.string.tailnet_lock_key_explainer)) + val statusItems by model.statusItems.collectAsState() + val nodeKey by model.nodeKey.collectAsState() + val tailnetLockKey by model.tailnetLockKey.collectAsState() + val tailnetLockTlPubKey = tailnetLockKey.replace("nlpub", "tlpub") + + Scaffold(topBar = { Header(R.string.tailnet_lock, onBack = backToSettings) }) { innerPadding -> + LoadingIndicator.Wrap { + Column( + modifier = Modifier + .padding(innerPadding) + .focusable() + .verticalScroll(rememberScrollState()) + .fillMaxSize() + ) { + ExplainerView() + + statusItems.forEach { statusItem -> + Lists.ItemDivider() + + ListItem( + leadingContent = { + Icon( + painter = painterResource(id = statusItem.icon), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + }, + headlineContent = { Text(stringResource(statusItem.title)) } + ) + } + //Node key + Lists.SectionDivider() + ClipboardValueView( + value = nodeKey, + title = stringResource(R.string.node_key), + subtitle = stringResource(R.string.node_key_explainer) + ) + + // Tailnet lock key + Lists.SectionDivider() + ClipboardValueView( + value = tailnetLockTlPubKey, + title = stringResource(R.string.tailnet_lock_key), + subtitle = stringResource(R.string.tailnet_lock_key_explainer) + ) + } } - } } - } } @Composable private fun ExplainerView() { - val handler = LocalUriHandler.current - - Lists.MultilineDescription { - ClickableText( - explainerText(), - onClick = { handler.openUri(Links.TAILNET_LOCK_KB_URL) }, - style = MaterialTheme.typography.bodyMedium) - } + val handler = LocalUriHandler.current + + Lists.MultilineDescription { + ClickableText( + explainerText(), + onClick = { handler.openUri(Links.TAILNET_LOCK_KB_URL) }, + style = MaterialTheme.typography.bodyMedium + ) + } } @Composable fun explainerText(): AnnotatedString { - val annotatedString = buildAnnotatedString { - withStyle(SpanStyle(color = MaterialTheme.colorScheme.defaultTextColor)) { - append(stringResource(id = R.string.tailnet_lock_explainer)) - } + val annotatedString = buildAnnotatedString { + withStyle(SpanStyle(color = MaterialTheme.colorScheme.defaultTextColor)) { + append(stringResource(id = R.string.tailnet_lock_explainer)) + } - pushStringAnnotation(tag = "tailnetLockSupportURL", annotation = Links.TAILNET_LOCK_KB_URL) + pushStringAnnotation(tag = "tailnetLockSupportURL", annotation = Links.TAILNET_LOCK_KB_URL) - withStyle( - style = - SpanStyle( + withStyle( + style = SpanStyle( color = MaterialTheme.colorScheme.link, - textDecoration = TextDecoration.Underline)) { - append(stringResource(id = R.string.learn_more)) + textDecoration = TextDecoration.Underline + ) + ) { + append(stringResource(id = R.string.learn_more)) } - pop() - } - return annotatedString + pop() + } + return annotatedString } @Composable @Preview fun TailnetLockSetupViewPreview() { - val vm = TailnetLockSetupViewModel() - vm.nodeKey.set("8BADF00D-EA7-1337-DEAD-BEEF") - vm.tailnetLockKey.set("C0FFEE-CAFE-50DA") - TailnetLockSetupView(backToSettings = {}, vm) + val vm = TailnetLockSetupViewModel() + vm.nodeKey.set("8BADF00D-EA7-1337-DEAD-BEEF") + vm.tailnetLockKey.set("C0FFEE-CAFE-50DA") + TailnetLockSetupView(backToSettings = {}, vm) }