Skip to content

Commit b4ca226

Browse files
authored
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 <[email protected]>
1 parent d94125e commit b4ca226

File tree

2 files changed

+78
-72
lines changed

2 files changed

+78
-72
lines changed

android/src/main/java/com/tailscale/ipn/ui/util/ClipboardValueView.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package com.tailscale.ipn.ui.util
55

66
import androidx.compose.foundation.clickable
7+
import androidx.compose.foundation.focusable
78
import androidx.compose.foundation.layout.height
89
import androidx.compose.foundation.layout.padding
910
import androidx.compose.foundation.layout.width
@@ -20,17 +21,15 @@ import androidx.compose.ui.text.AnnotatedString
2021
import androidx.compose.ui.unit.dp
2122
import com.tailscale.ipn.R
2223
import com.tailscale.ipn.ui.theme.titledListItem
23-
import com.tailscale.ipn.ui.util.AndroidTVUtil.isAndroidTV
2424

2525
@Composable
2626
fun ClipboardValueView(value: String, title: String? = null, subtitle: String? = null) {
2727
val localClipboardManager = LocalClipboardManager.current
2828
val modifier =
29-
if (isAndroidTV()) {
30-
Modifier
31-
} else {
32-
Modifier.clickable { localClipboardManager.setText(AnnotatedString(value)) }
33-
}
29+
Modifier.focusable()
30+
.clickable {
31+
localClipboardManager.setText(AnnotatedString(value))
32+
}
3433

3534
ListItem(
3635
colors = MaterialTheme.colorScheme.titledListItem,

android/src/main/java/com/tailscale/ipn/ui/view/TailnetLockSetupView.kt

Lines changed: 73 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -42,89 +42,96 @@ fun TailnetLockSetupView(
4242
backToSettings: BackNavigation,
4343
model: TailnetLockSetupViewModel = viewModel(factory = TailnetLockSetupViewModelFactory())
4444
) {
45-
val statusItems by model.statusItems.collectAsState()
46-
val nodeKey by model.nodeKey.collectAsState()
47-
val tailnetLockKey by model.tailnetLockKey.collectAsState()
48-
val tailnetLockTlPubKey = tailnetLockKey.replace("nlpub", "tlpub")
49-
50-
Scaffold(topBar = { Header(R.string.tailnet_lock, onBack = backToSettings) }) { innerPadding ->
51-
LoadingIndicator.Wrap {
52-
LazyColumn(modifier = Modifier.padding(innerPadding)) {
53-
item(key = "header") { ExplainerView() }
54-
55-
items(items = statusItems, key = { "status_${it.title}" }) { statusItem ->
56-
Lists.ItemDivider()
57-
58-
ListItem(
59-
leadingContent = {
60-
Icon(
61-
painter = painterResource(id = statusItem.icon),
62-
contentDescription = null,
63-
tint = MaterialTheme.colorScheme.onSurfaceVariant)
64-
},
65-
headlineContent = { Text(stringResource(statusItem.title)) })
66-
}
67-
68-
item(key = "nodeKey") {
69-
Lists.SectionDivider()
70-
71-
ClipboardValueView(
72-
value = nodeKey,
73-
title = stringResource(R.string.node_key),
74-
subtitle = stringResource(R.string.node_key_explainer))
75-
}
76-
77-
item(key = "tailnetLockKey") {
78-
Lists.SectionDivider()
79-
80-
ClipboardValueView(
81-
value = tailnetLockTlPubKey,
82-
title = stringResource(R.string.tailnet_lock_key),
83-
subtitle = stringResource(R.string.tailnet_lock_key_explainer))
45+
val statusItems by model.statusItems.collectAsState()
46+
val nodeKey by model.nodeKey.collectAsState()
47+
val tailnetLockKey by model.tailnetLockKey.collectAsState()
48+
val tailnetLockTlPubKey = tailnetLockKey.replace("nlpub", "tlpub")
49+
50+
Scaffold(topBar = { Header(R.string.tailnet_lock, onBack = backToSettings) }) { innerPadding ->
51+
LoadingIndicator.Wrap {
52+
Column(
53+
modifier = Modifier
54+
.padding(innerPadding)
55+
.focusable()
56+
.verticalScroll(rememberScrollState())
57+
.fillMaxSize()
58+
) {
59+
ExplainerView()
60+
61+
statusItems.forEach { statusItem ->
62+
Lists.ItemDivider()
63+
64+
ListItem(
65+
leadingContent = {
66+
Icon(
67+
painter = painterResource(id = statusItem.icon),
68+
contentDescription = null,
69+
tint = MaterialTheme.colorScheme.onSurfaceVariant
70+
)
71+
},
72+
headlineContent = { Text(stringResource(statusItem.title)) }
73+
)
74+
}
75+
//Node key
76+
Lists.SectionDivider()
77+
ClipboardValueView(
78+
value = nodeKey,
79+
title = stringResource(R.string.node_key),
80+
subtitle = stringResource(R.string.node_key_explainer)
81+
)
82+
83+
// Tailnet lock key
84+
Lists.SectionDivider()
85+
ClipboardValueView(
86+
value = tailnetLockTlPubKey,
87+
title = stringResource(R.string.tailnet_lock_key),
88+
subtitle = stringResource(R.string.tailnet_lock_key_explainer)
89+
)
90+
}
8491
}
85-
}
8692
}
87-
}
8893
}
8994

9095
@Composable
9196
private fun ExplainerView() {
92-
val handler = LocalUriHandler.current
93-
94-
Lists.MultilineDescription {
95-
ClickableText(
96-
explainerText(),
97-
onClick = { handler.openUri(Links.TAILNET_LOCK_KB_URL) },
98-
style = MaterialTheme.typography.bodyMedium)
99-
}
97+
val handler = LocalUriHandler.current
98+
99+
Lists.MultilineDescription {
100+
ClickableText(
101+
explainerText(),
102+
onClick = { handler.openUri(Links.TAILNET_LOCK_KB_URL) },
103+
style = MaterialTheme.typography.bodyMedium
104+
)
105+
}
100106
}
101107

102108
@Composable
103109
fun explainerText(): AnnotatedString {
104-
val annotatedString = buildAnnotatedString {
105-
withStyle(SpanStyle(color = MaterialTheme.colorScheme.defaultTextColor)) {
106-
append(stringResource(id = R.string.tailnet_lock_explainer))
107-
}
110+
val annotatedString = buildAnnotatedString {
111+
withStyle(SpanStyle(color = MaterialTheme.colorScheme.defaultTextColor)) {
112+
append(stringResource(id = R.string.tailnet_lock_explainer))
113+
}
108114

109-
pushStringAnnotation(tag = "tailnetLockSupportURL", annotation = Links.TAILNET_LOCK_KB_URL)
115+
pushStringAnnotation(tag = "tailnetLockSupportURL", annotation = Links.TAILNET_LOCK_KB_URL)
110116

111-
withStyle(
112-
style =
113-
SpanStyle(
117+
withStyle(
118+
style = SpanStyle(
114119
color = MaterialTheme.colorScheme.link,
115-
textDecoration = TextDecoration.Underline)) {
116-
append(stringResource(id = R.string.learn_more))
120+
textDecoration = TextDecoration.Underline
121+
)
122+
) {
123+
append(stringResource(id = R.string.learn_more))
117124
}
118-
pop()
119-
}
120-
return annotatedString
125+
pop()
126+
}
127+
return annotatedString
121128
}
122129

123130
@Composable
124131
@Preview
125132
fun TailnetLockSetupViewPreview() {
126-
val vm = TailnetLockSetupViewModel()
127-
vm.nodeKey.set("8BADF00D-EA7-1337-DEAD-BEEF")
128-
vm.tailnetLockKey.set("C0FFEE-CAFE-50DA")
129-
TailnetLockSetupView(backToSettings = {}, vm)
133+
val vm = TailnetLockSetupViewModel()
134+
vm.nodeKey.set("8BADF00D-EA7-1337-DEAD-BEEF")
135+
vm.tailnetLockKey.set("C0FFEE-CAFE-50DA")
136+
TailnetLockSetupView(backToSettings = {}, vm)
130137
}

0 commit comments

Comments
 (0)