refactor(sample, ui): redesign channel info and detail sheet#2638
refactor(sample, ui): redesign channel info and detail sheet#2638
Conversation
Redesign the long-press / swipe-action sheet on the channel list to match the new design system (Figma node 14889-440044), built entirely on top of existing SDK + stream_core_flutter components: - Header: StreamChannelAvatar + StreamChannelName (with mute / pin state indicators) + StreamChannelInfo for the member count. - Action rows: StreamListTile + a local StreamListTileTheme override for destructive (leave / delete) styling. - Sheet hosting: showStreamSheet for the drag-to-dismiss / scrim behavior. The sheet now follows the same dispatch pattern as StreamMessageWidget: - A sealed ChannelDetailAction (ViewChannelInfo / PinChannel / UnpinChannel / LeaveChannel / DeleteChannel) replaces the per-action callback API. - The sheet pops itself with the chosen action; the caller awaits the result and routes through a single switch — mirroring _onActionTap. - Destructive confirmations use a Material AlertDialog with two ghost StreamButtons, matching PollEndVoteDialog / PollDeleteOptionDialog and the SDK-internal StreamMessageActionConfirmationModal. SDK-level supporting changes: - Export StreamListTileTheme / StreamListTileThemeData from stream_chat_flutter so consumers can override list-tile styling without reaching into stream_core_flutter directly. - Drop the now-redundant direct stream_core_flutter import in stream_thread_list_tile.dart. - Remove mainAxisSize: .min from stream_poll_option_votes_sheet so it keeps filling the screen after the upstream StreamSheet shrink-wrap fix in stream_core_flutter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add `StreamAppBar` and `StreamAppBarProps` to the show-allowlist re-export so consumers (the sample app, downstream apps) can use the design system's app bar without a direct stream_core_flutter import. Also drop now-redundant `stream_core_flutter` imports in three SDK files (`channel_header.dart`, `channel_list_header.dart`, `thread_header.dart`) that the analyzer flagged as unnecessary once `StreamAppBar` flows through the main SDK barrel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite the 1-1 channel detail screen to match the new design system (Figma frame 8833:431680): - StreamAppBar replaces the bespoke Stack-with-back-button header. - Hero header with StreamUserAvatar (.xxl), heading-lg name with an inline mute-state indicator, and an online / last-seen subtitle. - Two card-grouped sections via a private _Section widget — Pinned Messages / Photos & Videos / Files for navigation, and Mute / Block / Delete for actions. - StreamSwitch drives the Mute toggle; the row's icon and label swap between mute / audio + Mute User / Unmute User reactively. - Block User shows a "not implemented" snackbar (no SDK API available for it in this sample). - Delete Conversation uses an AlertDialog + StreamButton confirmation matching the channel-list dialog pattern. - Drops the legacy Shared Groups subroute (not in the design) and the ignore_for_file: deprecated_member_use override that came with the old StreamChatTheme paths. Every value flows through stream_core_flutter primitives + semantics — no hard-coded colors / paddings, no StreamChatTheme.of legacy paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite the group channel detail screen to match the new design system
(Figma frame 8779:381156):
- StreamAppBar with a conditional outline-pill Edit button trailing.
- Hero header: StreamChannelAvatar (.xxl), heading-lg channel name with
an inline mute-state icon, StreamChannelInfo subtitle ("X members ·
Y online").
- Three card-grouped sections:
- Pinned Messages / Photos & Videos / Files (shared _Section + _Tile
pattern, mirrors Contact Info).
- Members card with a count + Add button header, the first 5 members
sorted with the current user first ("You"), and a View all footer
when the channel has more.
- Mute toggle (StreamSwitch swaps mute/audio icon + label) and a
destructive Leave Group action that confirms via AlertDialog.
- Member rows render Online / Last seen status as the subtitle and an
"Admin" trailing label for moderators / owners.
Stubs (snackbar): Editing the group, Adding members, the all-members
sheet — these land in chunks 4–7 alongside their dedicated routes /
sheets.
Drops the legacy file's inline name editing, the bespoke add-user modal,
the StreamBuilder<List<Member>> Scaffold scaffold, and every
StreamChatTheme.of legacy theme path. Net: 1046 → 437 lines, all values
flowing through stream_core_flutter primitives + semantics.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire up the two member-driven flows from the channel detail design
(Figma frames 8833:434949 and 8833:434317):
- showAllMembersSheet — full-list bottom sheet with a StreamSheetHeader
("X Members" + circular outline user-add trailing button), driven by
channel.state.membersStream and sorted with the current user first.
Tapping a member stacks the contact-detail sheet on top via
showStreamSheet's built-in stacked-sheet animation.
- showContactDetailSheet — compact sheet with a small avatar + name +
online status header and three actions: Send Direct Message, Mute /
Unmute User (reactive on client.state.currentUserStream's mute list),
Block User. Pops with a ContactDetailAction subtype the caller
dispatches via openContactDetail.
- openContactDetail — opens the sheet, awaits the action, dispatches
through a switch expression — same shape as
StreamMessageWidget._onActionTap. Handles: SendDirectMessage finds /
creates the distinct 1-1 channel and pushes via GoRouter (popping any
enclosing all-members sheet first via StreamSheetRoute.popSheet so the
channel page lands on the regular page stack); MuteUser /
UnmuteUser hit client.muteUser / unmuteUser; BlockUser shows a
not-implemented snackbar per the agreed scope.
- ChannelMemberTile — single member row lifted out of group_info_screen
so the preview list and the all-members sheet always render
identically.
group_info_screen now uses ChannelMemberTile + openContactDetail for
member taps and showAllMembersSheet for the View all footer; the inline
_MemberTile and the 'all-members sheet' snackbar stub are gone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire up the trailing _Edit_ button in Group Info to the redesigned
edit-group sheet (Figma frames 8833:446261, 8833:447144, and the
9857:302181 avatar picker):
- showEditGroupSheet — bottom sheet with a StreamSheetHeader (auto X
leading, "Edit" title, solid-primary checkmark trailing). The save
checkmark stays disabled until the user actually changes something.
- Hero avatar block — taps the avatar / "Upload" link to open a stacked
_AvatarPickerSheet_ with three actions (Take Photo / Choose Image /
Reset Picture, the latter rendered destructive via a local
StreamListTileTheme override).
- Image picking via image_picker (added to sample_app pubspec) and
uploaded immediately through the standalone client.sendImage API so
the preview swaps as soon as the upload settles. The session-level
override + reset flag drive a switch-expression preview that
short-circuits StreamChannelAvatar.
- StreamTextInput drives the channel name; save persists via
channel.updatePartial(set: {...}, unset: ['image']) in a single round
trip — picks up name changes, the new avatar URL, or an "image"
unset for resets.
Group Info's Edit-button stub now calls showEditGroupSheet directly.
The "Editing the group" snackbar is gone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous version shrink-wrapped to its content, which left a half-empty sheet sitting on top of the channel page. The Figma actually shows a full-height sheet with the keyboard up — match that: - Wrap the avatar + text-input column in an Expanded scrollable. With one Expanded child the outer Column fills available height, which in turn drags the StreamSheet up to its full-screen rest position via the upstream Stack(StackFit.loose) fix. - Pad the scrollable's bottom by `MediaQuery.viewInsetsOf(context).bottom` so the StreamTextInput stays above the keyboard once it animates up. The sheet route applies SafeArea(bottom: false) so the inset is still ours to consume. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract a separate _canSave getter that requires the name to be non-empty in addition to the existing dirty + not-saving checks. Empty group names aren't a meaningful identifier (and the API won't accept them), so the save checkmark stays disabled when the user clears the field. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The avatar picker is already pushed via showStreamSheet from inside the edit sheet's context, so it auto-stacks (parent scales down, picker animates in over it). What was breaking the visual was the soft keyboard staying up while the stacked sheet animated — it fought the parent sheet's MediaQuery viewInsets and produced a jittery push. Drop focus right before pushing the picker so the keyboard slides down first, then the stacked sheet animates over a stable parent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pushing a new route already steals focus, so the manual FocusScope.unfocus() before opening the avatar picker is unnecessary. Drop it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hand-styled TextButton + accent-colored Text was bypassing the design system. Swap it for `StreamButton(type: .ghost, style: .primary, size: .small)` so press states, hover, focus, and accent color all flow through StreamButtonTheme. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the SDK's StreamAttachmentUploadStateBuilder pattern so the avatar preview reports its own upload state instead of leaving the user guessing while the network round-trips: - Track determinate progress (0..1) in _uploadProgress; updates flow from client.sendImage's onSendProgress callback (count, total). - Fall back to an indeterminate spinner (`null` value) when total is 0 / unknown — same fallback the SDK's _defaultInProgressBuilder uses for content-length-less uploads. - Overlay a translucent ColoredBox + StreamLoadingSpinner(.md) inside a ClipOval on top of the avatar while uploading. Matches the SDK visual. - Gate _canSave on `_uploadProgress == null` so the user can't tap save before the URL has settled. Re-export StreamLoadingSpinner / StreamLoadingSpinnerSize from stream_chat_flutter so the sample-app side never has to reach into stream_core_flutter directly. Drop the now-redundant stream_core_flutter imports the analyzer flagged across 10 SDK files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CircleAvatar fallback was rendering the freshly-uploaded image at the right diameter (80px, matching StreamAvatarGroupSize.xxl) but *without* the 1px border that StreamChannelAvatar's image branch draws via StreamAvatar's foregroundDecoration. The result: a subtle visual shift the moment an upload settled and we swapped previews. Re-export StreamAvatar from stream_chat_flutter and render the override via StreamAvatar(imageUrl: ..., size: .xxl) instead, so the picked preview is pixel-identical to the channel's eventual rendering. Placeholder falls back to the member-group avatar so a transient load hiccup doesn't blank the slot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the GestureDetector wrapping the avatar so only the Upload StreamButton opens the picker. Two overlapping hit targets pointing at the same action made the affordance ambiguous and risked accidental opens when scrolling near the avatar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the Expanded + SingleChildScrollView and let the sheet shrink-wrap its content. The Stack(StackFit.loose) fix upstream means a mainAxisSize.min Column in the body keeps the StreamSheet at its intrinsic height — header + avatar + input + keyboard inset, no extra empty space above the keyboard. Keyboard handling stays via padding the body's bottom by MediaQuery.viewInsetsOf(context).bottom — the input still rides above the soft keyboard, the sheet just doesn't claim screen real estate it isn't using. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three changes pulled together: - Avatar reactivity: drop the local _imageOverride / _imageRemoved state. Pick + reset now persist eagerly via channel.updateImage / channel.updatePartial(unset: ['image']) — StreamChannelAvatar reloads via channel.imageStream, both in this sheet and everywhere else showing the channel's avatar. Save handles only the deferred name edit (it's the only thing that batches across keystrokes). - Spinner overlay sizing: replace the loose-constraints ClipOval/SizedBox.expand combo with an explicit width/height Container + BoxShape.circle decoration, so the dim-overlay geometry exactly matches the .xxl avatar (80×80) regardless of how Stack lays out non-positioned children. - Header button parity: drop the size: .small on the trailing checkmark in EditGroupSheet *and* the trailing user-add button in AllMembersSheet. The auto-implied close on the leading side renders at default .medium (40px), so the trailing buttons were 8px shorter and the header looked lopsided. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Walk back the eager updateImage / updatePartial(unset) — picking or resetting now stages the change locally; nothing hits `channel.image` until the user taps the save checkmark. Dismissing the sheet leaves the channel untouched even though the picked bytes have already shipped to the CDN as a standalone attachment. - _imageOverride: CDN URL of an in-session pick, displayed via StreamAvatar so the diameter + 1px border match the channel-image branch. - _imageRemoved: tracks the user's intent to clear the avatar; the preview falls back to the member-group avatar. - _save persists name + image (or unset) in a single channel.updatePartial round-trip, same as before. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs the deferred-save model exposed: - Avatar flicker: the preview was driven off `_imageOverride` (CDN URL), so after upload settled the avatar swapped to a `StreamAvatar` whose CachedNetworkImage had to round-trip the same URL through the cache — and during that round-trip its placeholder briefly showed the member-group fallback. Fix: track the picked file path in `_pickedPath` and render the preview via `Image.file` slotted into `StreamAvatar`'s placeholder, so the swap is instant. Same pattern the LLC uses in sendMessage attachment uploads — local file beats URL when both are available. - Orphan uploads: dismissing the sheet (or replacing a pick before saving) left the standalone-uploaded bytes leaking on the CDN. Now every successful upload's URL is appended to a `_trackedUploads` list. On save, the URL we just persisted is stripped from the list and the rest are deleted via `client.deleteImage`. On dispose (without save), every tracked URL is deleted. `client.deleteImage() .ignore()` keeps the cleanup fire-and-forget. Also clear the local preview when an upload fails so the channel's current avatar reverts — paired with the snackbar, the user knows to re-pick instead of seeing a phantom file image. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Once the standalone upload returns a CDN URL, render the preview from that URL via StreamAvatar (so the in-memory file image gets released) instead of holding the local file open for the lifetime of the sheet. The local file becomes the placeholder during the brief CachedNetworkImage round-trip — no flicker on the swap because both states render through the same StreamAvatar chrome. Switch ladder reads: - URL set: StreamAvatar(url) with file as placeholder - URL still null: StreamAvatar(null) with file as placeholder - Removed: member-group fallback - Untouched: StreamChannelAvatar (channel.imageStream-driven) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire up the Add buttons in the group-info members card and the all-members sheet to a redesigned Add Members sheet (Figma frames 9857:114080 + Selected / Search / No Result variants): - StreamSheetHeader with auto-implied close + a primary checkmark trailing button. Checkmark stays disabled until at least one row is ticked and re-enables once any add-members call completes. - StreamTextInput (with the search-icon leading slot) drives the filter on a StreamUserListController. Edits are debounced 350ms before they flow into the controller so we don't fire a query on every keystroke. - Paginated StreamUserListView. Custom itemBuilder renders a StreamListTile (avatar + name + StreamCheckbox.circular trailing); tapping anywhere on the row toggles selection. The controller's filter excludes the current user and existing channel members upfront so they don't show up as candidates. - Custom emptyBuilder — search icon + "No user found" caption (the No Result variant from the Figma). - On confirm: channel.addMembers is awaited, the sheet pops with `true`. On failure: the sheet stays open with a snackbar so the user can retry. Drops the now-unused _showNotImplementedSnack helper from group_info_screen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reuse the rounded pill SearchTextField that already drives the channel list's search instead of re-rolling a StreamTextInput + leading-icon combo. Keeps the search affordance visually consistent across the app and trims a few lines of styling that duplicated SearchTextField's internals. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap Flexible + shrinkWrap for Expanded so the user list gets bounded constraints and scrolls independently of the sheet. Shrink-wrapping glued the sheet's height to however many rows happened to be loaded, which fought the controller's pagination and the keyboard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the bespoke _NoResults widget with a centered StreamScrollViewEmptyWidget — same shape StreamChannelListView, StreamUserListView, and friends use for empty states (icon + caption, textTertiary icon at size 32, captionDefault textSecondary title). Loading isn't overridden, so the default centered StreamScrollViewLoadingWidget kicks in for free. Centering now lives at the call site (`Center(...)` wrapping the empty widget), matching the SDK convention so the empty / loading overlays land in the middle of the visible scroll area. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite the pinned-messages screen to match the new design (Figma
8833:437280 + 8833:437021):
- StreamAppBar replaces the bespoke AppBar + StreamBackButton.
- Body keeps the existing StreamMessageSearchListController + filter
(cid + pinned) and renders via StreamMessageSearchListView so the
list rows pick up the SDK's tile styling for free.
- Empty state is a private widget — pin icon at textTertiary, a bold
textPrimary headline ("No pinned messages") and a textSecondary
subtitle ("Long-press a message to pin it to the chat") — centered
via the SDK convention.
- All values flow through stream_core_flutter primitives + semantics;
the legacy file's StreamChatTheme.of paths and the
ignore_for_file: deprecated_member_use override are gone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite the photos/videos list to match the new design (Figma 8833:437788, 13495:418984, 8833:437329): - StreamAppBar replaces the bespoke AppBar + StreamBackButton. - Body is a 3-column GridView with 1px gutters fed by the existing StreamMessageSearchListController (filtered to image / video attachments). Each cell renders the attachment thumbnail via StreamNetworkImage; videos overlay a StreamVideoPlayIndicator. Tap opens StreamFullScreenMedia at that index, with every other media item wired up as a swipeable sibling. - LazyLoadScrollView drives pagination — loadMore on end-of-page, same as the legacy implementation. - Empty / loading / error states match the SDK convention — centered StreamScrollViewLoadingWidget while loading, custom empty (image icon + headline + subtitle) per the Figma, centered StreamScrollViewErrorWidget on failure. - Drops the per-video VideoPlayerController preinit cache; thumbnails use thumbUrl now, so we don't pay the cost of N video pipelines for rows the user might never tap. Playback still works because StreamFullScreenMedia spins up its own player when a video tile is opened. Re-export StreamScrollViewLoadingWidget, StreamScrollViewErrorWidget, and StreamNetworkImage from stream_chat_flutter so the sample app never has to reach into stream_core_flutter / src directly. Drop the now-redundant `src/scroll_view/stream_scroll_view_loading_widget.dart` and `src/scroll_view/stream_scroll_view_error_widget.dart` imports the analyzer flagged across 10 SDK files once these flow through the main barrel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite the file-attachments list to match the new design (Figma
8833:437706 + 8833:437407):
- StreamAppBar replaces the bespoke AppBar + StreamBackButton.
- Body groups attachments by the message month (March 2026, February
2026, …). Each section header is a light-surface band with a bold
caption; entries without a timestamp fall under "Earlier".
- File rows are StreamListTiles — leading StreamFileTypeIcon
(auto-derived from the attachment's mimeType), filename title with
ellipsis, human-readable file-size subtitle ("4 MB" / "5 KB"). Pure
preview, no tap action — mirrors the legacy implementation.
- Empty / loading / error states use the SDK convention — centered
StreamScrollViewLoadingWidget while loading, custom Figma empty
(folder icon + "No files" headline + textSecondary subtitle), and
centered StreamScrollViewErrorWidget on failure with a retry button.
- LazyLoadScrollView drives pagination — same as the legacy
implementation.
- Drops the unused VideoPlayerController cache, the
ignore_for_file: deprecated_member_use override, and every
StreamChatTheme.of legacy theme path.
A small `_humanizeBytes` helper lives at the bottom of the file —
returns "4 MB", "512 KB", "2 GB" — sized to match the figma's tight
unit format.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migrate StreamChannelHeader, StreamChannelListHeader, StreamThreadHeader, and StreamGalleryHeader to a single slot model (leading / title / subtitle / trailing) and a single theme type (StreamAppBarThemeData), replacing the legacy AppBar-style API. Per-header highlights: - StreamChannelHeader: new onChannelAvatarPressed callback for the default channel-avatar trailing, and an opt-in default StreamBackButton via automaticallyImplyLeading. - StreamChannelListHeader: renames onUserAvatarTap to onUserAvatarPressed, drops the leading slot (the avatar is always the signed-in user) and the default "new chat" trailing — consumers pass their own trailing widget. The avatar mirrors Material AppBar's auto-implied leading and opens the enclosing Scaffold's drawer when one exists. - StreamThreadHeader / StreamGalleryHeader: same slot model. - StreamFullScreenMedia: paints backgroundApp when the headers are visible and StreamColors.black when they're hidden. Theme: - StreamChannelHeaderThemeData, StreamChannelListHeaderThemeData, and StreamGalleryHeaderThemeData are deleted. The accessors on StreamChatThemeData (channelHeaderTheme, channelListHeaderTheme, threadHeaderTheme, galleryHeaderTheme) now hold StreamAppBarThemeData; per-instance overrides flow through a new style: StreamAppBarStyle? parameter on each header. - kStreamHeaderHeight (72 px) and StreamColors are re-exported from package:stream_chat_flutter/stream_chat_flutter.dart. The migrations/redesign/headers_and_icons.md guide is rewritten with per-header parameter mapping tables, before/after snippets, and an updated migration checklist covering the new slot model, removed parameters, theme migration, and the kStreamHeaderHeight constant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cumulative cleanup pass across the sample app: - Migrate stale Material AppBar usages to StreamAppBar (config / advanced options / new-chat / new-group-chat / location dialogs). - Switch legacy `actions: [...]` slots to the new `trailing:` slot on group / chat info screens. - Hide the "add member" affordance on distinct (1:1) channels in the members sheet — the API rejects member changes on those. - Extract avatar-picker rows into a `_PickerTile` helper in the edit group sheet. - Modernise list / option iteration with switch expressions and `collection.sorted`. - Restructure padding so info screens own their scroll padding rather than each section duplicating it. - Bump iOS / Android Ruby toolchain to 3.3.9. No SDK behaviour changes — all touched files belong to the sample app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Migrate example app to the new StreamChannelHeader slot model: swap `onBackPressed` / `onImageTap` / `showBackButton` for an explicit `leading: StreamBackButton(...)` + `trailing:` widget, and replace `showBackButton: false` with `automaticallyImplyLeading: false` in the split-view shell. - Align StreamGalleryFooter's preferredSize with the new kStreamHeaderHeight (72) so the footer matches the redesigned bar above it instead of sitting at Material's kToolbarHeight (56). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap legacy icons for their design-system counterparts in the attachment actions modal — `eyeFill` → `messageBubble` for "Show in Chat", `save` → `arrowDownCircle` for "Save Image / Video" — and shrink them from 24 to 20 px so they line up with the rest of the icon set. Visual-only change; no API impact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refresh `docs_screenshots` goldens and tests against the redesigned SDK widgets: - Bump the channel-header / channel-list-header golden constraints from kToolbarHeight (56) to kStreamHeaderHeight (72) and stop injecting an inline `const StreamChannelHeader(...)` / `const StreamChannelListHeader(...)` default — the test scaffolds now expect callers to pass an explicit `header:`. - Re-render every macOS golden (channel / draft list / message input / message list / message search / polls / thread list / user list / voice recording) so the bytes match the redesigned components. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a `reactionsCountText(int count)` translation alongside the existing `threadReplyCountText` / `*CountText` family, replacing the hardcoded `'1 Reaction' / '$visibleCount Reactions'` switch in the reaction detail sheet header with `context.translations .reactionsCountText(visibleCount)`. Each of the 11 bundled locales (ca, de, en, es, fr, hi, it, ja, ko, no, pt) gets its own override that mirrors that locale's existing thread-reply count style — English and Italian keep the singular / plural distinction; the others use a single `$count [plural]` form in line with the translator's prior choices. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ 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 |
Update changelogs, the localizations migration guide, and the DefaultTranslations / supported-locale tests to cover the new `reactionsCountText(int count)` translation. Also adds the override to `add_new_lang.dart` so the example compiles against the new abstract member. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`package:stream_chat_flutter/stream_chat_flutter.dart` already re-exports the stream_core_flutter symbols these files use, so the explicit imports were redundant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new Channel.isOneToOne getter that returns true when the channel is distinct and has exactly two members. Tightens Channel.isGroup from `memberCount != 2` to `memberCount > 2 || !isDistinct` so two-member non-distinct channels correctly report as groups and 1-member distinct channels no longer do. Tightens Channel.isDistinct to require the trailing dash in the prefix (`!members-`), matching the backend's DistinctChannelPrefix constant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds StreamChannelListTile.isPinned with a corresponding stream subscription in StreamChannelListItem. Pinned channels now render a pin icon alongside the existing mute icon in the title row. Renames MuteIconPosition -> AttributePosition and the corresponding StreamChannelListItemThemeData.muteIconPosition field -> attributePosition. The position now controls both mute and pin icons. Enum values renamed: title -> inlineTitle, subtitle -> trailingBottom. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces hardcoded SizedBox / Padding values with `streamSpacing` tokens and Row gap-spacing throughout StreamThreadListTile and ThreadFooter. Default padding now reads from the spacing scale (`EdgeInsets.all(sm)`) instead of the previous `EdgeInsets.symmetric(vertical: 14, horizontal: 8)`, matching the rest of the redesigned list tiles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a long-press surface (and slidable "more" affordance) on the channel list row that opens ChannelDetailSheet — View Info, Pin / Unpin, Mute / Unmute User, Block User, Leave Group, Delete. Mute and Block rows render only on 1-1 channels and target the other member. Wires the previously-stubbed BlockUser action in ContactDetailSheet to client.blockUser. ContactDetailAction subtypes now carry the target user in their payload so the dispatcher reads it directly off the action. Adds sample-app-level extensions on StreamChatClient — isUserMuted / userMutedStream / isUserBlocked / userBlockedStream — wrapping the verbose currentUserStream.map(...).distinct() pattern reused across ChannelDetailSheet, ContactDetailSheet, and the member tile. Channel info routing factored into a private _pushChannelInfo helper mirrored across channel_list.dart and channel_page.dart so the avatar tap and the View Info action follow the same shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes `AttachmentModalSheet` and `StreamChannelInfoBottomSheet` — both exported from the public API but had no callers in the SDK or its example apps. `ErrorAlertSheet` is no longer exported either; it was only used by `StreamMessageInput` internally and now lives next to its sole consumer under `lib/src/message_input/`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up the title-row line-height fix and the new mute/pin attribute slot in StreamChannelListTile. Adds isPinned / isPinnedStream stubs to the docs-screenshots mock channel so the goldens render without nulls. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Parallel `flutter pub get` across packages races on the shared git dependency cache at `~/.pub-cache/git/<package>-<sha>/`, intermittently failing CI with `index.lock: File exists`. Setting `runPubGetInParallel: false` serializes resolution at the cost of a slightly longer bootstrap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The gallery / full-screen-media headers now auto-imply their leading back button via `Navigator.canPop`, so the tests must push the header onto a real route for the assertion to reflect production rendering. Also drops the connectivity_plus method-channel mock in `gallery_header_test.dart` in favour of `StreamChat.connectivityStream`, removing the platform-channel setUp / tearDown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## v10.0.0 #2638 +/- ##
===========================================
+ Coverage 67.13% 67.16% +0.02%
===========================================
Files 418 413 -5
Lines 25200 24904 -296
===========================================
- Hits 16919 16726 -193
+ Misses 8281 8178 -103 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary
Redesigned channel info / detail sheet flows in the sample app, plus the
supporting SDK changes that enable them.
stream_chatChannel.isOneToOne(distinct + two members).Channel.isGrouptomemberCount > 2 || !isDistinctandChannel.isDistinctto require the trailing dash in!members-.stream_chat_flutterStreamChannelListTilenext to the existing mute icon,driven by
channel.isPinnedStream.MuteIconPosition→AttributePosition(valuesinlineTitle/trailingBottom) and the theme fieldmuteIconPosition→attributePosition. Now controls both mute andpin attribute icons.
AttachmentModalSheet,ErrorAlertSheet,StreamChannelInfoBottomSheet.ErrorAlertSheetis now an internal helper of
StreamMessageInput.StreamThreadListTilespacing with the design-system tokens.StreamAppBar.reactionsCountText).sample_appChannelDetailSheet(long-press a row in the channel list, orthe slidable "more" affordance). Rows: View Info, Pin / Unpin Chat,
Mute / Unmute User, Block User, Leave Group, Delete. Mute and Block
show only on 1-to-1 channels.
ContactDetailSheet'sBlockUseraction wired toclient.blockUser(was a no-op snackbar).
ContactDetailActionandChannelDetailActionsubtypes now carrythe target user as a payload field.
client_extensions.dartwithisUserMuted/userMutedStream/isUserBlocked/userBlockedStreamhelpers.Test plan
ChannelDetailSheetopens with theexpected rows for the channel type.
tap Unmute → flips back.
disappears from the list.
("conversation" vs "group").
disappears next to the channel name in the title row.
ChatInfoScreen(1:1) orGroupInfoScreen(group).regen.
🤖 Generated with Claude Code