Skip to content

Commit 29fc127

Browse files
renefloorxsahil03xclaude
authored
docs(ui): showcase, localization, theming, and composer snapshots (#2649)
* docs(ui): more docs screenshots Adds four new snapshot scenarios: - `flutter_showcase` — hero image with four phone crops on a split blue/black background. Two unique screens (message list + channel list), each rendered light and dark, with a Flutter logo card centred. - `localization_support` — two iPhone 13 frames side by side rendering the same modal in English and Italian, with a floating `StreamMessageComposer` whose placeholder pulls from `context.translations.writeAMessageLabel`. - `theming_default` / `theming_red` — same chat scaffold rendered against the docs default theme and a red brand/chrome variant to demonstrate `StreamColorScheme` overrides. - `message_input_custom_buttons` — composer snapshot demonstrating `messageComposerLeading` / `*InputLeading` / `*InputTrailing` / `*Trailing` builder overrides. The trailing button is a `StatefulWidget` that toggles between voice and send icons based on text emptiness. Also adds `docsScreenshotsDarkTheme()` (mirrors the light theme but with dark brightness, black scaffold/appBar, and `StreamColorScheme.dark()` text colours) and `stream_chat_localizations` to `dev_dependencies`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: Update Docs Snapshots * docs(ui): add deviceFrame param to docsGoldenTest, drop flutter_showcase `docsGoldenTest` gains a `DeviceInfo? deviceFrame` parameter. When set, the helper wraps the builder's widget in `DeviceFrame(device: …, isFrameVisible: true, screen: …)` so individual tests no longer need to construct the wrap themselves. The parameter is opt-in — leaving it null keeps the previous no-frame behavior for tests like `localization_support` that manage multiple frames in a single canvas. Migrate the in-PR theming snapshots and three existing v10.0.0 sites that used the inline `DeviceFrame(screen: …)` wrap: - `theming_default` / `theming_red` - `channel_list_view`, `slidable_channel_list` - `message_list_view` Each drops the `DeviceFrame(...)` wrapper around `MaterialApp(...)` and adds `deviceFrame: Devices.ios.iPhone13` to the `docsGoldenTest` call. Drop the `flutter_showcase` snapshot — no longer needed. Also fix `message_styles`: the body text said "This is a message from Bob." but the author (`_sender`) is now Noah Smith after the sample-user roster swap. Switch to an interpolation (`'… from ${_sender.name}.'`) so the copy can't drift again the next time a sample user is reassigned. Audited the rest of the suite — no other hardcoded user-name references in message bodies. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ui): match SDK trailing button size in custom composer snapshot `DefaultStreamMessageComposerInputTrailing` (the production widget that renders the send / mic button) ships at `StreamButtonSize.small` (32 px). Our `_CustomComposerTrailingButton` used `medium` (40 px) for both the voice and send branches, so the blue circle dwarfed the input pill. Drop both branches to `small` to match the production button's footprint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ui): start-align hint in custom composer snapshot The `message_input_custom_buttons` demo had buttons in both the `messageComposerInputLeading` (emoji) and `messageComposerInputTrailing` (attachment) slots. With buttons on both sides of the pill's `TextField`, the short "Type a message…" hint visually appeared centered between them, even though it was technically start-aligned within the field. Remove the inside trailing attachment button so the pill is just `[emoji | text]` — the hint now sits right after the emoji. Also bump the outer trailing mic/send button from default `medium` (40px) to `large` (48px) so it matches `DefaultStreamMessageComposerLeading`'s `StreamButtonSize.large` attachment button — the snapshot is meant to show "attachment on outer-left ↔ mic on outer-right" symmetry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ui): use ghost+secondary mic style at attachment size In the custom composer snapshot the outer trailing mic/send button was `solid+primary` (blue circle). Switch it to `ghost+secondary` so it visually matches the SDK's default `_VoiceRecordingButton`, while keeping `size: large` to match the outer leading attachment button's footprint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ui): switch custom composer trailing to outline type Outline now mirrors `DefaultStreamMessageComposerLeading`'s `type: outline, style: secondary` attachment button — the trailing mic/send becomes a visual twin of the leading attachment circle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ui): solid+primary send variant in custom composer trailing The voice branch keeps the outline+secondary style (matches an idle mic affordance), but the send branch flips to solid+primary so it visually signals "ready to send" — same blue circle the SDK uses by default in `DefaultStreamMessageComposerInputTrailing`. The snapshot doesn't change (controller starts empty so only the voice branch renders), but the demo code now models the toggle correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ui): bump change_position send to large, fix custom_send_icon bg `message_input_change_position` previously rendered the moved-out trailing via `DefaultStreamMessageComposerInputTrailing`, which is hard-coded to `size: small`. Replace with a direct `StreamButton.icon` at `size: large`, wrapped in a `Row` with a leading `SizedBox(spacing.xs)` gap — mirrors the spacing the leading `+` button gets on the other side and matches the trailing button size in `message_input_custom_buttons`. `message_input_custom_send_icon` builds its own `ThemeData` to demo a `StreamIcons` override, but didn't set `scaffoldBackgroundColor`. M3's default light scaffold has a faint pink tint, which produced a coloured bar above the divider unlike every other composer snapshot. Pin it to pure white to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ui): align custom composer snapshot height with siblings `message_input_custom_buttons` was 120 px tall while every other composer snapshot (default, change_position, custom_send_icon, slow_mode) is 100 px. Drop to 100 for consistency. The quoted-message snapshot stays at 160 since it needs room for the quoted-message header. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ui): override composer send icon via inherited Theme `message_input_custom_send_icon` was hand-building its own `ThemeData` to inject a `StreamIcons` override, which forced it to redeclare `scaffoldBackgroundColor` and re-derive the text theme from scratch. Use `docsScreenshotsTheme()` as the base and wrap the composer subtree in a `Builder` + `Theme(data: copyWith(extensions: ...))` so the icon override flows through Material's inherited `Theme` widget instead of a duplicated `ThemeData`. Drops the now-unused `stream_core_flutter` import in this test. Also bumps the `message_input_quoted_message` snapshot height to 180 so the reply header + composer fit cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ui): pull MaterialApp wrap into docsGoldenTest Every docs snapshot builder was rebuilding the same outer wrap — `MaterialApp(theme: docsScreenshotsTheme(), debugShowCheckedModeBanner: false, home: …)`. Move that wrap into `docsGoldenTest`'s default `app:` callback so the builder just returns the `home:` widget. Tests with custom theme/locale (theming_test, localization_support_test) opt out by passing their own `app:` callback. Net −101 lines across 13 files; no golden output changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(docs): run analyze/format/test on docs/** changes `stream_flutter_workflow.yml` only triggered on `packages/**` and `sample_app/**`, so docs-only PRs (this branch included) skipped the analyze, format-validate, and test jobs entirely. Add `docs/**` to the trigger paths. Also apply the formatter to two files the new check would have flagged (trailing newline + collapsed one-liner) so the validate step passes on this PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: Update Docs Snapshots * docs(ui): model custom composer snapshot on WhatsApp layout Re-arrange the slot demo so it mirrors a recognisable real-world chat composer (WhatsApp): emoji inside the pill on the left, attachment + camera icons inside on the right, and a solid green mic/send button outside on the right. `StreamButtonTheme` wraps the trailing button to override the primary solid background to WhatsApp's brand green (#25D366); the state toggle between voice and send icons stays the same. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ui): regenerate custom composer snapshot for WhatsApp layout The previous commit landed the code change but a botched rebase restored the old PNG. Regenerate so the file actually matches the emoji + attachment + camera + green-mic layout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ui): simplify custom composer to Stream-themed slots Drop the camera slot and the WhatsApp-green override on the trailing mic/send button. The snapshot now demonstrates the slot mechanics with a single attachment icon inside the pill and a default-styled mic/send outside, so the styling falls back to the SDK's primary accent colour instead of a hard-coded brand colour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(ui): drop emoji slot from custom composer snapshot The demo now exercises three slots — outer leading hidden, inner trailing (attachment), outer trailing (mic/send) — which is enough to illustrate slot overrides without the extra emoji noise. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: Update Docs Snapshots --------- Co-authored-by: Sahil Kumar <sahil@getstream.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: xsahil03x <25670178+xsahil03x@users.noreply.github.com>
1 parent 7ae7874 commit 29fc127

24 files changed

Lines changed: 1088 additions & 587 deletions

.github/workflows/stream_flutter_workflow.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
paths:
1010
- 'packages/**'
1111
- 'sample_app/**'
12+
- 'docs/**'
1213
- '.github/workflows/stream_flutter_workflow.yml'
1314
types:
1415
- opened

docs/docs_screenshots/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ dev_dependencies:
3333
sdk: flutter
3434
mocktail: ^1.0.5
3535
plugin_platform_interface: ^2.1.8
36+
stream_chat_localizations: ^10.0.0-beta.13
3637

3738
flutter:
3839
uses-material-design: true

docs/docs_screenshots/test/channel/channel_header_test.dart

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,13 @@ Widget _buildChannelHeaderScaffold({
1010
required MockChannel channel,
1111
StreamChannelHeader? header,
1212
}) {
13-
return MaterialApp(
14-
theme: docsScreenshotsTheme(),
15-
debugShowCheckedModeBanner: false,
16-
home: StreamChat(
17-
client: client,
18-
connectivityStream: Stream.value([ConnectivityResult.mobile]),
19-
child: StreamChannel(
20-
showLoading: false,
21-
channel: channel,
22-
child: Scaffold(appBar: header),
23-
),
13+
return StreamChat(
14+
client: client,
15+
connectivityStream: Stream.value([ConnectivityResult.mobile]),
16+
child: StreamChannel(
17+
showLoading: false,
18+
channel: channel,
19+
child: Scaffold(appBar: header),
2420
),
2521
);
2622
}

docs/docs_screenshots/test/channel/channel_list_header_test.dart

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,11 @@ Widget _buildListHeaderScaffold({
1111
required MockClient client,
1212
required PreferredSizeWidget Function(BuildContext) headerBuilder,
1313
}) {
14-
return MaterialApp(
15-
theme: docsScreenshotsTheme(),
16-
debugShowCheckedModeBanner: false,
17-
home: StreamChat(
18-
client: client,
19-
connectivityStream: Stream.value([ConnectivityResult.mobile]),
20-
child: Builder(
21-
builder: (context) => Scaffold(appBar: headerBuilder(context)),
22-
),
14+
return StreamChat(
15+
client: client,
16+
connectivityStream: Stream.value([ConnectivityResult.mobile]),
17+
child: Builder(
18+
builder: (context) => Scaffold(appBar: headerBuilder(context)),
2319
),
2420
);
2521
}

docs/docs_screenshots/test/channel/channel_preview_test.dart

Lines changed: 103 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import 'package:device_preview/device_preview.dart';
1+
import 'package:device_preview/device_preview.dart' show Devices;
22
import 'package:flutter/material.dart';
33
import 'package:flutter_slidable/flutter_slidable.dart';
44
import 'package:flutter_test/flutter_test.dart';
@@ -32,15 +32,11 @@ void main() {
3232
],
3333
);
3434

35-
return MaterialApp(
36-
theme: docsScreenshotsTheme(),
37-
debugShowCheckedModeBanner: false,
38-
home: StreamChat(
39-
client: client,
40-
connectivityStream: Stream.value([ConnectivityResult.mobile]),
41-
child: Scaffold(
42-
body: StreamChannelListItem(channel: channel),
43-
),
35+
return StreamChat(
36+
client: client,
37+
connectivityStream: Stream.value([ConnectivityResult.mobile]),
38+
child: Scaffold(
39+
body: StreamChannelListItem(channel: channel),
4440
),
4541
);
4642
},
@@ -50,6 +46,7 @@ void main() {
5046
'channel list view',
5147
fileName: 'channel_list_view',
5248
constraints: const BoxConstraints.tightFor(width: 430, height: 932),
49+
deviceFrame: Devices.ios.iPhone13,
5350
builder: () {
5451
final client = MockClient();
5552

@@ -117,34 +114,26 @@ void main() {
117114
stubQueryChannelsForGoldens(client, channels);
118115
stubMockClientCurrentUser(client, ownUser);
119116

120-
return DeviceFrame(
121-
device: Devices.ios.iPhone13,
122-
isFrameVisible: true,
123-
screen: MaterialApp(
124-
theme: docsScreenshotsTheme(),
125-
debugShowCheckedModeBanner: false,
126-
home: StreamChat(
127-
client: client,
128-
connectivityStream: Stream.value([ConnectivityResult.mobile]),
129-
child: Builder(
130-
builder: (context) {
131-
final icons = context.streamIcons;
132-
return Scaffold(
133-
appBar: StreamChannelListHeader(
134-
title: const Text('Chats'),
135-
trailing: StreamButton.icon(
136-
icon: Icon(icons.plus),
137-
onPressed: () {},
138-
),
139-
),
140-
body: StreamChannelListView(
141-
controller: controller,
142-
shrinkWrap: true,
143-
),
144-
);
145-
},
146-
),
147-
),
117+
return StreamChat(
118+
client: client,
119+
connectivityStream: Stream.value([ConnectivityResult.mobile]),
120+
child: Builder(
121+
builder: (context) {
122+
final icons = context.streamIcons;
123+
return Scaffold(
124+
appBar: StreamChannelListHeader(
125+
title: const Text('Chats'),
126+
trailing: StreamButton.icon(
127+
icon: Icon(icons.plus),
128+
onPressed: () {},
129+
),
130+
),
131+
body: StreamChannelListView(
132+
controller: controller,
133+
shrinkWrap: true,
134+
),
135+
);
136+
},
148137
),
149138
);
150139
},
@@ -175,42 +164,38 @@ void main() {
175164
],
176165
);
177166

178-
return MaterialApp(
179-
theme: docsScreenshotsTheme(),
180-
debugShowCheckedModeBanner: false,
181-
home: StreamChat(
182-
client: client,
183-
connectivityStream: Stream.value([ConnectivityResult.mobile]),
184-
child: Builder(
185-
builder: (context) {
186-
final icons = context.streamIcons;
187-
final colorScheme = context.streamColorScheme;
188-
return Scaffold(
189-
body: Slidable(
190-
groupTag: 'channels-actions',
191-
endActionPane: ActionPane(
192-
extentRatio: 0.4,
193-
motion: const BehindMotion(),
194-
children: [
195-
CustomSlidableAction(
196-
foregroundColor: colorScheme.textPrimary,
197-
backgroundColor: colorScheme.backgroundSurface,
198-
onPressed: (_) {},
199-
child: Icon(icons.more, size: 20),
200-
),
201-
CustomSlidableAction(
202-
foregroundColor: colorScheme.textOnAccent,
203-
backgroundColor: colorScheme.accentPrimary,
204-
onPressed: (_) {},
205-
child: Icon(icons.mute, size: 20),
206-
),
207-
],
208-
),
209-
child: StreamChannelListItem(channel: channel),
167+
return StreamChat(
168+
client: client,
169+
connectivityStream: Stream.value([ConnectivityResult.mobile]),
170+
child: Builder(
171+
builder: (context) {
172+
final icons = context.streamIcons;
173+
final colorScheme = context.streamColorScheme;
174+
return Scaffold(
175+
body: Slidable(
176+
groupTag: 'channels-actions',
177+
endActionPane: ActionPane(
178+
extentRatio: 0.4,
179+
motion: const BehindMotion(),
180+
children: [
181+
CustomSlidableAction(
182+
foregroundColor: colorScheme.textPrimary,
183+
backgroundColor: colorScheme.backgroundSurface,
184+
onPressed: (_) {},
185+
child: Icon(icons.more, size: 20),
186+
),
187+
CustomSlidableAction(
188+
foregroundColor: colorScheme.textOnAccent,
189+
backgroundColor: colorScheme.accentPrimary,
190+
onPressed: (_) {},
191+
child: Icon(icons.mute, size: 20),
192+
),
193+
],
210194
),
211-
);
212-
},
213-
),
195+
child: StreamChannelListItem(channel: channel),
196+
),
197+
);
198+
},
214199
),
215200
);
216201
},
@@ -220,6 +205,7 @@ void main() {
220205
'slidable channel list with header',
221206
fileName: 'slidable_channel_list',
222207
constraints: const BoxConstraints.tightFor(width: 430, height: 932),
208+
deviceFrame: Devices.ios.iPhone13,
223209
whilePerforming: (tester) async {
224210
await tester.drag(find.byType(StreamChannelListItem).first, const Offset(-200, 0));
225211
await tester.pumpAndSettle();
@@ -292,59 +278,51 @@ void main() {
292278
stubQueryChannelsForGoldens(client, channels);
293279
stubMockClientCurrentUser(client, ownUser);
294280

295-
return DeviceFrame(
296-
device: Devices.ios.iPhone13,
297-
isFrameVisible: true,
298-
screen: MaterialApp(
299-
theme: docsScreenshotsTheme(),
300-
debugShowCheckedModeBanner: false,
301-
home: StreamChat(
302-
client: client,
303-
connectivityStream: Stream.value([ConnectivityResult.mobile]),
304-
child: Builder(
305-
builder: (context) {
306-
final icons = context.streamIcons;
307-
final colorScheme = context.streamColorScheme;
308-
return Scaffold(
309-
appBar: StreamChannelListHeader(
310-
title: const Text('Chats'),
311-
trailing: StreamButton.icon(
312-
icon: Icon(icons.plus),
313-
onPressed: () {},
314-
),
315-
),
316-
body: StreamChannelListView(
317-
controller: controller,
318-
shrinkWrap: true,
319-
itemBuilder: (context, channels, index, defaultWidget) {
320-
return Slidable(
321-
groupTag: 'channels-actions',
322-
endActionPane: ActionPane(
323-
extentRatio: 0.4,
324-
motion: const BehindMotion(),
325-
children: [
326-
CustomSlidableAction(
327-
foregroundColor: colorScheme.textPrimary,
328-
backgroundColor: colorScheme.backgroundSurface,
329-
onPressed: (_) {},
330-
child: Icon(icons.more, size: 20),
331-
),
332-
CustomSlidableAction(
333-
foregroundColor: colorScheme.textOnAccent,
334-
backgroundColor: colorScheme.accentPrimary,
335-
onPressed: (_) {},
336-
child: Icon(icons.mute, size: 20),
337-
),
338-
],
281+
return StreamChat(
282+
client: client,
283+
connectivityStream: Stream.value([ConnectivityResult.mobile]),
284+
child: Builder(
285+
builder: (context) {
286+
final icons = context.streamIcons;
287+
final colorScheme = context.streamColorScheme;
288+
return Scaffold(
289+
appBar: StreamChannelListHeader(
290+
title: const Text('Chats'),
291+
trailing: StreamButton.icon(
292+
icon: Icon(icons.plus),
293+
onPressed: () {},
294+
),
295+
),
296+
body: StreamChannelListView(
297+
controller: controller,
298+
shrinkWrap: true,
299+
itemBuilder: (context, channels, index, defaultWidget) {
300+
return Slidable(
301+
groupTag: 'channels-actions',
302+
endActionPane: ActionPane(
303+
extentRatio: 0.4,
304+
motion: const BehindMotion(),
305+
children: [
306+
CustomSlidableAction(
307+
foregroundColor: colorScheme.textPrimary,
308+
backgroundColor: colorScheme.backgroundSurface,
309+
onPressed: (_) {},
310+
child: Icon(icons.more, size: 20),
339311
),
340-
child: defaultWidget,
341-
);
342-
},
343-
),
344-
);
345-
},
346-
),
347-
),
312+
CustomSlidableAction(
313+
foregroundColor: colorScheme.textOnAccent,
314+
backgroundColor: colorScheme.accentPrimary,
315+
onPressed: (_) {},
316+
child: Icon(icons.mute, size: 20),
317+
),
318+
],
319+
),
320+
child: defaultWidget,
321+
);
322+
},
323+
),
324+
);
325+
},
348326
),
349327
);
350328
},
156 KB
Loading

0 commit comments

Comments
 (0)