Improve channel list TalkBack accessibility#6436
Conversation
`MessagePreviewContent` and `DraftPreviewContent` exposed a consolidated `text` semantic on the parent `Row` to read the sender+message (or "Draft:"+text) as one utterance, but the child `Text` composables still merged their own text into the surrounding clickable's accessibility tree, so TalkBack announced each preview twice. Switching to `clearAndSetSemantics` replaces the children and emits the composed preview once.
`tagUser` in `TextUtils` decrements the mention's start index by 1 to include the leading `@` in the highlighted span. When the mentioned user name appears at index 0 of the message text, that produces a span start of -1. Compose tolerates the invalid span at render time, but TalkBack asks the platform to convert the `AnnotatedString` to a spannable, where `Spannable.setSpan` strictly validates and throws `IndexOutOfBoundsException: setSpan (-1 ...) starts before 0`, crashing the app the moment an accessibility service tries to read the message list. Clamp the highlight start to 0 in that edge case so the span stays valid; visually the leading `@` only drops out of the highlight when no `@` actually precedes the name.
Channel rows previously announced as "Channel item, …" with the unread
badge speaking just the digit, the mute icon silent, and no `onClickLabel`
on the row so TalkBack fell back to the generic "double-tap to activate"
hint with no verb.
Push the a11y content down to the leaves so Compose's natural merge
composes them in visual reading order:
- The mute `Icon` (both inline and trailing-bottom variants) now carries a
localized "muted" `contentDescription`.
- `UnreadCountIndicator` overrides its inner numeric `Text` with a plural
label ("N unread messages") via `clearAndSetSemantics` on the badge Box.
Same component is used by `ThreadItem`, which benefits too.
- The outer `Column` drops the static "Channel item" placeholder and
declares `onClickLabel = "Open conversation"` /
`onLongClickLabel = "Open conversation options"` on `combinedClickable`.
Channel name, timestamp, read-status icon, and message preview already
expose their own semantics, so the natural merge picks them up unchanged.
Adds the four new strings across all 7 supported locales and drops the
now-unused `stream_compose_cd_channel_item` placeholder.
The channel-list and thread-list header titles already render in heading
typography but had no `heading()` semantics, so TalkBack's swipe-by-heading
gesture skipped them. Add `Modifier.semantics { heading() }` to both the
Connected title and the Offline "Disconnected" Text in
`DefaultListHeaderCenterContent`. The Connecting branch stays untouched
since it shows a transient loading indicator, not a heading.
Because `DefaultListHeaderCenterContent` is shared between
`ChannelListHeader` and `ThreadListHeader`, both screens gain heading
navigation from this single change.
PR checklist ✅All required conditions are satisfied:
🎉 Great job! This PR is ready for review. |
WalkthroughThis PR enhances accessibility across channel list UI by introducing Compose semantics improvements. ChannelItem adds click/long-click labels and muted status descriptions; UnreadCountIndicator implements pluralized accessibility descriptions; HeaderScaffold marks headers as semantic headings; MessagePreviewContent switches to clearAndSetSemantics; and TextUtils fixes a mention span rendering crash. Supporting string resources are added across all language locales. ChangesChannel item and header accessibility semantics
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@stream-chat-android-compose/src/main/res/values-es/strings.xml`:
- Around line 59-61: Update the Spanish string resource named
stream_compose_channel_item_options so the TalkBack action includes the verb;
change its value from "Opciones de conversación" to "Abrir opciones de
conversación" to match the action phrasing used in
stream_compose_channel_item_open and provide consistent, clear action labels.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: f1a4f68c-bae7-4834-94fd-9744f2eb56c1
📒 Files selected for processing (13)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/ChannelItem.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/HeaderScaffold.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/MessagePreviewContent.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/UnreadCountIndicator.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/TextUtils.ktstream-chat-android-compose/src/main/res/values-es/strings.xmlstream-chat-android-compose/src/main/res/values-fr/strings.xmlstream-chat-android-compose/src/main/res/values-hi/strings.xmlstream-chat-android-compose/src/main/res/values-in/strings.xmlstream-chat-android-compose/src/main/res/values-it/strings.xmlstream-chat-android-compose/src/main/res/values-ja/strings.xmlstream-chat-android-compose/src/main/res/values-ko/strings.xmlstream-chat-android-compose/src/main/res/values/strings.xml
SDK Size Comparison 📏
|
The English `stream_compose_channel_item_options` is "Open conversation
options" — verb + noun. All seven locale translations dropped the verb
("Opciones de conversación", "Options de la conversation", and so on),
which left the TalkBack action label inconsistent with the parallel
`stream_compose_channel_item_open` ("Abrir conversación" / "Ouvrir la
conversation" / …) where every locale already includes the verb.
Re-add the verb so each `_options` label mirrors the verb-prefix
convention its sibling `_open` label uses.
|
|
🚀 Available in v7.2.0 |



Goal
Resolve TalkBack accessibility gaps on the Compose channel list:
MessagePreviewContentannounced previews twice, and a span computation intagUsercrashed the platform whenever an accessibility service tried to read a message whose text started with a mention.Implementation
Four self-contained commits, each scoped to one concern:
Fix duplicated TalkBack readout in channel preview— switchMessagePreviewContent/DraftPreviewContentfromsemantics { text = … }toclearAndSetSemantics, so childTexts are no longer also merged into the surrounding clickable.Fix TalkBack crash on mentions at the start of a message— clamp thestart - 1backtrack inTextUtils.tagUserto 0. Without an accessibility service the bad span renders harmlessly; with TalkBack on,Spannable.setSpan(-1, …)throws and crashes the message list.Make channel list rows accessible to TalkBack— leaf-first a11y. MuteIconcarries a localized "muted"contentDescription;UnreadCountIndicatoroverrides its bare numericTextwith a plural label ("N unread messages") viaclearAndSetSemanticson the badge (also benefitsThreadItem); outerColumndrops the static "Channel item" placeholder and declaresonClickLabel/onLongClickLabeloncombinedClickable. Compose's natural merge composes name, timestamp, read-status, and preview in visual reading order. Adds four new strings + translations for all 7 supported locales; drops the obsoletestream_compose_cd_channel_item.Mark list-header title as a TalkBack heading— addsModifier.semantics { heading() }to the titleText(and the offline "Disconnected"Text) inDefaultListHeaderCenterContent. Shared byChannelListHeaderandThreadListHeader, so both screens gain heading navigation.No public API surface changes (apiDump clean).
Testing
Manual steps on the Compose sample with TalkBack ON and Settings → Accessibility → TalkBack → Verbosity → Speak usage hints enabled:
"@Chewbacca hello"). With TalkBack on, opening the channel previously crashed withIndexOutOfBoundsException: setSpan (-1 …). It must now open normally.Summary by CodeRabbit
New Features
Bug Fixes
Localization