Skip to content

Commit 354a903

Browse files
authored
android: make tailnet lock setup view focusable and clickable (#544)
-use a shared InteractionSource for focusing and clicking to ensure they rely on the same state and to coordinate so that visual feedback is shown on scroll without affecting the click InteractionSource -use LocalIndication to ensure that the click interaction maintains the visual feedback when combined with focusable -use onFocusChanged to explicitly track the focus state Updates tailscale/corp#21737 Signed-off-by: kari-ts <[email protected]>
1 parent 6ec5423 commit 354a903

File tree

2 files changed

+79
-64
lines changed

2 files changed

+79
-64
lines changed

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

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,59 @@
33

44
package com.tailscale.ipn.ui.util
55

6+
import androidx.compose.foundation.LocalIndication
7+
import androidx.compose.foundation.background
68
import androidx.compose.foundation.clickable
79
import androidx.compose.foundation.focusable
8-
import androidx.compose.foundation.layout.height
10+
import androidx.compose.foundation.interaction.MutableInteractionSource
911
import androidx.compose.foundation.layout.padding
10-
import androidx.compose.foundation.layout.width
12+
import androidx.compose.foundation.layout.size
1113
import androidx.compose.material3.Icon
1214
import androidx.compose.material3.ListItem
1315
import androidx.compose.material3.MaterialTheme
1416
import androidx.compose.material3.Text
1517
import androidx.compose.runtime.Composable
18+
import androidx.compose.runtime.mutableStateOf
19+
import androidx.compose.runtime.remember
1620
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.focus.onFocusChanged
22+
import androidx.compose.ui.graphics.Color
1723
import androidx.compose.ui.platform.LocalClipboardManager
1824
import androidx.compose.ui.res.painterResource
1925
import androidx.compose.ui.res.stringResource
2026
import androidx.compose.ui.text.AnnotatedString
2127
import androidx.compose.ui.unit.dp
2228
import com.tailscale.ipn.R
23-
import com.tailscale.ipn.ui.theme.titledListItem
2429

2530
@Composable
2631
fun ClipboardValueView(value: String, title: String? = null, subtitle: String? = null) {
27-
val localClipboardManager = LocalClipboardManager.current
28-
val modifier =
29-
Modifier.focusable()
30-
.clickable {
31-
localClipboardManager.setText(AnnotatedString(value))
32-
}
32+
val isFocused = remember { mutableStateOf(false) }
33+
val localClipboardManager = LocalClipboardManager.current
34+
val interactionSource = remember { MutableInteractionSource() }
3335

34-
ListItem(
35-
colors = MaterialTheme.colorScheme.titledListItem,
36-
modifier = modifier,
37-
overlineContent = title?.let { { Text(it, style = MaterialTheme.typography.titleMedium) } },
38-
headlineContent = { Text(text = value, style = MaterialTheme.typography.bodyMedium) },
39-
supportingContent =
40-
subtitle?.let {
41-
{
42-
Text(
43-
it,
44-
modifier = Modifier.padding(top = 8.dp),
45-
style = MaterialTheme.typography.bodyMedium)
46-
}
47-
},
48-
trailingContent = {
49-
Icon(
50-
painterResource(R.drawable.clipboard),
51-
stringResource(R.string.copy_to_clipboard),
52-
modifier = Modifier.width(24.dp).height(24.dp))
53-
})
54-
}
36+
ListItem(
37+
modifier = Modifier
38+
.focusable(interactionSource = interactionSource)
39+
.onFocusChanged { focusState -> isFocused.value = focusState.isFocused }
40+
.clickable(
41+
interactionSource = interactionSource,
42+
indication = LocalIndication.current
43+
) { localClipboardManager.setText(AnnotatedString(value)) }
44+
.background(
45+
if (isFocused.value) MaterialTheme.colorScheme.primary.copy(alpha = 0.12f)
46+
else Color.Transparent
47+
),
48+
overlineContent = title?.let { { Text(it, style = MaterialTheme.typography.titleMedium) } },
49+
headlineContent = { Text(text = value, style = MaterialTheme.typography.bodyMedium) },
50+
supportingContent = subtitle?.let {
51+
{ Text(it, modifier = Modifier.padding(top = 8.dp), style = MaterialTheme.typography.bodyMedium) }
52+
},
53+
trailingContent = {
54+
Icon(
55+
painterResource(R.drawable.clipboard),
56+
contentDescription = stringResource(R.string.copy_to_clipboard),
57+
modifier = Modifier.size(24.dp)
58+
)
59+
}
60+
)
61+
}

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

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33

