diff --git a/migrations/redesign/README.md b/migrations/redesign/README.md index 5f1e3a45f8..80e0d6e655 100644 --- a/migrations/redesign/README.md +++ b/migrations/redesign/README.md @@ -12,6 +12,29 @@ The redesigned components aim to provide: Each component migration guide contains specific details about the changes and how to migrate. +## Package Architecture: Stream Core + Stream Chat + +The redesigned components are split across two packages: + +- **`stream_core_flutter`** — product-agnostic primitives: avatars, badges, buttons, sheets, app bars, message layout, reaction picker, and theming tokens. None of these classes depend on chat-domain models (`Channel`, `Message`, `User`). +- **`stream_chat_flutter`** — chat-domain wrappers that take chat models and delegate visuals to the primitives. + +Most core types are re-exported from `stream_chat_flutter`, so a single `package:stream_chat_flutter/stream_chat_flutter.dart` import is usually enough. When a migration doc says a type "moved to `stream_core_flutter`," it has **not** been removed — it has been re-homed to the primitives layer and (in most cases) re-exported here so existing imports keep working. + +### Core primitive ↔ chat wrapper + +| Core (in `stream_core_flutter`) | Chat wrapper (in `stream_chat_flutter`) | +| -------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| `StreamAvatar`, `StreamAvatarGroup`, `StreamAvatarStack` | `StreamUserAvatar`, `StreamUserAvatarGroup`, `StreamUserAvatarStack`, `StreamChannelAvatar` | +| `StreamReactionPicker` (takes `StreamReactionPickerItem`s) | `StreamMessageReactionPicker` (takes `Message`) | +| `StreamReactions` (takes `ReactionGroup`s) | `StreamMessageReactions` (takes `Message`) | + +### Frequently confused names + +- `StreamMessageLayout` — **not** `StreamMessagePlacement`. +- `kStreamToolbarHeight` — **not** `kStreamHeaderHeight`. +- `StreamAvatarTheme` / `StreamAvatarThemeData` — live in `stream_core_flutter`, re-exported from `stream_chat_flutter`; existing imports continue to work. + ## Theming The redesigned components use `StreamTheme` for theming. If no `StreamTheme` is provided, a default theme is automatically created based on `Theme.of(context).brightness` (light or dark mode). @@ -118,23 +141,24 @@ class MyCustomButton extends StatelessWidget { ## Components -| Component | Migration Guide | -|-----------|-----------------| -| Stream Avatar | [stream_avatar.md](stream_avatar.md) | -| Channel List Item | [channel_list_item.md](channel_list_item.md) | -| Message Actions | [message_actions.md](message_actions.md) | -| Reaction Picker / Reactions | [reaction_picker.md](reaction_picker.md) | -| Image CDN & Thumbnails | [image_cdn.md](image_cdn.md) | -| Message Widget & Message List | [message_widget.md](message_widget.md) | -| Message Composer | [message_composer.md](message_composer.md) | -| Unread Indicator | [unread_indicator.md](unread_indicator.md) | -| Unread Indicator Button | [unread_indicator_button.md](unread_indicator_button.md) | -| Reaction List & Detail Sheet | [reaction_list.md](reaction_list.md) | -| Audio Waveform Theme | [audio_theme.md](audio_theme.md) | -| Attachments & Polls | [attachments_and_polls.md](attachments_and_polls.md) | -| Media Viewer (Full-screen Media) | [media_viewer.md](media_viewer.md) | -| Headers, Icons & Configuration | [headers_and_icons.md](headers_and_icons.md) | -| Localizations | [localizations.md](localizations.md) | +| Component | Migration Guide | +| -------------------------------- | -------------------------------------------------------- | +| Stream Avatar | [stream_avatar.md](stream_avatar.md) | +| Channel List Item | [channel_list_item.md](channel_list_item.md) | +| Message Actions | [message_actions.md](message_actions.md) | +| Reaction Picker / Reactions | [reaction_picker.md](reaction_picker.md) | +| Image CDN & Thumbnails | [image_cdn.md](image_cdn.md) | +| Message Widget | [message_widget.md](message_widget.md) | +| Message List | [message_list.md](message_list.md) | +| Message Composer | [message_composer.md](message_composer.md) | +| Unread Indicator | [unread_indicator.md](unread_indicator.md) | +| Unread Indicator Button | [unread_indicator_button.md](unread_indicator_button.md) | +| Reaction List & Detail Sheet | [reaction_list.md](reaction_list.md) | +| Audio Waveform Theme | [audio_theme.md](audio_theme.md) | +| Attachments & Polls | [attachments_and_polls.md](attachments_and_polls.md) | +| Media Viewer (Full-screen Media) | [media_viewer.md](media_viewer.md) | +| Headers, Icons & Configuration | [headers_and_icons.md](headers_and_icons.md) | +| Localizations | [localizations.md](localizations.md) | ## Need Help? diff --git a/migrations/redesign/attachments_and_polls.md b/migrations/redesign/attachments_and_polls.md index b992e136c5..738f888689 100644 --- a/migrations/redesign/attachments_and_polls.md +++ b/migrations/redesign/attachments_and_polls.md @@ -16,6 +16,7 @@ This guide covers the migration for the redesigned attachment components, voice - [StreamUrlAttachment → StreamLinkPreviewAttachment](#streamurlattachment--streamlinkpreviewattachment) - [StreamVoiceRecordingAttachmentPlaylist](#streamvoicerecordingattachmentplaylist) - [Attachment Builders](#attachment-builders) +- [Removed Attachment Widgets](#removed-attachment-widgets) - [StreamPollInteractorThemeData](#streampollinteractorthemedata) - [Poll Dialogs → Poll Sheets](#poll-dialogs--poll-sheets) - [Poll Creator Dialog → Sheet](#poll-creator-dialog--sheet) @@ -26,20 +27,20 @@ This guide covers the migration for the redesigned attachment components, voice ## Quick Reference -| Symbol | Change | -|--------|--------| -| All attachment widgets | Adopt **Props + Component Factory** pattern (see below) | -| `StreamUrlAttachment` | **Renamed** to `StreamLinkPreviewAttachment` | -| `UrlAttachmentBuilder` | **Renamed** to `LinkPreviewAttachmentBuilder` | -| `shape` parameter | **Removed** from all attachment widgets | -| `constraints` parameter | Changed from required to optional on most attachments | -| `StreamImageAttachment` thumbnail params | `imageThumbnailSize`, `imageThumbnailResizeType`, `imageThumbnailCropType` replaced by `ImageResize? resize` | -| `StreamFileAttachment.backgroundColor` | **Removed** | -| `StreamPollInteractorThemeData` | Fully redesigned — old properties removed, new structured theme | -| `StreamPollOptionsDialog` / `StreamPollResultsDialog` / `StreamPollOptionVotesDialog` / `StreamPollCommentsDialog` | **Renamed** to `...Sheet` and now presented as modal bottom sheets | -| `StreamPollOptionsDialogThemeData` / `StreamPollResultsDialogThemeData` / `StreamPollOptionVotesDialogThemeData` / `StreamPollCommentsDialogThemeData` | **Renamed** to `...SheetThemeData` and fully redesigned | -| `StreamPollCreatorDialog` / `StreamPollCreatorFullScreenDialog` | **Replaced** by `StreamPollCreatorSheet` — see [Poll Creator Dialog → Sheet](#poll-creator-dialog--sheet) | -| `StreamVoiceRecordingAttachmentThemeData` | Fully redesigned — old properties removed, new design-token-based theme | +| Symbol | Change | +| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | +| All attachment widgets | Adopt **Props + Component Factory** pattern (see below) | +| `StreamUrlAttachment` | **Renamed** to `StreamLinkPreviewAttachment` | +| `UrlAttachmentBuilder` | **Renamed** to `LinkPreviewAttachmentBuilder` | +| `shape` parameter | **Removed** from all attachment widgets | +| `constraints` parameter | Changed from required to optional on most attachments | +| `StreamImageAttachment` thumbnail params | `imageThumbnailSize`, `imageThumbnailResizeType`, `imageThumbnailCropType` replaced by `ImageResize? resize` | +| `StreamFileAttachment.backgroundColor` | **Removed** | +| `StreamPollInteractorThemeData` | Fully redesigned — old properties removed, new structured theme | +| `StreamPollOptionsDialog` / `StreamPollResultsDialog` / `StreamPollOptionVotesDialog` / `StreamPollCommentsDialog` | **Renamed** to `...Sheet` and now presented as modal bottom sheets | +| `StreamPollOptionsDialogThemeData` / `StreamPollResultsDialogThemeData` / `StreamPollOptionVotesDialogThemeData` / `StreamPollCommentsDialogThemeData` | **Renamed** to `...SheetThemeData` and fully redesigned | +| `StreamPollCreatorDialog` / `StreamPollCreatorFullScreenDialog` | **Replaced** by `StreamPollCreatorSheet` — see [Poll Creator Dialog → Sheet](#poll-creator-dialog--sheet) | +| `StreamVoiceRecordingAttachmentThemeData` | Fully redesigned — old properties removed, new design-token-based theme | --- @@ -279,15 +280,79 @@ StreamVoiceRecordingAttachmentPlaylist( ## Attachment Builders -| Old Builder | New Builder | -|------------|------------| -| `UrlAttachmentBuilder` | `LinkPreviewAttachmentBuilder` | -| All others | Same name, updated constructor signatures | +| Old Builder | New Builder | +| ---------------------- | ----------------------------------------- | +| `UrlAttachmentBuilder` | `LinkPreviewAttachmentBuilder` | +| All others | Same name, updated constructor signatures | All builders have had their `shape` and `padding` parameters removed. If you subclass any attachment builder, update to use the new Props-based attachment constructors. --- +## Removed Attachment Widgets + +The following attachment-related widgets have been removed. Replace any direct references with the listed alternatives. + +| Removed Widget | Replacement | +| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `StreamFileAttachmentThumbnail` | Use `StreamImageAttachmentThumbnail` for images, `StreamVideoAttachmentThumbnail` for videos, or `StreamFileTypeIcon.fromMimeType(...)` for generic file icons | +| `StreamAttachmentUploadStateBuilder.successBuilder` | Removed — was unreachable in practice (the success state immediately transitions to the rendered attachment) | +| `AttachmentModalSheet` | Use the redesigned inline attachment picker — see [v10-migration.md](../v10-migration.md#attachment-picker) for the new sealed-class option types and the inline `StreamTabbedAttachmentPicker` / `StreamSystemAttachmentPicker` widgets | + +**`StreamFileAttachmentThumbnail` migration:** + +**Before:** + +```dart +StreamFileAttachmentThumbnail( + file: attachment.file, + fit: BoxFit.cover, +) +``` + +**After:** + +```dart +// Pick the right thumbnail based on the attachment type +if (attachment.type == AttachmentType.image) { + StreamImageAttachmentThumbnail(image: attachment, fit: BoxFit.cover); +} else if (attachment.type == AttachmentType.video) { + StreamVideoAttachmentThumbnail(video: attachment, fit: BoxFit.cover); +} else { + StreamFileTypeIcon.fromMimeType(attachment.mimeType); +} +``` + +**`StreamAttachmentUploadStateBuilder.successBuilder` migration:** + +**Before:** + +```dart +StreamAttachmentUploadStateBuilder( + message: message, + attachment: attachment, + failedBuilder: (context, errorMessage) => MyFailedWidget(), + preparingBuilder: (context) => MyPreparingWidget(), + inProgressBuilder: (context, sent, total) => MyProgressWidget(), + successBuilder: (context) => MySuccessWidget(), // removed +) +``` + +**After:** + +```dart +StreamAttachmentUploadStateBuilder( + message: message, + attachment: attachment, + failedBuilder: (context, errorMessage) => MyFailedWidget(), + preparingBuilder: (context) => MyPreparingWidget(), + inProgressBuilder: (context, sent, total) => MyProgressWidget(), + // successBuilder removed — the attachment renders directly once uploaded +) +``` + +--- + ## StreamPollInteractorThemeData ### Breaking Changes: @@ -341,24 +406,24 @@ Each sheet is now full-size with a small fixed peek from the screen top, and the **Renamed symbols:** -| Old | New | -|-----|-----| -| `StreamPollOptionsDialog` | `StreamPollOptionsSheet` | -| `StreamPollResultsDialog` | `StreamPollResultsSheet` | -| `StreamPollOptionVotesDialog` | `StreamPollOptionVotesSheet` | -| `StreamPollCommentsDialog` | `StreamPollCommentsSheet` | -| `showStreamPollOptionsDialog` | `showStreamPollOptionsSheet` | -| `showStreamPollResultsDialog` | `showStreamPollResultsSheet` | -| `showStreamPollOptionVotesDialog` | `showStreamPollOptionVotesSheet` | -| `showStreamPollCommentsDialog` | `showStreamPollCommentsSheet` | -| `StreamPollOptionsDialogTheme` / `StreamPollOptionsDialogThemeData` | `StreamPollOptionsSheetTheme` / `StreamPollOptionsSheetThemeData` | -| `StreamPollResultsDialogTheme` / `StreamPollResultsDialogThemeData` | `StreamPollResultsSheetTheme` / `StreamPollResultsSheetThemeData` | +| Old | New | +| --------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| `StreamPollOptionsDialog` | `StreamPollOptionsSheet` | +| `StreamPollResultsDialog` | `StreamPollResultsSheet` | +| `StreamPollOptionVotesDialog` | `StreamPollOptionVotesSheet` | +| `StreamPollCommentsDialog` | `StreamPollCommentsSheet` | +| `showStreamPollOptionsDialog` | `showStreamPollOptionsSheet` | +| `showStreamPollResultsDialog` | `showStreamPollResultsSheet` | +| `showStreamPollOptionVotesDialog` | `showStreamPollOptionVotesSheet` | +| `showStreamPollCommentsDialog` | `showStreamPollCommentsSheet` | +| `StreamPollOptionsDialogTheme` / `StreamPollOptionsDialogThemeData` | `StreamPollOptionsSheetTheme` / `StreamPollOptionsSheetThemeData` | +| `StreamPollResultsDialogTheme` / `StreamPollResultsDialogThemeData` | `StreamPollResultsSheetTheme` / `StreamPollResultsSheetThemeData` | | `StreamPollOptionVotesDialogTheme` / `StreamPollOptionVotesDialogThemeData` | `StreamPollOptionVotesSheetTheme` / `StreamPollOptionVotesSheetThemeData` | -| `StreamPollCommentsDialogTheme` / `StreamPollCommentsDialogThemeData` | `StreamPollCommentsSheetTheme` / `StreamPollCommentsSheetThemeData` | -| `StreamChatThemeData.pollOptionsDialogTheme` | `StreamChatThemeData.pollOptionsSheetTheme` | -| `StreamChatThemeData.pollResultsDialogTheme` | `StreamChatThemeData.pollResultsSheetTheme` | -| `StreamChatThemeData.pollOptionVotesDialogTheme` | `StreamChatThemeData.pollOptionVotesSheetTheme` | -| `StreamChatThemeData.pollCommentsDialogTheme` | `StreamChatThemeData.pollCommentsSheetTheme` | +| `StreamPollCommentsDialogTheme` / `StreamPollCommentsDialogThemeData` | `StreamPollCommentsSheetTheme` / `StreamPollCommentsSheetThemeData` | +| `StreamChatThemeData.pollOptionsDialogTheme` | `StreamChatThemeData.pollOptionsSheetTheme` | +| `StreamChatThemeData.pollResultsDialogTheme` | `StreamChatThemeData.pollResultsSheetTheme` | +| `StreamChatThemeData.pollOptionVotesDialogTheme` | `StreamChatThemeData.pollOptionVotesSheetTheme` | +| `StreamChatThemeData.pollCommentsDialogTheme` | `StreamChatThemeData.pollCommentsSheetTheme` | Each `...Sheet` widget also exposes a new optional `scrollController` parameter which `show*Sheet` wires to the enclosing `DraggableScrollableSheet`. @@ -368,35 +433,35 @@ All four theme data classes dropped their app-bar styling slots (`appBarElevatio **`StreamPollOptionsSheetThemeData`** -| Removed | Replacement | -|---------|-------------| -| `pollTitleTextStyle`, `pollTitleDecoration` | `questionStyle` (`StreamPollQuestionStyle`) | -| `pollOptionsListViewDecoration` | `optionsCardStyle` (`StreamPollCardStyle`) | -| — | New: `contentPadding`, `sectionSpacing`, `optionsItemSpacing`, `optionStyle` (`StreamPollOptionStyle`), `sheetHeaderStyle` | +| Removed | Replacement | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `pollTitleTextStyle`, `pollTitleDecoration` | `questionStyle` (`StreamPollQuestionStyle`) | +| `pollOptionsListViewDecoration` | `optionsCardStyle` (`StreamPollCardStyle`) | +| — | New: `contentPadding`, `sectionSpacing`, `optionsItemSpacing`, `optionStyle` (`StreamPollOptionStyle`), `sheetHeaderStyle` | **`StreamPollResultsSheetThemeData`** -| Removed | Replacement | -|---------|-------------| -| `pollTitleTextStyle`, `pollTitleDecoration` | `questionStyle` (`StreamPollQuestionStyle`) | -| `pollOptionsDecoration`, `pollOptionsWinnerDecoration`, `pollOptionsTextStyle`, `pollOptionsWinnerTextStyle`, `pollOptionsVoteCountTextStyle`, `pollOptionsWinnerVoteCountTextStyle`, `pollOptionsShowAllVotesButtonStyle` | `optionStyle` (`StreamPollOptionVotesStyle`) — bundles card chrome, text styles, winner trophy color/size, `footerDividerColor`, and `footerButtonStyle` for the "View all" action | -| — | New: `contentPadding`, `sectionSpacing`, `optionsItemSpacing`, `totalVoteCountTextStyle` (for the new total-vote-count footer), `sheetHeaderStyle` | +| Removed | Replacement | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `pollTitleTextStyle`, `pollTitleDecoration` | `questionStyle` (`StreamPollQuestionStyle`) | +| `pollOptionsDecoration`, `pollOptionsWinnerDecoration`, `pollOptionsTextStyle`, `pollOptionsWinnerTextStyle`, `pollOptionsVoteCountTextStyle`, `pollOptionsWinnerVoteCountTextStyle`, `pollOptionsShowAllVotesButtonStyle` | `optionStyle` (`StreamPollOptionVotesStyle`) — bundles card chrome, text styles, `winnerIconColor`/`winnerIconSize`, `footerDividerColor`, and `footerButtonStyle` for the "View all" action | +| — | New: `contentPadding`, `sectionSpacing`, `optionsItemSpacing`, `totalVoteCountTextStyle` (for the new total-vote-count footer), `sheetHeaderStyle` | **`StreamPollOptionVotesSheetThemeData`** -| Removed | Replacement | -|---------|-------------| +| Removed | Replacement | +| ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | `pollOptionVoteCountTextStyle`, `pollOptionWinnerVoteCountTextStyle`, `pollOptionVoteItemBackgroundColor`, `pollOptionVoteItemBorderRadius` | `optionStyle` (reuses `StreamPollOptionVotesStyle` from the results sheet) | -| — | New: `contentPadding`, `sheetHeaderStyle` | +| — | New: `contentPadding`, `sheetHeaderStyle` | Per-vote tile styling will ship later under a dedicated `StreamPollVoteListTile` theme. **`StreamPollCommentsSheetThemeData`** -| Removed | Replacement | -|---------|-------------| +| Removed | Replacement | +| --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `pollCommentItemBackgroundColor`, `pollCommentItemBorderRadius`, `updateYourCommentButtonStyle` (`ButtonStyle`) | `commentStyle` (reuses `StreamPollOptionVotesStyle`) — only its `cardStyle`, `footerDividerColor` and `footerButtonStyle` are consumed | -| — | New: `contentPadding`, `itemSpacing`, `sheetHeaderStyle` | +| — | New: `contentPadding`, `itemSpacing`, `sheetHeaderStyle` | ### Migration: @@ -450,11 +515,11 @@ The poll creator UI has been unified into a single bottom-sheet surface. The pre **Renamed / removed symbols:** -| Old | New | -|-----|-----| -| `showStreamPollCreatorDialog` | `showStreamPollCreatorSheet` | -| `StreamPollCreatorDialog` | `StreamPollCreatorSheet` | -| `StreamPollCreatorFullScreenDialog` | `StreamPollCreatorSheet` | +| Old | New | +| ----------------------------------- | ---------------------------- | +| `showStreamPollCreatorDialog` | `showStreamPollCreatorSheet` | +| `StreamPollCreatorDialog` | `StreamPollCreatorSheet` | +| `StreamPollCreatorFullScreenDialog` | `StreamPollCreatorSheet` | `showStreamPollCreatorSheet` keeps the `poll`, `config`, and `padding` parameters from the old dialog helper; the dialog-specific parameters (`barrierDismissible`, `barrierColor`, `barrierLabel`, `useSafeArea`, `useRootNavigator`, `routeSettings`, `anchorPoint`, `traversalEdgeBehavior`) are no longer accepted — the sheet always presents as a modal bottom sheet over a safe area. @@ -577,3 +642,6 @@ StreamVoiceRecordingAttachmentThemeData( - [ ] Update `StreamVoiceRecordingAttachmentThemeData` — see property mapping above - [ ] If using custom attachment builders, update to new Props-based constructors - [ ] If using component factory, register custom builders via `streamChatComponentBuilders` +- [ ] Replace `StreamFileAttachmentThumbnail` with `StreamImageAttachmentThumbnail` / `StreamVideoAttachmentThumbnail` or `StreamFileTypeIcon.fromMimeType(...)` +- [ ] Remove the `successBuilder` argument from `StreamAttachmentUploadStateBuilder` usages +- [ ] Replace any `AttachmentModalSheet` usage with the redesigned attachment picker (see [v10-migration.md](../v10-migration.md#attachment-picker)) diff --git a/migrations/redesign/audio_theme.md b/migrations/redesign/audio_theme.md index c03430f7ac..337ebcf21f 100644 --- a/migrations/redesign/audio_theme.md +++ b/migrations/redesign/audio_theme.md @@ -21,13 +21,13 @@ The audio waveform theme types and the `StreamAudioWaveform` / `StreamAudioWavef ## What Changed -| Item | Before | After | -|------|--------|-------| -| `StreamAudioWaveformTheme` | Defined in `stream_chat_flutter` | Moved to `stream_core_flutter`; no longer in `StreamChatThemeData` | -| `StreamAudioWaveformSliderTheme` | Defined in `stream_chat_flutter` | Moved to `stream_core_flutter`; no longer in `StreamChatThemeData` | -| `StreamAudioWaveform` widget | In `stream_chat_flutter` | Re-exported from `stream_core_flutter` via `stream_chat_flutter` | -| `StreamAudioWaveformSlider` widget | In `stream_chat_flutter` | Re-exported from `stream_core_flutter` via `stream_chat_flutter` | -| Theming entry point | `StreamChatThemeData.audioWaveformTheme` / `.audioWaveformSliderTheme` | `StreamTheme` (via `MaterialApp.theme.extensions`) | +| Item | Before | After | +| ---------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------ | +| `StreamAudioWaveformTheme` | Defined in `stream_chat_flutter` | Moved to `stream_core_flutter`; no longer in `StreamChatThemeData` | +| `StreamAudioWaveformSliderTheme` | Defined in `stream_chat_flutter` | Deleted; thumb/track styling is now configured via `StreamAudioWaveformThemeData` | +| `StreamAudioWaveform` widget | In `stream_chat_flutter` | Re-exported from `stream_core_flutter` via `stream_chat_flutter` | +| `StreamAudioWaveformSlider` widget | In `stream_chat_flutter` | Re-exported from `stream_core_flutter` via `stream_chat_flutter` | +| Theming entry point | `StreamChatThemeData.audioWaveformTheme` / `.audioWaveformSliderTheme` | `StreamAudioWaveformThemeData` via `StreamTheme` (added to `MaterialApp.theme.extensions`) | --- @@ -44,9 +44,6 @@ StreamChat( waveColor: Colors.blue, playedWaveColor: Colors.blueAccent, ), - audioWaveformSliderTheme: StreamAudioWaveformSliderThemeData( - thumbColor: Colors.blue, - ), ), child: ..., ) @@ -75,7 +72,7 @@ MaterialApp( ## Migration Checklist - [ ] Remove any `StreamChatThemeData.audioWaveformTheme` usages -- [ ] Remove any `StreamChatThemeData.audioWaveformSliderTheme` usages +- [ ] Remove any `StreamChatThemeData.audioWaveformSliderTheme` usages — `StreamAudioWaveformSliderTheme` was deleted; thumb/track styling is now configured via `StreamAudioWaveformThemeData` - [ ] Remove `audioWaveformTheme` and `audioWaveformSliderTheme` from any `StreamChatThemeData.copyWith()` calls — these parameters no longer exist and will cause a compile error - [ ] Move audio waveform color / style customizations into a `StreamTheme` extension on `MaterialApp` - [ ] Import paths for `StreamAudioWaveform` and `StreamAudioWaveformSlider` remain the same (`package:stream_chat_flutter/stream_chat_flutter.dart`) diff --git a/migrations/redesign/channel_list_item.md b/migrations/redesign/channel_list_item.md index 919beccb8b..559eb5cf9d 100644 --- a/migrations/redesign/channel_list_item.md +++ b/migrations/redesign/channel_list_item.md @@ -11,22 +11,23 @@ This guide covers the migration for the redesigned channel list item components - [Customizing Slots](#customizing-slots) - [Low-level Presentational Component](#low-level-presentational-component) - [Theme Migration](#theme-migration) +- [Removed Widgets](#removed-widgets) - [Migration Checklist](#migration-checklist) --- ## Quick Reference -| Old | New | -|-----|-----| -| `StreamChannelListTile` | `StreamChannelListItem` | -| Constructor props: `leading`, `title`, `subtitle`, `trailing` | `StreamChannelListItemProps` (via `StreamComponentFactory`) | -| `tileColor`, `visualDensity`, `contentPadding` | Removed — use `StreamChannelListItemThemeData` | -| `selectedTileColor` | Removed — use `StreamChannelListItemThemeData.backgroundColor` | -| `unreadIndicatorBuilder` | Removed | -| `StreamChannelPreviewThemeData` | `StreamChannelListItemThemeData` | -| `StreamChannelPreviewTheme.of(context)` | `StreamChannelListItemTheme.of(context)` | -| `StreamChatThemeData.channelPreviewTheme` | `StreamChatThemeData.channelListItemTheme` | +| Old | New | +| ------------------------------------------------------------- | -------------------------------------------------------------- | +| `StreamChannelListTile` | `StreamChannelListItem` | +| Constructor props: `leading`, `title`, `subtitle`, `trailing` | `StreamChannelListItemProps` (via `StreamComponentFactory`) | +| `tileColor`, `visualDensity`, `contentPadding` | Removed — use `StreamChannelListItemThemeData` | +| `selectedTileColor` | Removed — use `StreamChannelListItemThemeData.backgroundColor` | +| `unreadIndicatorBuilder` | Removed | +| `StreamChannelPreviewThemeData` | `StreamChannelListItemThemeData` | +| `StreamChannelPreviewTheme.of(context)` | `StreamChannelListItemTheme.of(context)` | +| `StreamChatThemeData.channelPreviewTheme` | `StreamChatThemeData.channelListItemTheme` | --- @@ -42,7 +43,7 @@ The old `StreamChannelListTile` accepted all slot widgets directly in its constr - `contentPadding` removed - `selectedTileColor` removed - `unreadIndicatorBuilder` removed -- `sendingIndicatorBuilder` removed from constructor — pass via `StreamChannelListItemProps` +- `sendingIndicatorBuilder` removed from constructor — override via `StreamComponentFactory` (see [Customizing Slots](#customizing-slots)) ### Migration @@ -128,27 +129,27 @@ StreamChannelListTile( ## Theme Migration -`StreamChannelPreviewThemeData` has been replaced by `StreamChannelListItemThemeData`. Additionally, the `StreamChannelPreviewTheme` inherited widget itself is deprecated — replace it with `StreamChannelListItemTheme`. +`StreamChannelPreviewThemeData` has been replaced by `StreamChannelListItemThemeData`. Additionally, the `StreamChannelPreviewTheme` inherited widget itself has been removed — replace it with `StreamChannelListItemTheme`. ### Property Mapping -| Old (`StreamChannelPreviewThemeData`) | New (`StreamChannelListItemThemeData`) | -|---------------------------------------|----------------------------------------| -| `titleStyle` | `titleStyle` | -| `subtitleStyle` | `subtitleStyle` | -| `lastMessageAtStyle` | `timestampStyle` | -| `avatarTheme` | Removed — use `StreamAvatarThemeData` directly | -| `unreadCounterColor` | Removed — use `StreamBadgeNotificationThemeData` | -| `indicatorIconSize` | Removed | -| `lastMessageAtFormatter` | Removed from theme — pass to `ChannelLastMessageDate(formatter: ...)` | +| Old (`StreamChannelPreviewThemeData`) | New (`StreamChannelListItemThemeData`) | +| ------------------------------------- | --------------------------------------------------------------------- | +| `titleStyle` | `titleStyle` | +| `subtitleStyle` | `subtitleStyle` | +| `lastMessageAtStyle` | `timestampStyle` | +| `avatarTheme` | Removed — use `StreamAvatarThemeData` directly | +| `unreadCounterColor` | Removed — use `StreamBadgeNotificationThemeData` | +| `indicatorIconSize` | Removed | +| `lastMessageAtFormatter` | Removed from theme — pass to `ChannelLastMessageDate(formatter: ...)` | ### New Properties -| Property | Type | Description | -|----------|------|-------------| -| `backgroundColor` | `WidgetStateProperty?` | Background color resolved per state (default, hover, pressed, selected) | -| `borderColor` | `Color?` | Bottom border color | -| `muteIconPosition` | `MuteIconPosition?` | Whether the mute icon appears in `title` or `subtitle` row | +| Property | Type | Description | +| ------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------- | +| `backgroundColor` | `WidgetStateProperty?` | Background color resolved per state (default, hover, pressed, selected) | +| `borderColor` | `Color?` | Bottom border color | +| `attributePosition` | `AttributePosition?` | Where the mute and pin icons appear: `inlineTitle` (in the title row) or `trailingBottom` (in the subtitle row) | ### Global Theme Migration @@ -256,15 +257,53 @@ ChannelLastMessageDate( --- +## Removed Widgets + +The following channel-list-related widgets have been removed. Replace any direct references with the listed alternatives. + +| Removed Widget | Replacement | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | +| `StreamChannelGridView` | Removed — build your own grid using `StreamChannelListController` and `GridView` (the list controller already paginates) | +| `StreamChannelGridTile` | Removed — render a custom tile widget inside your `GridView` | +| `StreamChannelInfoBottomSheet` | Removed — the sample app ships a reference implementation; copy or build your own channel info / details sheet | + +**`StreamChannelGridView` migration sketch:** + +**Before:** + +```dart +StreamChannelGridView( + controller: channelListController, + itemBuilder: (context, channels, index, defaultTile) => defaultTile, +) +``` + +**After:** + +```dart +PagedValueGridView( + controller: channelListController, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2), + itemBuilder: (context, channels, index) { + final channel = channels[index]; + return MyChannelGridTile(channel: channel); + }, +) +``` + +--- + ## Migration Checklist - [ ] Replace `StreamChannelListTile` with `StreamChannelListItem` - [ ] Remove `tileColor`, `visualDensity`, `contentPadding`, `selectedTileColor`, `unreadIndicatorBuilder` parameters - [ ] Move slot customization (`leading`, `title`, `subtitle`, `trailing`) to `StreamComponentFactory` -- [ ] Replace `StreamChannelPreviewTheme` inherited widget with `StreamChannelListItemTheme` — `StreamChannelPreviewTheme` is `@Deprecated` and will be removed in a future release +- [ ] Replace `StreamChannelPreviewTheme` inherited widget with `StreamChannelListItemTheme` — `StreamChannelPreviewTheme` has been removed - [ ] Replace `StreamChatThemeData.channelPreviewTheme` with `StreamChatThemeData.channelListItemTheme` - [ ] Rename `lastMessageAtStyle` to `timestampStyle` - [ ] Move `lastMessageAtFormatter` from theme to `ChannelLastMessageDate(formatter: ...)` - [ ] Replace `tileColor`/`selectedTileColor` with `StreamChannelListItemThemeData.backgroundColor` using `WidgetStateProperty` - [ ] Replace `unreadCounterColor` with `StreamBadgeNotificationThemeData` - [ ] Replace `avatarTheme` in `StreamChannelPreviewThemeData` with `StreamAvatarThemeData` on the avatar widget directly +- [ ] Remove any `StreamChannelGridView` / `StreamChannelGridTile` usage — build your own grid using `StreamChannelListController` with a `PagedValueGridView` +- [ ] Remove any `StreamChannelInfoBottomSheet` usage — copy the sample app's channel info sheet or build your own diff --git a/migrations/redesign/headers_and_icons.md b/migrations/redesign/headers_and_icons.md index 053a9f36c9..8061b40dbd 100644 --- a/migrations/redesign/headers_and_icons.md +++ b/migrations/redesign/headers_and_icons.md @@ -10,6 +10,8 @@ This guide covers several cross-cutting API changes in the Stream Chat Flutter S - [Header Widgets](#header-widgets) - [StreamChat.componentBuilders](#streamchatcomponentbuilders) - [StreamChatConfigurationData New Fields](#streamchatconfigurationdata-new-fields) +- [StreamChatCore Behavior Changes](#streamchatcore-behavior-changes) +- [Removed Widgets](#removed-widgets) - [Migration Checklist](#migration-checklist) --- @@ -32,8 +34,8 @@ StreamSvgIcon(icon: StreamSvgIcons.copy, color: Colors.red, size: 24) **After:** ```dart -Icon(context.streamIcons.reply20) -Icon(context.streamIcons.copy20, color: Colors.red, size: 24) +Icon(context.streamIcons.reply) +Icon(context.streamIcons.copy, color: Colors.red, size: 24) ``` `context.streamIcons` is a `StreamIcons` extension on `BuildContext` — it reads from the nearest `StreamTheme` in the widget tree. @@ -41,70 +43,70 @@ Icon(context.streamIcons.copy20, color: Colors.red, size: 24) ### Icon Name Mapping | Old (`StreamSvgIcons.*`) | New (`context.streamIcons.*`) | -|--------------------------|-------------------------------| -| `arrowRight` | `arrowRight20` | -| `attach` | `attachment20` | -| `award` | `trophy20` | -| `camera` | `camera20` | -| `check` | `checkmark20` | -| `checkAll` | `checks20` | -| `checkSend` | `checkmark20` | -| `circleUp` | `arrowUp20` | -| `close` | `xmark20` | -| `closeSmall` | `xmark16` | -| `contacts` | `users20` | -| `copy` | `copy20` | -| `delete` | `delete20` | -| `down` | `chevronDown20` | -| `download` | `download20` | -| `edit` | `edit20` | -| `emptyCircleRight` | `chevronRight20` | -| `error` | `exclamationCircleFill20` | -| `eye` | `eyeFill20` | -| `files` | `file20` | -| `flag` | `flag20` | -| `grid` | `gallery20` | -| `group` | `users20` | -| `left` | `chevronLeft20` | -| `lightning` | `bolt20` | -| `link` | `link20` | -| `lock` | `lock20` | -| `mentions` | `mention20` | -| `menuPoint` | `more20` | -| `message` | `messageBubble20` | -| `messageUnread` | `notification20` | -| `mic` | `voice20` | -| `mute` | `mute20` | -| `notification` | `bell20` | -| `pause` | `pauseFill20` | -| `penWrite` | `edit20` | -| `pictures` | `image20` | -| `pin` | `pin20` | -| `play` | `playFill20` | -| `polls` | `poll20` | -| `record` | `video20` | -| `reload` | `refresh20` | -| `reply` | `reply20` | -| `retry` | `retry20` | -| `right` | `chevronRight20` | -| `save` | `save20` | -| `search` | `search20` | -| `send` | `send20` | -| `sendMessage` | `send20` | -| `share` | `export20` | -| `shareArrow` | `share20` | -| `smile` | `emoji20` | -| `stop` | `stopFill20` | -| `threadReply` | `thread20` | -| `time` | `clock20` | -| `up` | `chevronUp20` | -| `user` | `user20` | -| `userAdd` | `userAdd20` | -| `userDelete` | `userRemove20` | -| `userRemove` | `userRemove20` | -| `userSettings` | `userCheck20` | -| `videoCall` | `videoFill20` | -| `volumeUp` | `audio20` | +| ------------------------ | ----------------------------- | +| `arrowRight` | `arrowRight` | +| `attach` | `attachment` | +| `award` | `trophy` | +| `camera` | `camera` | +| `check` | `checkmark` | +| `checkAll` | `checks` | +| `checkSend` | `checkmark` | +| `circleUp` | `arrowUp` | +| `close` | `xmark` | +| `closeSmall` | `xmark` | +| `contacts` | `users` | +| `copy` | `copy` | +| `delete` | `delete` | +| `down` | `chevronDown` | +| `download` | `download` | +| `edit` | `edit` | +| `emptyCircleRight` | `chevronRight` | +| `error` | `exclamationCircleFill` | +| `eye` | `eyeFill` | +| `files` | `file` | +| `flag` | `flag` | +| `grid` | `gallery` | +| `group` | `users` | +| `left` | `chevronLeft` | +| `lightning` | `bolt` | +| `link` | `link` | +| `lock` | `lock` | +| `mentions` | `mention` | +| `menuPoint` | `more` | +| `message` | `messageBubble` | +| `messageUnread` | `notification` | +| `mic` | `voice` | +| `mute` | `mute` | +| `notification` | `bell` | +| `pause` | `pauseFill` | +| `penWrite` | `edit` | +| `pictures` | `image` | +| `pin` | `pin` | +| `play` | `playFill` | +| `polls` | `poll` | +| `record` | `video` | +| `reload` | `refresh` | +| `reply` | `reply` | +| `retry` | `retry` | +| `right` | `chevronRight` | +| `save` | `save` | +| `search` | `search` | +| `send` | `send` | +| `sendMessage` | `send` | +| `share` | `export` | +| `shareArrow` | `share` | +| `smile` | `emoji` | +| `stop` | `stopFill` | +| `threadReply` | `thread` | +| `time` | `clock` | +| `up` | `chevronUp` | +| `user` | `user` | +| `userAdd` | `userAdd` | +| `userDelete` | `userRemove` | +| `userRemove` | `userRemove` | +| `userSettings` | `userCheck` | +| `videoCall` | `videoFill` | +| `volumeUp` | `audio` | The following icons have been **removed with no equivalent** in the new set: `cloudDownload`, `lolReaction`, `loveReaction`, `moon`, `settings`, `thumbsDownReaction`, `thumbsUpReaction`, `wutReaction`. @@ -113,17 +115,16 @@ The following icons have been **removed with no equivalent** in the new set: ## Header Widgets -All four chat headers — `StreamChannelHeader`, `StreamChannelListHeader`, -`StreamThreadHeader`, and `StreamGalleryHeader` — have been rebuilt on top -of the new design system's `StreamAppBar`. They now share a single slot -model (`leading` / `title` / `subtitle` / `trailing`) and a single theme -type (`StreamAppBarThemeData`), replacing the legacy -`AppBar`-style API. +Three chat headers — `StreamChannelHeader`, `StreamChannelListHeader`, and +`StreamThreadHeader` — have been rebuilt on top of the new design system's +`StreamAppBar`. They now share a single slot model (`leading` / `title` / +`subtitle` / `trailing`) and a single theme type (`StreamAppBarThemeData`), +replacing the legacy `AppBar`-style API. ### What changed across all headers * **New layout primitive.** The headers render a [`StreamAppBar`] with a - fixed 72-px height (`kStreamHeaderHeight`) instead of Material's + fixed 72-px height (`kStreamToolbarHeight`) instead of Material's `kToolbarHeight` (56 px). Pass the header directly to `Scaffold.appBar` as before — it implements `PreferredSizeWidget`. * **Slot model.** All four headers now expose `leading`, `title`, @@ -135,9 +136,9 @@ type (`StreamAppBarThemeData`), replacing the legacy to insert a default back button. To override, pass `leading:` directly; to suppress, pass `automaticallyImplyLeading: false`. * **Theme.** `StreamChannelHeaderThemeData`, `StreamChannelListHeaderThemeData`, - and `StreamGalleryHeaderThemeData` are deleted. The corresponding + and `StreamThreadHeaderThemeData` are deleted. The corresponding accessors on `StreamChatThemeData` (`channelHeaderTheme`, - `channelListHeaderTheme`, `threadHeaderTheme`, `galleryHeaderTheme`) + `channelListHeaderTheme`, `threadHeaderTheme`) now return [`StreamAppBarThemeData`]. * **Per-instance overrides.** A new `style: StreamAppBarStyle?` parameter lets callers override colours / padding / typography for one instance — @@ -149,14 +150,14 @@ type (`StreamAppBarThemeData`), replacing the legacy ### `StreamChannelHeader` -| Old parameter | New equivalent | -|---------------|----------------| -| `showBackButton: false` | `automaticallyImplyLeading: false` | -| `onBackPressed: cb` | `leading: StreamBackButton(onPressed: cb)` | -| `onTitleTap: cb` | `title: GestureDetector(onTap: cb, child: ...)` | -| `onImageTap: cb` | `onChannelAvatarPressed: (channel) => cb()` (or replace `trailing:`) | -| `showTypingIndicator: false` | `subtitle: Text(channel.name)` (or any custom widget) | -| `actions: [a, b]` | `trailing: Row(children: [a, b])` | +| Old parameter | New equivalent | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | +| `showBackButton: false` | `automaticallyImplyLeading: false` | +| `onBackPressed: cb` | `leading: StreamBackButton(onPressed: cb)` | +| `onTitleTap: cb` | `title: GestureDetector(onTap: cb, child: ...)` | +| `onImageTap: cb` | `onChannelAvatarPressed: (channel) => cb()` (or replace `trailing:`) | +| `showTypingIndicator: false` | `subtitle: Text(channel.name)` (or any custom widget) | +| `actions: [a, b]` | `trailing: Row(children: [a, b])` | | `centerTitle`, `elevation`, `bottom`, `bottomOpacity`, `backgroundColor` | Use `style: StreamAppBarStyle(backgroundColor: ...)` for the background; the rest are gone | **Before:** @@ -186,12 +187,12 @@ unread badge; the default trailing is the channel avatar wrapped in a ### `StreamChannelListHeader` -| Old parameter | New equivalent | -|---------------|----------------| -| `titleBuilder: (context, user) => ...` | `title: ...` (a `Widget`) | -| `onUserAvatarTap: cb` | `onUserAvatarPressed: cb` (renamed) | -| `onNewChatButtonTap: cb` | `trailing: StreamButton.icon(icon: Icon(context.streamIcons.plus), onPressed: cb)` | -| `preNavigationCallback`, `leading`, `actions`, `centerTitle`, `elevation`, `backgroundColor` | Removed — see notes below | +| Old parameter | New equivalent | +| -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `titleBuilder: (context, user) => ...` | `title: ...` (a `Widget`) | +| `onUserAvatarTap: cb` | `onUserAvatarPressed: cb` (renamed) | +| `onNewChatButtonTap: cb` | `trailing: StreamButton.icon(icon: Icon(context.streamIcons.plus), onPressed: cb)` | +| `preNavigationCallback`, `leading`, `actions`, `centerTitle`, `elevation`, `backgroundColor` | Removed — see notes below | The leading slot is no longer caller-overridable: the SDK always renders the signed-in user's avatar. When `onUserAvatarPressed` is null the @@ -228,31 +229,41 @@ StreamChannelListHeader( ### `StreamThreadHeader` -| Old parameter | New equivalent | -|---------------|----------------| -| `showBackButton: false` | `automaticallyImplyLeading: false` | -| `onBackPressed: cb` | `leading: StreamBackButton(onPressed: cb)` | -| `onTitleTap: cb` | `title: GestureDetector(onTap: cb, child: ...)` | -| `showTypingIndicator: false` | `subtitle: Text(...)` (or any custom widget) | -| `actions: [a, b]` | `trailing: Row(children: [a, b])` | -| `centerTitle`, `elevation`, `backgroundColor` | Use `style:`; the rest are gone | +| Old parameter | New equivalent | +| --------------------------------------------- | ----------------------------------------------- | +| `showBackButton: false` | `automaticallyImplyLeading: false` | +| `onBackPressed: cb` | `leading: StreamBackButton(onPressed: cb)` | +| `onTitleTap: cb` | `title: GestureDetector(onTap: cb, child: ...)` | +| `showTypingIndicator: false` | `subtitle: Text(...)` (or any custom widget) | +| `actions: [a, b]` | `trailing: Row(children: [a, b])` | +| `centerTitle`, `elevation`, `backgroundColor` | Use `style:`; the rest are gone | The default subtitle is a [`StreamTypingIndicator`] that falls back to the thread's reply count when nobody is typing. -### `StreamGalleryHeader` +### `StreamMediaGalleryPreviewHeader` -| Old parameter | New equivalent | -|---------------|----------------| -| `showBackButton: false` | `automaticallyImplyLeading: false` | -| `onBackPressed: cb` | `leading: StreamBackButton(onPressed: cb)` | -| `onTitleTap: cb` | `title: GestureDetector(onTap: cb, child: ...)` | -| `onImageTap: cb` | `trailing: GestureDetector(onTap: cb, child: ...)` | -| `elevation`, `backgroundColor` | Use `style:`; the rest are gone | +The gallery header was redesigned as `StreamMediaGalleryPreviewHeader`. +It wraps `StreamAppBar` and exposes only `title` and `subtitle` as `Widget?` +slots. All other per-instance overrides (back button, trailing actions, +colours) are handled by the underlying `StreamAppBar` defaults — no +extra parameters are exposed. -`onShowMessage`, `onReplyMessage`, and `attachmentActionsModalBuilder` -are unchanged. The default trailing is still an icon button that opens -the attachment actions modal. +```dart +StreamMediaGalleryPreviewHeader( + title: Text(message.user?.name ?? ''), + subtitle: Text( + context.translations.sentAtText( + date: message.createdAt, + time: message.createdAt, + ), + ), +) +``` + +The back affordance is auto-implied by `StreamAppBar`; there is no +`showBackButton`, `onBackPressed`, `onTitleTap`, `onImageTap`, or +`attachmentActionsModalBuilder` parameter on this widget. ### Theming @@ -280,10 +291,10 @@ a subtree, or pass `style:` directly on a single header instance. ### Header height -`kStreamHeaderHeight` (72 px) is now the canonical header height — +`kStreamToolbarHeight` (72 px) is now the canonical header height — exposed from `package:stream_chat_flutter/stream_chat_flutter.dart`. If you read `kToolbarHeight` (56 px) to size custom chrome that sits next -to a Stream header, switch to `kStreamHeaderHeight` to stay aligned. +to a Stream header, switch to `kStreamToolbarHeight` to stay aligned. --- @@ -298,7 +309,7 @@ to a Stream header, switch to `kStreamHeaderHeight` to stay aligned. StreamComponentFactory( builders: StreamComponentBuilders( extensions: streamChatComponentBuilders( - messageWidget: (context, props) => MyMessage(props: props), + messageItem: (context, props) => MyMessage(props: props), ), ), child: StreamChat( @@ -314,7 +325,7 @@ StreamChat( client: client, componentBuilders: StreamComponentBuilders( extensions: streamChatComponentBuilders( - messageWidget: (context, props) => MyMessage(props: props), + messageItem: (context, props) => MyMessage(props: props), ), ), child: MyApp(), @@ -331,11 +342,11 @@ Three new optional fields have been added to `StreamChatConfigurationData`. Exis ### New Fields -| Field | Type | Default | Description | -|-------|------|---------|-------------| -| `attachmentBuilders` | `List?` | `null` | Custom attachment widget builders. When non-null, these are **prepended** to the SDK's built-in builders so your types are matched first. | -| `reactionType` | `StreamReactionsType?` | `null` | Controls the visual style of the reactions display (e.g. segmented). Falls back to the SDK default when `null`. | -| `reactionPosition` | `StreamReactionsPosition?` | `null` | Controls where reactions appear relative to the message bubble (e.g. header). Falls back to the SDK default when `null`. | +| Field | Type | Default | Description | +| -------------------- | -------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `attachmentBuilders` | `List?` | `null` | Custom attachment widget builders. When non-null, these are **prepended** to the SDK's built-in builders so your types are matched first. | +| `reactionType` | `StreamReactionsType?` | `null` | Controls the visual style of the reactions display (e.g. segmented). Falls back to the SDK default when `null`. | +| `reactionPosition` | `StreamReactionsPosition?` | `null` | Controls where reactions appear relative to the message bubble (e.g. header). Falls back to the SDK default when `null`. | > **Note:** The `imageCDN` field was also added to `StreamChatConfigurationData`. It is covered in the [Image CDN & Thumbnails](image_cdn.md) guide. @@ -366,6 +377,69 @@ StreamChat( --- +## StreamChatCore Behavior Changes + +Two behavior changes affect how the chat client interacts with the connection lifecycle. Neither changes the public API surface, but both can affect apps that watch channels outside of the SDK's list controllers. + +### `client.recoverStateOnReconnect = false` on mount + +`StreamChatCore` now sets `client.recoverStateOnReconnect = false` when it mounts. The SDK's list controllers (`StreamChannelListController`, `StreamMessageListController`, etc.) drive their own refresh from the `EventType.connectionRecovered` event — this avoids a duplicate `queryChannels` round-trip and the historical event-replay flicker on reactions, polls, and quoted messages when reconnecting. + +If you watch a `Channel` directly (outside any list controller), subscribe to `connectionRecovered` yourself and call `channel.watch()` to refresh: + +```dart +late final StreamSubscription _sub; + +@override +void initState() { + super.initState(); + _sub = client.on(EventType.connectionRecovered).listen((_) { + channel.watch(); + }); +} + +@override +void dispose() { + _sub.cancel(); + super.dispose(); +} +``` + +If you do not want this behaviour, you can override it after mounting: + +```dart +client.recoverStateOnReconnect = true; +``` + +### `StreamChat.backgroundKeepAlive` default reduced + +The default `StreamChat.backgroundKeepAlive` has been reduced from **1 minute** to **15 seconds**. This covers quick app-switches and notification-shade checks while closing the WebSocket cleanly before the server's 35-second read timeout. To restore the previous behaviour, pass an explicit value: + +```dart +StreamChat( + client: client, + backgroundKeepAlive: const Duration(minutes: 1), + child: MyApp(), +) +``` + +--- + +## Removed Widgets + +The following miscellaneous widgets have been removed. Replace any direct references with the listed alternatives. + +| Removed Widget | Replacement | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `StreamMessageSearchGridView` | Removed — use `StreamMessageSearchListView` (still available), or build your own grid using `StreamMessageSearchListController` + `PagedValueGridView` | + +For composer-related removals, see [message_composer.md](message_composer.md#removed-widgets). +For attachment-related removals, see [attachments_and_polls.md](attachments_and_polls.md#removed-attachment-widgets). +For channel-list-related removals, see [channel_list_item.md](channel_list_item.md#removed-widgets). +For message / reactions-related removals, see [message_widget.md](message_widget.md#removed-widgets) and [reaction_list.md](reaction_list.md#removed-widgets). + +--- + ## Migration Checklist - [ ] Replace all `StreamSvgIcon(icon: StreamSvgIcons.*)` with `Icon(context.streamIcons.*)` using the mapping table above @@ -377,7 +451,10 @@ StreamChat( - [ ] Replace `titleBuilder: (context, user) => ...` with `title: ...` (a `Widget`) - [ ] Replace `actions: [a, b]` with `trailing: Row(children: [a, b])` — only one trailing slot is exposed - [ ] Drop `centerTitle`, `elevation`, `bottomOpacity`, `bottom`, and `backgroundColor` from header callsites; use `style: StreamAppBarStyle(backgroundColor: ...)` if you need to override the background -- [ ] Update theme overrides: `StreamChannelHeaderThemeData` / `StreamChannelListHeaderThemeData` / `StreamGalleryHeaderThemeData` are deleted — switch to `StreamAppBarThemeData` -- [ ] If you sized custom chrome to `kToolbarHeight` next to a Stream header, switch to `kStreamHeaderHeight` (72 px) +- [ ] Update theme overrides: `StreamChannelHeaderThemeData` / `StreamChannelListHeaderThemeData` / `StreamThreadHeaderThemeData` are deleted — switch to `StreamAppBarThemeData` +- [ ] If you sized custom chrome to `kToolbarHeight` next to a Stream header, switch to `kStreamToolbarHeight` (72 px) - [ ] Optionally move `StreamComponentFactory` wrapping into the `componentBuilders` parameter on `StreamChat` - [ ] Use the new `attachmentBuilders`, `reactionType`, and `reactionPosition` fields on `StreamChatConfigurationData` if you need custom attachment rendering or global reaction style control +- [ ] If you watch a `Channel` outside any list controller, subscribe to `EventType.connectionRecovered` and call `channel.watch()` (the SDK no longer auto-recovers on reconnect when `StreamChatCore` is mounted) +- [ ] If you relied on the old `StreamChat.backgroundKeepAlive` default of 1 minute, pass `backgroundKeepAlive: const Duration(minutes: 1)` explicitly — the new default is 15 seconds +- [ ] Remove any `StreamMessageSearchGridView` usage — switch to `StreamMessageSearchListView` or build your own grid diff --git a/migrations/redesign/image_cdn.md b/migrations/redesign/image_cdn.md index bb341447a3..9498be0933 100644 --- a/migrations/redesign/image_cdn.md +++ b/migrations/redesign/image_cdn.md @@ -17,12 +17,12 @@ This guide covers the migration for the redesigned image CDN handling and thumbn ## Quick Reference -| Component | Key Changes | -|-----------|-------------| -| [**StreamImageCDN**](#streamimagecdn) | New class replacing `getResizedImageUrl` String extension (stable cache keys now via `StreamImageCDN.cacheKey()`) | -| [**StreamImageAttachmentThumbnail**](#streamimageattachmentthumbnail) | `thumbnailSize`, `thumbnailResizeType`, `thumbnailCropType` → single `resize` parameter | -| [**StreamMediaAttachmentThumbnail**](#streammediaattachmentthumbnail) | `thumbnailSize`, `thumbnailResizeType`, `thumbnailCropType` → single `resize` parameter | -| [**StreamImageAttachment**](#streamimageattachment) | `imageThumbnailSize`, `imageThumbnailResizeType`, `imageThumbnailCropType` → single `resize` parameter | +| Component | Key Changes | +| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| [**StreamImageCDN**](#streamimagecdn) | New class replacing `getResizedImageUrl` String extension (stable cache keys now via `StreamImageCDN.cacheKey()`) | +| [**StreamImageAttachmentThumbnail**](#streamimageattachmentthumbnail) | `thumbnailSize`, `thumbnailResizeType`, `thumbnailCropType` → single `resize` parameter | +| [**StreamMediaAttachmentThumbnail**](#streammediaattachmentthumbnail) | `thumbnailSize`, `thumbnailResizeType`, `thumbnailCropType` → single `resize` parameter | +| [**StreamImageAttachment**](#streamimageattachment) | `imageThumbnailSize`, `imageThumbnailResizeType`, `imageThumbnailCropType` → single `resize` parameter | --- @@ -77,7 +77,7 @@ class MyImageCDN extends StreamImageCDN { StreamChat( client: client, - config: StreamChatConfigurationData( + streamChatConfigData: StreamChatConfigurationData( imageCDN: MyImageCDN(), ), child: ..., diff --git a/migrations/redesign/localizations.md b/migrations/redesign/localizations.md index 3ec863b077..9cc66dc2cf 100644 --- a/migrations/redesign/localizations.md +++ b/migrations/redesign/localizations.md @@ -8,6 +8,7 @@ This guide covers the breaking changes to `Translations` and `StreamChatLocaliza - [New Required Abstract Members](#new-required-abstract-members) - [Renamed Abstract Members](#renamed-abstract-members) +- [Changed Method Signatures](#changed-method-signatures) - [Changed Default String Values](#changed-default-string-values) - [Migration Checklist](#migration-checklist) @@ -177,11 +178,11 @@ String get endVoteConfirmationMessage => The following members were renamed. If you have overridden them in a custom `Translations` subclass you must update the override signature; otherwise the compiler will flag the old name as an unknown member. -| Old member | New member | Notes | -|------------|------------|-------| -| `String get questionsLabel` | `String questionLabel({bool isPlural = false})` | Now mirrors `optionLabel({bool isPlural})`. Pass `isPlural: true` to get the previous plural value, or call with no arguments for the singular "Question" label used in the poll results/options dialogs. | -| `String get endVoteConfirmationText` | `String get endVoteConfirmationTitle` | Renamed to reflect that this string is the dialog title rather than body text; see also the new default value in [Changed Default String Values](#changed-default-string-values). | -| `String get slowModeOnLabel` | `String slowModeOnLabel(int cooldownTimeOut)` | Now takes the remaining cooldown in seconds so the placeholder can render a live countdown (default English: `'Slow mode, wait ${cooldownTimeOut}s\u2026'`). The composer text input is also disabled and the trailing send button shows the remaining seconds while slow mode is active. | +| Old member | New member | Notes | +| ------------------------------------ | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `String get questionsLabel` | `String questionLabel({bool isPlural = false})` | Now mirrors `optionLabel({bool isPlural})`. Pass `isPlural: true` to get the previous plural value, or call with no arguments for the singular "Question" label used in the poll results/options dialogs. | +| `String get endVoteConfirmationText` | `String get endVoteConfirmationTitle` | Renamed to reflect that this string is the dialog title rather than body text; see also the new default value in [Changed Default String Values](#changed-default-string-values). | +| `String get slowModeOnLabel` | `String slowModeOnLabel(int cooldownTimeOut)` | Now takes the remaining cooldown in seconds so the placeholder can render a live countdown (default English: `'Slow mode, wait ${cooldownTimeOut}s\u2026'`). The composer text input is also disabled and the trailing send button shows the remaining seconds while slow mode is active. | Example migration: @@ -202,20 +203,56 @@ If you previously read `translations.questionsLabel`, replace it with `translati --- +## Changed Method Signatures + +### `attachmentsUploadProgressText` + +The `remaining:` parameter has been renamed to `completed:` and now represents the number of attachments that **have** been uploaded (rather than the number left). The total remains unchanged. + +**Before:** + +```dart +@override +String attachmentsUploadProgressText({ + required int remaining, + required int total, +}) => 'Uploaded ${total - remaining} of $total ...'; + +// Caller +translations.attachmentsUploadProgressText(remaining: 2, total: 5) +``` + +**After:** + +```dart +@override +String attachmentsUploadProgressText({ + required int completed, + required int total, +}) => 'Uploaded $completed of $total ...'; + +// Caller +translations.attachmentsUploadProgressText(completed: 3, total: 5) +``` + +If your custom `Translations` subclass overrides this method, update both the parameter name and the body (subtract is no longer needed — `completed` is the value you want to display directly). + +--- + ## Changed Default String Values The following strings changed their default English value in `DefaultTranslations`. If you have not overridden them in a custom `Translations` subclass you do not need to do anything, but you should review whether the new values are appropriate for your app. -| Getter | Old default | New default | -|--------|-------------|-------------| -| `threadReplyLabel` | `'Thread Reply'` | `'Thread'` | -| `threadReplyCountText(int)` | `'$count Thread Replies'` | `count == 1 ? '1 reply' : '$count replies'` | -| `alsoSendAsDirectMessageLabel` | `'Also send as direct message'` | `'Also send in Channel'` | -| `addMoreFilesLabel` | `'Add more files'` | `'Add more'` | -| `emptyMessagesText` | `'There are no messages currently'` | `'No messages yet'` | -| `writeAMessageLabel` | `'Write a message'` | `'Send a message'` | -| `endVoteConfirmationTitle` (was `endVoteConfirmationText`) | `'Are you sure you want to end the vote?'` | `'End This Poll?'` | -| `endVoteLabel` | `'End Vote'` | `'End Poll'` | +| Getter | Old default | New default | +| ---------------------------------------------------------- | ------------------------------------------ | ------------------------------------------- | +| `threadReplyLabel` | `'Thread Reply'` | `'Thread'` | +| `threadReplyCountText(int)` | `'$count Thread Replies'` | `count == 1 ? '1 reply' : '$count replies'` | +| `alsoSendAsDirectMessageLabel` | `'Also send as direct message'` | `'Also send in Channel'` | +| `addMoreFilesLabel` | `'Add more files'` | `'Add more'` | +| `emptyMessagesText` | `'There are no messages currently'` | `'No messages yet'` | +| `writeAMessageLabel` | `'Write a message'` | `'Send a message'` | +| `endVoteConfirmationTitle` (was `endVoteConfirmationText`) | `'Are you sure you want to end the vote?'` | `'End This Poll?'` | +| `endVoteLabel` | `'End Vote'` | `'End Poll'` | If your app overrides these in a `Translations` subclass, your custom values are unaffected. @@ -224,8 +261,9 @@ If your app overrides these in a `Translations` subclass, your custom values are ## Migration Checklist - [ ] Search your codebase for any class that `extends Translations` or `extends DefaultTranslations` -- [ ] Add implementations for all 35 new abstract members listed above — the compiler will flag missing ones +- [ ] Add implementations for all 38 new abstract members listed above — the compiler will flag missing ones - [ ] Update the signature of any `questionsLabel` override to `questionLabel({bool isPlural = false})`, and replace any call to `translations.questionsLabel` with `translations.questionLabel(isPlural: true)` - [ ] Rename any `endVoteConfirmationText` override (and consumer) to `endVoteConfirmationTitle` - [ ] Update the signature of any `slowModeOnLabel` override from `String get slowModeOnLabel` to `String slowModeOnLabel(int cooldownTimeOut)`, and update consumers to pass the cooldown seconds (e.g. `translations.slowModeOnLabel(cooldownTimeOut)`) +- [ ] Update any `attachmentsUploadProgressText` override and call site — rename `remaining:` to `completed:` and adjust the body / argument to pass the number already uploaded (no more `total - remaining`) - [ ] Review the changed default string values and decide whether to keep the new defaults or override them to preserve the old text diff --git a/migrations/redesign/media_viewer.md b/migrations/redesign/media_viewer.md index c5713a12ab..7663dacdc8 100644 --- a/migrations/redesign/media_viewer.md +++ b/migrations/redesign/media_viewer.md @@ -2,7 +2,7 @@ The full-screen media viewer and its thumbnail companion have been redesigned and split into two widgets — both built on the design system's `StreamMediaViewer`, `StreamAppBar`, and `StreamBottomAppBar` chrome. The legacy `StreamFullScreenMedia` (and its desktop/builder/stub variants) and the related `StreamMediaListView` / `StreamImageGallery` flow have all been replaced. -This guide also covers the related theme cleanups (`StreamGalleryFooterThemeData`, `StreamChatThemeData.galleryHeaderTheme`, `StreamChatThemeData.galleryFooterTheme`, `StreamAvatarThemeData`) and the removal of the `onShowMessage` / `attachmentActionsModalBuilder` callbacks from the message widget and message list view. +This guide also covers the related theme cleanups (`StreamGalleryFooterThemeData`, `StreamChatThemeData.galleryHeaderTheme`, `StreamChatThemeData.galleryFooterTheme`) and the removal of the `onShowMessage` / `attachmentActionsModalBuilder` callbacks from the message widget and message list view. It also notes that `StreamAvatarThemeData` has moved to `stream_core_flutter` (it was not removed) and is re-exported from `stream_chat_flutter`, so existing imports continue to work. --- @@ -23,19 +23,19 @@ This guide also covers the related theme cleanups (`StreamGalleryFooterThemeData ## Quick Reference -| Old | New | -|-----|-----| -| `StreamFullScreenMedia` / `StreamFullScreenMediaBuilder` / `FullScreenMediaWidget` / `FullScreenMediaDesktop` | `StreamMediaGalleryPreview` (single widget, all platforms) | -| `StreamAttachmentPackage` | `StreamMediaGalleryAttachment` | -| `StreamGalleryHeader` | `StreamMediaGalleryPreviewHeader` | -| `StreamGalleryFooter` | `StreamMediaGalleryPreviewFooter` | -| `VideoPackage` / `DesktopVideoPackage` / `GalleryNavigationItem` | **Removed from the public API** — each preview page now owns its own player state internally | -| *(none)* | `StreamMediaGallery` — **new** thumbnail-grid companion | -| `StreamMessageItem.onShowMessage` / `attachmentActionsModalBuilder` | **Removed** | -| `StreamMessageListView.onShowMessage` / `attachmentActionsModalBuilder` | **Removed** | -| `StreamGalleryFooterThemeData`, `StreamChatThemeData.imageFooterTheme` / `galleryFooterTheme` / `galleryHeaderTheme` | **Removed** | -| `StreamAvatarThemeData` | **Removed** — was unused | -| `Translations.photosAndVideosLabel` | **New** — used by the footer's thumbnail-grid sheet header | +| Old | New | +| -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `StreamFullScreenMedia` / `StreamFullScreenMediaBuilder` / `FullScreenMediaWidget` / `FullScreenMediaDesktop` | `StreamMediaGalleryPreview` (single widget, all platforms) | +| `StreamAttachmentPackage` | `StreamMediaGalleryAttachment` | +| `StreamGalleryHeader` | `StreamMediaGalleryPreviewHeader` | +| `StreamGalleryFooter` | `StreamMediaGalleryPreviewFooter` | +| `VideoPackage` / `DesktopVideoPackage` / `GalleryNavigationItem` | **Removed from the public API** — each preview page now owns its own player state internally | +| *(none)* | `StreamMediaGallery` — **new** thumbnail-grid companion | +| `StreamMessageItem.onShowMessage` / `attachmentActionsModalBuilder` | **Removed** | +| `StreamMessageListView.onShowMessage` / `attachmentActionsModalBuilder` | **Removed** | +| `StreamGalleryFooterThemeData`, `StreamChatThemeData.imageFooterTheme` / `galleryFooterTheme` / `galleryHeaderTheme` | **Removed** | +| `StreamAvatarThemeData` | **Moved** — now lives in `stream_core_flutter`, re-exported from `stream_chat_flutter`; existing imports continue to work | +| `Translations.photosAndVideosLabel` | **New** — used by the footer's thumbnail-grid sheet header | --- @@ -288,12 +288,12 @@ If you relied on either callback, replace the gallery preview via the component The following theme types and `StreamChatThemeData` fields have been removed: -| Removed | Notes | -|---------|-------| -| `StreamGalleryFooterThemeData` | The new footer is themed via `StreamBottomAppBarThemeData` from the design system. | -| `StreamChatThemeData.galleryFooterTheme` / `imageFooterTheme` (named param) | Footer themeing now flows through `StreamBottomAppBarThemeData`. | -| `StreamChatThemeData.galleryHeaderTheme` | Header themeing now flows through `StreamAppBarThemeData`. | -| `StreamAvatarThemeData` | Was unused after the avatar redesign. Use `StreamUserAvatarThemeData` from `stream_core_flutter` to theme avatars globally. | +| Removed | Notes | +| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| `StreamGalleryFooterThemeData` | The new footer is themed via `StreamBottomAppBarThemeData` from the design system. | +| `StreamChatThemeData.galleryFooterTheme` / `imageFooterTheme` (named param) | Footer themeing now flows through `StreamBottomAppBarThemeData`. | +| `StreamChatThemeData.galleryHeaderTheme` | Header themeing now flows through `StreamAppBarThemeData`. | +| `StreamAvatarThemeData` | The type itself was **not** removed. It moved to `stream_core_flutter` and remains in active use; it is re-exported from `stream_chat_flutter` so existing imports continue to work. | The full-screen page background and chrome bands themselves are themed via `StreamMediaViewerThemeData` (from `stream_core_flutter`, re-exported here). @@ -310,5 +310,5 @@ The full-screen page background and chrome bands themselves are themed via `Stre - [ ] Drop usages of `VideoPackage`, `DesktopVideoPackage`, `GalleryNavigationItem`, `FullScreenMediaWidget`, `FullScreenMediaDesktop`. - [ ] Drop `StreamMessageItem.onShowMessage` / `attachmentActionsModalBuilder`, `StreamMessageListView.onShowMessage` / `attachmentActionsModalBuilder` from any constructors. - [ ] Drop `StreamChatThemeData.galleryHeaderTheme`, `galleryFooterTheme` and `imageFooterTheme:` named-parameter usages. -- [ ] Drop `StreamAvatarThemeData` references. +- [ ] No action needed for `StreamAvatarThemeData` imports — the type moved to `stream_core_flutter` but is re-exported from `stream_chat_flutter`, so existing imports continue to work. - [ ] Optionally adopt `StreamMediaGallery` as a thumbnail grid for channel-level media listings. diff --git a/migrations/redesign/message_actions.md b/migrations/redesign/message_actions.md index 9b8d907108..51731a5a52 100644 --- a/migrations/redesign/message_actions.md +++ b/migrations/redesign/message_actions.md @@ -21,29 +21,29 @@ This guide covers the migration for the redesigned message action components in ## Quick Reference -| Symbol | Change | -|--------|--------| -| `StreamMessageAction` | **Removed** — replaced by `StreamContextMenuAction` | -| `StreamMessageActionItem` | **Removed** — rendering built into `StreamContextMenuAction` | -| `StreamMessageActionsModal.onActionTap` | **Removed** — use `onTap` per-action or await the dialog return value | -| `StreamMessageActionsModal.messageActions` | **Type changed**: `List` → `List` | -| `StreamMessageActionsModal.reverse` | **Removed** — use `alignment: AlignmentGeometry?` | -| `StreamMessageActionsModal.reactionPickerBuilder` | **Removed** — use `showReactionPicker: bool` | -| `StreamMessageReactionsModal` | **Deleted** — use `ReactionDetailSheet` (see [reaction_list.md](reaction_list.md)) | -| `StreamMessageReactionsModal.onReactionPicked` | **Removed** — await the dialog return value (`SelectReaction`) | -| `ModeratedMessageActionsModal.onActionTap` | **Removed** — use `onTap` per-action or await the dialog return value | -| `ModeratedMessageActionsModal.messageActions` | **Type changed**: `List` → `List` | -| `StreamMessageItem.customActions` | **Removed** — replaced by `actionsBuilder` (`MessageActionsBuilder?`) | -| `StreamMessageItem.onCustomActionTap` | **Removed** — use `onTap` directly on each `StreamContextMenuAction` in `actionsBuilder` | -| `CustomMessageAction` | **Removed** — no longer needed; custom actions use `onTap` directly | -| `OnMessageActionTap` | **Removed** — no longer needed | -| `StreamMessageItem.actionsBuilder` | **New** — `MessageActionsBuilder?` for the normal long-press menu | -| `StreamMessageActionsBuilder.buildActions` | **Changed**: return type `List`, `customActions` param **removed** | -| `StreamMessageActionsBuilder.buildBouncedErrorActions` | **Return type changed**: `List` → `List` | -| `MessageActionsBuilder` | **New typedef** — `List Function(BuildContext, List>)` | -| `StreamContextMenu` | **New** — exported from `stream_core_flutter` | -| `StreamContextMenuAction` | **New** — exported from `stream_core_flutter` | -| `StreamContextMenuSeparator` | **New** — exported from `stream_core_flutter` | +| Symbol | Change | +| ------------------------------------------------------ | ------------------------------------------------------------------------------------------- | +| `StreamMessageAction` | **Removed** — replaced by `StreamContextMenuAction` | +| `StreamMessageActionItem` | **Removed** — rendering built into `StreamContextMenuAction` | +| `StreamMessageActionsModal.onActionTap` | **Removed** — use `onTap` per-action or await the dialog return value | +| `StreamMessageActionsModal.messageActions` | **Type changed**: `List` → `List` | +| `StreamMessageActionsModal.reverse` | **Removed** — use `alignment: AlignmentGeometry?` | +| `StreamMessageActionsModal.reactionPickerBuilder` | **Removed** — use `showReactionPicker: bool` | +| `StreamMessageReactionsModal` | **Deleted** — use `ReactionDetailSheet` (see [reaction_list.md](reaction_list.md)) | +| `StreamMessageReactionsModal.onReactionPicked` | **Removed** — await the dialog return value (`SelectReaction`) | +| `ModeratedMessageActionsModal.onActionTap` | **Removed** — await the dialog return value (`MessageAction?`) | +| `ModeratedMessageActionsModal.messageActions` | **Type changed**: `List` → `List` | +| `StreamMessageItem.customActions` | **Removed** — replaced by `actionsBuilder` (`MessageActionsBuilder?`) | +| `StreamMessageItem.onCustomActionTap` | **Removed** — use `onTap` directly on each `StreamContextMenuAction` in `actionsBuilder` | +| `CustomMessageAction` | **Removed** — no longer needed; custom actions use `onTap` directly | +| `OnMessageActionTap` | **Removed** — no longer needed | +| `StreamMessageItem.actionsBuilder` | **New** — `MessageActionsBuilder?` for the normal long-press menu | +| `StreamMessageActionsBuilder.buildActions` | **Changed**: return type `List`, `customActions` param **removed** | +| `StreamMessageActionsBuilder.buildBouncedErrorActions` | **Return type changed**: `List` → `List` | +| `MessageActionsBuilder` | **New typedef** — `List Function(BuildContext, List>)` | +| `StreamContextMenu` | **New** — exported from `stream_core_flutter` | +| `StreamContextMenuAction` | **New** — exported from `stream_core_flutter` | +| `StreamContextMenuSeparator` | **New** — exported from `stream_core_flutter` | > **Note:** `MessageAction` and all its built-in subclasses (`SelectReaction`, `CopyMessage`, `DeleteMessage`, etc.) are **unchanged**. `CustomMessageAction` (the escape-hatch subclass) has been **removed** — it was only needed for the old `onCustomActionTap` dispatch pattern. @@ -81,7 +81,7 @@ StreamMessageAction( ```dart StreamContextMenuAction( value: QuotedReply(message: message), - leading: Icon(context.streamIcons.arrowShareLeft), + leading: Icon(context.streamIcons.reply), label: Text(context.translations.replyLabel), ) // The caller receives QuotedReply via the Future returned by showStreamDialog. @@ -91,7 +91,7 @@ StreamContextMenuAction( ```dart StreamContextMenuAction( value: QuotedReply(message: message), - leading: Icon(context.streamIcons.arrowShareLeft), + leading: Icon(context.streamIcons.reply), label: Text(context.translations.replyLabel), onTap: () => onReply(message), // called after the route is dismissed ) @@ -99,19 +99,19 @@ StreamContextMenuAction( ### Property mapping -| `StreamMessageAction` | `StreamContextMenuAction` | -|-----------------------|--------------------------| -| `action: T` | `value: T?` | -| `title: Widget?` | `label: Widget` (required) | -| `leading: Widget?` | `leading: Widget?` | -| `isDestructive: bool` | `isDestructive: bool` (or use `.destructive` constructor) | -| `iconColor: Color?` | Controlled via `StreamContextMenuActionTheme` | -| `titleTextColor: Color?` | Controlled via `StreamContextMenuActionTheme` | -| `titleTextStyle: TextStyle?` | Controlled via `StreamContextMenuActionTheme` | -| `backgroundColor: Color?` | Controlled via `StreamContextMenuActionTheme` | -| — | `onTap: VoidCallback?` (new) | -| — | `trailing: Widget?` (new) | -| — | `enabled: bool` (new) | +| `StreamMessageAction` | `StreamContextMenuAction` | +| ---------------------------- | --------------------------------------------------------- | +| `action: T` | `value: T?` | +| `title: Widget?` | `label: Widget` (required) | +| `leading: Widget?` | `leading: Widget?` | +| `isDestructive: bool` | `isDestructive: bool` (or use `.destructive` constructor) | +| `iconColor: Color?` | Controlled via `StreamContextMenuActionTheme` | +| `titleTextColor: Color?` | Controlled via `StreamContextMenuActionTheme` | +| `titleTextStyle: TextStyle?` | Controlled via `StreamContextMenuActionTheme` | +| `backgroundColor: Color?` | Controlled via `StreamContextMenuActionTheme` | +| — | `onTap: VoidCallback?` (new) | +| — | `trailing: Widget?` (new) | +| — | `enabled: bool` (new) | > **Important:** > - **`label` is now required** — `title: Widget?` was optional in `StreamMessageAction`; `label: Widget` is a required, non-nullable parameter in `StreamContextMenuAction`. Any call site that omitted `title` will fail to compile; you must supply a non-null `label` widget (typically a `Text`). @@ -251,12 +251,9 @@ StreamMessageReactionsModal( **After:** ```dart -final action = await showStreamDialog( +final action = await ReactionDetailSheet.show( context: context, - builder: (_) => StreamMessageReactionsModal( - message: message, - messageWidget: messageWidget, - ), + message: message, ); if (action is SelectReaction) { @@ -273,7 +270,7 @@ if (action is SelectReaction) { ### Breaking Changes -- `onActionTap: OnMessageActionTap?` parameter **removed** — move handling to `onTap` on each action or await the dialog return value +- `onActionTap: OnMessageActionTap?` parameter **removed** — move handling to awaiting the dialog return value - `messageActions` parameter type changed from `List` to `List` ### Migration @@ -303,29 +300,10 @@ ModeratedMessageActionsModal( ) ``` -**After (onTap per-action):** -```dart -ModeratedMessageActionsModal( - message: message, - messageActions: [ - StreamContextMenuAction( - value: ResendMessage(message: message), - label: Text(context.translations.sendAnywayLabel), - onTap: () => _resend(message), - ), - StreamContextMenuAction( - value: EditMessage(message: message), - label: Text(context.translations.editMessageLabel), - ), - StreamContextMenuAction.destructive( - value: HardDeleteMessage(message: message), - label: Text(context.translations.deleteMessageLabel), - ), - ], -) -``` - **After (await return value):** + +> **Important:** `ModeratedMessageActionsModal` renders actions as `AdaptiveDialogAction` buttons — it calls `Navigator.pop(context, action.props.value)` when an item is tapped and does **not** invoke `action.props.onTap`. Dispatch must be handled by awaiting the `Future` returned by `showStreamDialog`. + ```dart final action = await showStreamDialog( context: context, @@ -435,11 +413,11 @@ StreamMessageItem( Both static methods now return `List` instead of `List`. Additionally, the `customActions` parameter of `buildActions` has been **removed** — appending custom actions is now handled by `StreamMessageItem.actionsBuilder`. -| Method / Parameter | Old type | New type | -|--------------------|----------|----------| -| `buildActions` return | `List` | `List` | -| `buildBouncedErrorActions` return | `List` | `List` | -| `buildActions(customActions:)` | `Iterable?` | **Removed** | +| Method / Parameter | Old type | New type | +| --------------------------------- | -------------------------------- | ------------------------------- | +| `buildActions` return | `List` | `List` | +| `buildBouncedErrorActions` return | `List` | `List` | +| `buildActions(customActions:)` | `Iterable?` | **Removed** | ### Migration @@ -477,7 +455,7 @@ A top-level function from `package:stream_chat_flutter/stream_chat_flutter.dart` - **Re-wraps `StreamChatTheme`** across the route boundary so the theme is available inside the dialog even when `useRootNavigator: true` - **Applies a blur + scale transition** for a consistent Stream look -- **Returns `Future`** — the value passed to `Navigator.pop` inside the dialog, which is how `StreamMessageActionsModal`, `StreamMessageReactionsModal`, and `ModeratedMessageActionsModal` deliver the selected action back to the caller +- **Returns `Future`** — the value passed to `Navigator.pop` inside the dialog, which is how `StreamMessageActionsModal` and `ModeratedMessageActionsModal` deliver the selected action back to the caller ```dart // Replace showDialog with showStreamDialog when presenting Stream modals: @@ -515,7 +493,7 @@ StreamContextMenuAction( // Destructive action StreamContextMenuAction.destructive( value: DeleteMessage(message: message), - leading: Icon(context.streamIcons.trashBin), + leading: Icon(context.streamIcons.delete), label: Text('Delete'), ) ``` @@ -569,7 +547,7 @@ StreamContextMenu( - [ ] Remove all `StreamMessageActionItem` usages - [ ] Remove `onActionTap` from `StreamMessageActionsModal`; handle via per-action `onTap` or await the dialog return value - [ ] Remove `onReactionPicked` from `StreamMessageReactionsModal`; await a `SelectReaction` return value -- [ ] Remove `onActionTap` from `ModeratedMessageActionsModal`; handle via per-action `onTap` or await the dialog return value +- [ ] Remove `onActionTap` from `ModeratedMessageActionsModal`; await the `MessageAction?` return value of `showStreamDialog` instead - [ ] Replace `StreamMessageItem.customActions` with `actionsBuilder` - [ ] Update `StreamMessageActionsBuilder.buildActions` call sites — return type is now `List` and `customActions` parameter no longer exists - [ ] Update `StreamMessageActionsBuilder.buildBouncedErrorActions` call sites — return type is now `List` diff --git a/migrations/redesign/message_composer.md b/migrations/redesign/message_composer.md index 1900782ffc..ae7fa5d5dc 100644 --- a/migrations/redesign/message_composer.md +++ b/migrations/redesign/message_composer.md @@ -9,8 +9,12 @@ This guide covers the migration for the message composer components in the Strea - [Overview](#overview) - [StreamMessageComposer](#streammessagecomposer) - [StreamChatMessageInput (new)](#streamchatmessageinput-new) +- [StreamMessageComposerController Rename](#streammessagecomposercontroller-rename) +- [StreamMessageComposerInput Split](#streammessagecomposerinput-split) - [Message Input Placeholder API](#message-input-placeholder-api) - [Attachment Customization](#attachment-customization) +- [Removed Widgets](#removed-widgets) +- [Theme Removal: StreamMessageInputThemeData](#theme-removal-streammessageinputthemedata) - [Migration Checklist](#migration-checklist) --- @@ -19,10 +23,10 @@ This guide covers the migration for the message composer components in the Strea There are two distinct composer components with different responsibilities: -| Component | Responsibility | -|-----------|---------------| -| `StreamMessageComposer` | Full-featured widget: handles sending, editing, attachments, autocomplete, mentions, commands, OG previews, voice recording flow, etc. | -| `StreamChatMessageInput` | UI-only component: renders the composer layout using design system primitives. No business logic. | +| Component | Responsibility | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | +| `StreamMessageComposer` | Full-featured widget: handles sending, editing, attachments, autocomplete, mentions, commands, OG previews, voice recording flow, etc. | +| `StreamChatMessageInput` | UI-only component: renders the composer layout using design system primitives. No business logic. | `StreamMessageComposer` wraps `StreamChatMessageInput` for its visual layer. If you are using `StreamMessageComposer` today, it remains the right choice — it is not deprecated. `StreamChatMessageInput` exists for cases where you want to build your own message-sending logic and use the new design system UI. @@ -34,9 +38,9 @@ There are two distinct composer components with different responsibilities: ### Breaking Change: `hideSendAsDm` renamed to `canAlsoSendToChannelFromThread` (logic inverted) -| Old | New | -|-----|-----| -| `hideSendAsDm: true` | `canAlsoSendToChannelFromThread: false` | +| Old | New | +| ----------------------------------- | ---------------------------------------------------- | +| `hideSendAsDm: true` | `canAlsoSendToChannelFromThread: false` | | `hideSendAsDm: false` (old default) | `canAlsoSendToChannelFromThread: true` (new default) | The logic is **inverted**: the old parameter hid the "also send to channel" checkbox when `true`; the new parameter **shows** it when `true`. @@ -90,49 +94,49 @@ Many parameters that existed in `StreamMessageInput` have been removed from `Str These parameters have been removed. The composer layout is now fully owned by `StreamChatMessageInput` and its sub-components, customizable via `StreamComponentFactory`. -| Removed parameter | Migration path | -|-------------------|---------------| -| `maxHeight` | No direct replacement. The text field grows to fit its content without a height cap. | -| `maxLines` | No direct replacement. | -| `minLines` | No direct replacement. | -| `padding` | No direct replacement. Layout is controlled by the design system. | -| `textInputMargin` | No direct replacement. | -| `elevation` | No direct replacement. Visual styling is controlled by the design system theme. | -| `shadow` | No direct replacement. | -| `enableActionAnimation` | Removed. Actions no longer animate in/out. | -| `contentInsertionConfiguration` | Removed. | -| `sendButtonLocation` | Removed. The send button is always placed in the trailing position by the design system layout. | +| Removed parameter | Migration path | +| ------------------------------- | ----------------------------------------------------------------------------------------------- | +| `maxHeight` | No direct replacement. The text field grows to fit its content without a height cap. | +| `maxLines` | No direct replacement. | +| `minLines` | No direct replacement. | +| `padding` | No direct replacement. Layout is controlled by the design system. | +| `textInputMargin` | No direct replacement. | +| `elevation` | No direct replacement. Visual styling is controlled by the design system theme. | +| `shadow` | No direct replacement. | +| `enableActionAnimation` | Removed. Actions no longer animate in/out. | +| `contentInsertionConfiguration` | Removed. | +| `sendButtonLocation` | Removed. The send button is always placed in the trailing position by the design system layout. | #### Action and button parameters These parameters have been removed. To customize buttons and actions in the composer, override the relevant sub-component via `StreamComponentFactory`. -| Removed parameter | Migration path | -|-------------------|---------------| -| `actionsBuilder` | Override `messageComposerLeading` or `messageComposerTrailing` in `StreamComponentFactory`. | -| `spaceBetweenActions` | No direct replacement. | -| `actionsLocation` | Removed. The design system defines a fixed layout. | -| `attachmentButtonBuilder` | Override `messageComposerLeading` in `StreamComponentFactory`. | -| `commandButtonBuilder` | Override `messageComposerInputTrailing` in `StreamComponentFactory`. | -| `sendButtonBuilder` | Override `messageComposerTrailing` in `StreamComponentFactory`. | -| `idleSendIcon` | Override `messageComposerTrailing` in `StreamComponentFactory`. | -| `activeSendIcon` | Override `messageComposerTrailing` in `StreamComponentFactory`. | -| `showCommandsButton` | Override `messageComposerInputTrailing` in `StreamComponentFactory`. | +| Removed parameter | Migration path | +| ------------------------- | ------------------------------------------------------------------------------------------- | +| `actionsBuilder` | Override `messageComposerLeading` or `messageComposerTrailing` in `StreamComponentFactory`. | +| `spaceBetweenActions` | No direct replacement. | +| `actionsLocation` | Removed. The design system defines a fixed layout. | +| `attachmentButtonBuilder` | Override `messageComposerLeading` in `StreamComponentFactory`. | +| `commandButtonBuilder` | Override `messageComposerInputTrailing` in `StreamComponentFactory`. | +| `sendButtonBuilder` | Override `messageComposerTrailing` in `StreamComponentFactory`. | +| `idleSendIcon` | Override `messageComposerTrailing` in `StreamComponentFactory`. | +| `activeSendIcon` | Override `messageComposerTrailing` in `StreamComponentFactory`. | +| `showCommandsButton` | Override `messageComposerInputTrailing` in `StreamComponentFactory`. | #### Attachment builder parameters These parameters have been removed. Attachment rendering in the composer input header is now customizable via `StreamComponentFactory` — see [Attachment Customization](#attachment-customization). -| Removed parameter | Migration path | -|-------------------|---------------| -| `attachmentListBuilder` | Override `messageComposerAttachmentList` in `StreamComponentFactory`. | -| `fileAttachmentListBuilder` | Override `messageComposerAttachmentList` in `StreamComponentFactory`. | -| `mediaAttachmentListBuilder` | Override `messageComposerAttachmentList` in `StreamComponentFactory`. | -| `voiceRecordingAttachmentListBuilder` | Override `messageComposerAttachmentList` in `StreamComponentFactory`. | -| `fileAttachmentBuilder` | Override `messageComposerAttachment` in `StreamComponentFactory`. | -| `mediaAttachmentBuilder` | Override `messageComposerAttachment` in `StreamComponentFactory`. | -| `voiceRecordingAttachmentBuilder` | Override `messageComposerAttachment` in `StreamComponentFactory`. | -| `quotedMessageBuilder` | Override `messageComposerInputHeader` or `messageComposerInput` in `StreamComponentFactory`. | +| Removed parameter | Migration path | +| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | +| `attachmentListBuilder` | Override `messageComposerAttachmentList` in `StreamComponentFactory`. | +| `fileAttachmentListBuilder` | Override `messageComposerAttachmentList` in `StreamComponentFactory`. | +| `mediaAttachmentListBuilder` | Override `messageComposerAttachmentList` in `StreamComponentFactory`. | +| `voiceRecordingAttachmentListBuilder` | Override `messageComposerAttachmentList` in `StreamComponentFactory`. | +| `fileAttachmentBuilder` | Override `messageComposerAttachment` in `StreamComponentFactory`. | +| `mediaAttachmentBuilder` | Override `messageComposerAttachment` in `StreamComponentFactory`. | +| `voiceRecordingAttachmentBuilder` | Override `messageComposerAttachment` in `StreamComponentFactory`. | +| `quotedMessageBuilder` | Override `messageComposerInputHeader` or `messageComposerInput` in `StreamComponentFactory`. | | `quotedMessageAttachmentThumbnailBuilders` | Override `messageComposerInputHeader`, `messageComposerInput`, or `messageComposerAttachment` in `StreamComponentFactory`. | ### Attachment button visibility @@ -151,39 +155,40 @@ Use this when you want the new design system visuals with custom business logic. ### Constructor Parameters -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `onSendPressed` | `VoidCallback` | **required** | Called when the send button is pressed | -| `controller` | `StreamMessageComposerController?` | `null` | Controller for the input; created internally if not provided | -| `onAttachmentButtonPressed` | `VoidCallback?` | `null` | Called when the attachment button is pressed. When `null`, the attachment button is hidden. | -| `isPickerOpen` | `bool` | `false` | Whether the inline attachment picker is currently open | -| `focusNode` | `FocusNode?` | `null` | Focus node for the text field | -| `currentUserId` | `String?` | `null` | Current user's ID | -| `placeholder` | `String?` | `null` | Placeholder text for the input field. `StreamChatMessageInput` is a pure UI component — when wiring it up directly, compute this string yourself (use `MessageInputPlaceholder.resolve(controller)` from the [Message Input Placeholder API](#message-input-placeholder-api) if you want the built-in state machine), or pass `null` for no placeholder. `StreamMessageComposer` resolves it for you reactively from its controller. | -| `audioRecorderController` | `StreamAudioRecorderController?` | `null` | Enables the voice recording UI when provided | -| `sendVoiceRecordingAutomatically` | `bool` | `false` | Sends the voice recording immediately on finish | -| `feedback` | `AudioRecorderFeedback` | `const AudioRecorderFeedback()` | Haptic/audio feedback callbacks for the recording flow | -| `canAlsoSendToChannel` | `bool` | `false` | Shows the "also send to channel" checkbox (used in threads) | -| `onQuotedMessageCleared` | `VoidCallback?` | `null` | Called when the user removes the quoted message in the input header | -| `textInputAction` | `TextInputAction?` | `null` | The keyboard action button type | -| `keyboardType` | `TextInputType?` | `null` | The keyboard type for the text field | -| `textCapitalization` | `TextCapitalization` | `sentences` | Text capitalization behaviour for the text field | -| `autofocus` | `bool` | `false` | Whether the text field should autofocus when built | -| `autocorrect` | `bool` | `true` | Whether autocorrect is enabled | +| Parameter | Type | Default | Description | +| --------------------------------- | ---------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `onSendPressed` | `VoidCallback` | **required** | Called when the send button is pressed | +| `controller` | `StreamMessageComposerController?` | `null` | Controller for the input; created internally if not provided | +| `onAttachmentButtonPressed` | `VoidCallback?` | `null` | Called when the attachment button is pressed. When `null`, the attachment button is hidden. | +| `isPickerOpen` | `bool` | `false` | Whether the inline attachment picker is currently open | +| `focusNode` | `FocusNode?` | `null` | Focus node for the text field | +| `currentUserId` | `String?` | `null` | Current user's ID | +| `placeholder` | `String?` | `null` | Placeholder text for the input field. `StreamChatMessageInput` is a pure UI component — when wiring it up directly, compute this string yourself (use `MessageInputPlaceholder.resolve(controller)` from the [Message Input Placeholder API](#message-input-placeholder-api) if you want the built-in state machine), or pass `null` for no placeholder. `StreamMessageComposer` resolves it for you reactively from its controller. | +| `audioRecorderController` | `StreamAudioRecorderController?` | `null` | Enables the voice recording UI when provided | +| `sendVoiceRecordingAutomatically` | `bool` | `false` | Sends the voice recording immediately on finish | +| `feedback` | `AudioRecorderFeedback` | `const AudioRecorderFeedback()` | Haptic/audio feedback callbacks for the recording flow | +| `canAlsoSendToChannel` | `bool` | `false` | Shows the "also send to channel" checkbox (used in threads) | +| `onQuotedMessageCleared` | `VoidCallback?` | `null` | Called when the user removes the quoted message in the input header | +| `textInputAction` | `TextInputAction?` | `null` | The keyboard action button type | +| `keyboardType` | `TextInputType?` | `null` | The keyboard type for the text field | +| `textCapitalization` | `TextCapitalization` | `sentences` | Text capitalization behaviour for the text field | +| `autofocus` | `bool` | `false` | Whether the text field should autofocus when built | +| `autocorrect` | `bool` | `true` | Whether autocorrect is enabled | +| `isFloating` | `bool` | `false` | Whether the composer renders in a floating style (affects layout, shadow, and decoration) | ### Sub-components The layout is composed of named default sub-widgets that can be replaced via the `StreamComponentFactory`. Use the factory builder keys below to override any slot; the public default class (where one exists) can be referenced when you want to call the built-in implementation from inside a custom override. -| Factory builder key | Description | Public default class | -|---------------------|-------------|----------------------| -| `messageComposerLeading` | Left side of the composer row (e.g., attachment button) | `DefaultStreamMessageComposerLeading` | -| `messageComposerTrailing` | Right side of the composer row (empty by default; add a custom widget here to extend the outer row) | — | -| `messageComposerInput` | The whole input container (assembles header, leading, center, and trailing) | `DefaultStreamMessageComposerInput` | -| `messageComposerInputLeading` | Left side inside the input area (empty by default) | — | -| `messageComposerInputCenter` | The actual text field area (text input or audio recording UI) | `DefaultStreamMessageComposerInputCenter` | -| `messageComposerInputTrailing` | Right side inside the input area (send/mic button) | `DefaultStreamMessageComposerInputTrailing` | -| `messageComposerInputHeader` | Header above the input (reply/edit preview, attachment thumbnails) | — | +| Factory builder key | Description | Public default class | +| ------------------------------ | --------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `messageComposerLeading` | Left side of the composer row (e.g., attachment button) | `DefaultStreamMessageComposerLeading` | +| `messageComposerTrailing` | Right side of the composer row (empty by default; add a custom widget here to extend the outer row) | — | +| `messageComposerInput` | The whole input container (assembles header, leading, center, and trailing) | `DefaultStreamMessageComposerInput` | +| `messageComposerInputLeading` | Left side inside the input area (empty by default) | — | +| `messageComposerInputCenter` | The actual text field area (text input or audio recording UI) | `DefaultStreamMessageComposerInputCenter` | +| `messageComposerInputTrailing` | Right side inside the input area (send/mic button) | `DefaultStreamMessageComposerInputTrailing` | +| `messageComposerInputHeader` | Header above the input (reply/edit preview, attachment thumbnails) | — | ### Customization via Component Factory @@ -202,6 +207,77 @@ StreamComponentFactory( --- +## StreamMessageComposerController Rename + +The composer controller has been renamed from `StreamMessageInputController` to `StreamMessageComposerController` to match the widget it drives. The constructor no longer accepts a `message:` argument for entering edit mode — edit mode is now explicit. + +### Renamed symbols + +| Old | New | +| -------------------------------------------------------------- | ---------------------------------------------------- | +| `StreamMessageInputController` | `StreamMessageComposerController` | +| `StreamRestorableMessageInputController` | `StreamRestorableMessageComposerController` | +| `StreamMessageInputController.editingOriginalMessage` | `StreamMessageComposerController.messageBeingEdited` | +| `StreamMessageComposer.messageInputController` constructor arg | `messageComposerController` | + +### Edit-mode semantics + +The controller no longer accepts a pre-edit message via the constructor. Use `editMessage(message)` to enter edit mode and `cancelEditMessage()` to exit. Calling `clear()` no longer exits edit mode — it only clears the composer content. + +**Before:** + +```dart +final controller = StreamMessageInputController(message: existingMessage); +// ... +controller.clear(); // also exited edit mode +``` + +**After:** + +```dart +final controller = StreamMessageComposerController(); +controller.editMessage(existingMessage); +// ... +controller.cancelEditMessage(); // explicit exit; restores prior content +``` + +`isEditing` (a new convenience getter) returns `true` while a message is being edited; `messageBeingEdited` exposes the original `Message`. + +--- + +## StreamMessageComposerInput Split + +The composer input has been split into two distinct layers to make the **outer container** vs the **text-field center** unambiguous. The old `StreamMessageComposerInput` (text-field widget) has been replaced by `StreamMessageComposerInputCenter`, and a new `StreamMessageComposerInput` outer container now wraps the header, leading, center, and trailing slots. Both `MessageComposerInputProps` (outer container) and `MessageComposerInputCenterProps` (text-field center) exist as distinct classes with different field sets — this is a split, not a straight rename. + +| Old | New | +| ------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `StreamMessageComposerInput` (text-field widget) | `StreamMessageComposerInputCenter` | +| `DefaultStreamMessageComposerInput` | `DefaultStreamMessageComposerInputCenter` | +| `MessageComposerInputProps` | `MessageComposerInputCenterProps` (text-field center); `MessageComposerInputProps` now refers to the outer container | +| Factory builder key `messageComposerInput` (text field) | `messageComposerInputCenter` | + +> **Important:** The factory builder key `messageComposerInput` still exists, but **now overrides the outer input container** (header + leading + center + trailing) instead of just the text field. If you previously used `messageComposerInput` to swap the text field, switch to `messageComposerInputCenter` to preserve the old behaviour. + +**Before:** + +```dart +streamChatComponentBuilders( + messageComposerInput: (context, props) => MyCustomTextField(props: props), +) +``` + +**After:** + +```dart +streamChatComponentBuilders( + messageComposerInputCenter: (context, props) => MyCustomTextField(props: props), +) +``` + +The sub-component table in [StreamChatMessageInput (new)](#streamchatmessageinput-new) reflects the new names. + +--- + ## Message Input Placeholder API The input placeholder text (the dimmed text shown inside the input field when it is empty) is now driven by a sealed-class hierarchy that adapts to the current input state. The previous `HintType` enum and `HintGetter` typedef have been removed, and the customization hook on `StreamMessageComposer` is now called `placeholderBuilder`. @@ -212,25 +288,25 @@ The new placeholder types live in `lib/src/message_input/message_input_placehold ### What was removed -| Removed | Replacement | -|---------|-------------| +| Removed | Replacement | +| -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `enum HintType` (`searchGif`, `addACommentOrSend`, `slowModeOn`, `command`, `writeAMessage`) | `sealed class MessageInputPlaceholder` with `final` cases `WriteMessagePlaceholder`, `SlowModePlaceholder`, `CommandPlaceholder`, `AttachmentsPlaceholder` (each carries the contextual data for that state — see [Sealed-class state shape](#sealed-class-state-shape)) | -| `typedef HintGetter = String? Function(BuildContext, HintType, Command?)` | `typedef MessageInputPlaceholderBuilder = String? Function(BuildContext, MessageInputPlaceholder)` | -| `HintType resolveMessageInputHintType(controller)` | `MessageInputPlaceholder.resolve(controller)` factory | -| `Command? resolveActiveMessageInputCommand(context, controller)` | Removed. Use `controller.message.command` (a `String?`) directly. The SDK no longer looks up the full `Command` object from the channel config when resolving the placeholder. | -| `String? defaultMessageInputHintGetter(...)` | Removed from the public API. The default behaviour is now baked into `StreamMessageComposer.placeholderBuilder`'s default value. To customize, supply your own builder with an exhaustive `switch` over [`MessageInputPlaceholder`](#sealed-class-state-shape). | -| `StreamMessageInput.hintGetter` | `StreamMessageComposer.placeholderBuilder` | +| `typedef HintGetter = String? Function(BuildContext, HintType, Command?)` | `typedef MessageInputPlaceholderBuilder = String? Function(BuildContext, MessageInputPlaceholder)` | +| `HintType resolveMessageInputHintType(controller)` | `MessageInputPlaceholder.resolve(controller)` factory | +| `Command? resolveActiveMessageInputCommand(context, controller)` | Removed. Use `controller.message.command` (a `String?`) directly. The SDK no longer looks up the full `Command` object from the channel config when resolving the placeholder. | +| `String? defaultMessageInputHintGetter(...)` | Removed from the public API. The default behaviour is now baked into `StreamMessageComposer.placeholderBuilder`'s default value. To customize, supply your own builder with an exhaustive `switch` over [`MessageInputPlaceholder`](#sealed-class-state-shape). | +| `StreamMessageInput.hintGetter` | `StreamMessageComposer.placeholderBuilder` | ### Behavior change: precedence The order in which input states are evaluated to pick a placeholder has changed: -| | Old order | New order | -|---|---|---| -| 1 | `command` | `slowMode` | -| 2 | `attachments` | `command` | -| 3 | `slowMode` | `attachments` | -| 4 | `writeMessage` | `writeMessage` | +| | Old order | New order | +| --- | -------------- | -------------- | +| 1 | `command` | `slowMode` | +| 2 | `attachments` | `command` | +| 3 | `slowMode` | `attachments` | +| 4 | `writeMessage` | `writeMessage` | When slow mode is active and a command is also active (or attachments are present), the slow-mode placeholder now wins. This matches the iOS SDK. To restore the old order, override `placeholderBuilder` and short-circuit on `CommandPlaceholder` / `AttachmentsPlaceholder` before falling back to the default. @@ -238,11 +314,11 @@ When slow mode is active and a command is also active (or attachments are presen The default builder now renders dedicated placeholders for Stream's built-in user-target commands, matching the redesigned Figma: -| Command | Placeholder (English) | Localization key | -|---------|----------------------|-------------------| -| `/giphy` | `Search GIFs` | `searchGifLabel` | -| `/mute`, `/unmute`, `/ban`, `/unban` | `@username` | `commandUsernameLabel` (new) | -| Any other backend command | `Send a message` | `writeAMessageLabel` | +| Command | Placeholder (English) | Localization key | +| ------------------------------------ | --------------------- | ---------------------------- | +| `/giphy` | `Search GIFs` | `searchGifLabel` | +| `/mute`, `/unmute`, `/ban`, `/unban` | `@username` | `commandUsernameLabel` (new) | +| Any other backend command | `Send a message` | `writeAMessageLabel` | `commandUsernameLabel` is a new translation key — see the [Localizations migration guide](localizations.md) if you have a custom `Translations` subclass. @@ -266,13 +342,13 @@ Once the cooldown reaches zero the input is automatically re-enabled and the reg Each case carries the contextual data relevant to that input state. Pattern-match on these fields in your `placeholderBuilder` to render rich, state-aware placeholders. -| Case | Field | Type | Description | -|------|-------|------|-------------| -| `WriteMessagePlaceholder` | `isEditing` | `bool` | `true` when the input is editing an existing message instead of composing a new one. Useful for swapping the placeholder while editing. | -| `SlowModePlaceholder` | `cooldownTimeOut` | `int` | Remaining slow-mode cooldown in seconds. Mirrors `StreamMessageComposerController.cooldownTimeOut`. | -| `SlowModePlaceholder` | `cooldown` | `Duration` | Convenience getter wrapping `cooldownTimeOut` for formatting timer strings. | -| `CommandPlaceholder` | `command` | `String` | Active command name (e.g. `'giphy'`, `'mute'`, `'ban'`, or any backend-defined command). | -| `AttachmentsPlaceholder` | `attachments` | `List` | Pending attachments held by the input. OG link previews are still included — filter via `Attachment.ogScrapeUrl` if you only want user-added ones. | +| Case | Field | Type | Description | +| ------------------------- | ----------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `WriteMessagePlaceholder` | `isEditing` | `bool` | `true` when the input is editing an existing message instead of composing a new one. Useful for swapping the placeholder while editing. | +| `SlowModePlaceholder` | `cooldownTimeOut` | `int` | Remaining slow-mode cooldown in seconds. Mirrors `StreamMessageComposerController.cooldownTimeOut`. | +| `SlowModePlaceholder` | `cooldown` | `Duration` | Convenience getter wrapping `cooldownTimeOut` for formatting timer strings. | +| `CommandPlaceholder` | `command` | `String` | Active command name (e.g. `'giphy'`, `'mute'`, `'ban'`, or any backend-defined command). | +| `AttachmentsPlaceholder` | `attachments` | `List` | Pending attachments held by the input. OG link previews are still included — filter via `Attachment.ogScrapeUrl` if you only want user-added ones. | Example using the new fields (note that the sealed type forces an exhaustive switch — every case must be handled): @@ -343,7 +419,8 @@ StreamMessageComposer( placeholderBuilder: (context, placeholder) { final translations = context.translations; return switch (placeholder) { - SlowModePlaceholder() => translations.slowModeOnLabel, + SlowModePlaceholder(:final cooldownTimeOut) => + translations.slowModeOnLabel(cooldownTimeOut), CommandPlaceholder(command: 'weather') => 'Type a city name', CommandPlaceholder(command: 'tip') => 'Type @user amount', CommandPlaceholder(command: 'poll') => 'Type your question', @@ -372,10 +449,10 @@ Renders the full list of attachment thumbnails in the composer. The old `StreamM **Props class:** `StreamMessageComposerAttachmentListProps` -| Property | Type | Description | -|----------|------|-------------| -| `attachments` | `Iterable` | The attachments to display (OG link previews are filtered out before this widget receives them) | -| `onRemovePressed` | `ValueSetter?` | Called when the user removes an attachment | +| Property | Type | Description | +| ----------------- | -------------------------- | ----------------------------------------------------------------------------------------------- | +| `attachments` | `Iterable` | The attachments to display (OG link previews are filtered out before this widget receives them) | +| `onRemovePressed` | `ValueSetter?` | Called when the user removes an attachment | Override the whole list using the `messageComposerAttachmentList` builder key: @@ -396,10 +473,10 @@ Renders a single attachment thumbnail inside the list. Use this to customise how **Props class:** `StreamMessageComposerAttachmentProps` -| Property | Type | Description | -|----------|------|-------------| -| `attachment` | `Attachment` | The attachment to render | -| `onRemovePressed` | `ValueSetter?` | Called when the user taps the remove button | +| Property | Type | Description | +| ------------------------- | -------------------------------- | ---------------------------------------------------------------- | +| `attachment` | `Attachment` | The attachment to render | +| `onRemovePressed` | `ValueSetter?` | Called when the user taps the remove button | | `audioPlaylistController` | `StreamAudioPlaylistController?` | Shared playlist controller for audio/voice-recording attachments | Override individual attachment items using the `messageComposerAttachment` builder key: @@ -421,20 +498,87 @@ streamChatComponentBuilders( ### Built-in attachment builder helpers -The following public widgets are provided as building blocks for custom attachment renderers: +The following public widget is provided as a building block for custom attachment renderers: -| Widget | Description | -|--------|-------------| -| `StreamAudioAttachmentBuilder` | Renders an audio or voice-recording attachment with playback controls | -| `StreamFileAttachmentBuilder` | Renders a generic file attachment with file type icon, name, and size | +| Widget | Description | +| ------------------------------ | --------------------------------------------------------------------------------- | | `StreamMediaAttachmentBuilder` | Renders an image, video, or GIF attachment thumbnail with an optional media badge | -| `RemoveAttachmentButton` | The standard filled icon button used to dismiss an attachment | + +--- + +## Removed Widgets + +The following composer-adjacent widgets have been removed. Replace any direct references with the listed alternatives. + +| Removed Widget | Replacement | +| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `AttachmentButton` | Built into `StreamMessageComposer` / `StreamChatMessageInput`; override `messageComposerLeading` via `StreamComponentFactory` to customize | +| `StreamQuotedMessageWidget` | Use `StreamQuotedMessage` | +| `EditMessageSheet` | Editing is handled inline by the composer via `controller.editMessage(message)` — see [StreamMessageComposerController Rename](#streammessagecomposercontroller-rename) | +| `StreamMessageSendButton` | Part of the composer internals; override `messageComposerInputTrailing` via `StreamComponentFactory` to customize the send button | +| `ErrorAlertSheet` | No longer publicly exported (still used internally by `StreamMessageComposer`) — surface attachment/composer errors via your own UI (e.g. a `SnackBar` or `AlertDialog`) using the typed errors documented in [v10-migration.md](../v10-migration.md#streamattachmentpickercontroller) | + +`AttachmentButton` example: + +**Before:** + +```dart +AttachmentButton( + color: Colors.blue, + onPressed: () => openCustomPicker(), +) +``` + +**After:** override the leading slot via the component factory. + +```dart +streamChatComponentBuilders( + messageComposerLeading: (context, props) => IconButton( + icon: Icon(context.streamIcons.attachment, color: Colors.blue), + onPressed: () => openCustomPicker(), + ), +) +``` + +`StreamQuotedMessageWidget` example: + +**Before:** + +```dart +StreamQuotedMessageWidget(message: quotedMessage) +``` + +**After:** + +```dart +StreamQuotedMessage(message: quotedMessage) +``` + +--- + +## Theme Removal: StreamMessageInputThemeData + +`StreamMessageInputThemeData` and the `StreamChatThemeData.messageInputTheme` accessor have been **removed**. The composer is now styled via the design-system primitives — `context.streamColorScheme` for colors and `context.streamRadius` for corner radii — read directly by the sub-components. `StreamMessageComposer` has no `backgroundColor` or `border` constructor parameters; use `StreamComponentFactory` builder overrides to customize the appearance of individual sub-components. + +| Removed | Replacement | +| --------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `StreamMessageInputThemeData` | Design-system primitives on `StreamTheme` + per-sub-component overrides via `StreamComponentFactory` | +| `StreamChatThemeData.messageInputTheme` | Removed | + +There is no one-to-one replacement — most fields on the old `StreamMessageInputThemeData` mapped to the legacy composer layout, which is now owned by `StreamChatMessageInput` and customized via slot overrides. See [StreamChatMessageInput (new)](#streamchatmessageinput-new) and the sub-component factory keys for the supported customization surface. --- ## Migration Checklist - [ ] Rename `StreamMessageInput` to `StreamMessageComposer` in all usages +- [ ] Rename `StreamMessageInputController` to `StreamMessageComposerController` (and `StreamRestorableMessageInputController` to `StreamRestorableMessageComposerController`) +- [ ] Rename `StreamMessageComposer.messageInputController` constructor argument to `messageComposerController` +- [ ] Replace `StreamMessageComposerController(message: existingMessage)` with `controller.editMessage(existingMessage)` to enter edit mode +- [ ] Replace `controller.clear()` (used to exit edit mode) with `controller.cancelEditMessage()` — `clear()` no longer exits edit mode +- [ ] Rename `editingOriginalMessage` reads to `messageBeingEdited` +- [ ] Replace the old `StreamMessageComposerInput` text-field widget with `StreamMessageComposerInputCenter` (and `DefaultStreamMessageComposerInput` → `DefaultStreamMessageComposerInputCenter`); note that `MessageComposerInputProps` and `MessageComposerInputCenterProps` are now distinct classes — update props usage to the center variant for text-field customization +- [ ] Switch any factory override that previously used the `messageComposerInput` key to render a custom text field over to `messageComposerInputCenter` — the old key now overrides the outer input container - [ ] Rename `hideSendAsDm` to `canAlsoSendToChannelFromThread` in all `StreamMessageComposer` usages and invert the value - [ ] Review usages of `attachmentLimit` — it is now `int?` and defaults to no limit; set an explicit value if you relied on the old default of `10` - [ ] Remove any usage of `maxHeight`, `maxLines`, `minLines`, `padding`, `textInputMargin`, `elevation`, `shadow`, `enableActionAnimation`, `contentInsertionConfiguration`, `sendButtonLocation` @@ -444,6 +588,9 @@ The following public widgets are provided as building blocks for custom attachme - [ ] Replace `quotedMessageBuilder` / `quotedMessageAttachmentThumbnailBuilders` with `messageComposerInputHeader` or `messageComposerAttachment` overrides in `StreamComponentFactory` - [ ] If adopting `StreamChatMessageInput` directly, wire up your own send/attachment logic via `onSendPressed` and `onAttachmentButtonPressed` - [ ] Move any composer UI customizations to `StreamComponentFactory` +- [ ] Remove any direct references to `AttachmentButton`, `StreamQuotedMessageWidget`, `EditMessageSheet`, `StreamMessageSendButton`, and `ErrorAlertSheet` — see [Removed Widgets](#removed-widgets) +- [ ] Replace `StreamQuotedMessageWidget` with `StreamQuotedMessage` +- [ ] Remove any `StreamMessageInputThemeData` / `StreamChatThemeData.messageInputTheme` usage — style via `StreamTheme` design-system primitives and `StreamComponentFactory` slot overrides - [ ] Rename `StreamMessageInput.hintGetter` to `placeholderBuilder` and rewrite the callback to switch over `MessageInputPlaceholder` cases (`SlowModePlaceholder`, `CommandPlaceholder`, `AttachmentsPlaceholder`, `WriteMessagePlaceholder`) instead of the removed `HintType` enum. If you build directly on `StreamChatMessageInput`, compute the placeholder string yourself via `MessageInputPlaceholder.resolve(controller)` and pass it via the `placeholder: String` parameter. - [ ] Review the new placeholder precedence (`slowMode > command > attachments > writeMessage`) and override `placeholderBuilder` if you need to preserve the old order - [ ] Add command-specific placeholders for any backend-defined commands you ship by pattern-matching on `CommandPlaceholder.command` in your `placeholderBuilder` diff --git a/migrations/redesign/message_list.md b/migrations/redesign/message_list.md new file mode 100644 index 0000000000..94b6d9d150 --- /dev/null +++ b/migrations/redesign/message_list.md @@ -0,0 +1,304 @@ +# Message List Migration Guide + +This guide covers migrating `StreamMessageListView` from the old design to the new redesigned API, including the stable-release changes that split behavior flags and builders into dedicated configuration objects. + +For changes to the individual message item / message widget, see [message_widget.md](message_widget.md). + +--- + +## Table of Contents + +- [Quick Reference](#quick-reference) +- [Config & Builders Split](#config--builders-split) + - [Behavior flags → `config`](#behavior-flags--config) + - [Builder callbacks → `builders`](#builder-callbacks--builders) +- [Builder Signature Changes](#builder-signature-changes) +- [New List-Level Callbacks](#new-list-level-callbacks) +- [Changed: `showUnreadCountOnScrollToBottom` Default](#changed-showunreadcountonscrolltobottom-default) +- [Removed: `MessageDetails`](#removed-messagedetails) +- [StreamDraftListView Removed](#streamdraftlistview-removed) +- [Migration Checklist](#migration-checklist) + +--- + +## Quick Reference + +| Old | New | +| -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| `StreamMessageListView(swipeToReply: true, paginationLimit: 20, ...)` | `StreamMessageListView(config: StreamMessageListViewConfiguration(...))` | +| `StreamMessageListView(loadingBuilder: ..., emptyBuilder: ..., ...)` | `StreamMessageListView(builders: StreamMessageListViewBuilders(...))` | +| `messageBuilder` / `parentMessageBuilder` | `messageBuilder` (root) / `builders.parentMessage` | +| `MessageBuilder` typedef | `StreamMessageItemBuilder` typedef | +| `ParentMessageBuilder` typedef | `StreamMessageItemBuilder` typedef | +| `OnQuotedMessageTap = void Function(String?)` | `void Function(Message quotedMessage)` | +| `MessageDetails` argument | Removed — alignment via `StreamMessageLayout.of(context)`, user via `StreamChat.of(context)` | +| `showUnreadCountOnScrollToBottom: false` (old default) | `showUnreadCountOnScrollToBottom: true` (new default) | +| `StreamDraftListView` / `StreamDraftListTile` / `StreamDraftListTileTheme` | Removed — use `StreamDraftListController` + `PagedValueListView` | + +--- + +## Config & Builders Split + +`StreamMessageListView` no longer accepts behavior flags and builder callbacks as flat constructor parameters. Behavior flags now live on `StreamMessageListViewConfiguration` (passed via `config:`) and slot builder callbacks on `StreamMessageListViewBuilders` (passed via `builders:`). The root `messageBuilder` for regular messages is unchanged. + +**Before:** + +```dart +StreamMessageListView( + swipeToReply: true, + paginationLimit: 20, + markReadWhenAtTheBottom: false, + showScrollToBottom: true, + showUnreadIndicator: true, + reverse: true, + loadingBuilder: (context) => const MyLoader(), + emptyBuilder: (context) => const MyEmpty(), + errorBuilder: (context, error) => MyError(error), + dateDividerBuilder: (date) => MyDateDivider(date: date), + threadSeparatorBuilder: (context, parent) => MyThreadSeparator(parent: parent), + scrollToBottomBuilder: (unread, tap) => MyScrollToBottom(unread: unread, onTap: tap), +) +``` + +**After:** + +```dart +StreamMessageListView( + config: StreamMessageListViewConfiguration( + swipeToReply: true, + paginationLimit: 20, + markReadWhenAtTheBottom: false, + showScrollToBottom: true, + showUnreadIndicator: true, + reverse: true, + ), + builders: StreamMessageListViewBuilders( + loading: (context) => const MyLoader(), + empty: (context) => const MyEmpty(), + error: (context, error) => MyError(error), + dateDivider: (date) => MyDateDivider(date: date), + threadSeparator: (context, parent) => MyThreadSeparator(parent: parent), + scrollToBottomButton: (unread, tap) => MyScrollToBottom(unread: unread, onTap: tap), + ), +) +``` + +### Behavior flags → `config` + +All boolean/scalar flags move to `StreamMessageListViewConfiguration`: + +| Old flat parameter | New field on `StreamMessageListViewConfiguration` | +| ----------------------------------- | ------------------------------------------------- | +| `markReadWhenAtTheBottom` | `markReadWhenAtTheBottom` | +| `swipeToReply` | `swipeToReply` | +| `showScrollToBottom` | `showScrollToBottom` | +| `showUnreadCountOnScrollToBottom` | `showUnreadCountOnScrollToBottom` | +| `showUnreadIndicator` | `showUnreadIndicator` | +| `highlightInitialMessage` | `highlightInitialMessage` | +| `showConnectionStateTile` | `showConnectionStateTile` | +| `showFloatingDateDivider` | `showFloatingDateDivider` | +| `fadeFloatingDateDividerNearInline` | `fadeFloatingDateDividerNearInline` | +| `reverse` | `reverse` | +| `shrinkWrap` | `shrinkWrap` | +| `paginationLimit` | `paginationLimit` | +| `keyboardDismissBehavior` | `keyboardDismissBehavior` | +| `scrollPhysics` | `scrollPhysics` | + +### Builder callbacks → `builders` + +All slot builder callbacks (except the root `messageBuilder` for regular messages) move to `StreamMessageListViewBuilders`. Several callbacks are also renamed: + +| Old flat parameter | New field on `StreamMessageListViewBuilders` | +| ----------------------------------- | -------------------------------------------- | +| `emptyBuilder` | `empty` | +| `loadingBuilder` | `loading` | +| `errorBuilder` | `error` | +| `headerBuilder` | `header` | +| `footerBuilder` | `footer` | +| `messageListBuilder` | `content` | +| `dateDividerBuilder` | `dateDivider` | +| `floatingDateDividerBuilder` | `floatingDateDivider` | +| `threadSeparatorBuilder` | `threadSeparator` | +| `unreadMessagesSeparatorBuilder` | `unreadMessagesSeparator` | +| `scrollToBottomBuilder` | `scrollToBottomButton` | +| `paginationLoadingIndicatorBuilder` | `paginationLoadingIndicator` | +| `spacingWidgetBuilder` | `spacing` | +| `systemMessageBuilder` | `systemMessage` | +| `ephemeralMessageBuilder` | `ephemeralMessage` | +| `moderatedMessageBuilder` | `moderatedMessage` | +| `parentMessageBuilder` | `parentMessage` | + +`messageBuilder` (for regular messages) stays as a flat parameter on `StreamMessageListView` itself. + +--- + +## Builder Signature Changes + +Both `messageBuilder` and `parentMessage` (the new home for `parentMessageBuilder`) now use the same typedef: + +**Before:** + +```dart +typedef MessageBuilder = Widget Function( + BuildContext context, + MessageDetails details, + List messages, + StreamMessageItem defaultMessageWidget, +); + +typedef ParentMessageBuilder = Widget Function( + BuildContext context, + Message? parentMessage, + StreamMessageItem defaultMessageWidget, +); +``` + +**After:** + +```dart +typedef StreamMessageItemBuilder = Widget Function( + BuildContext context, + Message message, + StreamMessageItemProps defaultProps, +); +``` + +The old builders received a pre-built `StreamMessageItem` that you could `copyWith`. The new builders receive `StreamMessageItemProps` — raw configuration data. Use `StreamMessageItem.fromProps(props:)` to build the default widget through the component factory. + +**Before:** + +```dart +StreamMessageListView( + messageBuilder: (context, details, messages, defaultWidget) { + return defaultWidget.copyWith(showReactions: false); + }, + parentMessageBuilder: (context, parent, defaultWidget) { + return defaultWidget.copyWith(showReactions: false); + }, +) +``` + +**After:** + +```dart +StreamMessageListView( + // Option 1: use the default widget as-is + messageBuilder: (context, message, defaultProps) { + return StreamMessageItem.fromProps(props: defaultProps); + }, + + // Option 2: customize props before building + messageBuilder: (context, message, defaultProps) { + return StreamMessageItem.fromProps( + props: defaultProps.copyWith( + actionsBuilder: (context, actions) => [...actions, myAction], + ), + ); + }, + + // Option 3: replace entirely with a custom widget + messageBuilder: (context, message, defaultProps) { + return MyCustomMessageWidget(message: message); + }, + builders: StreamMessageListViewBuilders( + parentMessage: (context, parent, defaultProps) { + return StreamMessageItem.fromProps(props: defaultProps); + }, + ), +) +``` + +> **Important:** The `messageBuilder` callback now receives a `BuildContext` that has `StreamMessageLayout` in its ancestor chain. You can call `StreamMessageLayout.alignmentDirectionalOf(context)` to determine message alignment. + +--- + +## New List-Level Callbacks + +These callbacks were previously only configurable per-message on `StreamMessageItem`. They are now available at the list level and forwarded to all messages: + +| New Parameter | Type | +| -------------------- | --------------------------------- | +| `onEditMessageTap` | `void Function(Message)?` | +| `onReplyTap` | `void Function(Message)?` | +| `onUserAvatarTap` | `void Function(User)?` | +| `onReactionsTap` | `void Function(Message)?` | +| `onQuotedMessageTap` | `void Function(Message)?` | +| `onMessageLinkTap` | `void Function(Message, String)?` | +| `onUserMentionTap` | `void Function(User)?` | + +--- + +## Changed: `showUnreadCountOnScrollToBottom` Default + +The default flipped from `false` to `true`. The flag now lives inside `StreamMessageListViewConfiguration`. + +```dart +// Old +StreamMessageListView(showUnreadCountOnScrollToBottom: false) + +// New +StreamMessageListView( + config: StreamMessageListViewConfiguration( + showUnreadCountOnScrollToBottom: true, // new default + ), +) +``` + +--- + +## Removed: `MessageDetails` + +The `MessageDetails` class has been removed. The old `messageBuilder` received it as an argument with `userId`, `message`, `messages`, and `index`. The new builder receives just `Message` and `StreamMessageItemProps`. The user ID is accessible via `StreamChat.of(context).currentUser?.id`. Message alignment is provided by `StreamMessageLayout.of(context)`. + +```dart +// Inside a messageBuilder: +final currentUser = StreamChat.of(context).currentUser; +final alignment = StreamMessageLayout.alignmentDirectionalOf(context); +final isMyMessage = message.user?.id == currentUser?.id; +``` + +--- + +## StreamDraftListView Removed + +`StreamDraftListView`, `StreamDraftListTile`, `StreamDraftListTileTheme`, `StreamDraftListTileThemeData`, and `StreamChatThemeData.draftListTileTheme` have been removed. The opinionated draft list UI was extracted out of the SDK — build your own list using `StreamDraftListController` (from `stream_chat_flutter_core`) with a generic `PagedValueListView`. See the sample app for a reference implementation. + +**Before:** + +```dart +StreamDraftListView( + controller: draftListController, + onDraftTap: (draft) => openDraft(draft), +) +``` + +**After:** + +```dart +PagedValueListView( + controller: draftListController, + itemBuilder: (context, drafts, index) { + final draft = drafts[index]; + return ListTile( + title: Text(draft.message.text ?? ''), + onTap: () => openDraft(draft), + ); + }, +) +``` + +`StreamDraftListController` itself is unchanged. + +--- + +## Migration Checklist + +- [ ] Move all `StreamMessageListView` behavior flags into `config: StreamMessageListViewConfiguration(...)` +- [ ] Move all `StreamMessageListView` builder callbacks into `builders: StreamMessageListViewBuilders(...)` and rename per the table above (e.g. `loadingBuilder` → `loading`, `emptyBuilder` → `empty`, `dateDividerBuilder` → `dateDivider`) +- [ ] Keep `messageBuilder` as a top-level parameter; move `parentMessageBuilder` into `builders.parentMessage` +- [ ] Update `messageBuilder` / `parentMessage` callbacks to the new `StreamMessageItemBuilder` signature `(BuildContext, Message, StreamMessageItemProps)` +- [ ] Replace `defaultWidget.copyWith(...)` calls with `StreamMessageItem.fromProps(props: defaultProps.copyWith(...))` +- [ ] Replace `MessageDetails` usage — use `StreamMessageLayout.of(context)` for alignment, `StreamChat.of(context).currentUser` for user ID +- [ ] Update `onQuotedMessageTap` from `void Function(String?)` to `void Function(Message)` +- [ ] Review the new `showUnreadCountOnScrollToBottom` default (`true`) and explicitly set `false` in `config` if you want the old behaviour +- [ ] Replace `StreamDraftListView` / `StreamDraftListTile` with a `PagedValueListView` driven by `StreamDraftListController` diff --git a/migrations/redesign/message_widget.md b/migrations/redesign/message_widget.md index abaff8606e..05e47d39d0 100644 --- a/migrations/redesign/message_widget.md +++ b/migrations/redesign/message_widget.md @@ -1,6 +1,8 @@ -# Message Widget & Message List Migration Guide +# Message Widget Migration Guide -This guide covers migrating the message widget and message list view from the old design (`feat/design-refresh`) to the new redesigned API. +This guide covers migrating the message widget (individual message item) from the old design (`feat/design-refresh`) to the new redesigned API. + +For changes to `StreamMessageListView`, see [message_list.md](message_list.md). --- @@ -12,12 +14,9 @@ This guide covers migrating the message widget and message list view from the ol - [Removed Parameters](#removed-parameters) - [New Parameters](#new-parameters) - [Changed Signatures](#changed-signatures) -- [StreamMessageListView](#streammessagelistview) - - [Builder Signature Changes](#builder-signature-changes) - - [New List-Level Callbacks](#new-list-level-callbacks) - - [Removed: MessageDetails](#removed-messagedetails) - [Custom Actions Migration](#custom-actions-migration) - [Theme Migration](#theme-migration) +- [Removed Widgets](#removed-widgets) - [Swipeable Message Example](#swipeable-message-example) - [Deleted Classes & Files](#deleted-classes--files) - [Typedef Changes](#typedef-changes) @@ -27,30 +26,29 @@ This guide covers migrating the message widget and message list view from the ol ## Quick Reference -| Old | New | -|-----|-----| -| `StreamMessageItem` (50+ params) | `StreamMessageItem` (thin shell) + `StreamMessageItemProps` | -| `MessageWidgetContent` | `DefaultStreamMessageItem` + `StreamMessageContent` | -| `BottomRow` | `StreamMessageFooter` | -| `StreamMessageText` (message_text.dart) | `StreamMessageText` (components/stream_message_text.dart) | -| `StreamDeletedMessage` | `StreamMessageDeleted` | -| `MessageCard` | `core.StreamMessageBubble` | -| `TextBubble` | `core.StreamMessageBubble` | -| `PinnedMessage` | `StreamMessageHeader` widget | -| `QuotedMessage` | Inline in `StreamMessageContent` | -| `Username` | Inline in `StreamMessageFooter` | -| `SendingIndicatorBuilder` | `StreamMessageSendingStatus` | -| `ThreadReplyPainter` | `core.StreamMessageReplies` | -| `ThreadParticipants` | Inline in `core.StreamMessageReplies` | -| `UserAvatarTransform` | `StreamUserAvatar` (inline in `DefaultStreamMessageItem`) | -| `DisplayWidget` enum | `StreamVisibility` (from theme) | -| `MessageBuilder` typedef | `StreamMessageItemBuilder` typedef | -| `ParentMessageBuilder` typedef | `StreamMessageItemBuilder` typedef | -| `OnQuotedMessageTap = void Function(String?)` | `void Function(Message quotedMessage)` | -| `StreamMessageItem.customActions` | `StreamMessageItemProps.actionsBuilder` | -| `StreamMessageItem.onCustomActionTap` | Use `onTap` per `StreamContextMenuAction` | -| `CustomMessageAction` | Removed — use `StreamContextMenuAction` with `onTap` | -| `StreamMessageItem.copyWith()` | `StreamMessageItemProps.copyWith()` | +| Old | New | +| ------------------------------------------------------------------ | ----------------------------------------------------------- | +| `StreamMessageItem` (50+ params) | `StreamMessageItem` (thin shell) + `StreamMessageItemProps` | +| `MessageWidgetContent` | `DefaultStreamMessageItem` + `StreamMessageContent` | +| `BottomRow` | `StreamMessageFooter` | +| `StreamMessageText` (message_text.dart) | `StreamMessageText` (components/stream_message_text.dart) | +| `StreamMarkdownMessage` | `StreamMessageText` | +| `StreamDeletedMessage` | `StreamMessageDeleted` | +| `MessageCard` | `core.StreamMessageBubble` | +| `TextBubble` | `core.StreamMessageBubble` | +| `PinnedMessage` | `StreamMessageHeader` widget | +| `QuotedMessage` | Inline in `StreamMessageContent` | +| `Username` | Inline in `StreamMessageFooter` | +| `SendingIndicatorBuilder` | `StreamMessageSendingStatus` | +| `ThreadReplyPainter` | `core.StreamMessageReplies` | +| `ThreadParticipants` | Inline in `core.StreamMessageReplies` | +| `UserAvatarTransform` | `StreamUserAvatar` (inline in `DefaultStreamMessageItem`) | +| `DisplayWidget` enum | `StreamVisibility` (from theme) | +| `StreamMessageItem.customActions` | `StreamMessageItemProps.actionsBuilder` | +| `StreamMessageItem.onCustomActionTap` | Use `onTap` per `StreamContextMenuAction` | +| `CustomMessageAction` | Removed — use `StreamContextMenuAction` with `onTap` | +| `StreamMessageItem.copyWith()` | `StreamMessageItemProps.copyWith()` | +| `StreamMessageThemeData` / `ownMessageTheme` / `otherMessageTheme` | `StreamMessageItemThemeData` (placement-aware) | --- @@ -104,7 +102,7 @@ StreamMessageListView( ) ``` -Both can be combined — the component factory applies first, then the per-list `messageBuilder` can further customize or wrap the result. +Both can be combined — the component factory applies first, then the per-list `messageBuilder` can further customize or wrap the result. See [message_list.md](message_list.md) for details on the `messageBuilder` signature and the new `config` / `builders` split on `StreamMessageListView`. --- @@ -116,193 +114,99 @@ These parameters have been removed entirely. See the **Migration Path** column f #### Visibility Booleans -| Old Parameter | Migration Path | -|---|---| -| `showReactions` | Controlled via `StreamMessageItemThemeData` visibility | -| `showDeleteMessage` | Controlled via channel permissions (`canDeleteOwnMessage`, `canDeleteAnyMessage`) | -| `showEditMessage` | Controlled via channel permissions (`canUpdateOwnMessage`, `canUpdateAnyMessage`) | -| `showReplyMessage` | Controlled via channel permissions (`canSendReply`) | -| `showThreadReplyMessage` | Controlled via channel permissions (`canSendReply`) | -| `showMarkUnreadMessage` | Shown automatically when applicable | -| `showResendMessage` | Shown automatically for failed messages | -| `showCopyMessage` | Shown automatically when message has text | -| `showFlagButton` | Controlled via channel permissions (`canFlagMessage`) | -| `showPinButton` | Controlled via channel permissions (`canPinMessage`) | -| `showPinHighlight` | Controlled via `StreamMessageItemThemeData` background color | -| `showReactionPicker` | Removed | -| `showUsername` | Controlled via `StreamMessageItemThemeData.metadataVisibility` | -| `showTimestamp` | Controlled via `StreamMessageItemThemeData.metadataVisibility` | -| `showEditedLabel` | Controlled via `StreamMessageItemThemeData.metadataVisibility` | -| `showSendingIndicator` | Controlled via `StreamMessageItemThemeData.metadataVisibility` | -| `showThreadReplyIndicator` | Controlled via `StreamMessageItemThemeData.repliesVisibility` | -| `showInChannelIndicator` | Shown automatically via `StreamMessageHeader` | -| `showUserAvatar` (`DisplayWidget`) | Controlled via `StreamMessageItemThemeData.avatarVisibility` | +| Old Parameter | Migration Path | +| ---------------------------------- | --------------------------------------------------------------------------------- | +| `showReactions` | Controlled via `StreamMessageItemThemeData` visibility | +| `showDeleteMessage` | Controlled via channel permissions (`canDeleteOwnMessage`, `canDeleteAnyMessage`) | +| `showEditMessage` | Controlled via channel permissions (`canUpdateOwnMessage`, `canUpdateAnyMessage`) | +| `showReplyMessage` | Controlled via channel permissions (`canSendReply`) | +| `showThreadReplyMessage` | Controlled via channel permissions (`canSendReply`) | +| `showMarkUnreadMessage` | Shown automatically when applicable | +| `showResendMessage` | Shown automatically for failed messages | +| `showCopyMessage` | Shown automatically when message has text | +| `showFlagButton` | Controlled via channel permissions (`canFlagMessage`) | +| `showPinButton` | Controlled via channel permissions (`canPinMessage`) | +| `showPinHighlight` | Controlled via `StreamMessageItemThemeData` background color | +| `showReactionPicker` | Removed | +| `showUsername` | Controlled via `StreamMessageItemThemeData.metadataVisibility` | +| `showTimestamp` | Controlled via `StreamMessageItemThemeData.metadataVisibility` | +| `showEditedLabel` | Controlled via `StreamMessageItemThemeData.metadataVisibility` | +| `showSendingIndicator` | Controlled via `StreamMessageItemThemeData.metadataVisibility` | +| `showThreadReplyIndicator` | Controlled via `StreamMessageItemThemeData.repliesVisibility` | +| `showInChannelIndicator` | Shown automatically via `StreamMessageHeader` | +| `showUserAvatar` (`DisplayWidget`) | Controlled via `StreamMessageItemThemeData.avatarVisibility` | #### Builder Callbacks -| Old Parameter | Migration Path | -|---|---| -| `userAvatarBuilder` | Use component factory to replace `DefaultStreamMessageItem` | -| `textBuilder` | Use component factory to replace `StreamMessageContent` | -| `quotedMessageBuilder` | Use component factory to replace `StreamMessageContent` | -| `deletedMessageBuilder` | Use component factory to replace `StreamMessageContent` | -| `editMessageInputBuilder` | Removed; use `onEditMessageTap` callback instead | -| `bottomRowBuilderWithDefaultWidget` | Use component factory; `StreamMessageFooter` is the new equivalent | -| `reactionPickerBuilder` | Configured globally via `StreamChatConfigurationData.reactionIconResolver` | -| `reactionIndicatorBuilder` | Replaced by `StreamMessageReactions` component | +| Old Parameter | Migration Path | +| ----------------------------------- | -------------------------------------------------------------------------- | +| `userAvatarBuilder` | Use component factory to replace `DefaultStreamMessageItem` | +| `textBuilder` | Use component factory to replace `StreamMessageContent` | +| `quotedMessageBuilder` | Use component factory to replace `StreamMessageContent` | +| `deletedMessageBuilder` | Use component factory to replace `StreamMessageContent` | +| `editMessageInputBuilder` | Removed; use `onEditMessageTap` callback instead | +| `bottomRowBuilderWithDefaultWidget` | Use component factory; `StreamMessageFooter` is the new equivalent | +| `reactionPickerBuilder` | Configured globally via `StreamChatConfigurationData.reactionIconResolver` | +| `reactionIndicatorBuilder` | Replaced by `StreamMessageReactions` component | #### Shape & Style -| Old Parameter | Migration Path | -|---|---| -| `shape` | Controlled via `StreamMessageBubble` theming in `stream_core_flutter` | -| `borderSide` | Controlled via `StreamMessageBubble` theming | -| `borderRadiusGeometry` | Controlled via `StreamMessageBubble` theming | -| `attachmentShape` | Controlled via attachment builder theming | -| `textPadding` | Controlled via `StreamMessageBubble` content padding theming | -| `attachmentPadding` | Configured internally by `StreamMessageAttachments` | -| `messageTheme` | Resolved from context via `StreamMessageItemTheme.of(context)` | +| Old Parameter | Migration Path | +| ---------------------- | --------------------------------------------------------------------- | +| `shape` | Controlled via `StreamMessageBubble` theming in `stream_core_flutter` | +| `borderSide` | Controlled via `StreamMessageBubble` theming | +| `borderRadiusGeometry` | Controlled via `StreamMessageBubble` theming | +| `attachmentShape` | Controlled via attachment builder theming | +| `textPadding` | Controlled via `StreamMessageBubble` content padding theming | +| `attachmentPadding` | Configured internally by `StreamMessageAttachments` | +| `messageTheme` | Resolved from context via `StreamMessageItemTheme.of(context)` | #### Other Removed Parameters -| Old Parameter | Migration Path | -|---|---| -| `reverse` | Determined by `StreamMessagePlacement` context (set by list view) | -| `translateUserAvatar` | Removed; avatar positioning is theme-driven | -| `onConfirmDeleteTap` | Handled internally by `StreamMessageActionsBuilder` | -| `onShowMessage` | Removed | -| `onReactionsHover` | Removed | -| `customActions` | Use `actionsBuilder` on `StreamMessageItemProps` | -| `onCustomActionTap` | Use `actionsBuilder` on `StreamMessageItemProps` | -| `onAttachmentTap` | Handle in custom attachment builders | -| `imageAttachmentThumbnailSize` | Configured in attachment builders | -| `imageAttachmentThumbnailResizeType` | Configured in attachment builders | -| `imageAttachmentThumbnailCropType` | Configured in attachment builders | -| `attachmentActionsModalBuilder` | Configured in attachment builders | -| `attachmentBuilders` | Moved to `StreamChatConfigurationData.attachmentBuilders` (still overridable per-message via `StreamMessageItemProps.attachmentBuilders`) | -| `copyWith()` on `StreamMessageItem` | Use `StreamMessageItemProps.copyWith()` instead | +| Old Parameter | Migration Path | +| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `reverse` | Determined by `StreamMessageLayout` context (set by list view) | +| `translateUserAvatar` | Removed; avatar positioning is theme-driven | +| `onConfirmDeleteTap` | Handled internally by `StreamMessageActionsBuilder` | +| `onShowMessage` | Removed | +| `onReactionsHover` | Removed | +| `customActions` | Use `actionsBuilder` on `StreamMessageItemProps` | +| `onCustomActionTap` | Use `actionsBuilder` on `StreamMessageItemProps` | +| `onAttachmentTap` | Handle in custom attachment builders | +| `imageAttachmentThumbnailSize` | Configured in attachment builders | +| `imageAttachmentThumbnailResizeType` | Configured in attachment builders | +| `imageAttachmentThumbnailCropType` | Configured in attachment builders | +| `attachmentActionsModalBuilder` | Configured in attachment builders | +| `attachmentBuilders` | Moved to `StreamChatConfigurationData.attachmentBuilders` (still overridable per-message via `StreamMessageItemProps.attachmentBuilders`) | +| `copyWith()` on `StreamMessageItem` | Use `StreamMessageItemProps.copyWith()` instead | ### New Parameters -| New Parameter | Description | -|---|---| -| `padding` | Outer padding around the message item (overrides theme) | -| `spacing` | Horizontal spacing between avatar and content (overrides theme) | -| `backgroundColor` | Background color for the message row (overrides theme) | -| `maxWidth` | Max content width in logical pixels (default: `264`) | -| `onMessageLinkTap` | `void Function(Message, String)` — receives message and URL | -| `onUserMentionTap` | `void Function(User)` — receives the mentioned user | -| `onQuotedMessageTap` | `void Function(Message)` — receives the quoted message object | -| `onReactionsTap` | `void Function(Message)` — overrides default reaction detail sheet | -| `reactionSorting` | `Comparator` for reaction display order | -| `actionsBuilder` | `MessageActionsBuilder` for customizing the actions list | -| `onMessageActions` | Override the default long-press modal entirely | -| `onBouncedErrorMessageActions` | Override the bounced-error modal entirely | -| `onEditMessageTap` | Called when edit action is selected | +| New Parameter | Description | +| ------------------------------ | ------------------------------------------------------------------ | +| `padding` | Outer padding around the message item (overrides theme) | +| `spacing` | Horizontal spacing between avatar and content (overrides theme) | +| `backgroundColor` | Background color for the message row (overrides theme) | +| `maxWidth` | Max content width in logical pixels (default: `264` on `StreamMessageItem`, `272` on `StreamMessageItemProps`) | +| `onMessageLinkTap` | `void Function(Message, String)` — receives message and URL | +| `onUserMentionTap` | `void Function(User)` — receives the mentioned user | +| `onQuotedMessageTap` | `void Function(Message)` — receives the quoted message object | +| `onReactionsTap` | `void Function(Message)` — overrides default reaction detail sheet | +| `reactionSorting` | `Comparator` for reaction display order | +| `actionsBuilder` | `MessageActionsBuilder` for customizing the actions list | +| `onMessageActions` | Override the default long-press modal entirely | +| `onBouncedErrorMessageActions` | Override the bounced-error modal entirely | +| `onEditMessageTap` | Called when edit action is selected | ### Changed Signatures -| Callback | Old Signature | New Signature | -|---|---|---| -| Link tap | `void Function(String url)` | `void Function(Message message, String url)` | -| Mention tap | `void Function(User user)` | `void Function(User user)` (renamed: `onMentionTap` → `onUserMentionTap`) | -| Quoted message tap | `void Function(String? quotedMessageId)` | `void Function(Message quotedMessage)` | -| Thread tap | `void Function(Message message)` | `void Function(Message message)` (unchanged signature, renamed: `onThreadTap`) | -| Reply tap | `void Function(Message message)` | `void Function(Message message)` (new: `onReplyTap`) | - ---- - -## StreamMessageListView - -### Builder Signature Changes - -Both `messageBuilder` and `parentMessageBuilder` now use the same typedef: - -**Before:** -```dart -typedef MessageBuilder = Widget Function( - BuildContext context, - MessageDetails details, - List messages, - StreamMessageItem defaultMessageWidget, -); - -typedef ParentMessageBuilder = Widget Function( - BuildContext context, - Message? parentMessage, - StreamMessageItem defaultMessageWidget, -); -``` - -**After:** -```dart -typedef StreamMessageItemBuilder = Widget Function( - BuildContext context, - Message message, - StreamMessageItemProps defaultProps, -); -``` - -The old builders received a pre-built `StreamMessageItem` that you could `copyWith`. The new builders receive `StreamMessageItemProps` — raw configuration data. Use `StreamMessageItem.fromProps(props:)` to build the default widget through the component factory. - -**Before:** -```dart -StreamMessageListView( - messageBuilder: (context, details, messages, defaultWidget) { - return defaultWidget.copyWith(showReactions: false); - }, -) -``` - -**After:** -```dart -StreamMessageListView( - messageBuilder: (context, message, defaultProps) { - // Build default widget (goes through component factory) - return StreamMessageItem.fromProps(props: defaultProps); - - // Or customize props before building - return StreamMessageItem.fromProps( - props: defaultProps.copyWith( - actionsBuilder: (context, actions) => [...actions, myAction], - ), - ); - - // Or replace entirely - return MyCustomMessageWidget(message: message); - }, -) -``` - -> **Important:** The `messageBuilder` callback now receives a `BuildContext` that has `StreamMessagePlacement` in its ancestor chain. You can call `StreamMessagePlacement.alignmentDirectionalOf(context)` to determine message alignment. - -### New List-Level Callbacks - -These callbacks were previously only configurable per-message on `StreamMessageItem`. They are now available at the list level and forwarded to all messages: - -| New Parameter | Type | -|---|---| -| `onEditMessageTap` | `void Function(Message)?` | -| `onReplyTap` | `void Function(Message)?` | -| `onUserAvatarTap` | `void Function(User)?` | -| `onReactionsTap` | `void Function(Message)?` | -| `onQuotedMessageTap` | `void Function(Message)?` | -| `onMessageLinkTap` | `void Function(Message, String)?` | -| `onUserMentionTap` | `void Function(User)?` | - -### Changed: `showUnreadCountOnScrollToBottom` Default - -```dart -// Old -showUnreadCountOnScrollToBottom: false - -// New -showUnreadCountOnScrollToBottom: true -``` - -### Removed: MessageDetails - -The old `messageBuilder` received `MessageDetails` which contained `userId`, `message`, `messages`, and `index`. The new builder receives just `Message` and `StreamMessageItemProps`. The user ID is accessible via `StreamChat.of(context).currentUser?.id`. Message alignment is provided by `StreamMessagePlacement.of(context)`. +| Callback | Old Signature | New Signature | +| ------------------ | ---------------------------------------- | ------------------------------------------------------------------------------ | +| Link tap | `void Function(String url)` | `void Function(Message message, String url)` | +| Mention tap | `void Function(User user)` | `void Function(User user)` (renamed: `onMentionTap` → `onUserMentionTap`) | +| Quoted message tap | `void Function(String? quotedMessageId)` | `void Function(Message quotedMessage)` | +| Thread tap | `void Function(Message message)` | `void Function(Message parentMessage, Message? threadMessage)` (renamed: `onThreadTap`) | +| Reply tap | `void Function(Message message)` | `void Function(Message message)` (new: `onReplyTap`) | --- @@ -377,6 +281,15 @@ actionsBuilder: (context, defaultActions) { ## Theme Migration +`StreamMessageThemeData` and the paired `ownMessageTheme` / `otherMessageTheme` accessors on `StreamChatThemeData` have been **removed**. Use `StreamMessageItemThemeData` instead — it is placement-aware and exposes the new structured visibility system. + +| Removed | Replacement | +| ------------------------------------------ | -------------------------------------------------------------------------- | +| `StreamMessageThemeData` | `StreamMessageItemThemeData` | +| `StreamChatThemeData.ownMessageTheme` | Placement-aware styling via `StreamMessageLayoutProperty.resolveWith` on individual style fields (e.g. `bubble`, `metadata`) | +| `StreamChatThemeData.otherMessageTheme` | Placement-aware styling via `StreamMessageLayoutProperty.resolveWith` on individual style fields (e.g. `bubble`, `metadata`) | +| `StreamMessageItem.messageTheme` parameter | Resolved from context via `StreamMessageItemTheme.of(context)` | + **Before (explicit `messageTheme` parameter):** ```dart StreamMessageItem( @@ -392,7 +305,7 @@ StreamMessageItem( StreamMessageItem(message: message) ``` -`StreamMessageItemTheme` is provided by `StreamChatTheme` and resolved based on `StreamMessagePlacement` (alignment, stack position, etc.). +`StreamMessageItemTheme` is provided by `StreamChatTheme` and resolved based on `StreamMessageLayout` (alignment, stack position, etc.). ### StreamMessageItemThemeData @@ -400,27 +313,51 @@ The old per-property visibility booleans are replaced by a structured visibility ```dart StreamMessageItemThemeData( - avatarVisibility: StreamMessageStyleVisibility( - incoming: StreamVisibility.visible, - outgoing: StreamVisibility.gone, - ), - annotationVisibility: StreamMessageStyleVisibility(...), - metadataVisibility: StreamMessageStyleVisibility(...), - repliesVisibility: StreamMessageStyleVisibility(...), - - incoming: StreamMessageItemStyle( - padding: EdgeInsets.all(4), - backgroundColor: Colors.white, - ), - outgoing: StreamMessageItemStyle( - padding: EdgeInsets.all(4), - backgroundColor: Colors.blue.shade50, + // Visibility fields take a StreamMessageLayoutVisibility resolved per layout. + avatarVisibility: StreamMessageLayoutVisibility.resolveWith((layout) { + // Hide avatar for end-aligned (outgoing) messages. + return layout.alignment == StreamMessageAlignment.end + ? StreamVisibility.gone + : StreamVisibility.visible; + }), + metadataVisibility: StreamMessageLayoutVisibility.resolveWith((layout) { + return switch (layout.stackPosition) { + StreamMessageStackPosition.bottom || + StreamMessageStackPosition.single => StreamVisibility.visible, + _ => StreamVisibility.gone, + }; + }), + + // Placement-aware bubble color lives on the bubble style field. + bubble: StreamMessageBubbleStyle( + backgroundColor: StreamMessageLayoutProperty.resolveWith((layout) { + return layout.alignment == StreamMessageAlignment.end + ? Colors.blue.shade50 + : Colors.white; + }), ), ) ``` --- +## Removed Widgets + +The following widgets have been removed from the SDK. Replace any direct references with the listed alternatives. + +| Removed Widget | Notes | +| ---------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| `StreamMarkdownMessage` | Use `StreamMessageText` | +| `StreamMessageThemeData` (and `ownMessageTheme` / `otherMessageTheme`) | Use `StreamMessageItemThemeData` — see [Theme Migration](#theme-migration) | + +For attachment-related removals (`StreamFileAttachmentThumbnail`, `StreamAttachmentUploadStateBuilder.successBuilder`, etc.), see [attachments_and_polls.md](attachments_and_polls.md). + +For composer-related removals (`AttachmentButton`, `StreamQuotedMessageWidget`, `EditMessageSheet`, `StreamMessageSendButton`, etc.), see [message_composer.md](message_composer.md). + +For reactions-related removals (`DesktopReactionsBuilder`, `StreamMessageReactionsModal`), see [reaction_list.md](reaction_list.md) and [message_actions.md](message_actions.md). + +--- + ## Swipeable Message Example ```dart @@ -430,7 +367,7 @@ StreamMessageListView( if (message.isDeleted || message.state.isFailed) return defaultWidget; - final alignment = StreamMessagePlacement.alignmentDirectionalOf(context); + final alignment = StreamMessageLayout.alignmentDirectionalOf(context); final isEnd = alignment == AlignmentDirectional.centerEnd; return Swipeable( @@ -448,35 +385,32 @@ StreamMessageListView( ## Deleted Classes & Files -| Old File | Old Class | Replacement | -|---|---|---| -| `message_widget_content.dart` | `MessageWidgetContent` | `DefaultStreamMessageItem` + `StreamMessageContent` | -| `message_widget_content_components.dart` | Various internal helpers | Merged into `components/` sub-widgets | -| `bottom_row.dart` | `BottomRow` | `StreamMessageFooter` | -| `message_text.dart` | `StreamMessageText` | `components/stream_message_text.dart` | -| `deleted_message.dart` | `StreamDeletedMessage` | `StreamMessageDeleted` | -| `message_card.dart` | `MessageCard` | `core.StreamMessageBubble` | -| `text_bubble.dart` | `TextBubble` | `core.StreamMessageBubble` | -| `pinned_message.dart` | `PinnedMessage` | `StreamMessageHeader` widget | -| `quoted_message.dart` | `QuotedMessage` | Inline in `StreamMessageContent` | -| `thread_painter.dart` | `ThreadReplyPainter` | `core.StreamMessageReplies` | -| `thread_participants.dart` | `ThreadParticipants` | Inline in `core.StreamMessageReplies` | -| `user_avatar_transform.dart` | `UserAvatarTransform` | `StreamUserAvatar` (inline in `DefaultStreamMessageItem`) | -| `username.dart` | `Username` | Inline in `StreamMessageFooter` | -| `sending_indicator_builder.dart` | `SendingIndicatorBuilder` | `StreamMessageSendingStatus` | +| Old File | Old Class | Replacement | +| ---------------------------------------- | ------------------------- | --------------------------------------------------------- | +| `message_widget_content.dart` | `MessageWidgetContent` | `DefaultStreamMessageItem` + `StreamMessageContent` | +| `message_widget_content_components.dart` | Various internal helpers | Merged into `components/` sub-widgets | +| `bottom_row.dart` | `BottomRow` | `StreamMessageFooter` | +| `message_text.dart` | `StreamMessageText` | `components/stream_message_text.dart` | +| `deleted_message.dart` | `StreamDeletedMessage` | `StreamMessageDeleted` | +| `message_card.dart` | `MessageCard` | `core.StreamMessageBubble` | +| `text_bubble.dart` | `TextBubble` | `core.StreamMessageBubble` | +| `pinned_message.dart` | `PinnedMessage` | `StreamMessageHeader` widget | +| `quoted_message.dart` | `QuotedMessage` | Inline in `StreamMessageContent` | +| `thread_painter.dart` | `ThreadReplyPainter` | `core.StreamMessageReplies` | +| `thread_participants.dart` | `ThreadParticipants` | Inline in `core.StreamMessageReplies` | +| `user_avatar_transform.dart` | `UserAvatarTransform` | `StreamUserAvatar` (inline in `DefaultStreamMessageItem`) | +| `username.dart` | `Username` | Inline in `StreamMessageFooter` | +| `sending_indicator_builder.dart` | `SendingIndicatorBuilder` | `StreamMessageSendingStatus` | --- ## Typedef Changes -| Old Typedef | New Typedef | -|---|---| -| `MessageBuilder = Widget Function(BuildContext, MessageDetails, List, StreamMessageItem)` | `StreamMessageItemBuilder = Widget Function(BuildContext, Message, StreamMessageItemProps)` | -| `ParentMessageBuilder = Widget Function(BuildContext, Message?, StreamMessageItem)` | `StreamMessageItemBuilder` (same as above) | -| `OnQuotedMessageTap = void Function(String?)` | Removed — use `void Function(Message)` directly | -| — | `MessageActionsBuilder = List Function(BuildContext, List>)` (new) | +| Old Typedef | New Typedef | +| ----------- | -------------------------------------------------------------------------------------------------------- | +| — | `MessageActionsBuilder = List Function(BuildContext, List>)` (new) | -> **Note:** `MessageBuilder` and `ParentMessageBuilder` are removed from `typedefs.dart`. The new `StreamMessageItemBuilder` is defined in `message_list_view.dart` and exported via the barrel file. +For the `MessageBuilder` / `ParentMessageBuilder` typedef changes and the new `StreamMessageItemBuilder`, see [message_list.md](message_list.md#builder-signature-changes). --- @@ -487,14 +421,15 @@ StreamMessageListView( - [ ] Remove `customActions` and `onCustomActionTap` — use `actionsBuilder` via component factory or `StreamMessageItemProps.copyWith()` - [ ] Remove all per-widget builder callbacks (`userAvatarBuilder`, `textBuilder`, `quotedMessageBuilder`, `deletedMessageBuilder`, `bottomRowBuilderWithDefaultWidget`, `reactionPickerBuilder`, `reactionIndicatorBuilder`) — use component factory instead - [ ] Remove `shape`, `borderSide`, `borderRadiusGeometry`, `attachmentShape`, `textPadding`, `attachmentPadding` — controlled via `StreamMessageBubble` theming -- [ ] Remove `reverse` — determined by `StreamMessagePlacement` context +- [ ] Remove `reverse` — determined by `StreamMessageLayout` context - [ ] Remove `translateUserAvatar` — avatar positioning is theme-driven -- [ ] Update `messageBuilder` / `parentMessageBuilder` callbacks to new `StreamMessageItemBuilder` signature -- [ ] Replace `MessageDetails` usage — use `StreamMessagePlacement.of(context)` for alignment, `StreamChat.of(context).currentUser` for user ID +- [ ] Replace `StreamMessageThemeData` / `ownMessageTheme` / `otherMessageTheme` with `StreamMessageItemThemeData` - [ ] Update `onLinkTap` to `onMessageLinkTap` with new signature `void Function(Message, String)` - [ ] Update `onMentionTap` to `onUserMentionTap` - [ ] Update `onQuotedMessageTap` from `void Function(String?)` to `void Function(Message)` - [ ] Replace `StreamDeletedMessage` with `StreamMessageDeleted` +- [ ] Replace `StreamMarkdownMessage` with `StreamMessageText` - [ ] Replace `StreamMessageAction` with `StreamContextMenuAction` (see [message_actions.md](message_actions.md)) - [ ] Replace `StreamSvgIcon(icon: StreamSvgIcons.*)` with `Icon(context.streamIcons.*)` - [ ] Remove `StreamMessageItem.copyWith()` usage — use `StreamMessageItemProps.copyWith()` instead +- [ ] For `StreamMessageListView` migration (config / builders split, `messageBuilder` signature changes, etc.), see [message_list.md](message_list.md) diff --git a/migrations/redesign/reaction_list.md b/migrations/redesign/reaction_list.md index df9539beb3..d16b714b50 100644 --- a/migrations/redesign/reaction_list.md +++ b/migrations/redesign/reaction_list.md @@ -9,6 +9,7 @@ This guide covers the new reaction list controller, view, and detail sheet intro - [StreamReactionListController](#streamreactionlistcontroller) - [StreamReactionListView](#streamreactionlistview) - [ReactionDetailSheet](#reactiondetailsheet) +- [Removed Widgets](#removed-widgets) - [Migration Checklist](#migration-checklist) --- @@ -29,20 +30,20 @@ StreamReactionListController({ }) ``` -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `client` | `StreamChatClient` | **required** | The Stream chat client | -| `messageId` | `String` | **required** | ID of the message to load reactions for | -| `filter` | `Filter?` | `null` | Query filter; supports fields `type`, `user_id`, `created_at` | -| `sort` | `SortOrder?` | `null` | Sort order; only `created_at` is backend-supported (`ReactionSortKey.createdAt`) | -| `limit` | `int` | `25` | Page size | +| Parameter | Type | Default | Description | +| ----------- | ---------------------- | ------------ | -------------------------------------------------------------------------------- | +| `client` | `StreamChatClient` | **required** | The Stream chat client | +| `messageId` | `String` | **required** | ID of the message to load reactions for | +| `filter` | `Filter?` | `null` | Query filter; supports fields `type`, `user_id`, `created_at` | +| `sort` | `SortOrder?` | `null` | Sort order; only `created_at` is backend-supported (`ReactionSortKey.createdAt`) | +| `limit` | `int` | `25` | Page size | ### Methods -| Method | Description | -|--------|-------------| -| `doInitialLoad()` | Loads the first page of reactions | -| `loadMore(String? nextPageKey)` | Loads the next page using cursor-based pagination | +| Method | Description | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------- | +| `doInitialLoad()` | Loads the first page of reactions | +| `loadMore(String? nextPageKey)` | Loads the next page using cursor-based pagination | | `refresh({bool resetValue = true})` | Reloads from the beginning; resets active filter/sort to constructor values when `resetValue` is `true` | ### Runtime Filter / Sort Changes @@ -88,15 +89,15 @@ StreamReactionListView({ }) ``` -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `controller` | `StreamReactionListController` | yes | Provides and paginates the reaction data | -| `itemBuilder` | `StreamReactionListViewIndexedWidgetBuilder` | yes | Builds each reaction item | -| `separatorBuilder` | `PagedValueScrollViewIndexedWidgetBuilder?` | no | Builds separators between items (defaults to `SizedBox.shrink`) | -| `emptyBuilder` | `WidgetBuilder?` | no | Widget shown when there are no reactions | -| `loadingBuilder` | `WidgetBuilder?` | no | Widget shown during initial load | -| `errorBuilder` | `Widget Function(BuildContext, StreamChatError)?` | no | Widget shown on error | -| `loadMoreTriggerIndex` | `int` | no | How many items from the end to trigger the next page load (default: 3) | +| Parameter | Type | Required | Description | +| ---------------------- | ----------------------------------------------------- | -------- | ---------------------------------------------------------------------- | +| `controller` | `StreamReactionListController` | yes | Provides and paginates the reaction data | +| `itemBuilder` | `StreamReactionListViewIndexedWidgetBuilder` | yes | Builds each reaction item | +| `separatorBuilder` | `PagedValueScrollViewIndexedWidgetBuilder?` | no | Builds separators between items (defaults to `SizedBox.shrink`) | +| `emptyBuilder` | `WidgetBuilder?` | no | Widget shown when there are no reactions | +| `loadingBuilder` | `WidgetBuilder?` | no | Widget shown during initial load | +| `errorBuilder` | `Widget Function(BuildContext, StreamChatError)?` | no | Widget shown on error | +| `loadMoreTriggerIndex` | `int` | no | How many items from the end to trigger the next page load (default: 3) | ### Usage @@ -117,7 +118,7 @@ StreamReactionListView( ## ReactionDetailSheet -`ReactionDetailSheet` replaces the old `MessageReactionsModal`. It shows a bottom sheet with the total reaction count, emoji filter chips per reaction type, and a scrollable list of reactors using `StreamReactionListController` internally. +`ReactionDetailSheet` replaces the old `StreamMessageReactionsModal`. It shows a bottom sheet with the total reaction count, emoji filter chips per reaction type, and a scrollable list of reactors using `StreamReactionListController` internally. ### Showing the Sheet @@ -137,19 +138,19 @@ final action = await ReactionDetailSheet.show( ### Parameters of `show` -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `context` | `BuildContext` | yes | Build context | -| `message` | `Message` | yes | The message whose reactions to display | -| `initialReactionType` | `String?` | no | Pre-selects this reaction type chip when the sheet opens | +| Parameter | Type | Required | Description | +| --------------------- | -------------- | -------- | -------------------------------------------------------- | +| `context` | `BuildContext` | yes | Build context | +| `message` | `Message` | yes | The message whose reactions to display | +| `initialReactionType` | `String?` | no | Pre-selects this reaction type chip when the sheet opens | -### Migration from `MessageReactionsModal` +### Migration from `StreamMessageReactionsModal` **Before:** ```dart showDialog( context: context, - builder: (_) => MessageReactionsModal(message: message), + builder: (_) => StreamMessageReactionsModal(message: message), ); ``` @@ -165,9 +166,23 @@ await ReactionDetailSheet.show( --- +## Removed Widgets + +The following reactions-related widgets have been removed. Replace any direct references with the listed alternatives. + +| Removed Widget | Replacement | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `DesktopReactionsBuilder` | Use `ReactionDetailSheet.show(context: context, message: message)` for the full reactors list, or override `reactions` (`StreamComponentBuilder?`) via `StreamComponentFactory` to customize the inline reaction chips | +| `StreamMessageReactionsModal` | Use `ReactionDetailSheet` (see [ReactionDetailSheet](#reactiondetailsheet)) | + +For action-related reaction widget changes (e.g. `StreamMessageReactionsModal` migration), see [message_actions.md](message_actions.md#streammessagereactionsmodal). + +--- + ## Migration Checklist -- [ ] Replace `MessageReactionsModal` with `ReactionDetailSheet.show()` +- [ ] Replace `StreamMessageReactionsModal` with `ReactionDetailSheet.show()` - [ ] Use `StreamReactionListController` to load/paginate reactions programmatically - [ ] Use `StreamReactionListView` with a `StreamReactionListController` for custom reaction list UIs - [ ] For runtime reaction-type filtering, set `controller.filter` and call `controller.doInitialLoad()` +- [ ] Remove any `DesktopReactionsBuilder` usage — replace with `ReactionDetailSheet.show(...)` or a custom `reactions` factory override diff --git a/migrations/redesign/reaction_picker.md b/migrations/redesign/reaction_picker.md index ce2acf3433..3c2c29db0e 100644 --- a/migrations/redesign/reaction_picker.md +++ b/migrations/redesign/reaction_picker.md @@ -11,7 +11,7 @@ This guide covers the migration for the redesigned reaction picker and reaction - [Removed Icon-List APIs](#removed-icon-list-apis) - [ReactionIconResolver and DefaultReactionIconResolver](#reactioniconresolver-and-defaultreactioniconresolver) - [StreamMessageReactionPicker](#streammessagereactionpicker-formerly-streamreactionpicker) -- [StreamReactionIndicator](#streamreactionindicator) +- [StreamMessageReactions](#streammessagereactions) - [New Components](#new-components) - [Migration Checklist](#migration-checklist) @@ -19,18 +19,18 @@ This guide covers the migration for the redesigned reaction picker and reaction ## Quick Reference -| Symbol | Change | -|--------|--------| -| `StreamChatConfigurationData.reactionIcons` | **Removed** — replaced by `reactionIconResolver` | -| `StreamChatConfigurationData.reactionIconResolver` | **New** — optional (default: `DefaultReactionIconResolver()`). Replaces `reactionIcons` | -| `ReactionIconResolver` | **New** — abstract contract for mapping reaction type → `StreamEmojiContent` | -| `DefaultReactionIconResolver` | **New** — ready-to-use default; extend to customize `defaultReactions`, `emojiCode`, or `resolve` | -| `ReactionPickerIconList` / `ReactionIndicatorIconList` | **Removed** — list rendering now lives inside picker/indicator widgets | -| `ReactionPickerIcon` / `ReactionIndicatorIcon` | **Removed** — use resolver-based reaction mapping instead | -| `StreamReactionPicker` | **Renamed** to `StreamMessageReactionPicker` — reaction set from `config.reactionIconResolver.defaultReactions` only | -| `StreamReactionPickerTheme` / `StreamReactionPickerThemeData` | **New** (from `stream_core_flutter`) — theme-based visual customisation for the picker | -| `StreamReactionIndicator` | **Changed** — uses `config.reactionIconResolver.resolve(type)` only | -| `ReactionDetailSheet` | **New** — `ReactionDetailSheet.show()` for reaction details bottom sheet | +| Symbol | Change | +| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| `StreamChatConfigurationData.reactionIcons` | **Removed** — replaced by `reactionIconResolver` | +| `StreamChatConfigurationData.reactionIconResolver` | **New** — optional (default: `DefaultReactionIconResolver()`). Replaces `reactionIcons` | +| `ReactionIconResolver` | **New** — abstract contract for mapping reaction type → `StreamEmojiContent` | +| `DefaultReactionIconResolver` | **New** — ready-to-use default; extend to customize `defaultReactions`, `emojiCode`, or `resolve` | +| `ReactionPickerIconList` / `ReactionIndicatorIconList` | **Removed** — list rendering now lives inside picker/indicator widgets | +| `ReactionPickerIcon` / `ReactionIndicatorIcon` | **Removed** — use resolver-based reaction mapping instead | +| `StreamReactionPicker` | **Renamed** to `StreamMessageReactionPicker` — reaction set from `config.reactionIconResolver.defaultReactions` only | +| `StreamReactionPickerTheme` / `StreamReactionPickerThemeData` | **New** (from `stream_core_flutter`) — theme-based visual customization for the picker | +| `StreamMessageReactions` | **New** — renders emoji chips for a message's reaction groups; replaces ad-hoc indicator usage | +| `ReactionDetailSheet` | **New** — `ReactionDetailSheet.show()` for reaction details bottom sheet | > **Note:** If you were using default reactions only, behavior stays the same (`like`, `haha`, `love`, `wow`, `sad`). Migration is required only for custom reaction icon/type setups. @@ -264,48 +264,57 @@ StreamReactionPickerTheme( --- -## StreamReactionIndicator +## StreamMessageReactions -### Breaking Changes: +`StreamMessageReactions` is the widget that renders a message's reaction groups as emoji chips overlaid on, or placed beneath, a message bubble. It replaces any prior ad-hoc indicator usage. Reaction icons are resolved through `StreamChatConfigurationData.reactionIconResolver`. -- Indicator icons are resolved only through `config.reactionIconResolver.resolve(type)` -- Old icon-list based customization paths were removed +### Constructor -### Migration +```dart +StreamMessageReactions({ + Key? key, + required Message message, + StreamReactionsType? type, // defaults to StreamReactionsType.segmented + StreamReactionsPosition? position, // defaults to StreamReactionsPosition.header on mobile, + // StreamReactionsPosition.footer on desktop/web + bool? overlap, // defaults to true on mobile, false on desktop/web + Comparator? sorting, // defaults to ReactionSorting.byFirstReactionAt + VoidCallback? onPressed, + Widget? child, // typically the message bubble +}) +``` + +### Usage + +`StreamMessageReactions` is used internally by `StreamMessageContent` and `StreamMessageScaffold`. If you need to render reactions outside the standard message widget, use it directly: -**Before:** ```dart -StreamChat( - client: client, - streamChatConfigData: StreamChatConfigurationData( - reactionIcons: [ /* old icon list */ ], - ), - child: MyApp(), +StreamMessageReactions( + message: message, + onPressed: () { /* show reaction detail sheet */ }, + child: myMessageBubble, ) ``` -**After:** +### Customization + +Reaction icons are resolved globally — configure them on `StreamChatConfigurationData.reactionIconResolver` (see [ReactionIconResolver and DefaultReactionIconResolver](#reactioniconresolver-and-defaultreactioniconresolver)). + +Visual layout properties (`type`, `position`, `overlap`) can be set per widget or as defaults via `StreamChatConfigurationData`: + ```dart StreamChat( client: client, streamChatConfigData: StreamChatConfigurationData( reactionIconResolver: const MyReactionIconResolver(), + reactionType: StreamReactionsType.segmented, + reactionPosition: StreamReactionsPosition.header, + reactionOverlap: true, ), child: MyApp(), ) ``` -Then keep indicator usage unchanged: - -```dart -StreamReactionIndicator( - message: message, - onTap: onTap, -) -``` - -Customize via `reactionIconResolver` in config. - --- ## New Components diff --git a/migrations/redesign/stream_avatar.md b/migrations/redesign/stream_avatar.md index 4897a4c7f2..2b8a156e46 100644 --- a/migrations/redesign/stream_avatar.md +++ b/migrations/redesign/stream_avatar.md @@ -18,12 +18,12 @@ This guide covers the migration for the redesigned avatar components in Stream C ## Quick Reference -| Component | Key Changes | -|-----------|-------------| -| [**StreamUserAvatar**](#streamuseravatar) | `constraints` → `size` enum, `showOnlineStatus` → `showOnlineIndicator`, `onTap` removed | -| [**StreamChannelAvatar**](#streamchannelavatar) | `constraints` → `size` enum, `onTap` and builder callbacks removed | -| [**StreamGroupAvatar**](#streamgroupavatar) | Renamed to `StreamUserAvatarGroup`, `members` → `users` | -| [**StreamUserAvatarStack**](#streamuseravatarstack) | New component for overlapping avatars | +| Component | Key Changes | +| --------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| [**StreamUserAvatar**](#streamuseravatar) | `constraints` → `size` enum, `showOnlineStatus` → `showOnlineIndicator`, `onTap` removed | +| [**StreamChannelAvatar**](#streamchannelavatar) | `constraints` → `size` enum, `onTap` and builder callbacks removed | +| [**StreamGroupAvatar**](#streamgroupavatar) | Renamed to `StreamUserAvatarGroup`, `members` → `users` | +| [**StreamUserAvatarStack**](#streamuseravatarstack) | New component for overlapping avatars | --- @@ -159,12 +159,12 @@ GestureDetector( ### Parameters: -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `users` | `Iterable` | required | Users to display | -| `size` | `StreamAvatarStackSize?` | `.sm` | Size of avatars | -| `max` | `int` | `5` | Max avatars before overflow badge | -| `overlap` | `double` | `0.33` | Overlap fraction (0.0 - 1.0) | +| Parameter | Type | Default | Description | +| --------- | ------------------------ | -------- | --------------------------------- | +| `users` | `Iterable` | required | Users to display | +| `size` | `StreamAvatarStackSize?` | `.sm` | Size of avatars | +| `max` | `int` | `5` | Max avatars before overflow badge | +| `overlap` | `double` | `0.33` | Overlap fraction (0.0 - 1.0) | ### Usage: @@ -186,29 +186,29 @@ StreamUserAvatarStack( ### StreamAvatarSize -| Old Constraints | New Size | Diameter | -|-----------------|----------|----------| -| `BoxConstraints.tight(Size(20, 20))` | `.xs` | 20px | -| `BoxConstraints.tight(Size(24, 24))` | `.sm` | 24px | -| `BoxConstraints.tight(Size(32, 32))` | `.md` | 32px | -| `BoxConstraints.tight(Size(40, 40))` | `.lg` | 40px | -| `BoxConstraints.tight(Size(48, 48))` | `.xl` | 48px | -| `BoxConstraints.tight(Size(80, 80))` | `.xxl` | 80px | +| Old Constraints | New Size | Diameter | +| ------------------------------------ | -------- | -------- | +| `BoxConstraints.tight(Size(20, 20))` | `.xs` | 20px | +| `BoxConstraints.tight(Size(24, 24))` | `.sm` | 24px | +| `BoxConstraints.tight(Size(32, 32))` | `.md` | 32px | +| `BoxConstraints.tight(Size(40, 40))` | `.lg` | 40px | +| `BoxConstraints.tight(Size(48, 48))` | `.xl` | 48px | +| `BoxConstraints.tight(Size(80, 80))` | `.xxl` | 80px | ### StreamAvatarGroupSize -| Old Constraints | New Size | Diameter | -|-----------------|----------|----------| -| `BoxConstraints.tight(Size(40, 40))` | `.lg` | 40px | -| `BoxConstraints.tight(Size(48, 48))` | `.xl` | 48px | -| `BoxConstraints.tight(Size(80, 80))` | `.xxl` | 80px | +| Old Constraints | New Size | Diameter | +| ------------------------------------ | -------- | -------- | +| `BoxConstraints.tight(Size(40, 40))` | `.lg` | 40px | +| `BoxConstraints.tight(Size(48, 48))` | `.xl` | 48px | +| `BoxConstraints.tight(Size(80, 80))` | `.xxl` | 80px | ### StreamAvatarStackSize -| Old Constraints | New Size | Diameter | -|-----------------|----------|----------| -| `BoxConstraints.tight(Size(20, 20))` | `.xs` | 20px | -| `BoxConstraints.tight(Size(24, 24))` | `.sm` | 24px | +| Old Constraints | New Size | Diameter | +| ------------------------------------ | -------- | -------- | +| `BoxConstraints.tight(Size(20, 20))` | `.xs` | 20px | +| `BoxConstraints.tight(Size(24, 24))` | `.sm` | 24px | > **Note:** > If your old constraints don't match exactly, choose the closest available size. diff --git a/migrations/redesign/unread_indicator.md b/migrations/redesign/unread_indicator.md index 2a41b87f54..61d084451c 100644 --- a/migrations/redesign/unread_indicator.md +++ b/migrations/redesign/unread_indicator.md @@ -17,18 +17,20 @@ parameters have been removed. ### Breaking Changes - `backgroundColor`, `textColor`, and `textStyle` constructor parameters - removed — styling is now controlled via `StreamTheme`. -- The widget is now wrapped in `IgnorePointer`; it does not respond to taps - itself. + removed — styling is now controlled via `StreamBadgeNotificationThemeData` + (applied through `StreamBadgeNotificationTheme`). `StreamUnreadIndicator` + itself has no direct theme hook. +- The widget has no `GestureDetector` or `InkWell`; it is non-interactive by + default and does not respond to taps. - Now supports named constructors for different unread count types. ### Named Constructors -| Constructor | Description | -|-------------|-------------| -| `StreamUnreadIndicator()` | Shows total unread message count | +| Constructor | Description | +| ----------------------------------------------- | ------------------------------------------------------------------------------ | +| `StreamUnreadIndicator()` | Shows total unread message count | | `StreamUnreadIndicator.channels({String? cid})` | Shows unread channel count; optionally filtered to a specific channel by `cid` | -| `StreamUnreadIndicator.threads({String? id})` | Shows unread thread count | +| `StreamUnreadIndicator.threads({String? id})` | Shows unread thread count. **Note:** the `id` parameter is not yet supported (currently a no-op); the total unread thread count is always shown regardless of the value passed. | ### Overlay Mode @@ -36,11 +38,11 @@ parameters have been removed. passing a `child`. When `child` is non-null, the badge is positioned over the child using `alignment` and `offset`. -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| `child` | `Widget?` | `null` | Widget to overlay the badge on. When `null`, only the badge is rendered. | -| `alignment` | `AlignmentGeometry?` | `AlignmentDirectional.topEnd` | Alignment of the badge relative to `child`. | -| `offset` | `Offset?` | `Offset(8, -6)` (mirrored in RTL) | Pixel offset applied after `alignment`. | +| Parameter | Type | Default | Description | +| ----------- | -------------------- | --------------------------------- | ------------------------------------------------------------------------ | +| `child` | `Widget?` | `null` | Widget to overlay the badge on. When `null`, only the badge is rendered. | +| `alignment` | `AlignmentGeometry?` | `AlignmentDirectional.topEnd` | Alignment of the badge relative to `child`. | +| `offset` | `Offset?` | `Offset(8, -6)` (mirrored in RTL) | Pixel offset applied after `alignment`. | ```dart // Standalone badge (no child). diff --git a/migrations/redesign/unread_indicator_button.md b/migrations/redesign/unread_indicator_button.md index 72fcce3e20..1a9d7639ea 100644 --- a/migrations/redesign/unread_indicator_button.md +++ b/migrations/redesign/unread_indicator_button.md @@ -28,10 +28,9 @@ Use `StreamComponentFactory` instead for app-wide customisation. ### 4. `UnreadIndicatorButton` callback renames -| Before | After | -|----------------------|----------------| -| `onTap` | `onJumpTap` | -| `onDismissTap` | `onDismissTap` | +| Before | After | +| ------- | ----------- | +| `onTap` | `onJumpTap` | ### 5. `streamChatComponentBuilders` — `unreadIndicator` parameter removed @@ -158,10 +157,10 @@ UnreadIndicatorButton( ## Props Mapping Reference -| Old (`UnreadIndicatorProps`) | New (`StreamJumpToUnreadButtonProps`) | -|-------------------------------|--------------------------------------| -| `unreadCount` (int) | `label` (String) — pre-formatted | -| `onTap` (callback) | `onJumpPressed` (VoidCallback?) | -| `onDismissTap` (callback) | `onDismissPressed` (VoidCallback?) | -| — | `leadingIcon` (IconData?) | -| — | `trailingIcon` (IconData?) | +| Old (`UnreadIndicatorProps`) | New (`StreamJumpToUnreadButtonProps`) | +| ---------------------------- | ------------------------------------- | +| `unreadCount` (int) | `label` (String) — pre-formatted | +| `onTap` (callback) | `onJumpPressed` (VoidCallback?) | +| `onDismissTap` (callback) | `onDismissPressed` (VoidCallback?) | +| — | `leadingIcon` (IconData?) | +| — | `trailingIcon` (IconData?) | diff --git a/migrations/v10-migration.md b/migrations/v10-migration.md index 31b4dd4b51..39823eb050 100644 --- a/migrations/v10-migration.md +++ b/migrations/v10-migration.md @@ -30,6 +30,9 @@ This guide covers all breaking changes in **Stream Chat Flutter SDK v10.0.0**. W - [File Upload](#file-upload) - [AttachmentFileUploader](#attachmentfileuploader) - [Unread Threads Banner](#unread-threads-banner) +- [Stable Release Changes](#stable-release-changes) + - [ClientState Collection Immutability](#stream_chat-clientstate-collection-immutability) + - [SortOption Constructor Rename](#stream_chat-sortoption-constructor-rename) - [Appendix: Beta Release Timeline](#appendix-beta-release-timeline) - [Migration Checklist](#migration-checklist) @@ -37,16 +40,17 @@ This guide covers all breaking changes in **Stream Chat Flutter SDK v10.0.0**. W ## Who Should Read This -| Upgrading From | Sections to Review | -|----------------|-------------------| -| **v9.x** | All sections | -| [**v10.0.0-beta.1**](#v1000-beta1) | All sections introduced after beta.1 | -| [**v10.0.0-beta.3**](#v1000-beta3) | Sections introduced in beta.4 and later | -| [**v10.0.0-beta.4**](#v1000-beta4) | Sections introduced in beta.7 and later | -| [**v10.0.0-beta.7**](#v1000-beta7) | Sections introduced in beta.8 and later | -| [**v10.0.0-beta.8**](#v1000-beta8) | Sections introduced in beta.9 and later | -| [**v10.0.0-beta.9**](#v1000-beta9) | Sections introduced in beta.12 | -| [**v10.0.0-beta.12**](#v1000-beta12) | No additional changes | +| Upgrading From | Sections to Review | +| ------------------------------------ | ------------------------------------------------- | +| **v9.x** | All sections | +| [**v10.0.0-beta.1**](#v1000-beta1) | All sections introduced after beta.1 | +| [**v10.0.0-beta.3**](#v1000-beta3) | Sections introduced in beta.4 and later | +| [**v10.0.0-beta.4**](#v1000-beta4) | Sections introduced in beta.7 and later | +| [**v10.0.0-beta.7**](#v1000-beta7) | Sections introduced in beta.8 and later | +| [**v10.0.0-beta.8**](#v1000-beta8) | Sections introduced in beta.9 and later | +| [**v10.0.0-beta.9**](#v1000-beta9) | Sections introduced in beta.12 | +| [**v10.0.0-beta.12**](#v1000-beta12) | [Stable Release Changes](#stable-release-changes) | +| **v10.0.0** (stable) | [Stable Release Changes](#stable-release-changes) | Each breaking change section includes an **"Introduced in"** tag so you can quickly identify which changes apply to your upgrade path. @@ -54,14 +58,14 @@ Each breaking change section includes an **"Introduced in"** tag so you can quic ## Quick Reference -| Feature Area | Key Changes | -|-------------|-------------| -| [**Attachment Picker**](#attachment-picker) | Sealed class hierarchy, builder pattern for options, typed result handling | -| [**Reactions**](#reactions) | `Reaction` object API, explicit `onReactionPicked` callbacks required | -| [**Message UI**](#message-ui) | New `onAttachmentTap` signature with fallback support, generic `StreamMessageAction` | -| [**Message State**](#message-state--deletion) | `MessageDeleteScope` replaces `bool hard`, delete-for-me support | -| [**File Upload**](#file-upload) | Four new abstract methods on `AttachmentFileUploader` | -| [**Unread Threads Banner**](#unread-threads-banner) | Wrapper pattern with `child`, `enabled`, `onRefresh`; removed `onTap`, `minHeight` | +| Feature Area | Key Changes | +| --------------------------------------------------- | ------------------------------------------------------------------------------------ | +| [**Attachment Picker**](#attachment-picker) | Sealed class hierarchy, builder pattern for options, typed result handling | +| [**Reactions**](#reactions) | `Reaction` object API, explicit `onReactionPicked` callbacks required | +| [**Message UI**](#message-ui) | `StreamAttachmentWidgetTapCallback` signature `void Function(Message, Attachment)`; sealed `MessageAction` hierarchy with `actionsBuilder` | +| [**Message State**](#message-state--deletion) | `MessageDeleteScope` replaces `bool hard`, delete-for-me support | +| [**File Upload**](#file-upload) | Four new abstract methods on `AttachmentFileUploader` | +| [**Unread Threads Banner**](#unread-threads-banner) | Wrapper pattern with `child`, `enabled`, `onRefresh`; removed `onTap`, `minHeight` | --- @@ -121,7 +125,7 @@ class LocationAttachmentPickerType extends CustomAttachmentPickerType { ```dart final option = AttachmentPickerOption( title: 'Gallery', - icon: Icon(Icons.photo_library), + icon: Icons.photo_library, supportedTypes: [AttachmentPickerType.images, AttachmentPickerType.videos], optionViewBuilder: (context, controller) { return GalleryPickerView(controller: controller); @@ -130,7 +134,7 @@ final option = AttachmentPickerOption( final webOrDesktopOption = WebOrDesktopAttachmentPickerOption( title: 'File Upload', - icon: Icon(Icons.upload_file), + icon: Icons.upload_file, type: AttachmentPickerType.files, ); ``` @@ -140,7 +144,7 @@ final webOrDesktopOption = WebOrDesktopAttachmentPickerOption( // For custom UI pickers (gallery, polls) final tabbedOption = TabbedAttachmentPickerOption( title: 'Gallery', - icon: Icon(Icons.photo_library), + icon: Icons.photo_library, supportedTypes: [AttachmentPickerType.images, AttachmentPickerType.videos], optionViewBuilder: (context, controller) { return GalleryPickerView(controller: controller); @@ -150,7 +154,7 @@ final tabbedOption = TabbedAttachmentPickerOption( // For system pickers (camera, file dialogs) final systemOption = SystemAttachmentPickerOption( title: 'Camera', - icon: Icon(Icons.camera_alt), + icon: Icons.camera_alt, supportedTypes: [AttachmentPickerType.images], onTap: (context, controller) => pickFromCamera(), ); @@ -168,8 +172,8 @@ final systemOption = SystemAttachmentPickerOption( #### Key Changes: -- Now returns `StreamAttachmentPickerResult` instead of `AttachmentPickerValue` -- Improved type safety and clearer intent handling +- `showStreamAttachmentPickerModalBottomSheet` has been **removed**. The attachment picker is now an inline widget embedded directly inside `StreamMessageComposer`. There is no longer a separate modal bottom sheet function to call. +- To customise available picker options, use `attachmentPickerOptionsBuilder` on `StreamMessageComposer` (see [customAttachmentPickerOptions](#customattachmentpickeroptions)). #### Migration Steps: @@ -179,32 +183,22 @@ final result = await showStreamAttachmentPickerModalBottomSheet( context: context, controller: controller, ); - -// result is AttachmentPickerValue ``` **After:** -```dart -final result = await showStreamAttachmentPickerModalBottomSheet( - context: context, - controller: controller, -); -// result is StreamAttachmentPickerResult -switch (result) { - case AttachmentsPicked(): - // Handle picked attachments - case PollCreated(): - // Handle created poll - case AttachmentPickerError(): - // Handle error - case CustomAttachmentPickerResult(): - // Handle custom result -} +Remove the direct call. The picker now opens automatically from within `StreamMessageComposer`. To control which options are available, pass `attachmentPickerOptionsBuilder`: + +```dart +StreamMessageComposer( + attachmentPickerOptionsBuilder: (context, defaultOptions) { + return [...defaultOptions]; // modify as needed + }, +) ``` > **Important:** -> Always handle the new `StreamAttachmentPickerResult` return type with proper switch cases. +> The picker is now inline. If you previously opened the picker programmatically, integrate it into the composer instead of calling a separate function. --- @@ -214,8 +208,8 @@ switch (result) { #### Key Changes: -- `StreamMobileAttachmentPickerBottomSheet` → `StreamTabbedAttachmentPickerBottomSheet` -- `StreamWebOrDesktopAttachmentPickerBottomSheet` → `StreamSystemAttachmentPickerBottomSheet` +- `StreamMobileAttachmentPickerBottomSheet` and `StreamWebOrDesktopAttachmentPickerBottomSheet` have been replaced by **inline widgets** — there are no longer separate bottom-sheet classes. +- The new widgets are `StreamTabbedAttachmentPicker` (for mobile, tabbed interface) and `StreamSystemAttachmentPicker` (for web/desktop, system dialogs). Neither has a `BottomSheet` suffix — they are plain inline `Widget`s embedded inside the composer. #### Migration Steps: @@ -235,22 +229,25 @@ StreamWebOrDesktopAttachmentPickerBottomSheet( ``` **After:** + +The pickers are now managed internally by `StreamMessageComposer`. Direct instantiation is only needed for advanced custom layouts: + ```dart -StreamTabbedAttachmentPickerBottomSheet( - context: context, +// Tabbed picker (mobile default) +StreamTabbedAttachmentPicker( controller: controller, - customOptions: [tabbedOption], + options: {tabbedOption}, ); -StreamSystemAttachmentPickerBottomSheet( - context: context, +// System picker (web/desktop default) +StreamSystemAttachmentPicker( controller: controller, - customOptions: [systemOption], + options: {systemOption}, ); ``` > **Important:** -> The new names better reflect their respective layouts and functionality. +> The bottom-sheet classes have been removed entirely. The new `StreamTabbedAttachmentPicker` and `StreamSystemAttachmentPicker` are inline widgets, not sheets. In most cases you do not need to instantiate them directly — use `attachmentPickerOptionsBuilder` on `StreamMessageComposer` to customise picker options. --- @@ -271,7 +268,7 @@ StreamMessageComposer( customAttachmentPickerOptions: [ TabbedAttachmentPickerOption( key: 'custom-location', - icon: const Icon(Icons.location_on), + icon: Icons.location_on, supportedTypes: [AttachmentPickerType.images], optionViewBuilder: (context, controller) { return CustomLocationPicker(); @@ -290,7 +287,7 @@ StreamMessageComposer( ...defaultOptions, TabbedAttachmentPickerOption( key: 'custom-location', - icon: const Icon(Icons.location_on), + icon: Icons.location_on, supportedTypes: [AttachmentPickerType.images], optionViewBuilder: (context, controller) { return CustomLocationPicker(); @@ -321,29 +318,10 @@ StreamMessageComposer( ) ``` -**Using with `showStreamAttachmentPickerModalBottomSheet`:** -```dart -final result = await showStreamAttachmentPickerModalBottomSheet( - context: context, - optionsBuilder: (context, defaultOptions) { - return [ - ...defaultOptions, - TabbedAttachmentPickerOption( - key: 'custom-option', - icon: const Icon(Icons.star), - supportedTypes: [AttachmentPickerType.images], - optionViewBuilder: (context, controller) { - return CustomPickerView(); - }, - ), - ]; - }, -); -``` - > **Important:** -> - The builder pattern gives you access to default options, allowing more flexible customization -> - The builder works with both mobile (tabbed) and desktop (system) pickers +> - The builder pattern gives you access to default options, allowing more flexible customization. +> - The builder works with both mobile (tabbed) and desktop (system) pickers. +> - `showStreamAttachmentPickerModalBottomSheet` has been removed. Use `attachmentPickerOptionsBuilder` on `StreamMessageComposer` as shown above. --- @@ -503,9 +481,10 @@ client.sendReaction( #### Key Changes: -- New `StreamReactionPicker.builder` constructor -- Added properties: `padding`, `scrollable`, `borderRadius` -- Automatic reaction handling removed — must now use `onReactionPicked` +- The chat-domain reaction picker widget is now `StreamMessageReactionPicker` (in `stream_chat_flutter`). +- It wraps the domain-agnostic `StreamReactionPicker` from `stream_core_flutter`, resolving reaction icons automatically via `ReactionIconResolver`. +- The widget only accepts `message` and `onReactionPicked` — there are no `padding`, `scrollable`, `borderRadius`, or `reactionIcons` parameters on the chat-level class. +- Automatic reaction handling (sending to the channel) has been removed from the widget — you must supply `onReactionPicked`. #### Migration Steps: @@ -516,33 +495,21 @@ StreamReactionPicker( ); ``` -**After (Recommended – Builder):** -```dart -StreamReactionPicker.builder( - context, - message, - (Reaction reaction) { - // Explicitly handle reaction - }, -); -``` - -**After (Alternative – Direct Configuration):** +**After:** ```dart -StreamReactionPicker( +StreamMessageReactionPicker( message: message, - reactionIcons: StreamChatConfiguration.of(context).reactionIcons, onReactionPicked: (Reaction reaction) { - // Handle reaction here + // Handle reaction — e.g. send or remove via channel + channel.sendReaction(message, reaction); }, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - scrollable: true, - borderRadius: BorderRadius.circular(24), ); ``` -> **Important:** -> Automatic reaction handling has been removed. You must explicitly handle reactions using `onReactionPicked`. +> **Important:** +> - Use `StreamMessageReactionPicker` (not `StreamReactionPicker`) for chat use cases. +> - `StreamReactionPicker` from `stream_core_flutter` is the domain-agnostic primitive; it accepts `List` and `onReactionPicked` (a `ValueSetter`), not chat `Message` objects. +> - `onReactionPicked` is now required for any reaction to be handled. --- @@ -552,55 +519,46 @@ StreamReactionPicker( #### Key Changes: -- `message` parameter has been removed -- `reactionIcons` type changed from `List` to `List` -- `onReactionPicked` callback renamed to `onIconPicked` with new signature: `ValueSetter` -- `iconBuilder` parameter changed from default value to nullable with internal fallback -- Message-specific logic (checking for own reactions) moved to parent widget +- `ReactionPickerIconList` and `ReactionPickerIcon` have been **removed**. They no longer exist in the public API. +- Their functionality has been absorbed into the domain-agnostic `StreamReactionPicker` (from `stream_core_flutter`) and its `StreamReactionPickerItem` model. #### Migration Steps: -**Before:** +If you were using `ReactionPickerIconList` directly, replace it with `StreamMessageReactionPicker` for chat use cases: + ```dart -ReactionPickerIconList( +// Use the high-level chat wrapper instead +StreamMessageReactionPicker( message: message, - reactionIcons: icons, onReactionPicked: (reaction) { - // Handle reaction channel.sendReaction(message, reaction); }, ) ``` -**After:** +For advanced cases that required direct control over each icon, use `StreamReactionPicker` with `StreamReactionPickerItem` objects: + ```dart -// Map StreamReactionIcon to ReactionPickerIcon with selection state -final ownReactions = [...?message.ownReactions]; -final ownReactionsMap = {for (final it in ownReactions) it.type: it}; - -final pickerIcons = icons.map((icon) { - return ReactionPickerIcon( - type: icon.type, - builder: icon.builder, - isSelected: ownReactionsMap[icon.type] != null, - ); -}).toList(); - -ReactionPickerIconList( - reactionIcons: pickerIcons, - onIconPicked: (pickerIcon) { - final reaction = ownReactionsMap[pickerIcon.type] ?? - Reaction(type: pickerIcon.type); - // Handle reaction +// Build items with selection state from the message's own reactions +final ownReactionsMap = {for (final r in [...?message.ownReactions]) r.type: r}; + +StreamReactionPicker( + items: reactionTypes.map((type) => StreamReactionPickerItem( + key: type, + emoji: resolver.resolve(type), + isSelected: ownReactionsMap[type] != null, + )).toList(), + onReactionPicked: (item) { + final reaction = ownReactionsMap[item.key] ?? Reaction(type: item.key); channel.sendReaction(message, reaction); }, ) ``` > **Important:** -> - This is typically an internal widget used by `StreamReactionPicker` -> - If you were using it directly, you now need to handle reaction selection state externally -> - Use `StreamReactionPicker` for most use cases instead of `ReactionPickerIconList` +> - `ReactionPickerIconList` and `ReactionPickerIcon` no longer exist. +> - Use `StreamMessageReactionPicker` for most chat scenarios. +> - If you need the core picker directly, use `StreamReactionPicker` with `StreamReactionPickerItem` (from `stream_core_flutter`). --- @@ -610,9 +568,8 @@ ReactionPickerIconList( #### Key Changes: -- Based on `StreamMessageModal` for consistency -- `messageTheme` removed — inferred automatically -- Reaction handling must now be handled via `onReactionPicked` +- `StreamMessageReactionsModal` has been **removed**. +- The reaction detail view is now `ReactionDetailSheet`, shown as a modal bottom sheet via `ReactionDetailSheet.show(...)`. #### Migration Steps: @@ -628,18 +585,16 @@ StreamMessageReactionsModal( **After:** ```dart -StreamMessageReactionsModal( +ReactionDetailSheet.show( + context: context, message: message, - messageWidget: myMessageWidget, - reverse: true, - onReactionPicked: (SelectReaction reactionAction) { - // Handle reaction explicitly - }, ); ``` -> **Important:** -> `messageTheme` has been removed. Reaction handling must now be explicit using `onReactionPicked`. +> **Important:** +> - `StreamMessageReactionsModal` no longer exists. Remove all references to it. +> - `ReactionDetailSheet` fetches and paginates reactions from the server; it does not need a pre-built message widget. +> - To intercept reaction detail display, provide a custom `onReactionsTap` callback to `StreamMessageItem` — the default implementation calls `ReactionDetailSheet.show`. --- @@ -655,70 +610,42 @@ Updates to message widgets, attachment handling, and custom action patterns. #### Key Changes: -- `onAttachmentTap` callback signature has changed to support custom attachment handling with automatic fallback to default behavior. -- Callback now receives `BuildContext` as the first parameter. -- Returns `FutureOr` to indicate whether the attachment was handled. -- Returning `true` skips default behavior, `false` uses default handling (URLs, images, videos, giphys). +- `onAttachmentTap` is a `StreamAttachmentWidgetTapCallback` — a `void Function(Message message, Attachment attachment)` typedef defined on `StreamAttachmentWidgetBuilder`. +- It does **not** receive `BuildContext` and does **not** return `FutureOr`. The signature is simply `void Function(Message, Attachment)`. +- It is not a parameter on `StreamMessageItem` directly. Pass custom attachment builders via the `attachmentBuilders` parameter, each of which may set its own `onTap` using this signature. #### Migration Steps: **Before:** ```dart -StreamMessageItem( - message: message, - onAttachmentTap: (message, attachment) { - // Could only override - no way to fallback to default behavior - if (attachment.type == 'location') { - showLocationDialog(context, attachment); - } - // Other attachment types (images, videos, URLs) lost default behavior - }, -) +// Old attachment builder with custom tap +myBuilder.onTap = (message, attachment) { ... }; ``` **After:** ```dart -StreamMessageItem( - message: message, - onAttachmentTap: (context, message, attachment) async { - if (attachment.type == 'location') { - await showLocationDialog(context, attachment); - return true; // Handled by custom logic - } - return false; // Use default behavior for images, videos, URLs, etc. - }, -) -``` +// The typedef is: +// typedef StreamAttachmentWidgetTapCallback = void Function(Message, Attachment); -**Example: Handling multiple custom types** -```dart +// Supply custom attachment builders to StreamMessageItem to intercept taps: StreamMessageItem( message: message, - onAttachmentTap: (context, message, attachment) async { - switch (attachment.type) { - case 'location': - await Navigator.push( - context, - MaterialPageRoute(builder: (_) => MapView(attachment)), - ); - return true; - - case 'product': - await showProductDialog(context, attachment); - return true; - - default: - return false; // Images, videos, URLs use default viewer - } - }, + attachmentBuilders: [ + MyCustomAttachmentBuilder( + onAttachmentTap: (Message message, Attachment attachment) { + if (attachment.type == 'location') { + showLocationDialog(context, attachment); + } + }, + ), + ], ) ``` > **Important:** -> - The callback now requires `BuildContext` as the first parameter -> - Must return `FutureOr` - `true` if handled, `false` for default behavior -> - Default behavior automatically handles URL previews, images, videos, and giphys -> - Supports both synchronous and asynchronous operations +> - The signature is `void Function(Message, Attachment)` — no `BuildContext`, no return value. +> - `onAttachmentTap` is a property on individual `StreamAttachmentWidgetBuilder` subclasses, not on `StreamMessageItem` itself. +> - Each built-in attachment builder (image, video, file, giphy, link preview) accepts an `onAttachmentTap` in its constructor if you need to override its tap behavior. --- @@ -759,14 +686,16 @@ StreamMessageItem( #### Key Changes: -- Now generic: `StreamMessageAction` -- Individual `onTap` handlers removed — use `onCustomActionTap` instead -- Added new styling props for better customization +- `StreamMessageAction` as a standalone widget class does **not** exist. The message action system uses the sealed `MessageAction` class hierarchy combined with `StreamContextMenuAction`. +- Built-in actions are concrete `final` subclasses of the `MessageAction` sealed class: `SelectReaction`, `CopyMessage`, `DeleteMessage`, `EditMessage`, `FlagMessage`, `PinMessage`, `ThreadReply`, `QuotedReply`, and others. +- To inject custom actions into the long-press menu, provide an `actionsBuilder` callback on `StreamMessageItem`. The builder receives the default list of `StreamContextMenuAction`s and must return a `List`. +- There is no `customActions` parameter or `onCustomActionTap` callback on `StreamMessageItem`. #### Migration Steps: -**Before:** +**Before (v9 pattern):** ```dart +// v9-style action with individual onTap final customAction = StreamMessageAction( title: Text('Custom Action'), leading: Icon(Icons.settings), @@ -776,30 +705,30 @@ final customAction = StreamMessageAction( ); ``` -**After (Type-safe):** +**After:** ```dart -final customAction = StreamMessageAction( - action: CustomMessageAction( - message: message, - extraData: {'type': 'custom_action'}, - ), - title: Text('Custom Action'), - leading: Icon(Icons.settings), - isDestructive: false, - iconColor: Colors.blue, -); - StreamMessageItem( message: message, - customActions: [customAction], - onCustomActionTap: (CustomMessageAction action) { - // Handle action here + actionsBuilder: (context, defaultActions) { + return [ + ...defaultActions, + StreamContextMenuAction( + value: CopyMessage(message: message), // use nearest built-in or subclass + leading: const Icon(Icons.settings), + label: const Text('Custom Action'), + onTap: () { + // Handle custom action directly in onTap + }, + ), + ]; }, -); +) ``` -> **Important:** -> Individual `onTap` callbacks have been removed. Always handle actions using the centralized `onCustomActionTap`. +> **Important:** +> - There is no `StreamMessageAction` widget class; use `StreamContextMenuAction` from `stream_core_flutter`. +> - `StreamContextMenuAction` accepts `onTap` directly — no separate callback dispatch is needed. +> - `actionsBuilder` on `StreamMessageItem` provides the default actions list, which you can extend, filter, or reorder before returning. --- @@ -1045,6 +974,215 @@ ValueListenableBuilder>( --- +## Stable Release Changes + +> **Introduced in:** v10.0.0 (stable) + +The following breaking changes landed between beta.12 and the stable release. + +### StreamMessageListView Config / Builders Split + +Behavior flags moved to `StreamMessageListViewConfiguration`; builder callbacks moved to `StreamMessageListViewBuilders`. `messageBuilder` stays at the root. + +```dart +// Before +StreamMessageListView( + swipeToReply: true, + paginationLimit: 20, + loadingBuilder: (context) => const MyLoader(), + emptyBuilder: (context) => const MyEmpty(), +) + +// After +StreamMessageListView( + config: StreamMessageListViewConfiguration( + swipeToReply: true, + paginationLimit: 20, + ), + builders: StreamMessageListViewBuilders( + loading: (context) => const MyLoader(), + empty: (context) => const MyEmpty(), + ), +) +``` + +--- + +### StreamMessageComposerController Rename + +| Old | New | +| ---------------------------------------------- | ------------------------------------------- | +| `StreamMessageInputController` | `StreamMessageComposerController` | +| `StreamRestorableMessageInputController` | `StreamRestorableMessageComposerController` | +| `editingOriginalMessage` | `messageBeingEdited` | +| `StreamMessageComposer.messageInputController` | `messageComposerController` | + +**Edit-mode semantics changed:** + +```dart +// Before — constructor accepted a non-initial message +final controller = StreamMessageComposerController(message: existingMessage); +controller.clear(); // also exited edit mode + +// After — enter edit mode explicitly +final controller = StreamMessageComposerController(); +controller.editMessage(existingMessage); +controller.cancelEditMessage(); // explicit exit +``` + +--- + +### StreamMessageComposerInput Split + +`StreamMessageComposerInput` has been **split** — both names now exist and serve different roles: + +| Class | Role | +| ----------------------------------------- | ----------------------------------------------------------------- | +| `StreamMessageComposerInput` | Outer container (the full input row including leading/trailing) | +| `StreamMessageComposerInputCenter` | Center content only (text field, attachments preview, etc.) | +| `DefaultStreamMessageComposerInput` | Default outer container implementation | +| `DefaultStreamMessageComposerInputCenter` | Default center implementation | +| `MessageComposerInputProps` | Props for the outer container | +| `MessageComposerInputCenterProps` | Props for the center widget | + +If you were previously using `StreamMessageComposerInput` to customise the **text field area**, you need to target `StreamMessageComposerInputCenter` (and the `messageComposerInputCenter` builder key) instead. The `messageComposerInput` builder key now controls the outer container. + +> **Important:** +> This is a split, not a rename. `StreamMessageComposerInput` still exists; targeting it replaces the entire input row. To replace only the text field area, use `StreamMessageComposerInputCenter`. + +--- + +### StreamDraftListView and Related Classes Removed + +`StreamDraftListView`, `StreamDraftListTile`, `StreamDraftListTileTheme`, `StreamDraftListTileThemeData`, and `StreamChatThemeData.draftListTileTheme` have been removed. Use `StreamDraftListController` with a generic `PagedValueListView`. See the sample app for a reference implementation. + +--- + +### Removed Widgets + +| Removed Widget | Notes | +| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `AttachmentButton` | Replaced by the attachment button inside `StreamMessageComposer` | +| `StreamQuotedMessageWidget` | Use `StreamQuotedMessage` | +| `EditMessageSheet` | Editing is handled inline by the composer | +| `StreamMessageSendButton` | Part of the composer internals | +| `DesktopReactionsBuilder` | Use `ReactionDetailSheet` | +| `StreamChannelGridView` / `StreamChannelGridTile` | Removed | +| `StreamMessageSearchGridView` | Removed | +| `AttachmentModalSheet` | Removed | +| `ErrorAlertSheet` | No longer publicly exported; still used internally by `StreamMessageComposer` | +| `StreamChannelInfoBottomSheet` | Removed | +| `StreamMarkdownMessage` | Use `StreamMessageText` | +| `StreamAttachmentUploadStateBuilder.successBuilder` | Removed (unreachable) | +| `StreamFileAttachmentThumbnail` | Use `StreamImageAttachmentThumbnail` / `StreamVideoAttachmentThumbnail` or `StreamFileTypeIcon.fromMimeType(...)` | + +--- + +### Theme Removals + +| Removed | Notes | +| ------------------------------------------------------------------ | --------------------------------------------- | +| `StreamMessageThemeData` / `ownMessageTheme` / `otherMessageTheme` | Use `StreamMessageItemThemeData` | +| `StreamMessageInputThemeData` / `messageInputTheme` | Use design-system primitives on `StreamTheme` | +| `StreamChannelPreviewThemeData` / `channelPreviewTheme` | Use `StreamChannelListItemThemeData` | + +--- + +### Poll Dialogs → Sheets + +| Old | New | +| ----------------------------------------------------------------- | --------------------------------------------------------------- | +| `StreamPollCreatorDialog` / `showStreamPollCreatorDialog` | `StreamPollCreatorSheet` / `showStreamPollCreatorSheet` | +| `StreamPollOptionsDialog` / `showStreamPollOptionsDialog` | `StreamPollOptionsSheet` / `showStreamPollOptionsSheet` | +| `StreamPollResultsDialog` / `showStreamPollResultsDialog` | `StreamPollResultsSheet` / `showStreamPollResultsSheet` | +| `StreamPollOptionVotesDialog` / `showStreamPollOptionVotesDialog` | `StreamPollOptionVotesSheet` / `showStreamPollOptionVotesSheet` | +| `StreamPollCommentsDialog` / `showStreamPollCommentsDialog` | `StreamPollCommentsSheet` / `showStreamPollCommentsSheet` | + +--- + +### Translations: attachmentsUploadProgressText + +The parameter `remaining:` has been renamed to `completed:`. + +```dart +// Before +translations.attachmentsUploadProgressText(remaining: 2, total: 5) + +// After +translations.attachmentsUploadProgressText(completed: 3, total: 5) +``` + +--- + +### StreamChatCore: recoverStateOnReconnect and backgroundKeepAlive + +`StreamChatCore` now sets `client.recoverStateOnReconnect = false` on mount. If you watch a `Channel` outside any list controller, subscribe to `client.on(EventType.connectionRecovered)` and call `channel.watch()` to refresh on reconnect. + +The default `StreamChat.backgroundKeepAlive` has been reduced from 1 minute to 15 seconds. + +--- + +### stream_chat: ClientState Collection Immutability + +`ClientState.channels`, `ClientState.users`, and `ClientState.activeLiveLocations` are now backed by immutable subject types (`ImmutableMapBehaviorSubject` / `ImmutableListBehaviorSubject`). The map and list values they expose are unmodifiable — mutating them at runtime throws an `UnsupportedError`. + +In addition, the following setters and methods are now `@internal` and must not be called from application code: + +- `ClientState.channels=` (setter) +- `ClientState.addChannels(...)` +- `ClientState.removeChannel(...)` +- `ClientState.activeLiveLocations=` (setter) + +#### Migration Steps: + +If your code mutated these collections directly, remove those mutations. All state changes must go through the public SDK APIs (`StreamChatClient`, `Channel`, etc.): + +```dart +// Before — direct mutation (no longer allowed) +client.state.channels[channel.cid] = channel; +client.state.addChannels({channel.cid: channel}); +client.state.removeChannel(channel.cid); + +// After — use the SDK APIs; state is managed internally +await client.queryChannels(...); // populates channels automatically +await channel.watch(); // adds/refreshes a single channel in state +``` + +> **Important:** +> - Reading `channels`, `users`, and `activeLiveLocations` is unchanged — the getters still work. +> - Do not cast the returned collections to `Map`/`List` and call mutating methods; those calls will throw at runtime. +> - Code that previously relied on `addChannels` or `removeChannel` for optimistic state management must be reworked to use the SDK's own query and watch methods. + +--- + +### stream_chat: SortOption Constructor Rename + +The unnamed positional constructor `SortOption(field, direction)` has been removed. Use the named constructors instead. + +```dart +// Before +const [SortOption('last_message_at', direction: SortOption.DESC)] +const [SortOption('name', direction: SortOption.ASC)] + +// After +const [SortOption.desc('last_message_at')] +const [SortOption.asc('name')] +``` + +> **Important:** +> - `SortOption.desc()` defaults `nullOrdering` to `NullOrdering.nullsFirst` +> - `SortOption.asc()` defaults `nullOrdering` to `NullOrdering.nullsLast` + +--- + +### stream_chat: Channel.isOneToOne and Message.updateWith + +- `Channel.isOneToOne` added — returns `true` for distinct two-member channels. +- `Channel.isGroup` semantics changed: two-member non-distinct channels now return `true`. Use `!channel.isOneToOne` for "DM" checks. +- `Message.syncWith` deprecated in favor of `Message.updateWith` (note the flipped arguments: `local.updateWith(remote)` replaces `remote.syncWith(local)`). + +--- + ## Appendix: Beta Release Timeline This appendix provides a chronological reference of breaking changes by beta version for users upgrading from specific pre-release versions. @@ -1086,10 +1224,35 @@ This appendix provides a chronological reference of breaking changes by beta ver - [StreamAttachmentPickerController](#streamattachmentpickercontroller) +### v10.0.0 (stable) + +- [Stable Release Changes](#stable-release-changes) + --- ## Migration Checklist +### For v10.0.0 (stable) +- [ ] Move `StreamMessageListView` behavior flags to `config: StreamMessageListViewConfiguration(...)` +- [ ] Move `StreamMessageListView` builder callbacks to `builders: StreamMessageListViewBuilders(...)` +- [ ] Rename `StreamMessageInputController` → `StreamMessageComposerController` +- [ ] Rename `StreamRestorableMessageInputController` → `StreamRestorableMessageComposerController` +- [ ] Rename `StreamMessageComposer.messageInputController` → `messageComposerController` +- [ ] Replace `StreamMessageComposerController(message: ...)` with `.editMessage()` to enter edit mode +- [ ] Replace `controller.clear()` (used to exit edit mode) with `controller.cancelEditMessage()` +- [ ] Rename `editingOriginalMessage` → `messageBeingEdited` +- [ ] If you customised the text field area via `StreamMessageComposerInput`, switch to `StreamMessageComposerInputCenter` (and builder key `messageComposerInputCenter`); `StreamMessageComposerInput` now controls the entire outer input row +- [ ] Remove `StreamDraftListView`, `StreamDraftListTile`, and theme classes — use `StreamDraftListController` + `PagedValueListView` +- [ ] Replace removed widgets: `AttachmentButton`, `StreamQuotedMessageWidget`, `EditMessageSheet`, `StreamMessageSendButton`, etc. (see table) +- [ ] Remove `StreamMessageThemeData` / `StreamMessageInputThemeData` / `StreamChannelPreviewThemeData` usage +- [ ] Replace `StreamPollCreatorDialog` and other poll dialog helpers with the `...Sheet` equivalents +- [ ] Rename `attachmentsUploadProgressText(remaining: ...)` → `attachmentsUploadProgressText(completed: ...)` +- [ ] If watching a channel outside a list controller, subscribe to `connectionRecovered` and call `channel.watch()` +- [ ] Replace `SortOption('field', direction: SortOption.DESC)` with `SortOption.desc('field')` and `SortOption('field', direction: SortOption.ASC)` with `SortOption.asc('field')` +- [ ] Migrate `Message.syncWith(local)` → `local.updateWith(remote)` (arguments are flipped) +- [ ] Review `Channel.isGroup` usage — use `channel.isOneToOne` for two-member DM checks +- [ ] Remove any code that mutates `ClientState.channels`, `ClientState.users`, or `ClientState.activeLiveLocations` directly (setters and `addChannels`/`removeChannel` are now `@internal`) + ### Unread Threads Banner - [ ] Replace `Column` + `Expanded` layout with `StreamUnreadThreadsBanner(child: StreamThreadListView(...))` - [ ] Replace `onTap` with `onRefresh` (returns `Future`) @@ -1101,10 +1264,8 @@ This appendix provides a chronological reference of breaking changes by beta ver - [ ] Replace `ArgumentError('The maximum number of attachments is...')` with `AttachmentLimitReachedError` (provides `maxCount` property) ### For v10.0.0-beta.9: -- [ ] Update `onAttachmentTap` callback signature to include `BuildContext` as first parameter -- [ ] Return `FutureOr` from `onAttachmentTap` - `true` if handled, `false` for default behavior -- [ ] Leverage automatic fallback to default handling for standard attachment types (images, videos, URLs) -- [ ] Update any direct usage of `ReactionPickerIconList` to handle reaction selection state externally +- [ ] Update any custom `StreamAttachmentWidgetBuilder` subclasses whose `onAttachmentTap` callback now has the signature `void Function(Message, Attachment)` (no `BuildContext`, no return value) +- [ ] Replace any direct usage of `ReactionPickerIconList` / `ReactionPickerIcon` with `StreamMessageReactionPicker` or `StreamReactionPicker` + `StreamReactionPickerItem` ### For v10.0.0-beta.8: - [ ] Replace `customAttachmentPickerOptions` with `attachmentPickerOptionsBuilder` to access and modify default options @@ -1122,15 +1283,16 @@ This appendix provides a chronological reference of breaking changes by beta ver ### For v10.0.0-beta.3: - [ ] Update attachment picker options to use `SystemAttachmentPickerOption` or `TabbedAttachmentPickerOption` -- [ ] Handle new `StreamAttachmentPickerResult` return type from attachment picker -- [ ] Use renamed bottom sheet classes (`StreamTabbedAttachmentPickerBottomSheet`, `StreamSystemAttachmentPickerBottomSheet`) +- [ ] Remove calls to `showStreamAttachmentPickerModalBottomSheet` — the picker is now inline inside `StreamMessageComposer` +- [ ] Replace `StreamMobileAttachmentPickerBottomSheet` / `StreamWebOrDesktopAttachmentPickerBottomSheet` with inline `StreamTabbedAttachmentPicker` / `StreamSystemAttachmentPicker` (no `BottomSheet` suffix) +- [ ] Fix `icon:` values from `Icon(Icons.xxx)` (Widget) to `Icons.xxx` (IconData) on all picker option constructors ### For v10.0.0-beta.1: -- [ ] Use `StreamReactionPicker.builder` or supply `onReactionPicked` -- [ ] Convert all `StreamMessageAction` instances to type-safe generic usage -- [ ] Centralize handling with `onCustomActionTap` -- [ ] Remove deprecated props like `showReactionTail` and `messageTheme` +- [ ] Replace `StreamReactionPicker(message: ...)` with `StreamMessageReactionPicker(message: ..., onReactionPicked: ...)` and supply the `onReactionPicked` callback +- [ ] Remove `StreamMessageReactionsModal` usage — replaced by `ReactionDetailSheet.show(context: context, message: message)` +- [ ] Replace `StreamMessageAction` + `customActions` + `onCustomActionTap` patterns with `actionsBuilder` on `StreamMessageItem`, returning `StreamContextMenuAction` widgets +- [ ] Remove deprecated `showReactionTail` parameter from `StreamMessageItem` --- -**You're ready to migrate!** For additional help, visit the [Stream Chat Flutter documentation](https://getstream.io/chat/docs/sdk/flutter/) or open an issue on [GitHub](https://github.com/GetStream/stream-chat-flutter/issues). \ No newline at end of file +**You're ready to migrate!** For additional help, visit the [Stream Chat Flutter documentation](https://getstream.io/chat/docs/sdk/flutter/) or open an issue on [GitHub](https://github.com/GetStream/stream-chat-flutter/issues). diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index fe78dfd1ef..7a5d580354 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -3,6 +3,7 @@ 🔄 Internal / Non-breaking - Composer UI primitives (`StreamMessageComposerInputField`, `VoiceRecordingCallback`, and the outer/inner layout containers) are now owned by `stream_chat_flutter` and exported from this package. They were previously supplied by `stream_core_flutter`. The public API of `StreamMessageComposer` / `StreamChatMessageInput` and its sub-components is unchanged. +- Re-export `StreamAvatarTheme` and `StreamAvatarThemeData` from `stream_core_flutter` so consumers can theme avatars without adding a separate `stream_core_flutter` import. 🛑️ Breaking @@ -22,6 +23,7 @@ - Removed `StreamFileAttachmentThumbnail`; use `StreamImageAttachmentThumbnail` / `StreamVideoAttachmentThumbnail` or `StreamFileTypeIcon.fromMimeType(...)`. - Removed `StreamMessageThemeData` (ownMessageTheme / otherMessageTheme) and `StreamMessageInputThemeData` (messageInputTheme). - Removed `StreamChannelPreviewThemeData` (channelPreviewTheme). +- Removed the public `MessageDetails` class. The new `messageBuilder` signature no longer receives it — use `StreamChat.of(context).currentUser` and `StreamMessageLayout.of(context)` instead. See [`migrations/redesign/message_list.md`](../../migrations/redesign/message_list.md#removed-messagedetails). - Renamed `StreamPollOptionsDialog` / `StreamPollResultsDialog` / `StreamPollOptionVotesDialog` / `StreamPollCommentsDialog` (and their `show*` helpers and `...DialogThemeData` types) → `...Sheet`. They now render as modal bottom sheets. - Replaced `StreamPollCreatorDialog` / `StreamPollCreatorFullScreenDialog` (and `showStreamPollCreatorDialog`) with a single `StreamPollCreatorSheet` (`showStreamPollCreatorSheet`) that renders as a modal bottom sheet, matching the other poll sheets. `StreamPollCreatorWidget` gained an optional `scrollController` parameter. - Removed `primaryActionStyle` and `secondaryActionStyle` from `StreamPollCreatorThemeData`. Use the new `sheetHeaderStyle.trailingStyle` / `sheetHeaderStyle.leadingStyle` instead — see [`migrations/redesign/attachments_and_polls.md`](../../migrations/redesign/attachments_and_polls.md). diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart index 0fc27d2d49..e5342c8044 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart @@ -47,7 +47,7 @@ abstract class StreamAttachmentWidgetBuilder { /// * [VideoAttachmentBuilder] /// * [VoiceRecordingAttachmentPlaylistBuilder] /// * [PollAttachmentBuilder] - /// * [UrlAttachmentBuilder] + /// * [LinkPreviewAttachmentBuilder] /// * [FallbackAttachmentBuilder] /// /// You can use this list as a starting point for your own list of builders. diff --git a/packages/stream_chat_flutter/lib/src/attachment/link_preview_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/link_preview_attachment.dart index 3308f540b5..8e92ee3fbe 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/link_preview_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/link_preview_attachment.dart @@ -14,8 +14,6 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// StreamLinkPreviewAttachment( /// message: message, /// urlAttachment: urlAttachment, -/// hostDisplayName: 'GitHub', -/// messageTheme: messageTheme, /// ) /// ``` /// {@end-tool} diff --git a/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_gallery_item.dart b/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_gallery_item.dart index 97488c7d80..9bcd649c9e 100644 --- a/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_gallery_item.dart +++ b/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_gallery_item.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_core_flutter/stream_core_flutter.dart'; /// A single tile inside a [StreamMediaGallery]. /// diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart index 0204df1e01..e0e5f9c5e8 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart @@ -6,6 +6,16 @@ import 'package:stream_chat_flutter/src/message_input/attachment_picker/options/ import 'package:stream_chat_flutter/src/misc/empty_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +/// {@template streamAttachmentPickerOptionsBuilder} +/// Signature for a function that creates a list of [AttachmentPickerOption]s +/// to be used in the attachment picker. +/// +/// The function receives the [BuildContext] and a list of [defaultOptions] +/// that can be modified or extended. +/// {@endtemplate} +typedef AttachmentPickerOptionsBuilder = + List Function(BuildContext context, List defaultOptions); + /// Inline widget for the system attachment picker interface. /// /// Shows a list of options that launch native platform dialogs. diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart deleted file mode 100644 index 4ae640eb6d..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:stream_chat_flutter/src/message_input/attachment_picker/stream_attachment_picker_option.dart'; - -/// {@template streamAttachmentPickerOptionsBuilder} -/// Signature for a function that creates a list of [AttachmentPickerOption]s -/// to be used in the attachment picker. -/// -/// The function receives the [BuildContext] and a list of [defaultOptions] -/// that can be modified or extended. -/// {@endtemplate} -typedef AttachmentPickerOptionsBuilder = - List Function(BuildContext context, List defaultOptions); diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_details.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_details.dart deleted file mode 100644 index cc5c99eccc..0000000000 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_details.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// {@template messageDetails} -/// Class for message details -/// {@endtemplate} -// ignore: prefer-match-file-name -class MessageDetails { - /// {@macro messageDetails} - MessageDetails( - String currentUserId, - this.message, - List messages, - this.index, - ) { - isMyMessage = message.user?.id == currentUserId; - isLastUser = index + 1 < messages.length && message.user?.id == messages[index + 1].user?.id; - isNextUser = index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id; - } - - /// True if the message belongs to the current user - late final bool isMyMessage; - - /// True if the user message is the same of the previous message - late final bool isLastUser; - - /// True if the user message is the same of the next message - late final bool isNextUser; - - /// The message - final Message message; - - /// The index of the message - final int index; -} diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart index d757255249..4514ea29d7 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart @@ -1222,7 +1222,7 @@ class _StreamMessageListViewState extends State { (null, final threadBuilder?) => (Message parentMessage, Message? threadMessage) async { Widget threadPage = StreamChatConfiguration( // This is needed to provide the nearest reaction icons to the - // StreamMessageReactionsModal. + // ReactionDetailSheet. data: StreamChatConfiguration.of(context), child: StreamChannel( channel: streamChannel!.channel, diff --git a/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart index 680d98e86a..311255da45 100644 --- a/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart @@ -43,7 +43,7 @@ class StreamMessageActionsModal extends StatelessWidget { /// Alignment of the modal content. /// /// When null (the default), falls back to - /// [StreamMessagePlacement.alignmentDirectionalOf]. + /// [StreamMessageLayout.alignmentDirectionalOf]. final AlignmentGeometry? alignment; /// Controls whether to show the reaction picker at the top of the modal. diff --git a/packages/stream_chat_flutter/lib/src/utils/stream_image_cdn.dart b/packages/stream_chat_flutter/lib/src/utils/stream_image_cdn.dart index 091de7e528..72928963a1 100644 --- a/packages/stream_chat_flutter/lib/src/utils/stream_image_cdn.dart +++ b/packages/stream_chat_flutter/lib/src/utils/stream_image_cdn.dart @@ -102,7 +102,7 @@ class ImageResize { /// ```dart /// StreamChat( /// client: client, -/// config: StreamChatConfigurationData( +/// streamChatConfigData: StreamChatConfigurationData( /// imageCDN: MyImageCDN(), /// ), /// child: ..., diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index dcf2680a3f..217cc41215 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -13,6 +13,8 @@ export 'package:stream_core_flutter/stream_core_flutter.dart' StreamAvatarGroupSize, StreamAvatarSize, StreamAvatarStackSize, + StreamAvatarTheme, + StreamAvatarThemeData, StreamBottomAppBar, StreamBottomAppBarStyle, StreamBottomAppBarTheme, @@ -181,7 +183,6 @@ export 'src/media_gallery_preview/video_player/stream_video_player.dart'; export 'src/message_action/message_action.dart'; export 'src/message_action/message_actions_builder.dart'; export 'src/message_input/attachment_picker/stream_attachment_picker.dart'; -export 'src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart'; export 'src/message_input/attachment_picker/stream_attachment_picker_controller.dart'; export 'src/message_input/attachment_picker/stream_attachment_picker_option.dart'; export 'src/message_input/attachment_picker/stream_attachment_picker_result.dart'; @@ -194,7 +195,6 @@ export 'src/message_input/message_input_placeholder.dart'; export 'src/message_input/stream_message_composer.dart'; export 'src/message_input/stream_message_composer_attachment_list.dart'; export 'src/message_input/stream_message_text_field.dart'; -export 'src/message_list_view/message_details.dart'; export 'src/message_list_view/message_list_view.dart'; export 'src/message_list_view/stream_message_list_view_builders.dart'; export 'src/message_list_view/stream_message_list_view_configuration.dart';