Repository navigation
feat(ui): adapt the message bubble and media grids to wide screens #3034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e0a0f7c
60e05b3
dc8a10a
9e4d310
35a615d
70a4e9c
9fbfe3a
4fb0c8c
d0ea5a6
1c29a1c
3647581
656599e
cdf2fd4
2226eaa
23324df
3f8c879
41a97c6
f371b9b
a3c0852
db80ea5
e881b2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| 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. | ||
| /// | ||
| /// 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({ | ||
| 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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two small things:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Named them in 656599e ( |
||
| < _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( | ||
| crossAxisCount: crossAxisCount, | ||
| mainAxisSpacing: mainAxisSpacing, | ||
| crossAxisSpacing: crossAxisSpacing, | ||
| ); | ||
|
|
||
| return delegate.getLayout(constraints); | ||
| } | ||
|
|
||
| @override | ||
| bool shouldRelayout(StreamMediaGridDelegate oldDelegate) { | ||
| return oldDelegate.mainAxisSpacing != mainAxisSpacing || oldDelegate.crossAxisSpacing != crossAxisSpacing; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,7 +76,7 @@ class StreamMessageItem extends StatelessWidget { | |
| EdgeInsetsGeometry? padding, | ||
| double? spacing, | ||
| Color? backgroundColor, | ||
| double maxWidth = 264, | ||
| double maxWidth = 272, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changing this default from 264 to 272 widens every bubble built with the constructor by 8px, including the long-press preview, custom builders and customer goldens. The migration doc is updated, but there's no
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added in cdf2fd4: " |
||
| bool swipeToReply = false, | ||
| void Function(Message)? onMessageTap, | ||
| void Function(Message)? onMessageLongPress, | ||
|
|
@@ -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 `264` when not specified. | ||
| /// widget. Defaults to `272`. | ||
| final double maxWidth; | ||
|
|
||
| /// Whether swiping the message triggers a quoted-reply action. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| 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'; | ||
|
|
||
| void main() { | ||
| // 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, | ||
| 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<GridView>(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<void> _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<GridView>(find.byType(GridView)); | ||
| final sliver = tester.renderObject<RenderSliverGrid>(find.byType(SliverGrid, skipOffstage: false)); | ||
| final layout = grid.gridDelegate.getLayout(sliver.constraints) as SliverGridRegularTileLayout; | ||
| return layout.crossAxisCount; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't exported from
stream_chat_flutter.dart. Not a blocker if the default above is typed asSliverGridDelegate, but it means a customer who wants the adaptive grid with different spacing can't get it: setting anygridDelegateloses the adaptive columns. Worth exporting (plus anAddedchangelog line) now or in a follow-up?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping it internal for now.
gridDelegateon both galleries accepts anySliverGridDelegate, so a customer who wants different spacing or counts isn't stuck, and exporting it would make the 600/840 breakpoints public API. Happy to export it with anAddedline once someone asks for it.