feat(ui): pick any participant layout from a menu - #1332
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## v2 #1332 +/- ##
=====================================
Coverage ? 29.08%
=====================================
Files ? 374
Lines ? 28922
Branches ? 0
=====================================
Hits ? 8413
Misses ? 20509
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
4d70250 to
111f078
Compare
dab5a5c to
009904c
Compare
009904c to
f4a420a
Compare
StreamLayoutButton flipped between two layouts and drew one of two icons. It now draws the layout in effect and offers the rest through a StreamAdaptiveMenuAnchor — a context menu on desktop and web, a bottom sheet on Android and iOS. Given two layouts it toggles instead, which is what its default pair does. ParticipantLayoutMode grows to auto, grid, speakerTop, speakerBottom, speakerLeft, speakerRight and speakerOneToOne, mapping the four bar layouts onto the ParticipantsBarAlignment the spotlight view already had. spotlight and pictureInPicture stay as deprecated aliases with dart fix transforms. The anchored menu now fills the row of the selected option, as the sheet already did. That needs StreamContextMenuAnchor.defaultActionStyle: StreamContextMenuActionTheme.of reads only the nearest theme, so varying one row has to re-merge the design's metrics rather than layer over them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eens Builds the exclusion as a list and filters with whereNot, rather than a negated compound condition inside a comprehension. Both lists are const, so a rebuild allocates neither. Also formats stream_layout_button_test.dart, which `melos run format` skipped while it was still untracked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The default and the layouts it can apply to were two booleans apart: the platform default was computed for every layout and then discarded for the five that give the local participant a tile. One switch now says, per layout, whether the self-view floats and what it defaults to. No behaviour change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The four bar layouts mapped their name onto the participants bar's edge: speakerTop set ParticipantsBarAlignment.top, which renders Column([bar, spotlight]) and puts the speaker at the bottom. The design names them after the speaker — speaker-top-fill.svg draws the large tile along the top — so the menu offered a row whose icon and label said the opposite of what it rendered. All four now put the bar on the edge opposite the speaker. That moves the spotlight alias too: it used to take the spotlight view's default bottom bar, which is speakerTop under the corrected naming, so canonical, fix_data.yaml and the livestream single-host layout follow. Also guard the speaker branch against an empty participant list, where participants.first threw StateError. Five layouts reach that branch now and every one of them is a tap away in the new menu. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The device split buttons already expose this as menuDirection; direction read as the button's own direction rather than its menu's. It defaults to down alongside them now, so every call control with a menu defaults the same way. Both are new in this unreleased major, so no transform is needed. Also pin the mapping: menuDirection.up offsets the panel by -8 and down by 8, which nothing asserted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
enableLocalVideo is enableFloatingSelfView, and its builder is floatingSelfViewBuilder. The flag decides whether the local participant's self-view floats over the layout or they take a tile like anybody else, so the old name read as "show my video at all" — which the grid and the four bar layouts ignore, because those give them a tile either way. dart fix --apply renames both on StreamCallParticipants. The pair is also on RegularCallParticipantsContent, which is not exported and needs no transform. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A speaker layout with no participants bar shows nobody but the spotlight, so it now goes to a remote whenever there is one. Under speakerOneToOne with the self-view off, the speaker sort could put the local participant first and spotlight them, leaving every remote off the screen. A bar layout still spotlights whoever the sort put first, the local participant included, since everybody else is in the bar. StreamCallParticipants derives its sort from layoutMode, but recalculated only when the participants or the call changed, so picking a speaker layout swapped the preset without reordering the list and spotlighted whoever the previous preset had first. On a quiet call no emission arrived to correct it. Both are covered by tests that fail without the change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Under auto, the self-view floats on mobile only while at most two other people are in the call. Beyond that the grid is crowded enough on a phone that the inset covers a tile, so the local participant takes one of their own. Desktop is unchanged, and an explicit enableFloatingSelfView still wins either way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
993cc16 to
64ac245
Compare
The re-sort was keyed on the resolved sort, which is a function: an inline sort: closure is a new object on every build, so a call site passing one re-sorted on every rebuild. layoutMode.sorting is one of two cached presets, so comparing those says what was meant — the layout changed the preset — and never compares a caller's closure. Also says in the changelog that enableFloatingSelfView is ignored, rather than merely unread, on the layouts that give the local participant a tile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
StreamLayoutButtonflipped between two layouts and drew one of two icons. It now draws the layout in effect and offers the rest through aStreamAdaptiveMenuAnchor: a context menu on a desktop platform, a bottom sheet on Android and iOS. Given two layouts it toggles instead of opening a menu, which is what its default pair does.Design: menu, sheet.
The seven layouts
autogridspeakerTop/speakerBottom/speakerLeft/speakerRightspeakerOneToOneA layout is named after where the speaker sits, not the bar — that is what the design's icons draw, so
speakerTopsetsParticipantsBarAlignment.bottom. The four map onto the alignmentCallParticipantsSpotlightViewalready had andRegularCallParticipantsContentnever set. Screen share is untouched:StreamCallParticipants.buildreturnsScreenShareCallParticipantsContentbeforelayoutModeis read, and that widget picks its own alignment from the window aspect ratio.Notes for review
autois the new default forStreamCallContent,StreamCallParticipantsandRegularCallParticipantsContent.gridnow gives the local participant a tile instead of floating them. The livestream widgets still default togrid, and are otherwise untouched — the new layouts there are for a later PR with designs of their own.spotlight→speakerTop,pictureInPicture→speakerOneToOne, both deprecated withfix_data.yamltransforms.spotlighttook the spotlight view's default bottom bar, which isspeakerTopunder the naming above.dart fix --applymigrates call sites; verified end to end on a probe file.enableLocalVideoisenableFloatingSelfViewnow, andlocalVideoParticipantBuilderisfloatingSelfViewBuilder. The flag decides whether the self-view floats over the layout or the local participant takes a tile like anybody else — the old name read as "show my video at all", which the grid and the four bar layouts ignore because those give them a tile either way.dart fix --applyrenames both.autoandspeakerOneToOneread it, the two layouts that leave the local participant out of the arrangement. UnderspeakerOneToOneit defaults to on; underautoin a group call, to on for mobile with at most two other people and off beyond that. That threshold matches the other SDKs: Android gates oncallParticipants.size in 2..3, React Native onremoteParticipants.length < 3, Swift onparticipants.count <= 3.speakerOneToOnefloats the self-view on desktop too, where the flag otherwise defaults to off. The inset is that layout — without it the local participant, who gets no tile and no bar either, disappears from the call. An explicitfalsestill wins.participants.firstthrewStateErroron an empty list; five layouts reach that branch now and each is one tap away, so it falls through to the grid instead.StreamCallParticipantsderives its sort fromlayoutModebut recalculated only when the participants or the call changed, so picking a speaker layout swapped the preset without reordering and spotlighted whoever the previous preset had first. On a quiet call nothing arrived to correct it. Pre-existing, but a seven-entry menu makes it reachable.StreamContextMenuAnchor.defaultActionStyleis new and is the interesting bit. The design fills the selected row, which the sheet did viaStreamListTile(selected:)but the anchored menu could not —StreamContextMenuActionhas no selected state, and the radio indicator that would otherwise mark it is suppressed by a per-rowleadingicon. Nesting a secondStreamContextMenuActionThemedoes not work:.ofusesdependOnInheritedWidgetOfExactType, so it shadows rather than layers, and the selected row would drop to core's 40px/242px/20px defaults while its siblings keep the design's 32px/200px/16px. Hoisting the row style makes it re-mergeable. A test pins the metrics alongside the fill.menuDirection, notdirection, matching the device split buttons, and defaulting todownalongside them so every call control with a menu defaults the same way.speakerLeft/speakerRightbelow 768px, where a column of participants beside the speaker leaves the speaker too narrow.Testing
flutter analyzeclean forstream_video_flutterand the sample. 399 tests pass locally; the 38 golden tests need CI to generate their baselines.Four test files cover the menu, the toggle,
menuDirection, what each layout renders, the self-view rules including the mobile threshold and the desktop default, the re-sort on a layout change, and the enum's own derivations —canonical,selectable,barAlignment,isSpeakerLayoutandsorting. The two fixes above each have a test that fails without the change. The menu is asserted rather than snapshotted — the CI golden capture drops anything painted into anOverlay.Checked by eye on macOS and iOS against the Figma: seven rows on the menu, five on the sheet at phone width, per-row icons, active row filled.
🤖 Generated with Claude Code