feat(ui): channel-list & message-preview accessibility pass - #2827
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>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (5)
📒 Files selected for processing (6)
💤 Files with no reviewable changes (1)
🚧 Files skipped from review as they are similar to previous changes (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.77% 72.60% +0.83%
==========================================
Files 426 426
Lines 26897 27401 +504
==========================================
+ Hits 19304 19894 +590
+ Misses 7593 7507 -86 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
These screenshots seem broken (not sure if this surfaces an actual bug)
There was a problem hiding this comment.
Nice catch, I will check them
…pshot mock - Format recent timestamps with Jiffy's locale-aware `jm` in both the visual `formatRecentDateTime` and the default `AccessibilityTranslations`, so time follows the locale convention (24h in Europe, 12h in India/US) instead of a hardcoded 24h clock. Aligns the default a11y formatter with all 11 locale bundles. - Stub `isGroup`/`isOneToOne` on the docs_screenshots MockChannel so the channel-list snapshots render instead of throwing a null-bool TypeError. - Add a locale-pinned test asserting de/fr/it render 24h and en/hi render 12h. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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 `@packages/stream_chat_flutter/lib/src/utils/date_formatter.dart`:
- Around line 36-38: Update the date/time formatting flow around
Jiffy.parseFromDateTime and jiffyDate.jm to use the active Flutter context
locale, rather than relying on process-wide Jiffy locale state; alternatively,
establish and test synchronization between Flutter locale changes and
Jiffy.setLocale so recent-time output matches the translated 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e745616f-b394-42e0-b7c9-a5d153e5432c
📒 Files selected for processing (7)
docs/docs_screenshots/test/src/mocks.dartpackages/stream_chat_flutter/CHANGELOG.mdpackages/stream_chat_flutter/lib/src/localization/accessibility_translations.dartpackages/stream_chat_flutter/lib/src/utils/date_formatter.dartpackages/stream_chat_flutter/test/src/misc/timestamp_test.dartpackages/stream_chat_flutter/test/src/utils/date_formatter_test.dartpackages/stream_chat_localizations/test/translations_test.dart
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/stream_chat_flutter/CHANGELOG.md
- packages/stream_chat_flutter/test/src/misc/timestamp_test.dart
- packages/stream_chat_flutter/lib/src/localization/accessibility_translations.dart
Resolved dependency conflicts: kept the stream_core_flutter git dep and bumped its ref to the stream-core-flutter main merge commit for #132 (semanticsLabel), while taking master's 10.2.0 version bumps and the new stream_thumbnail dep. CHANGELOG conflicts resolved by keeping both sides' entries.
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, timestamps, and message status icons.