feat(ui): channel-list & message-preview accessibility pass#2827
feat(ui): channel-list & message-preview accessibility pass#2827xsahil03x wants to merge 13 commits into
Conversation
…ttachment picker" The tooltip was hard-coded to "Open attachment picker", so screen readers announced "Open attachment picker" even when the picker was already expanded. State-specific detail comes from the existing hint/onTapHint (which already switch on isPickerOpen); the tooltip only needs to be state-agnostic. Fixes FLU-589. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…up, StreamUserAvatarStack Thin pass-through of the new stream_core_flutter `semanticsLabel` parameter. null (default) composes through; non-null exposes the avatar as a single labeled image node. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends `AccessibilityTranslations` with strings and formatters used by
the channel-list row and message-preview a11y wiring:
* Preview prefixes: `outgoingMessagePreviewLabel` (`"You"`),
`incomingMessagePreviewLabel({senderName})`, `pollPreviewLabel`,
`draftPreviewLabel`, `systemMessagePreviewLabel`.
* Delivery status: `messageSending` / `messageSent` /
`messageDelivered` / `messageRead` status labels.
* Channel-list row: `unreadMessagesLabel(count)`, `channelGroupLabel`,
`channelMutedLabel`, `channelPinnedLabel`.
* `formatRecentDateTime(DateTime)` — locale-aware bucketed timestamp
for the last-message-date announcement.
Every supported locale (ca / de / en / es / fr / hi / it / ja / ko / no /
pt) ships a full native-language implementation.
Also fixes `voiceRecordingText` casing to sentence case
(`"Voice recording"`) — the English translation was the odd one out;
every other locale already used its native equivalent of sentence case.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an optional interface `AccessibleMessagePreviewFormatter` extending
`MessagePreviewFormatter` with `formatMessageSemanticsLabel` and
`formatDraftMessageSemanticsLabel` — plain-text natural-language
previews suitable for `Text.semanticsLabel` / `Semantics.label`.
`StreamMessagePreviewText` and `StreamDraftMessagePreviewText` wire the
returned label into `Text.rich`'s `semanticsLabel`.
Existing custom formatters that only `implements MessagePreviewFormatter`
continue to work; the SDK falls back to a visual-derived label when the
formatter is not accessibility-aware.
`StreamMessagePreviewFormatter` (the default) implements the new
interface, composing:
* A speaker prefix — `"You"` for own messages, sender's full name for
other users in group channels, no prefix in 1-on-1 channels.
* A type-context fragment — `"Poll"`, `"Photo"`, `"2 photos"`,
`"Video"`, `"File"`, `"Voice recording"`, `"Link"`, `"Location"` —
for message kinds whose visual icon carries meaning.
* The message body (or attachment fallback).
Deleted and system messages skip the speaker prefix (state / event
descriptions, not authored content). Inline icon `WidgetSpan`
placeholders are stripped so screen readers never announce "object".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`StreamChannelAvatar` emits a default `"Group"` label for group channels, restoring the "this is a group" context the multi-user avatar stack conveys visually; direct-message avatars stay silent because the counterpart's identity is already announced by the surrounding row title. Callers can override via the new param; an empty string leaves the avatar silent. `StreamTimestamp` defaults to a bucketed natural-language phrasing via `AccessibilityTranslations.formatRecentDateTime`, so screen readers get a clear time reference even when the visible text is abbreviated (`"2h"`, `"Sat"`, `"1/15"`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mary Previously, screen readers walked the channel-list row fragment by fragment — announcing avatar initials, timestamp, message body, and unlabeled muted / pinned icons as separate stops. This commit wraps the tile in `MergeSemantics`, labels the icons via `channelMutedLabel` / `channelPinnedLabel`, and announces the unread badge via `unreadMessagesLabel(count)`. Group avatars now emit the default `"Group"` label; direct-message avatars stay silent. `StreamSendingIndicator` labels its icon with the bare delivery-state word (`"Sending"` / `"Sent"` / `"Delivered"` / `"Read"`) via `AccessibilityTranslations`. The channel-list row wraps the indicator in `ExcludeSemantics` so scan noise doesn't repeat "Sent" / "Read" on every own-message row; the visual icon is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tyle Drops rationale prose from the `semanticsLabel` field docs on `StreamUserAvatar`, `StreamUserAvatarGroup`, and `StreamUserAvatarStack` so they match the tight two-branch shape used across the sibling `stream_core_flutter` avatar widgets. Same for the CHANGELOG entry — trimmed to a single one-liner. Also unconditionally wraps the placeholder initials in `ExcludeSemantics` — the initials are decorative visual identifiers, not information a screen reader can act on. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe PR adds localized accessibility semantics for message previews, drafts, avatars, timestamps, sending indicators, and channel/thread list tiles. It introduces formatter and translation APIs, wires labels into widgets, updates supported locales, adds coverage, and pins ChangesAccessibility semantics
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant ChannelListTile
participant MessagePreviewFormatter
participant AccessibilityTranslations
participant FlutterSemantics
ChannelListTile->>MessagePreviewFormatter: format message or draft label
MessagePreviewFormatter->>AccessibilityTranslations: resolve localized labels
AccessibilityTranslations-->>MessagePreviewFormatter: return formatted text
MessagePreviewFormatter-->>ChannelListTile: provide semanticsLabel
ChannelListTile->>FlutterSemantics: merge tile, avatar, preview, and timestamp semantics
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
# Conflicts: # packages/stream_chat_flutter/CHANGELOG.md
Points the git dep at c711bba, the commit on the open GetStream/stream-core-flutter#132 branch that adds `semanticsLabel` to `StreamAvatar` / `StreamAvatarGroup` / `StreamAvatarStack`. Required so our chat-side widgets compile; must be swapped to the merged main ref once #132 lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously, screen readers walked the thread-list row fragment by fragment — announcing avatar initial, channel name, sender + message, reply count, and timestamp as separate stops. This commit wraps the tile in `MergeSemantics` so the row exposes as a single node with one composed label, and labels the unread badge via `AccessibilityTranslations.unreadMessagesLabel(count)`. Fixes FLU-591. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/stream_chat_flutter/lib/src/localization/accessibility_translations.dart (1)
599-614: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winDefault time format diverges from every locale implementation.
DefaultAccessibilityTranslations.formatRecentDateTimeformats the time withjiffyDate.format(pattern: 'H:mm')(24‑hour, matching the interface doc examples like"Today at 14:30"), but all locale implementations (_AccessibilityTranslationsEn,_ca,_de,_es,_fr,_hi,_it) usejiffyDate.jm, which renders 12‑hour2:30 PMfor English. SinceDefaultTranslations.accessibilityreturns this default whileStreamChatLocalizationsEnreturns_AccessibilityTranslationsEn, an English user hears a different timestamp format depending on whetherstream_chat_localizationsis wired up.Consider aligning the default with the locale bundles (use
jiffyDate.jm) or vice‑versa so the spoken timestamp is consistent.♻️ Align default with locale implementations
- final time = jiffyDate.format(pattern: 'H:mm'); + final time = jiffyDate.jm;🤖 Prompt for 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. In `@packages/stream_chat_flutter/lib/src/localization/accessibility_translations.dart` around lines 599 - 614, Update DefaultAccessibilityTranslations.formatRecentDateTime to use the same time formatting as the locale implementations, replacing the 24-hour jiffyDate format with the shared 12-hour jiffyDate.jm representation while preserving all existing date-label branches.
🤖 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.
Nitpick comments:
In
`@packages/stream_chat_flutter/lib/src/localization/accessibility_translations.dart`:
- Around line 599-614: Update
DefaultAccessibilityTranslations.formatRecentDateTime to use the same time
formatting as the locale implementations, replacing the 24-hour jiffyDate format
with the shared 12-hour jiffyDate.jm representation while preserving all
existing date-label branches.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1eb1478d-104d-47fd-bbe2-c60a28d57b61
📒 Files selected for processing (36)
docs/docs_screenshots/pubspec.yamlmelos.yamlpackages/stream_chat_flutter/CHANGELOG.mdpackages/stream_chat_flutter/lib/src/channel/stream_draft_message_preview_text.dartpackages/stream_chat_flutter/lib/src/channel/stream_message_preview_text.dartpackages/stream_chat_flutter/lib/src/components/avatar/stream_channel_avatar.dartpackages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar.dartpackages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_group.dartpackages/stream_chat_flutter/lib/src/components/avatar/stream_user_avatar_stack.dartpackages/stream_chat_flutter/lib/src/indicators/sending_indicator.dartpackages/stream_chat_flutter/lib/src/localization/accessibility_translations.dartpackages/stream_chat_flutter/lib/src/localization/translations.dartpackages/stream_chat_flutter/lib/src/misc/timestamp.dartpackages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_item.dartpackages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dartpackages/stream_chat_flutter/lib/src/utils/message_preview_formatter.dartpackages/stream_chat_flutter/pubspec.yamlpackages/stream_chat_flutter/test/src/channel/stream_draft_message_preview_text_test.dartpackages/stream_chat_flutter/test/src/channel/stream_message_preview_text_test.dartpackages/stream_chat_flutter/test/src/misc/timestamp_test.dartpackages/stream_chat_flutter/test/src/scroll_view/channel_scroll_view/stream_channel_list_item_test.dartpackages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/stream_thread_list_tile_test.dartpackages/stream_chat_localizations/CHANGELOG.mdpackages/stream_chat_localizations/example/lib/add_new_lang.dartpackages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dartpackages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dartpackages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dartpackages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dartpackages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dartpackages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dartpackages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dartpackages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dartpackages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dartpackages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dartpackages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dartpackages/stream_chat_localizations/test/translations_test.dart
`StreamChannelAvatar` now reads `channel.isGroup` / `channel.isOneToOne` to pick its default `semanticsLabel`; mocktail returns `null` for un-stubbed bool getters, which fails a runtime type check. Register both as `false` in the base `MockChannel` so existing test files that don't care about a11y keep passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2827 +/- ##
==========================================
+ Coverage 71.35% 72.19% +0.83%
==========================================
Files 430 430
Lines 26945 27449 +504
==========================================
+ Hits 19226 19816 +590
+ Misses 7719 7633 -86 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Summary
Second batch of accessibility fixes across the channel list and thread list. Screen readers now speak one coherent row summary per channel / thread instead of navigating fragment-by-fragment, and preview text emits natural-language a11y labels instead of the visual
"You: <message>"style.AccessibilityTranslationsgains the strings and formatters used by channel-list rows, thread-list rows, and message previews (delivery status, preview prefixes, muted / pinned / group / unread labels,formatRecentDateTime). All 11 locales ship native-language implementations.AccessibleMessagePreviewFormatterinterface layered on top ofMessagePreviewFormatter; consumers who onlyimplements MessagePreviewFormatterstill work via a visual-derived fallback.semanticsLabelonStreamChannelAvatarandStreamTimestamp.StreamChannelListTilemerges into a single accessible row summary; muted / pinned icons and unread badge are labeled; the sending indicator wraps inExcludeSemanticsinside the row to avoid scan noise.StreamThreadListTilemerges into a single accessible row summary with labeled unread badge.voiceRecordingTextfixed to sentence case.Linear tickets
Dependency
Depends on GetStream/stream-core-flutter#132 for the base
StreamAvatar/StreamAvatarGroup/StreamAvatarStacksemanticsLabelfield. The pubspec pins to the head of that PR (c711bba); swap to a merged main ref once it lands, then flip to ready-for-review.Test plan
melos run test:all— new preview / list-item / avatar / timestamp / thread-tile / localization tests pass.melos run analyze— clean.stream_chat_localizationspackage sits at 100% line coverage.Notes on doc / CHANGELOG style
Public dartdocs and CHANGELOG entries are trimmed to match the sibling
stream_core_fluttershape: one short summary line, blank separator, 1–3 sentence body. Docs describe the contract, not the implementation.🤖 Generated with Claude Code
Summary by CodeRabbit
semanticsLabelsupport for avatars, avatar stacks/groups, and timestamps.