Skip to content

Commit d911b89

Browse files
authored
android: allow HealthView banner content to be copied (#829)
Health warnings can be long-pressed to copy the full message to the clipboard. updates tailscale/corp#45431 Signed-off-by: Will Hannah <willh@tailscale.com>
1 parent 11da0cc commit d911b89

5 files changed

Lines changed: 55 additions & 1 deletion

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,6 @@ libtailscale-sources.jar
5454
.DS_Store
5555

5656
tailscale.version
57+
58+
# local tmp folder
59+
.tmp/

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ release-tv: jarsign-env $(RELEASE_TV_AAB)
158158

159159
# gradle-dependencies groups together the android sources and libtailscale needed to assemble tests/debug/release builds.
160160
.PHONY: gradle-dependencies
161-
gradle-dependencies: $(shell find android -type f -not -path "android/build/*" -not -path '*/.*') $(LIBTAILSCALE_AAR) tailscale.version
161+
gradle-dependencies: $(shell find android -type f -not -path "android/build/*" -not -path "android/libs/*" -not -path '*/.*') $(LIBTAILSCALE_AAR) tailscale.version
162162

163163
$(RELEASE_AAB): version gradle-dependencies
164164
@echo "Building release AAB"

android/src/main/java/com/tailscale/ipn/ui/model/Health.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ class Health {
3434
} == true
3535
}
3636

37+
/** Hydrated value copied to the clipboard for debugging. */
38+
val clipboardText: String
39+
get() = "$WarnableCode: $Title" + if (Text.isNotEmpty()) "\n$Text" else ""
40+
3741
override fun compareTo(other: UnhealthyState): Int {
3842
// Compare by severity first
3943
val severityComparison = other.Severity.compareTo(Severity)

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

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

6+
import android.os.Build
7+
import android.widget.Toast
8+
import androidx.compose.foundation.ExperimentalFoundationApi
69
import androidx.compose.foundation.background
10+
import androidx.compose.foundation.combinedClickable
711
import androidx.compose.foundation.layout.Arrangement
812
import androidx.compose.foundation.layout.Box
913
import androidx.compose.foundation.layout.Column
@@ -13,6 +17,8 @@ import androidx.compose.foundation.layout.size
1317
import androidx.compose.foundation.lazy.LazyColumn
1418
import androidx.compose.foundation.lazy.items
1519
import androidx.compose.foundation.shape.RoundedCornerShape
20+
import androidx.compose.material3.DropdownMenu
21+
import androidx.compose.material3.DropdownMenuItem
1622
import androidx.compose.material3.Icon
1723
import androidx.compose.material3.ListItem
1824
import androidx.compose.material3.MaterialTheme
@@ -21,16 +27,23 @@ import androidx.compose.material3.Text
2127
import androidx.compose.runtime.Composable
2228
import androidx.compose.runtime.collectAsState
2329
import androidx.compose.runtime.getValue
30+
import androidx.compose.runtime.mutableStateOf
31+
import androidx.compose.runtime.remember
32+
import androidx.compose.runtime.setValue
2433
import androidx.compose.ui.Alignment
2534
import androidx.compose.ui.Modifier
2635
import androidx.compose.ui.draw.clip
36+
import androidx.compose.ui.platform.LocalClipboardManager
37+
import androidx.compose.ui.platform.LocalContext
2738
import androidx.compose.ui.res.painterResource
2839
import androidx.compose.ui.res.stringResource
40+
import androidx.compose.ui.text.AnnotatedString
2941
import androidx.compose.ui.unit.dp
3042
import androidx.lifecycle.viewmodel.compose.viewModel
3143
import com.tailscale.ipn.R
3244
import com.tailscale.ipn.ui.model.Health
3345
import com.tailscale.ipn.ui.theme.success
46+
import com.tailscale.ipn.ui.util.AndroidTVUtil.isAndroidTV
3447
import com.tailscale.ipn.ui.viewModel.HealthViewModel
3548

3649
@Composable
@@ -73,15 +86,30 @@ fun HealthView(backToSettings: BackNavigation, model: HealthViewModel = viewMode
7386
}
7487
}
7588

89+
@OptIn(ExperimentalFoundationApi::class)
7690
@Composable
7791
fun HealthWarningView(warning: Health.UnhealthyState) {
92+
val localClipboardManager = LocalClipboardManager.current
93+
val context = LocalContext.current
94+
val copiedText = stringResource(R.string.copied)
95+
var menuExpanded by remember { mutableStateOf(false) }
96+
97+
// Android TV has no clipboard.
98+
val itemModifier =
99+
if (isAndroidTV()) {
100+
Modifier
101+
} else {
102+
Modifier.combinedClickable(onClick = {}, onLongClick = { menuExpanded = true })
103+
}
104+
78105
Box(modifier = Modifier.background(color = MaterialTheme.colorScheme.surfaceContainerLow)) {
79106
Box(
80107
modifier =
81108
Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp)
82109
.clip(shape = RoundedCornerShape(10.dp, 10.dp, 10.dp, 10.dp))
83110
.fillMaxWidth()) {
84111
ListItem(
112+
modifier = itemModifier,
85113
colors = warning.Severity.listItemColors(),
86114
headlineContent = {
87115
if (warning.Title.isNotEmpty()) {
@@ -94,6 +122,23 @@ fun HealthWarningView(warning: Health.UnhealthyState) {
94122
supportingContent = {
95123
Text(warning.Text, style = MaterialTheme.typography.bodyMedium)
96124
})
125+
126+
// Copy for now; KB-page links to follow.
127+
DropdownMenu(expanded = menuExpanded, onDismissRequest = { menuExpanded = false }) {
128+
DropdownMenuItem(
129+
leadingIcon = {
130+
Icon(painter = painterResource(R.drawable.clipboard), contentDescription = null)
131+
},
132+
text = { Text(text = stringResource(R.string.copy)) },
133+
onClick = {
134+
localClipboardManager.setText(AnnotatedString(warning.clipboardText))
135+
// Android 13+ shows its own copy confirmation.
136+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
137+
Toast.makeText(context, copiedText, Toast.LENGTH_SHORT).show()
138+
}
139+
menuExpanded = false
140+
})
141+
}
97142
}
98143
}
99144
}

android/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@
178178
<string name="this_node_is_trusted">This node is trusted to change the Tailnet lock configuration.</string>
179179
<string name="this_node_is_not_trusted">This node is not trusted to change the Tailnet lock configuration.</string>
180180
<string name="copy_to_clipboard">Copy to clipboard</string>
181+
<string name="copy">Copy</string>
182+
<string name="copied">Copied</string>
181183
<string name="node_key">Node key</string>
182184
<string name="tailnet_lock_key">Tailnet lock key</string>
183185
<string name="node_key_explainer">Used to sign this node from another signing device in your tailnet.</string>

0 commit comments

Comments
 (0)