44
package com.tailscale.ipn.ui.view
55

6+
import androidx.compose.foundation.LocalIndication
7+
import androidx.compose.foundation.clickable
68
import androidx.compose.foundation.focusable
7-
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.interaction.MutableInteractionSource
810
import androidx.compose.foundation.layout.fillMaxSize
911
import androidx.compose.foundation.layout.padding
10-
import androidx.compose.foundation.rememberScrollState
12+
import androidx.compose.foundation.lazy.LazyColumn
13+
import androidx.compose.foundation.lazy.items
1114
import androidx.compose.foundation.text.ClickableText
12-
import androidx.compose.foundation.verticalScroll
1315
import androidx.compose.material3.Icon
1416
import androidx.compose.material3.ListItem
1517
import androidx.compose.material3.MaterialTheme
@@ -18,7 +20,9 @@ import androidx.compose.material3.Text
1820
import androidx.compose.runtime.Composable
1921
import androidx.compose.runtime.collectAsState
2022
import androidx.compose.runtime.getValue
23+
import androidx.compose.runtime.remember
2124
import androidx.compose.ui.Modifier
25+
import androidx.compose.ui.focus.onFocusChanged
2226
import androidx.compose.ui.platform.LocalUriHandler
2327
import androidx.compose.ui.res.painterResource
2428
import androidx.compose.ui.res.stringResource
@@ -52,40 +56,44 @@ fun TailnetLockSetupView(
5256

5357
Scaffold(topBar = { Header(R.string.tailnet_lock, onBack = backToSettings) }) { innerPadding ->
5458
LoadingIndicator.Wrap {
55-
Column(
56-
modifier =
57-
Modifier.padding(innerPadding)
58-
.focusable()
59-
.verticalScroll(rememberScrollState())
60-
.fillMaxSize()) {
61-
ExplainerView()
59+
LazyColumn(modifier = Modifier.padding(innerPadding).fillMaxSize()) {
60+
item { ExplainerView() }
6261

63-
statusItems.forEach { statusItem ->
64-
Lists.ItemDivider()
62+
items(statusItems) { statusItem ->
63+
val interactionSource = remember { MutableInteractionSource() }
64+
ListItem(
65+
modifier =
66+
Modifier.focusable(
67+
interactionSource = interactionSource)
68+
.clickable(
69+
interactionSource = interactionSource,
70+
indication = LocalIndication.current
71+
) {},
72+
leadingContent = {
73+
Icon(
74+
painter = painterResource(id = statusItem.icon),
75+
contentDescription = null,
76+
tint = MaterialTheme.colorScheme.onSurfaceVariant)
77+
},
78+
headlineContent = { Text(stringResource(statusItem.title)) })
79+
}
6580

66-
ListItem(
67-
leadingContent = {
68-
Icon(
69-
painter = painterResource(id = statusItem.icon),
70-
contentDescription = null,
71-
tint = MaterialTheme.colorScheme.onSurfaceVariant)
72-
},
73-
headlineContent = { Text(stringResource(statusItem.title)) })
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+
item {
82+
// Node key section
83+
Lists.SectionDivider()
84+
ClipboardValueView(
85+
value = nodeKey,
86+
title = stringResource(R.string.node_key),
87+
subtitle = stringResource(R.string.node_key_explainer))
8188

82-
// Tailnet lock key
83-
Lists.SectionDivider()
84-
ClipboardValueView(
85-
value = tailnetLockTlPubKey,
86-
title = stringResource(R.string.tailnet_lock_key),
87-
subtitle = stringResource(R.string.tailnet_lock_key_explainer))
88-
}
89+
// Tailnet lock key section
90+
Lists.SectionDivider()
91+
ClipboardValueView(
92+
value = tailnetLockTlPubKey,
93+
title = stringResource(R.string.tailnet_lock_key),
94+
subtitle = stringResource(R.string.tailnet_lock_key_explainer))
95+
}
96+
}
8997
}
9098
}
9199
}

0 commit comments

Comments
 (0)