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 <kari@tailscale.com>
pull/486/head^2
kari-ts 3 months ago committed by GitHub
parent d94125e767
commit b4ca226eb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -4,6 +4,7 @@
package com.tailscale.ipn.ui.util package com.tailscale.ipn.ui.util
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
@ -20,17 +21,15 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.tailscale.ipn.R import com.tailscale.ipn.R
import com.tailscale.ipn.ui.theme.titledListItem import com.tailscale.ipn.ui.theme.titledListItem
import com.tailscale.ipn.ui.util.AndroidTVUtil.isAndroidTV
@Composable @Composable
fun ClipboardValueView(value: String, title: String? = null, subtitle: String? = null) { fun ClipboardValueView(value: String, title: String? = null, subtitle: String? = null) {
val localClipboardManager = LocalClipboardManager.current val localClipboardManager = LocalClipboardManager.current
val modifier = val modifier =
if (isAndroidTV()) { Modifier.focusable()
Modifier .clickable {
} else { localClipboardManager.setText(AnnotatedString(value))
Modifier.clickable { localClipboardManager.setText(AnnotatedString(value)) } }
}
ListItem( ListItem(
colors = MaterialTheme.colorScheme.titledListItem, colors = MaterialTheme.colorScheme.titledListItem,

@ -42,89 +42,96 @@ fun TailnetLockSetupView(
backToSettings: BackNavigation, backToSettings: BackNavigation,
model: TailnetLockSetupViewModel = viewModel(factory = TailnetLockSetupViewModelFactory()) model: TailnetLockSetupViewModel = viewModel(factory = TailnetLockSetupViewModelFactory())
) { ) {
val statusItems by model.statusItems.collectAsState() val statusItems by model.statusItems.collectAsState()
val nodeKey by model.nodeKey.collectAsState() val nodeKey by model.nodeKey.collectAsState()
val tailnetLockKey by model.tailnetLockKey.collectAsState() val tailnetLockKey by model.tailnetLockKey.collectAsState()
val tailnetLockTlPubKey = tailnetLockKey.replace("nlpub", "tlpub") val tailnetLockTlPubKey = tailnetLockKey.replace("nlpub", "tlpub")
Scaffold(topBar = { Header(R.string.tailnet_lock, onBack = backToSettings) }) { innerPadding -> Scaffold(topBar = { Header(R.string.tailnet_lock, onBack = backToSettings) }) { innerPadding ->
LoadingIndicator.Wrap { LoadingIndicator.Wrap {
LazyColumn(modifier = Modifier.padding(innerPadding)) { Column(
item(key = "header") { ExplainerView() } modifier = Modifier
.padding(innerPadding)
items(items = statusItems, key = { "status_${it.title}" }) { statusItem -> .focusable()
Lists.ItemDivider() .verticalScroll(rememberScrollState())
.fillMaxSize()
ListItem( ) {
leadingContent = { ExplainerView()
Icon(
painter = painterResource(id = statusItem.icon), statusItems.forEach { statusItem ->
contentDescription = null, Lists.ItemDivider()
tint = MaterialTheme.colorScheme.onSurfaceVariant)
}, ListItem(
headlineContent = { Text(stringResource(statusItem.title)) }) leadingContent = {
} Icon(
painter = painterResource(id = statusItem.icon),
item(key = "nodeKey") { contentDescription = null,
Lists.SectionDivider() tint = MaterialTheme.colorScheme.onSurfaceVariant
)
ClipboardValueView( },
value = nodeKey, headlineContent = { Text(stringResource(statusItem.title)) }
title = stringResource(R.string.node_key), )
subtitle = stringResource(R.string.node_key_explainer)) }
} //Node key
Lists.SectionDivider()
item(key = "tailnetLockKey") { ClipboardValueView(
Lists.SectionDivider() value = nodeKey,
title = stringResource(R.string.node_key),
ClipboardValueView( subtitle = stringResource(R.string.node_key_explainer)
value = tailnetLockTlPubKey, )
title = stringResource(R.string.tailnet_lock_key),
subtitle = stringResource(R.string.tailnet_lock_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 @Composable
private fun ExplainerView() { private fun ExplainerView() {
val handler = LocalUriHandler.current val handler = LocalUriHandler.current
Lists.MultilineDescription { Lists.MultilineDescription {
ClickableText( ClickableText(
explainerText(), explainerText(),
onClick = { handler.openUri(Links.TAILNET_LOCK_KB_URL) }, onClick = { handler.openUri(Links.TAILNET_LOCK_KB_URL) },
style = MaterialTheme.typography.bodyMedium) style = MaterialTheme.typography.bodyMedium
} )
}
} }
@Composable @Composable
fun explainerText(): AnnotatedString { fun explainerText(): AnnotatedString {
val annotatedString = buildAnnotatedString { val annotatedString = buildAnnotatedString {
withStyle(SpanStyle(color = MaterialTheme.colorScheme.defaultTextColor)) { withStyle(SpanStyle(color = MaterialTheme.colorScheme.defaultTextColor)) {
append(stringResource(id = R.string.tailnet_lock_explainer)) 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( withStyle(
style = style = SpanStyle(
SpanStyle(
color = MaterialTheme.colorScheme.link, color = MaterialTheme.colorScheme.link,
textDecoration = TextDecoration.Underline)) { textDecoration = TextDecoration.Underline
append(stringResource(id = R.string.learn_more)) )
) {
append(stringResource(id = R.string.learn_more))
} }
pop() pop()
} }
return annotatedString return annotatedString
} }
@Composable @Composable
@Preview @Preview
fun TailnetLockSetupViewPreview() { fun TailnetLockSetupViewPreview() {
val vm = TailnetLockSetupViewModel() val vm = TailnetLockSetupViewModel()
vm.nodeKey.set("8BADF00D-EA7-1337-DEAD-BEEF") vm.nodeKey.set("8BADF00D-EA7-1337-DEAD-BEEF")
vm.tailnetLockKey.set("C0FFEE-CAFE-50DA") vm.tailnetLockKey.set("C0FFEE-CAFE-50DA")
TailnetLockSetupView(backToSettings = {}, vm) TailnetLockSetupView(backToSettings = {}, vm)
} }

Loading…
Cancel
Save