fix(ui): finish accessibility roles and action labels for clickable surfaces (#5170)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
James Rich 2026-04-17 12:33:38 -05:00 committed by GitHub
parent 56cbc3670d
commit dd74e501f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 75 additions and 10 deletions

View file

@ -1276,4 +1276,12 @@
<string name="filter_icon">Filter</string>
<string name="remove_filter">Remove filter</string>
<string name="show_iaq_legend">Show air quality legend</string>
<string name="action_show_message_status">Show message status</string>
<string name="action_send_reply">Send reply</string>
<string name="action_copy_message">Copy message</string>
<string name="action_select_message">Select message</string>
<string name="action_delete_message">Delete message</string>
<string name="action_react_with_emoji">React with emoji</string>
<string name="action_select_device">Select device</string>
<string name="action_select_network">Select network</string>
</resources>

View file

@ -17,13 +17,13 @@
package org.meshtastic.feature.connections.ui.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.selection.selectable
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
@ -41,11 +41,15 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.action_select_device
import org.meshtastic.core.resources.add
import org.meshtastic.core.resources.bluetooth
import org.meshtastic.core.resources.network
@ -108,11 +112,19 @@ fun DeviceListItem(
is DeviceListEntry.Mock -> stringResource(Res.string.add)
}
val selectLabel = stringResource(Res.string.action_select_device)
val isSelected = connectionState is ConnectionState.Connected
val clickableModifier =
if (onDelete != null) {
Modifier.combinedClickable(onClick = onSelect, onLongClick = onDelete)
Modifier.semantics { selected = isSelected }
.combinedClickable(
onClickLabel = selectLabel,
role = Role.RadioButton,
onClick = onSelect,
onLongClick = onDelete,
)
} else {
Modifier.clickable(onClick = onSelect)
Modifier.selectable(selected = isSelected, role = Role.RadioButton, onClick = onSelect)
}
ListItem(

View file

@ -36,11 +36,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.action_copy_message
import org.meshtastic.core.resources.action_delete_message
import org.meshtastic.core.resources.action_react_with_emoji
import org.meshtastic.core.resources.action_select_message
import org.meshtastic.core.resources.action_send_reply
import org.meshtastic.core.resources.action_show_message_status
import org.meshtastic.core.resources.copy
import org.meshtastic.core.resources.delete
import org.meshtastic.core.resources.device_metrics_label_value
@ -55,6 +62,7 @@ import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.Reply
import org.meshtastic.core.ui.icon.SelectAll
@Suppress("LongMethod")
@Composable
fun MessageActionsContent(
quickEmojis: List<String>,
@ -83,20 +91,35 @@ fun MessageActionsContent(
Text(stringResource(Res.string.device_metrics_label_value, title, statusText.orEmpty()))
},
leadingContent = { MessageStatusIcon(status = status) },
modifier = Modifier.clickable(onClick = onStatus),
modifier =
Modifier.clickable(
onClickLabel = stringResource(Res.string.action_show_message_status),
role = Role.Button,
onClick = onStatus,
),
)
}
ListItem(
headlineContent = { Text(stringResource(Res.string.reply)) },
leadingContent = { Icon(MeshtasticIcons.Reply, contentDescription = stringResource(Res.string.reply)) },
modifier = Modifier.clickable(onClick = onReply),
modifier =
Modifier.clickable(
onClickLabel = stringResource(Res.string.action_send_reply),
role = Role.Button,
onClick = onReply,
),
)
ListItem(
headlineContent = { Text(stringResource(Res.string.copy)) },
leadingContent = { Icon(MeshtasticIcons.Copy, contentDescription = stringResource(Res.string.copy)) },
modifier = Modifier.clickable(onClick = onCopy),
modifier =
Modifier.clickable(
onClickLabel = stringResource(Res.string.action_copy_message),
role = Role.Button,
onClick = onCopy,
),
)
ListItem(
@ -104,13 +127,23 @@ fun MessageActionsContent(
leadingContent = {
Icon(MeshtasticIcons.SelectAll, contentDescription = stringResource(Res.string.select))
},
modifier = Modifier.clickable(onClick = onSelect),
modifier =
Modifier.clickable(
onClickLabel = stringResource(Res.string.action_select_message),
role = Role.Button,
onClick = onSelect,
),
)
ListItem(
headlineContent = { Text(stringResource(Res.string.delete)) },
leadingContent = { Icon(MeshtasticIcons.Delete, contentDescription = stringResource(Res.string.delete)) },
modifier = Modifier.clickable(onClick = onDelete),
modifier =
Modifier.clickable(
onClickLabel = stringResource(Res.string.action_delete_message),
role = Role.Button,
onClick = onDelete,
),
)
}
}
@ -130,7 +163,12 @@ private fun QuickEmojiRow(quickEmojis: List<String>, onReact: (String) -> Unit,
Modifier.size(40.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.surfaceVariant)
.clickable { onReact(emoji) },
.clickable(
onClickLabel = stringResource(Res.string.action_react_with_emoji),
role = Role.Button,
) {
onReact(emoji)
},
contentAlignment = Alignment.Center,
) {
Text(text = emoji, style = MaterialTheme.typography.titleMedium)

View file

@ -76,6 +76,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
@ -87,6 +88,7 @@ import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.action_select_network
import org.meshtastic.core.resources.apply
import org.meshtastic.core.resources.back
import org.meshtastic.core.resources.cancel
@ -489,7 +491,12 @@ internal fun NetworkRow(network: WifiNetwork, isSelected: Boolean, onClick: () -
}
},
colors = ListItemDefaults.colors(containerColor = containerColor),
modifier = Modifier.clickable(onClick = onClick),
modifier =
Modifier.clickable(
onClickLabel = stringResource(Res.string.action_select_network),
role = Role.Button,
onClick = onClick,
),
)
}