Skip to content

Commit 0f1df4c

Browse files
authored
Clean up TalkBack accessibility semantics (#6498)
* compose: Move voice recording row labels onto child leaves The locked and overview rows set contentDescription on a mergeDescendants container, which concatenates with the child descriptions (duration, progress) so any new child silently shifts the announce. Move the label onto a child leaf (the leading icon and the waveform) so the announce is composed from the children in visual order, and keep the merge only as the single TalkBack focus stop. * compose: Tidy empty merge-semantics blocks Remove the empty Modifier.semantics(mergeDescendants = true) {} blocks on Rows that already have a clickable or toggleable, where the merge boundary is implicit. Keep the load-bearing ones (containers with no click handler) and add a short comment explaining that they group their children into a single TalkBack focus stop. * compose: Lead the Giphy preview announcement with the preview label The focused ephemeral Giphy preview announced "Only visible to you, [alt text], Giphy". Compose a single description that leads with "Giphy preview", then the alt text, then "Only visible to you", and apply it via clearAndSetSemantics so the merged stop reads in that order. Add the stream_compose_giphy_preview_label string in all supported locales. * compose: Group the media attachment grid as a TalkBack traversal group Add isTraversalGroup to the multi-image grid container so that once focus is inside the grid, a swipe walks every tile in order before leaving, instead of letting the surrounding list pull focus across rows mid-grid. * compose: Document Giphy preview a11y ownership on MessageGiphyContent MessageGiphyContent composes and owns the focused preview's merged TalkBack description and clears the inner GiphyAttachmentContent semantics. Note in the KDoc that customizing the preview's accessibility announcement should override MessageGiphyContent rather than GiphyAttachmentContent.
1 parent 4878952 commit 0f1df4c

19 files changed

Lines changed: 33 additions & 10 deletions

File tree

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentContent.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import androidx.compose.ui.platform.testTag
5959
import androidx.compose.ui.res.painterResource
6060
import androidx.compose.ui.res.stringResource
6161
import androidx.compose.ui.semantics.contentDescription
62+
import androidx.compose.ui.semantics.isTraversalGroup
6263
import androidx.compose.ui.semantics.semantics
6364
import androidx.compose.ui.semantics.testTag
6465
import androidx.compose.ui.text.style.TextAlign
@@ -168,7 +169,11 @@ public fun MediaAttachmentContent(
168169
stringResource(UiCommonR.string.stream_ui_message_list_semantics_message_attachments, attachments.size)
169170
Row(
170171
modifier = Modifier
171-
.semantics { this.contentDescription = description }
172+
.semantics {
173+
this.contentDescription = description
174+
// Group the grid so a TalkBack swipe walks every tile before leaving it.
175+
isTraversalGroup = true
176+
}
172177
.padding(MessageStyling.messageSectionPadding),
173178
horizontalArrangement = Arrangement.spacedBy(gridSpacing),
174179
) {

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/GiphyMessageContent.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import androidx.compose.ui.platform.LocalView
4848
import androidx.compose.ui.platform.testTag
4949
import androidx.compose.ui.res.painterResource
5050
import androidx.compose.ui.res.stringResource
51+
import androidx.compose.ui.semantics.contentDescription
5152
import androidx.compose.ui.semantics.semantics
5253
import androidx.compose.ui.tooling.preview.Preview
5354
import androidx.compose.ui.unit.dp
@@ -96,6 +97,8 @@ public fun GiphyMessageContent(
9697
val cancelledAnnouncement = stringResource(R.string.stream_compose_message_list_giphy_cancelled)
9798
val shuffledAnnouncement = stringResource(R.string.stream_compose_message_list_giphy_shuffled)
9899

100+
val giphyPreviewLabel = stringResource(R.string.stream_compose_giphy_preview_label)
101+
99102
val isTouchExplorationEnabled = rememberIsTouchExplorationEnabled()
100103
val previewFocusRequester = remember { FocusRequester() }
101104
// Track whether the preview has already requested focus so that a LazyColumn dispose +
@@ -120,7 +123,8 @@ public fun GiphyMessageContent(
120123
.applyIf(isTouchExplorationEnabled) {
121124
focusRequester(previewFocusRequester).focusable()
122125
}
123-
.semantics(mergeDescendants = true) {},
126+
// Lead the preview's merged TalkBack announcement with the "Giphy preview" label.
127+
.semantics(mergeDescendants = true) { contentDescription = giphyPreviewLabel },
124128
) {
125129
Row(
126130
modifier = Modifier

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/QuotedMessage.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ internal fun MessageComposerQuotedMessage(
129129
message = message,
130130
currentUser = currentUser,
131131
replyMessage = null,
132+
// No click handler on the composer quoted preview; merge into one TalkBack stop.
132133
modifier = Modifier.semantics(mergeDescendants = true) {},
133134
)
134135

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/poll/PollAnswers.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ internal fun PollAnswersItem(
178178
verticalArrangement = Arrangement.spacedBy(StreamTokens.spacingXs),
179179
) {
180180
Column(
181+
// No click handler; merge into one TalkBack stop.
181182
modifier = Modifier.semantics(mergeDescendants = true) {},
182183
verticalArrangement = Arrangement.spacedBy(StreamTokens.spacingXs),
183184
) {

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/poll/PollOptionVotesDialog.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ private fun Content(
164164
Row(
165165
modifier = Modifier
166166
.fillMaxWidth()
167+
// No click handler; merge into one TalkBack stop.
167168
.semantics(mergeDescendants = true) {},
168169
horizontalArrangement = Arrangement.End,
169170
) {

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/poll/PollVoteItem.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ internal fun PollVoteItem(
4444
val borderSize = 2.dp
4545

4646
Row(
47+
// No click handler; merge into one TalkBack stop.
4748
modifier = modifier.semantics(mergeDescendants = true) {},
4849
verticalAlignment = Alignment.CenterVertically,
4950
horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacingSm),

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/attachments/poll/PollSwitchList.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,7 @@ private fun PollSwitchHeader(
179179
value = enabled,
180180
role = Role.Switch,
181181
onValueChange = onCheckedChange,
182-
)
183-
.semantics(mergeDescendants = true) {},
182+
),
184183
verticalAlignment = Alignment.CenterVertically,
185184
) {
186185
Column(
@@ -230,8 +229,7 @@ private fun LimitVotesPerPerson(
230229
value = enabled,
231230
role = Role.Switch,
232231
onValueChange = onCheckedChange,
233-
)
234-
.semantics(mergeDescendants = true) {},
232+
),
235233
verticalAlignment = Alignment.CenterVertically,
236234
) {
237235
Column(

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/AudioRecordingContent.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,14 @@ internal fun MessageComposerAudioRecordingLockedContent(
196196
modifier = RecordingBarModifier
197197
.focusRequester(rowFocusRequester)
198198
.focusable()
199-
.semantics(mergeDescendants = true) { contentDescription = rowLabel },
199+
// Merge the row into one TalkBack stop; the leading icon owns the row label.
200+
.semantics(mergeDescendants = true) {},
200201
verticalAlignment = Alignment.CenterVertically,
201202
) {
202203
Box(modifier = Modifier.size(48.dp), contentAlignment = Alignment.Center) {
203204
Icon(
204205
painter = painterResource(id = R.drawable.stream_design_ic_voice),
205-
contentDescription = null,
206+
contentDescription = rowLabel,
206207
tint = ChatTheme.colors.accentError,
207208
)
208209
}
@@ -314,7 +315,8 @@ private fun RowScope.OverviewPlaybackRow(
314315
.fillMaxHeight()
315316
.focusRequester(focusRequester)
316317
.focusable()
317-
.semantics(mergeDescendants = true) { contentDescription = rowLabel },
318+
// Merge the row into one TalkBack stop; the waveform owns the row label.
319+
.semantics(mergeDescendants = true) {},
318320
verticalAlignment = Alignment.CenterVertically,
319321
) {
320322
val playbackInMs = (currentProgress * state.durationInMs).toInt()
@@ -330,6 +332,7 @@ private fun RowScope.OverviewPlaybackRow(
330332
StaticWaveformSlider(
331333
modifier = Modifier
332334
.fillMaxSize()
335+
.semantics { contentDescription = rowLabel }
333336
.padding(start = StreamTokens.spacingMd, top = 8.dp, bottom = 8.dp),
334337
waveformData = state.waveform,
335338
progress = currentProgress,

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerEditIndicator.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ private fun EditIndicatorCard(body: QuotedMessageBody) {
8585
)
8686
.padding(StreamTokens.spacingXs)
8787
.height(IntrinsicSize.Min)
88+
// No click handler; merge into one TalkBack stop.
8889
.semantics(mergeDescendants = true) {},
8990
horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacingXs),
9091
) {

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/header/ChannelHeader.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@ internal fun DefaultChannelHeaderCenterContent(
200200
onClickLabel = onHeaderTitleClickLabel,
201201
role = Role.Button,
202202
) { callback(channel) }
203-
.semantics(mergeDescendants = true) {}
204203
},
205204
horizontalAlignment = Alignment.CenterHorizontally,
206205
verticalArrangement = Arrangement.Center,

0 commit comments

Comments
 (0)