From e0a0f7c0928705f00dc66cd2974c0167addc72c5 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 30 Sep 2026 19:15:29 +0200 Subject: [PATCH 01/11] fix(ui): default StreamMessageItem.maxWidth to 272, like its props StreamMessageItem defaulted maxWidth to 264 while StreamMessageItemProps defaulted it to 272. The message list builds props directly, so it used 272, while anything built with the StreamMessageItem constructor, such as the long-press actions preview, was 8 pixels narrower. 272 is the width the design gives a bubble holding an attachment: a 256 attachment with 8 of padding on each side. A lower cap squeezes it. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01AP6bV4L7FQYjHoHZeomLxr --- migrations/redesign/message_widget.md | 2 +- packages/stream_chat_flutter/CHANGELOG.md | 6 ++++++ .../lib/src/message_widget/stream_message_item.dart | 4 ++-- .../test/src/message_widget/stream_message_item_test.dart | 8 ++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/migrations/redesign/message_widget.md b/migrations/redesign/message_widget.md index 55f5e31133..08eff9c1c6 100644 --- a/migrations/redesign/message_widget.md +++ b/migrations/redesign/message_widget.md @@ -198,7 +198,7 @@ These parameters have been removed entirely. See the **Migration Path** column f | `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`) | +| `maxWidth` | Max content width in logical pixels (default: `272`) | | `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 | diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index ffa1845f7c..3b843386f5 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +🔄 Changed + +- `StreamMessageItem` now defaults `maxWidth` to `272`, the width the message list already uses, so messages built with it are no longer 8 pixels narrower. + ## 10.5.0 ✅ Added diff --git a/packages/stream_chat_flutter/lib/src/message_widget/stream_message_item.dart b/packages/stream_chat_flutter/lib/src/message_widget/stream_message_item.dart index 06d0ff07f1..2b151dfe03 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/stream_message_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/stream_message_item.dart @@ -74,7 +74,7 @@ class StreamMessageItem extends StatelessWidget { EdgeInsetsGeometry? padding, double? spacing, Color? backgroundColor, - double maxWidth = 264, + double maxWidth = 272, bool swipeToReply = false, void Function(Message)? onMessageTap, void Function(Message)? onMessageLongPress, @@ -228,7 +228,7 @@ class StreamMessageItemProps { /// /// The content uses at most this width while still respecting the parent /// [Flex] constraints. Use [double.infinity] to impose no cap from this - /// widget. Defaults to `264` when not specified. + /// widget. Defaults to `272` when not specified. final double maxWidth; /// Whether swiping the message triggers a quoted-reply action. diff --git a/packages/stream_chat_flutter/test/src/message_widget/stream_message_item_test.dart b/packages/stream_chat_flutter/test/src/message_widget/stream_message_item_test.dart index 3f2f0d92d0..6de5a99e30 100644 --- a/packages/stream_chat_flutter/test/src/message_widget/stream_message_item_test.dart +++ b/packages/stream_chat_flutter/test/src/message_widget/stream_message_item_test.dart @@ -214,6 +214,14 @@ void main() { }); }); + test('StreamMessageItem defaults to the same maxWidth as StreamMessageItemProps', () { + final message = Message(text: 'Hello'); + + final item = StreamMessageItem(message: message); + + expect(item.props.maxWidth, StreamMessageItemProps(message: message).maxWidth); + }); + // The widget tests above deliberately never see the shipped strings, so pin // the default table's pluralization here. test('DefaultTranslations pluralizes the thread reply count', () { From dc8a10af0b8b23789f4ff24490e599fa5a5e7b0a Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 1 Oct 2026 12:54:23 +0200 Subject: [PATCH 02/11] feat(ui): show more media tiles per row on wider screens StreamMediaGallery and StreamPhotoGallery always showed 3 tiles per row, so a wide screen got huge tiles and, on a foldable's inner display, a column straddling the fold. They now show 3 tiles per row when narrower than 600, 4 when narrower than 840 and 6 otherwise. The counts from 600 up are even, so the tiles split evenly at a fold. StreamMediaGallery gains a gridDelegate to change the layout, and its crossAxisCount is deprecated, since one count no longer describes the grid. defaultStreamPhotoGalleryDelegate keeps its 3-column layout but is deprecated, as it is no longer the default. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01AP6bV4L7FQYjHoHZeomLxr --- packages/stream_chat_flutter/CHANGELOG.md | 6 ++ .../media_gallery/stream_media_gallery.dart | 58 +++++++++++++++---- .../stream_media_grid_delegate.dart | 44 ++++++++++++++ .../photo_gallery/stream_photo_gallery.dart | 8 ++- .../stream_media_gallery_test.dart | 57 ++++++++++++++++++ .../stream_photo_gallery_test.dart | 32 ++++++++++ 6 files changed, 191 insertions(+), 14 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/media_gallery/stream_media_grid_delegate.dart create mode 100644 packages/stream_chat_flutter/test/src/media_gallery/stream_media_gallery_test.dart create mode 100644 packages/stream_chat_flutter/test/src/scroll_view/photo_gallery/stream_photo_gallery_test.dart diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 48191321dd..bbfeac433c 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -4,12 +4,18 @@ - Added `automaticallyImplyLeading` to `StreamChannelPage` and `StreamThreadPage`, which hides the header's back button when `false`. - Added a `selected` parameter to `StreamThreadListTile`, which highlights the tile, and `StreamThreadListTile.copyWith`. +- Added `StreamMediaGallery.gridDelegate` to change how the gallery lays out its tiles. 🔄 Changed - `StreamMessageComposer`'s top border now spans the full width, including under the side safe-area insets, and also shows above the no-permission message. - Suggestions from `customAutocompleteTriggers` now span the composer's full width, so their content has to keep clear of the side safe-area insets itself. - `StreamMessageItem` now defaults `maxWidth` to `272`, the width the message list already uses, so messages built with it are no longer 8 pixels narrower. +- `StreamMediaGallery` and `StreamPhotoGallery` now show 3, 4 or 6 tiles per row depending on their width, instead of always 3. + +⚠️ Deprecated + +- Deprecated `StreamMediaGallery.crossAxisCount` and `defaultStreamPhotoGalleryDelegate`; pass a `gridDelegate` instead. 🐞 Fixed diff --git a/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_gallery.dart b/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_gallery.dart index 4b715fb114..0481f852a4 100644 --- a/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_gallery.dart +++ b/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_gallery.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import '../../stream_chat_flutter.dart'; +import 'stream_media_grid_delegate.dart'; /// A scrollable grid of [StreamMediaGalleryAttachment]s — the thumbnail /// companion to [StreamMediaGalleryPreview]. @@ -40,7 +41,8 @@ class StreamMediaGallery extends StatelessWidget { StreamMediaGallery({ super.key, required List attachments, - int crossAxisCount = 3, + @Deprecated('Use gridDelegate instead.') int? crossAxisCount, + SliverGridDelegate? gridDelegate, EdgeInsetsGeometry? padding, ScrollController? scrollController, ValueChanged? onItemTap, @@ -48,6 +50,7 @@ class StreamMediaGallery extends StatelessWidget { }) : props = .new( attachments: attachments, crossAxisCount: crossAxisCount, + gridDelegate: gridDelegate, padding: padding, scrollController: scrollController, onItemTap: onItemTap, @@ -79,18 +82,36 @@ class StreamMediaGalleryProps { /// Creates properties for a media gallery. const StreamMediaGalleryProps({ required this.attachments, - this.crossAxisCount = 3, + @Deprecated('Use gridDelegate instead.') int? crossAxisCount, + this.gridDelegate, this.padding, this.scrollController, this.onItemTap, this.onItemLongPress, - }); + }) : assert( + crossAxisCount == null || gridDelegate == null, + 'Only one of crossAxisCount or gridDelegate can be provided. ' + 'Prefer gridDelegate; crossAxisCount is deprecated.', + ), + _crossAxisCount = crossAxisCount; /// The attachments to display, in render order. final List attachments; - /// Number of tiles per row. Defaults to 3. - final int crossAxisCount; + /// The fixed number of tiles per row given to the constructor, or 3 when none + /// was. + /// + /// Without a fixed number, the grid lays out its tiles with [gridDelegate] or, + /// when that is null, picks the number of tiles per row from its width. + @Deprecated('Use gridDelegate instead. The number of tiles per row now depends on the grid width.') + int get crossAxisCount => _crossAxisCount ?? 3; + final int? _crossAxisCount; + + /// How the grid lays out its tiles. + /// + /// When null, the grid shows 3, 4 or 6 square tiles per row, depending on + /// its width. + final SliverGridDelegate? gridDelegate; /// The padding around this grid. /// @@ -123,17 +144,11 @@ class DefaultStreamMediaGallery extends StatelessWidget { @override Widget build(BuildContext context) { - final spacing = context.streamSpacing; - return GridView.builder( padding: props.padding, controller: props.scrollController, itemCount: props.attachments.length, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: props.crossAxisCount, - crossAxisSpacing: spacing.xxxs, - mainAxisSpacing: spacing.xxxs, - ), + gridDelegate: _gridDelegate(context), itemBuilder: (context, index) { final ga = props.attachments[index]; return StreamMediaGalleryItem( @@ -145,4 +160,23 @@ class DefaultStreamMediaGallery extends StatelessWidget { }, ); } + + SliverGridDelegate _gridDelegate(BuildContext context) { + if (props.gridDelegate case final gridDelegate?) return gridDelegate; + + final spacing = context.streamSpacing; + + if (props._crossAxisCount case final crossAxisCount?) { + return SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + crossAxisSpacing: spacing.xxxs, + mainAxisSpacing: spacing.xxxs, + ); + } + + return StreamMediaGridDelegate( + crossAxisSpacing: spacing.xxxs, + mainAxisSpacing: spacing.xxxs, + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_grid_delegate.dart b/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_grid_delegate.dart new file mode 100644 index 0000000000..64887d4ac2 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_grid_delegate.dart @@ -0,0 +1,44 @@ +import 'package:flutter/rendering.dart'; + +/// Lays out a media grid's square tiles, with more tiles per row as the grid +/// gets wider. +/// +/// The grid has 3 tiles per row when narrower than 600, 4 when narrower than +/// 840, and 6 otherwise, and the tiles share the width between them. +class StreamMediaGridDelegate extends SliverGridDelegate { + /// Creates a delegate for a media grid with the given spacing between tiles. + const StreamMediaGridDelegate({ + this.mainAxisSpacing = 0.0, + this.crossAxisSpacing = 0.0, + }) : assert(mainAxisSpacing >= 0, 'mainAxisSpacing must not be negative.'), + assert(crossAxisSpacing >= 0, 'crossAxisSpacing must not be negative.'); + + /// The number of logical pixels between each tile along the main axis. + final double mainAxisSpacing; + + /// The number of logical pixels between each tile along the cross axis. + final double crossAxisSpacing; + + @override + SliverGridLayout getLayout(SliverConstraints constraints) { + final crossAxisExtent = constraints.crossAxisExtent; + final crossAxisCount = switch (crossAxisExtent) { + < 600 => 3, // A phone or a foldable's outer display, with the design's 3 tiles per row. + < 840 => 4, // A foldable or tablet in portrait. Even, so the tiles split evenly at a fold. + _ => 6, // A foldable or tablet in landscape. Even, so the tiles split evenly at a fold. + }; + + final delegate = SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + mainAxisSpacing: mainAxisSpacing, + crossAxisSpacing: crossAxisSpacing, + ); + + return delegate.getLayout(constraints); + } + + @override + bool shouldRelayout(StreamMediaGridDelegate oldDelegate) { + return oldDelegate.mainAxisSpacing != mainAxisSpacing || oldDelegate.crossAxisSpacing != crossAxisSpacing; + } +} diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart index a5f88a5f2d..3810756ab7 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart @@ -3,9 +3,11 @@ import 'package:flutter/material.dart'; import 'package:photo_manager/photo_manager.dart' show AssetEntity, ThumbnailFormat, ThumbnailSize; import '../../../stream_chat_flutter.dart'; +import '../../media_gallery/stream_media_grid_delegate.dart'; import '../../utils/network_error_text.dart'; -/// Default grid delegate for [StreamPhotoGallery]. +/// A grid delegate with 3 tiles per row, whatever the grid's width. +@Deprecated('StreamPhotoGallery now picks its tiles per row from its width. Pass a gridDelegate to change it.') const defaultStreamPhotoGalleryDelegate = SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 2, @@ -23,7 +25,7 @@ class StreamPhotoGallery extends StatelessWidget { const StreamPhotoGallery({ super.key, required this.controller, - this.gridDelegate = defaultStreamPhotoGalleryDelegate, + this.gridDelegate = const StreamMediaGridDelegate(mainAxisSpacing: 2, crossAxisSpacing: 2), this.itemBuilder, this.emptyBuilder, this.loadMoreErrorBuilder, @@ -61,6 +63,8 @@ class StreamPhotoGallery extends StatelessWidget { /// A delegate that controls the layout of the children within /// the [PagedValueGridView]. + /// + /// Defaults to 3, 4 or 6 square tiles per row, depending on the grid's width. final SliverGridDelegate gridDelegate; /// A builder that is called to build items in the [PagedValueGridView]. diff --git a/packages/stream_chat_flutter/test/src/media_gallery/stream_media_gallery_test.dart b/packages/stream_chat_flutter/test/src/media_gallery/stream_media_gallery_test.dart new file mode 100644 index 0000000000..80b2c21d15 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/media_gallery/stream_media_gallery_test.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +Future _pumpGallery(WidgetTester tester, {required double width, required StreamMediaGallery gallery}) async { + tester.view + ..devicePixelRatio = 1 + ..physicalSize = Size(width, 800); + addTearDown(tester.view.reset); + + await tester.pumpWidget(MaterialApp(home: gallery)); +} + +int _tilesPerRow(WidgetTester tester) { + final grid = tester.widget(find.byType(GridView)); + final sliver = tester.renderObject(find.byType(SliverGrid, skipOffstage: false)); + final layout = grid.gridDelegate.getLayout(sliver.constraints) as SliverGridRegularTileLayout; + return layout.crossAxisCount; +} + +void main() { + for (final (width, tilesPerRow) in [(402.0, 3), (669.0, 4), (867.0, 6)]) { + testWidgets('StreamMediaGallery shows $tilesPerRow tiles per row when $width wide', (tester) async { + await _pumpGallery( + tester, + width: width, + gallery: StreamMediaGallery(attachments: const []), + ); + + expect(_tilesPerRow(tester), tilesPerRow); + }); + } + + testWidgets('StreamMediaGallery keeps the given crossAxisCount at any width', (tester) async { + await _pumpGallery( + tester, + width: 867, + // ignore: deprecated_member_use_from_same_package + gallery: StreamMediaGallery(attachments: const [], crossAxisCount: 2), + ); + + expect(_tilesPerRow(tester), 2); + }); + + testWidgets('StreamMediaGallery lays out its tiles with the given gridDelegate', (tester) async { + const gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 5); + + await _pumpGallery( + tester, + width: 402, + gallery: StreamMediaGallery(attachments: const [], gridDelegate: gridDelegate), + ); + + expect(tester.widget(find.byType(GridView)).gridDelegate, same(gridDelegate)); + }); +} diff --git a/packages/stream_chat_flutter/test/src/scroll_view/photo_gallery/stream_photo_gallery_test.dart b/packages/stream_chat_flutter/test/src/scroll_view/photo_gallery/stream_photo_gallery_test.dart new file mode 100644 index 0000000000..c437d28c41 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/scroll_view/photo_gallery/stream_photo_gallery_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +void main() { + test('StreamPhotoGallery defaults to 4 tiles per row on a 669-wide grid', () { + final controller = StreamPhotoGalleryController(); + addTearDown(controller.dispose); + + final gallery = StreamPhotoGallery(controller: controller); + + final layout = gallery.gridDelegate.getLayout(_verticalConstraints(crossAxisExtent: 669)); + expect((layout as SliverGridRegularTileLayout).crossAxisCount, 4); + }); +} + +SliverConstraints _verticalConstraints({required double crossAxisExtent}) { + return SliverConstraints( + axisDirection: AxisDirection.down, + growthDirection: GrowthDirection.forward, + userScrollDirection: ScrollDirection.idle, + scrollOffset: 0, + precedingScrollExtent: 0, + overlap: 0, + remainingPaintExtent: 800, + crossAxisExtent: crossAxisExtent, + crossAxisDirection: AxisDirection.right, + viewportMainAxisExtent: 800, + remainingCacheExtent: 800, + cacheOrigin: 0, + ); +} From 35a615d1846a95d22ebd1cbef3e909a0ce7d8550 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 1 Oct 2026 14:37:08 +0200 Subject: [PATCH 03/11] refactor(ui): make the adaptive grid the photo gallery's default delegate defaultStreamPhotoGalleryDelegate now holds the grid that shows more tiles per row as it gets wider, instead of staying a deprecated three-column delegate beside a new default. The grid docs describe that behaviour rather than the breakpoints behind it. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01AP6bV4L7FQYjHoHZeomLxr --- packages/stream_chat_flutter/CHANGELOG.md | 5 +++-- .../lib/src/media_gallery/stream_media_gallery.dart | 6 +++--- .../src/media_gallery/stream_media_grid_delegate.dart | 8 ++++---- .../photo_gallery/stream_photo_gallery.dart | 10 +++------- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index b1ec33e7f1..1ae7df4e41 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -11,11 +11,12 @@ - `StreamMessageComposer`'s top border now spans the full width, including under the side safe-area insets, and also shows above the no-permission message. - Suggestions from `customAutocompleteTriggers` now span the composer's full width, so their content has to keep clear of the side safe-area insets itself. - `StreamMessageItem` now defaults `maxWidth` to `272`, the width the message list already uses, so messages built with it are no longer 8 pixels narrower. -- `StreamMediaGallery` and `StreamPhotoGallery` now show 3, 4 or 6 tiles per row depending on their width, instead of always 3. +- `StreamMediaGallery` and `StreamPhotoGallery` now show more tiles per row on wider screens, instead of always 3. +- `defaultStreamPhotoGalleryDelegate` now shows more tiles per row on wider grids, so it no longer has a `crossAxisCount`; use a `SliverGridDelegateWithFixedCrossAxisCount` for a fixed count. ⚠️ Deprecated -- Deprecated `StreamMediaGallery.crossAxisCount` and `defaultStreamPhotoGalleryDelegate`; pass a `gridDelegate` instead. +- Deprecated `StreamMediaGallery.crossAxisCount`; pass a `gridDelegate` instead. 🐞 Fixed diff --git a/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_gallery.dart b/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_gallery.dart index 0481f852a4..5be04377f9 100644 --- a/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_gallery.dart +++ b/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_gallery.dart @@ -107,10 +107,10 @@ class StreamMediaGalleryProps { int get crossAxisCount => _crossAxisCount ?? 3; final int? _crossAxisCount; - /// How the grid lays out its tiles. + /// The delegate that lays out the grid's tiles. /// - /// When null, the grid shows 3, 4 or 6 square tiles per row, depending on - /// its width. + /// When null, the grid shows square tiles, with more tiles per row as it gets + /// wider. final SliverGridDelegate? gridDelegate; /// The padding around this grid. diff --git a/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_grid_delegate.dart b/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_grid_delegate.dart index 64887d4ac2..c26a3eb4f1 100644 --- a/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_grid_delegate.dart +++ b/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_grid_delegate.dart @@ -1,10 +1,10 @@ import 'package:flutter/rendering.dart'; -/// Lays out a media grid's square tiles, with more tiles per row as the grid -/// gets wider. +/// A grid delegate that lays out a media grid's square tiles, with more tiles +/// per row as the grid gets wider. /// -/// The grid has 3 tiles per row when narrower than 600, 4 when narrower than -/// 840, and 6 otherwise, and the tiles share the width between them. +/// The tiles share the grid's width between them, so a wider grid gets more +/// tiles rather than larger ones. class StreamMediaGridDelegate extends SliverGridDelegate { /// Creates a delegate for a media grid with the given spacing between tiles. const StreamMediaGridDelegate({ diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart index 3810756ab7..23c1501802 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart @@ -6,10 +6,8 @@ import '../../../stream_chat_flutter.dart'; import '../../media_gallery/stream_media_grid_delegate.dart'; import '../../utils/network_error_text.dart'; -/// A grid delegate with 3 tiles per row, whatever the grid's width. -@Deprecated('StreamPhotoGallery now picks its tiles per row from its width. Pass a gridDelegate to change it.') -const defaultStreamPhotoGalleryDelegate = SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, +/// Default grid delegate for [StreamPhotoGallery]. +const defaultStreamPhotoGalleryDelegate = StreamMediaGridDelegate( mainAxisSpacing: 2, crossAxisSpacing: 2, ); @@ -25,7 +23,7 @@ class StreamPhotoGallery extends StatelessWidget { const StreamPhotoGallery({ super.key, required this.controller, - this.gridDelegate = const StreamMediaGridDelegate(mainAxisSpacing: 2, crossAxisSpacing: 2), + this.gridDelegate = defaultStreamPhotoGalleryDelegate, this.itemBuilder, this.emptyBuilder, this.loadMoreErrorBuilder, @@ -63,8 +61,6 @@ class StreamPhotoGallery extends StatelessWidget { /// A delegate that controls the layout of the children within /// the [PagedValueGridView]. - /// - /// Defaults to 3, 4 or 6 square tiles per row, depending on the grid's width. final SliverGridDelegate gridDelegate; /// A builder that is called to build items in the [PagedValueGridView]. From 70a4e9c6532290dbc2fa871872349c8b1f1fca67 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 1 Oct 2026 14:43:17 +0200 Subject: [PATCH 04/11] docs(ui): keep the FLU-825 changelog to what upgraders can see Drop the StreamMessageItem.maxWidth entry, since the message list already used 272, and describe the photo gallery's default by what it shows rather than by its type. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01AP6bV4L7FQYjHoHZeomLxr --- packages/stream_chat_flutter/CHANGELOG.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 1ae7df4e41..78fb29786c 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -10,13 +10,11 @@ - `StreamMessageComposer`'s top border now spans the full width, including under the side safe-area insets, and also shows above the no-permission message. - Suggestions from `customAutocompleteTriggers` now span the composer's full width, so their content has to keep clear of the side safe-area insets itself. -- `StreamMessageItem` now defaults `maxWidth` to `272`, the width the message list already uses, so messages built with it are no longer 8 pixels narrower. -- `StreamMediaGallery` and `StreamPhotoGallery` now show more tiles per row on wider screens, instead of always 3. -- `defaultStreamPhotoGalleryDelegate` now shows more tiles per row on wider grids, so it no longer has a `crossAxisCount`; use a `SliverGridDelegateWithFixedCrossAxisCount` for a fixed count. +- `StreamMediaGallery` and `StreamPhotoGallery`, including `defaultStreamPhotoGalleryDelegate`, now show more tiles per row on wider screens instead of always 3. Pass a `SliverGridDelegateWithFixedCrossAxisCount` as `gridDelegate` to keep a fixed count. ⚠️ Deprecated -- Deprecated `StreamMediaGallery.crossAxisCount`; pass a `gridDelegate` instead. +- Deprecated `StreamMediaGallery.crossAxisCount` in favour of `gridDelegate`. 🐞 Fixed From 9fbfe3a32d3830377fb96a6c465cd21fa082005d Mon Sep 17 00:00:00 2001 From: xsahil03x <25670178+xsahil03x@users.noreply.github.com> Date: Thu, 1 Oct 2026 13:10:12 +0000 Subject: [PATCH 05/11] chore: Update Goldens --- .../ci/stream_message_item_in_list_dark.png | Bin 5148 -> 5148 bytes .../ci/stream_message_item_in_list_light.png | Bin 5044 -> 5045 bytes .../ci/stream_message_item_preview_dark.png | Bin 5001 -> 5001 bytes .../ci/stream_message_item_preview_light.png | Bin 6298 -> 6299 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/stream_message_item_in_list_dark.png b/packages/stream_chat_flutter/test/src/message_widget/goldens/ci/stream_message_item_in_list_dark.png index 7a037ab9d5dff73f7782f1ee32f5baed568a45d2..0fd70a37d94787a527e642ba86ecf2c21fadc5c1 100644 GIT binary patch delta 66 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 K42k;J?*13)Qt;pa delta 5180 zcmZu#2UL^Gw*CVsMHCCYiy)z@cmRn3B2}uPchLh#lMVt>5|C(8A=AS)#&+NVDoApi3mFz3YHz0Z`IyS-; zHiH<0y5jk_CW;o)q$Tfy%ZYdm$Qr(;MGS zBBvyfri6XE2vnMkjTMl<1`0^Ck@J(QjUw0JV#=seW_V)Jfi63jKWMhH&HAg*b_|mRVf?<}IQi_a=En_ipQkFxI z@!hyQ$Jz%krrm(XcC#FH@Aq9NA2Le=${QOg>p@6q<4viD4h~uQ`(w2qSfP@Xua~i$%bZD$k3UX8MFINM z13x-blO@{nEX1KPHgEa@@QeEq0@3G`!PGn+jXVqSb{bxwa{&IolgMJR7(jwI22ti) z=1EzDAai^B`gYet&&V^-BNPg#1+DQ0kKc%5E2q?ZA5%Kqt^62p6$+bTh$qG6B49(+Q z&(T1)gPmRG^o34#Ha3L0O4f7bT;dE}@CO8ff+S<7_ZaHnws5CU1NVB|EvG!2W5R3X zv%+5uj8=aA#x1E09~f59e)w_y%FG2a0(jZBU)#thfjM%H9s>9@$uyi^e5c`0#8d-{ z6@RRYk8JQ94JHB*$Yt3qQ-gr^-3(=ymN_~UdX|l?Q%OuK)SjdbCzxKGrK6xwDe&J( znNln)cGYq_CPQ*QqH@>gsCiT3CM4 z?3?ChqsnqypGcG1-}(5;-as>Fo3e9r$2*9JsVz|tLl2@a@pwU?8b_#-Kdt-a)BfZ- zr5QuUumA3PBWbaPu-4*>Z3ytS)7Sr)+mJw-ogl&w3t$+r%*Duw>bva^?h{={M{PnT zv{G&^P5tpSbgrBI2^*pZ%0O2TL)v|OeZ%e?K02mttE=1oKzsWM)`?x>j7g5IJTI3~ zs(~RXt;fC2-3a@PU0Bs*=uD-LmnC=*rM>S*J6E-B#xt1g{ChY)AlPOLl8|9oA4T_X z-`md=*t+IRY4E0;!^mML*hNrx)!2I6jY-T_)e*_aZ%)qldLzm@=Dc0ZYUSOoc&G^N z|Ek0-|JuOr?vYs0Z3+xu$%LLK$~!J2sVy?XEh=@AB>UD9GuQEUlhl#$jogE8Jz~OH zq{JeT2hm2T?R|9_BX5uTgX@^`#do`r{Ybv@k*MkY!intl&jeXtdXynS6$vq+n z+&^Kb{CPk$o}+UhHm2ibj7nS0%VT_*k5^i23U9PNh;c|AiCTLid_i`B57U)izWs&R zp-2kjyTwDz`!pU&(oOC9KFjd(w#;kyls3X>F_JUX)%&Bg0VEVW{s#xK#a7<+6<$5G zSTWMz+SDzmAom*-ea}JZSzZoZ>tG9a;4*Kc3func{Qj7b4FSh~+i?M3-e^dmQSqhu zNXgUPK^UO=GMI;{TjZ**gY7t?dKI%Rs_)+Dh_!EMsQrt<;h$d*I%ILSlbTMdYQMet z>Bpx*nssY8u@V*)w^Xz(96%j}L{2CGb9Oy!(pjcHhp2!ZUy4=h=38RmwTis!jV7rV z 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 Date: Fri, 16 Oct 2026 03:17:00 +0200 Subject: [PATCH 06/11] refactor(ui): name the media grid's width breakpoints Co-Authored-By: Claude Opus 5.5 (1M context) --- .../src/media_gallery/stream_media_grid_delegate.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_grid_delegate.dart b/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_grid_delegate.dart index c26a3eb4f1..6679c6c39f 100644 --- a/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_grid_delegate.dart +++ b/packages/stream_chat_flutter/lib/src/media_gallery/stream_media_grid_delegate.dart @@ -1,5 +1,8 @@ import 'package:flutter/rendering.dart'; +const _kMediumGridMinWidth = 600.0; +const _kExpandedGridMinWidth = 840.0; + /// A grid delegate that lays out a media grid's square tiles, with more tiles /// per row as the grid gets wider. /// @@ -23,9 +26,9 @@ class StreamMediaGridDelegate extends SliverGridDelegate { SliverGridLayout getLayout(SliverConstraints constraints) { final crossAxisExtent = constraints.crossAxisExtent; final crossAxisCount = switch (crossAxisExtent) { - < 600 => 3, // A phone or a foldable's outer display, with the design's 3 tiles per row. - < 840 => 4, // A foldable or tablet in portrait. Even, so the tiles split evenly at a fold. - _ => 6, // A foldable or tablet in landscape. Even, so the tiles split evenly at a fold. + < _kMediumGridMinWidth => 3, // The design's 3 tiles per row. + < _kExpandedGridMinWidth => 4, // Even, so the tiles split evenly at a fold. + _ => 6, // Even, so the tiles split evenly at a fold. }; final delegate = SliverGridDelegateWithFixedCrossAxisCount( From cdf2fd4344dca14b2f266c54906d56408836b662 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 16 Oct 2026 03:17:02 +0200 Subject: [PATCH 07/11] docs(ui): note StreamMessageItem's new maxWidth default in the changelog Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/stream_chat_flutter/CHANGELOG.md | 1 + .../lib/src/message_widget/stream_message_item.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 4e2ca5a536..b0ba3f88a8 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -15,6 +15,7 @@ - `StreamMessageComposer`'s top border now spans the full width, including under the side safe-area insets, and also shows above the no-permission message. - Suggestions from `customAutocompleteTriggers` now span the composer's full width, so their content has to keep clear of the side safe-area insets itself. - `StreamMediaGallery` and `StreamPhotoGallery`, including `defaultStreamPhotoGalleryDelegate`, now show more tiles per row on wider screens instead of always 3. Pass a `SliverGridDelegateWithFixedCrossAxisCount` as `gridDelegate` to keep a fixed count. +- `StreamMessageItem`'s `maxWidth` now defaults to `272`, the same as `StreamMessageItemProps`. ⚠️ Deprecated diff --git a/packages/stream_chat_flutter/lib/src/message_widget/stream_message_item.dart b/packages/stream_chat_flutter/lib/src/message_widget/stream_message_item.dart index 1e69233475..ba131c94e9 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/stream_message_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/stream_message_item.dart @@ -230,7 +230,7 @@ class StreamMessageItemProps extends Equatable { /// /// The content uses at most this width while still respecting the parent /// [Flex] constraints. Use [double.infinity] to impose no cap from this - /// widget. Defaults to `272` when not specified. + /// widget. Defaults to `272`. final double maxWidth; /// Whether swiping the message triggers a quoted-reply action. From 2226eaa93821d684312cb8213dcfb6861b75f913 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 16 Oct 2026 03:17:03 +0200 Subject: [PATCH 08/11] test(ui): cover the media grid's breakpoints and the gallery's delegate assert Co-Authored-By: Claude Opus 5.5 (1M context) --- .../stream_media_gallery_test.dart | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/media_gallery/stream_media_gallery_test.dart b/packages/stream_chat_flutter/test/src/media_gallery/stream_media_gallery_test.dart index 80b2c21d15..0d26f64b0e 100644 --- a/packages/stream_chat_flutter/test/src/media_gallery/stream_media_gallery_test.dart +++ b/packages/stream_chat_flutter/test/src/media_gallery/stream_media_gallery_test.dart @@ -3,24 +3,9 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -Future _pumpGallery(WidgetTester tester, {required double width, required StreamMediaGallery gallery}) async { - tester.view - ..devicePixelRatio = 1 - ..physicalSize = Size(width, 800); - addTearDown(tester.view.reset); - - await tester.pumpWidget(MaterialApp(home: gallery)); -} - -int _tilesPerRow(WidgetTester tester) { - final grid = tester.widget(find.byType(GridView)); - final sliver = tester.renderObject(find.byType(SliverGrid, skipOffstage: false)); - final layout = grid.gridDelegate.getLayout(sliver.constraints) as SliverGridRegularTileLayout; - return layout.crossAxisCount; -} - void main() { - for (final (width, tilesPerRow) in [(402.0, 3), (669.0, 4), (867.0, 6)]) { + // Each side of the 600 and 840 breakpoints. + for (final (width, tilesPerRow) in [(599.0, 3), (600.0, 4), (839.0, 4), (840.0, 6)]) { testWidgets('StreamMediaGallery shows $tilesPerRow tiles per row when $width wide', (tester) async { await _pumpGallery( tester, @@ -54,4 +39,30 @@ void main() { expect(tester.widget(find.byType(GridView)).gridDelegate, same(gridDelegate)); }); + + test('StreamMediaGallery rejects both a crossAxisCount and a gridDelegate', () { + const gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 5); + + expect( + // ignore: deprecated_member_use_from_same_package + () => StreamMediaGallery(attachments: const [], crossAxisCount: 2, gridDelegate: gridDelegate), + throwsAssertionError, + ); + }); +} + +Future _pumpGallery(WidgetTester tester, {required double width, required StreamMediaGallery gallery}) async { + tester.view + ..devicePixelRatio = 1 + ..physicalSize = Size(width, 800); + addTearDown(tester.view.reset); + + await tester.pumpWidget(MaterialApp(home: gallery)); +} + +int _tilesPerRow(WidgetTester tester) { + final grid = tester.widget(find.byType(GridView)); + final sliver = tester.renderObject(find.byType(SliverGrid, skipOffstage: false)); + final layout = grid.gridDelegate.getLayout(sliver.constraints) as SliverGridRegularTileLayout; + return layout.crossAxisCount; } From 23324df3ce884c5fe39d49bfe160f31c4f7d4c49 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 16 Oct 2026 03:20:49 +0200 Subject: [PATCH 09/11] fix(ui): declare defaultStreamPhotoGalleryDelegate as a SliverGridDelegate Its type was inferred from the delegate behind it, so switching to the adaptive grid changed the public type to one that isn't exported. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/stream_chat_flutter/CHANGELOG.md | 3 ++- .../photo_gallery/stream_photo_gallery.dart | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index b0ba3f88a8..b2fd8937d8 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -14,7 +14,8 @@ - `StreamMessageListView` now rebuilds only the message rows that changed; a custom row builder should read changing outside state through an inherited widget. - `StreamMessageComposer`'s top border now spans the full width, including under the side safe-area insets, and also shows above the no-permission message. - Suggestions from `customAutocompleteTriggers` now span the composer's full width, so their content has to keep clear of the side safe-area insets itself. -- `StreamMediaGallery` and `StreamPhotoGallery`, including `defaultStreamPhotoGalleryDelegate`, now show more tiles per row on wider screens instead of always 3. Pass a `SliverGridDelegateWithFixedCrossAxisCount` as `gridDelegate` to keep a fixed count. +- `StreamMediaGallery` and `StreamPhotoGallery`, including `defaultStreamPhotoGalleryDelegate`, now show more tiles per row in wider grids instead of always 3. Pass a `SliverGridDelegateWithFixedCrossAxisCount` as `gridDelegate` to keep a fixed count. +- `defaultStreamPhotoGalleryDelegate` is now declared as a `SliverGridDelegate`. - `StreamMessageItem`'s `maxWidth` now defaults to `272`, the same as `StreamMessageItemProps`. ⚠️ Deprecated diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart index 23c1501802..8567780d9d 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/photo_gallery/stream_photo_gallery.dart @@ -6,8 +6,10 @@ import '../../../stream_chat_flutter.dart'; import '../../media_gallery/stream_media_grid_delegate.dart'; import '../../utils/network_error_text.dart'; -/// Default grid delegate for [StreamPhotoGallery]. -const defaultStreamPhotoGalleryDelegate = StreamMediaGridDelegate( +/// The grid delegate a [StreamPhotoGallery] uses when none is given. +/// +/// Shows square tiles, with more tiles per row as the gallery gets wider. +const SliverGridDelegate defaultStreamPhotoGalleryDelegate = StreamMediaGridDelegate( mainAxisSpacing: 2, crossAxisSpacing: 2, ); @@ -59,8 +61,9 @@ class StreamPhotoGallery extends StatelessWidget { /// The [StreamPhotoGalleryController] used to control the grid of users. final StreamPhotoGalleryController controller; - /// A delegate that controls the layout of the children within - /// the [PagedValueGridView]. + /// The delegate that lays out the gallery's tiles. + /// + /// Defaults to [defaultStreamPhotoGalleryDelegate]. final SliverGridDelegate gridDelegate; /// A builder that is called to build items in the [PagedValueGridView]. From 3f8c879a420da3c096dcdb8729746ac3de45cf83 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 16 Oct 2026 03:20:51 +0200 Subject: [PATCH 10/11] docs(repo): declare public defaults with the type their consumers accept Co-Authored-By: Claude Opus 5.5 (1M context) --- STYLE_GUIDE.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md index d83fed232a..01de3241c2 100644 --- a/STYLE_GUIDE.md +++ b/STYLE_GUIDE.md @@ -714,8 +714,22 @@ final label = switch (state) { All public API members must have explicit type annotations — parameters, fields, and return types (`type_annotate_public_apis`, `always_declare_return_types`). This -includes top-level declarations and static members. The linter will reject a public -member without a declared type. +includes top-level declarations and static members. The linter rejects a public member +without a declared type, with one gap described below. + +A public constant or field that serves as a default declares the most general type its +consumers accept, not the class that implements it today: `SliverGridDelegate`, +`ShapeBorder`, `Widget`. The declared type is the API; the implementation behind it can +then change without changing it. `type_annotate_public_apis` doesn't flag a top-level +`const` whose type is inferred from its initializer, so reviewers check this one. + +```dart +// BAD: the public type is whatever implementation the default uses today. +const defaultGalleryDelegate = StreamMediaGridDelegate(mainAxisSpacing: 2); + +// GOOD: +const SliverGridDelegate defaultGalleryDelegate = StreamMediaGridDelegate(mainAxisSpacing: 2); +``` Avoid `dynamic` in public APIs. If the type is unknown, prefer `Object?` and casting; `dynamic` disables all static checking. From a3c08528cf9d83531086a89af6d01e19b45af5e3 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 16 Oct 2026 12:29:04 +0200 Subject: [PATCH 11/11] docs(repo): note the lint also misses an inferred top-level final Co-authored-by: renefloor Co-Authored-By: Claude Opus 5.5 (1M context) --- STYLE_GUIDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md index 01de3241c2..3bdf7b85c0 100644 --- a/STYLE_GUIDE.md +++ b/STYLE_GUIDE.md @@ -721,7 +721,7 @@ A public constant or field that serves as a default declares the most general ty consumers accept, not the class that implements it today: `SliverGridDelegate`, `ShapeBorder`, `Widget`. The declared type is the API; the implementation behind it can then change without changing it. `type_annotate_public_apis` doesn't flag a top-level -`const` whose type is inferred from its initializer, so reviewers check this one. +`const` or `final` whose type is inferred from its initializer, so reviewers check this one. ```dart // BAD: the public type is whatever implementation the default uses today.