Repository navigation
feat(ui, samples): two-pane channel list and channel on wide windows and foldables #3023
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
Merged
xsahil03x
merged 38 commits into
sahil/flu-823-iphone-duo-polish-viewer-fit-thumbnails-autocomplete-height
from
sahil/flu-826-sample-app-two-pane-channel-list-and-channel-on-the-inner
Oct 16, 2026
+1,254
−176
Merged
Changes from all commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
7452f05
feat(samples): show the channel list and channel side by side on regu…
xsahil03x b229378
feat(samples): split the channel list and channel at a fold or hinge
xsahil03x 20c54ef
chore(samples): update the Android Gradle wrapper to 9.3.1
xsahil03x 8511290
feat(ui): add automaticallyImplyLeading to StreamChannelPage and Stre…
xsahil03x 78fa203
feat(samples): make the channel pane behave like a split view's detail
xsahil03x 59ed593
fix(samples): stop an open channel's state leaking into the next one
xsahil03x c8fd516
fix(samples): open drafts in the channel pane
xsahil03x ab9f6e4
docs(ui): note that onBackPressed is not called without a back button
xsahil03x 3216023
docs(samples): tidy the split view docs and constants
xsahil03x 1b1fbbc
fix(samples): keep the channel list's selection in place while scrolling
xsahil03x 30c93db
fix(ui): stop StreamThreadListTile overflowing when narrow
xsahil03x 13def83
feat(ui): add StreamThreadListTile.selected and copyWith
xsahil03x 07b69dc
feat(samples): open threads, drafts and reminders in the channel pane
xsahil03x 7de3804
feat(samples): open the drawer over both panes
xsahil03x f07067f
fix(ui): stop the message actions modal throwing after its message le…
xsahil03x 6b963b4
fix(samples): swap channels in place beside the list and close their …
xsahil03x c8a2564
feat(samples): show one pane on regular windows narrower than 840
xsahil03x 81baeb5
fix(samples): keep the channel list's selection from showing behind t…
xsahil03x 96d4ed7
feat(samples): ease the split into place when a fold appears or disap…
xsahil03x ac627b8
fix(samples): close the login progress dialog without asking the rout…
xsahil03x ae99d38
fix(samples): update the Crashlytics Gradle plugin to 3.0.8 for Gradle 9
xsahil03x d50bf7f
refactor(samples): classify the window by width and height separately
xsahil03x f8641c7
Merge branch 'sahil/flu-823-iphone-duo-polish-viewer-fit-thumbnails-a…
xsahil03x 5f43140
Merge commit '419161627' into worktree-flu-826-two-pane
xsahil03x c590111
feat(samples): let people resize the split view's panes
xsahil03x 054f216
Merge #3021 into the FLU-826 branch
xsahil03x 4e82cd0
Merge #3021 into the FLU-826 branch
xsahil03x 73d0bd5
fix(samples): close the split view's drawer on back before the channel
xsahil03x 4ac15ab
fix(samples): keep the split view's resized width across one-pane lay…
xsahil03x febffee
fix(samples): resize the split view by touch only from a handle
xsahil03x dec3f8e
docs(samples): describe the split view's handle and resized width acc…
xsahil03x d9d02db
Revert "fix(samples): update the Crashlytics Gradle plugin to 3.0.8 f…
xsahil03x 8ff68d0
Revert "chore(samples): update the Android Gradle wrapper to 9.3.1"
xsahil03x b81c16f
Merge the FLU-823 branch into the FLU-826 branch
xsahil03x dffea08
Merge the FLU-823 branch into the FLU-826 branch
xsahil03x cc1bc92
fix(samples): open a channel from a notification or DM with goNamed
xsahil03x 40bcf16
fix(ui): drop a message action picked after its message leaves the tree
xsahil03x 86348b0
Merge the FLU-823 branch into the FLU-826 branch
xsahil03x File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
...s/stream_chat_flutter/test/src/message_widget/stream_message_item_actions_modal_test.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:flutter_test/flutter_test.dart'; | ||
| import 'package:mocktail/mocktail.dart'; | ||
| import 'package:stream_chat_flutter/stream_chat_flutter.dart'; | ||
|
|
||
| import '../mocks.dart'; | ||
|
|
||
| void main() { | ||
| testWidgets('keeps the actions modal working after its message leaves the tree', (tester) async { | ||
| final client = MockClient(); | ||
| final channel = _mockChannel(client); | ||
|
|
||
| await tester.pumpWidget(_app(client: client, channel: channel, showMessage: true)); | ||
| await tester.pumpAndSettle(); | ||
| await _longPress(tester, find.byType(StreamMessageItem)); | ||
|
|
||
| // Rebuilding the app rebuilds the navigator's routes, the open modal among them. | ||
| await tester.pumpWidget(_app(client: client, channel: channel, showMessage: false)); | ||
|
|
||
| expect(tester.takeException(), isNull); | ||
| expect(find.byKey(const Key('MessageItem')), findsOneWidget); | ||
| }); | ||
|
|
||
| testWidgets('StreamMessageItem drops a modal action picked after its message leaves the tree', (tester) async { | ||
| final client = MockClient(); | ||
| final channel = _mockChannel(client); | ||
| final threadTaps = <Message>[]; | ||
| void onThreadTap(Message parent, Message? _) => threadTaps.add(parent); | ||
|
|
||
| await tester.pumpWidget(_app(client: client, channel: channel, showMessage: true, onThreadTap: onThreadTap)); | ||
| await tester.pumpAndSettle(); | ||
| await _longPress(tester, find.byType(StreamMessageItem)); | ||
|
|
||
| await tester.pumpWidget(_app(client: client, channel: channel, showMessage: false, onThreadTap: onThreadTap)); | ||
| await tester.tap(find.text('Thread Reply')); | ||
| await tester.pumpAndSettle(); | ||
|
|
||
| expect(threadTaps, isEmpty); | ||
| }); | ||
| } | ||
|
|
||
| MockChannel _mockChannel(MockClient client) { | ||
| final currentUser = OwnUser(id: 'current-user'); | ||
| final clientState = MockClientState(); | ||
| final channel = MockChannel(ownCapabilities: const [ChannelCapability.sendMessage, ChannelCapability.sendReply]); | ||
| final channelState = MockChannelState(); | ||
|
|
||
| when(() => client.state).thenReturn(clientState); | ||
| when(() => clientState.currentUser).thenReturn(currentUser); | ||
| when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(currentUser)); | ||
| when(() => channel.client).thenReturn(client); | ||
| when(() => channel.state).thenReturn(channelState); | ||
|
|
||
| return channel; | ||
| } | ||
|
|
||
| Widget _app({ | ||
| required MockClient client, | ||
| required MockChannel channel, | ||
| required bool showMessage, | ||
| void Function(Message, Message?)? onThreadTap, | ||
| }) { | ||
| final message = Message( | ||
| id: 'test-message', | ||
| text: 'Long press me', | ||
| createdAt: DateTime(2026), | ||
| user: User(id: 'other-user'), | ||
| state: MessageState.sent, | ||
| ); | ||
|
|
||
| return MaterialApp( | ||
| builder: (context, child) => StreamChat(client: client, child: child), | ||
| home: StreamChannel( | ||
| channel: channel, | ||
| child: Scaffold( | ||
| body: showMessage ? StreamMessageItem(message: message, onThreadTap: onThreadTap) : const SizedBox.shrink(), | ||
| ), | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| // Holds past kLongPressTimeout, which a plain tester.longPress does not | ||
| // reliably cross. | ||
| Future<void> _longPress(WidgetTester tester, Finder finder) async { | ||
| final gesture = await tester.startGesture(tester.getCenter(finder)); | ||
| await tester.pump(const Duration(milliseconds: 700)); | ||
| await gesture.up(); | ||
| await tester.pumpAndSettle(); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 fixes the modal's builder, but the action still runs on
contextafter the modal closes. Line 990 calls_onActionTap(context, channel, action).ignore()afterawait showStreamDialog(...), with nocontext.mountedcheck, and that is the context this comment says can be gone.If the message leaves the tree while the modal is open:
_selectReactioncallsStreamChatConfiguration.of(context)again (line 1089), the lookup this PR moved up front._maybeDeleteMessageand_maybeFlagMessagecallshowStreamDialog(context: context).These throw inside the async
_onActionTap, and.ignore()discards the error. The modal closes, nothing happens, and nothing is logged.Suggested fix:
configurationalready read here into_selectReaction.await, or bail out or log on!context.mounted.stream_message_item_actions_modal_test.dartto tap an action (for example a reaction) after the message is removed.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.
Went with bailing out on
!context.mountedin 40bcf16, after the actions modal, the bounced-message dialog and the reactions sheet. That matches Flutter'sPopupMenuButton, which drops the selection when the button is no longer mounted.The item only leaves the tree while its modal is open when the message is removed from the channel, so the action is dropped rather than run against a message that's gone. The new test taps Thread Reply after the message is removed and expects
onThreadTapnot to be called. Thread Reply is one of the actions that never touchcontext, so the test fails without the check.