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