Skip to content

feat(ui): add StreamScaffold with floating app-bar / bottom-bar support - #146

Open
xsahil03x wants to merge 59 commits into
mainfrom
feat/stream-scaffold
Open

feat(ui): add StreamScaffold with floating app-bar / bottom-bar support#146
xsahil03x wants to merge 59 commits into
mainfrom
feat/stream-scaffold

Conversation

@xsahil03x

@xsahil03x xsahil03x commented Aug 4, 2026

Copy link
Copy Markdown
Member

What

Adds StreamScaffold — a full-page scaffold that supports both regular and floating app-bar / bottom-bar layouts — and rounds out floating support across the toolbar bars:

  • StreamAppBar now propagates its resolved floating state to its slots.
  • StreamBottomAppBar gains a floating variant to match StreamAppBar.
  • Adds StreamSafeArea, a shared safe-area primitive, and uses it for the floating StreamBottomNavBar pill's inset.
  • StreamToolbarButton gains an optional type override.

How it handles insets

When a bar floats, the scaffold enlarges the body's MediaQuery.padding by the measured height of that bar (top for a floating app bar, bottom for a floating bottom bar). So:

  • Standard scrollables (ListView / GridView) and SafeArea auto-inset their content — no manual padding needed.
  • Scroll views that don't consume MediaQuery.padding (CustomScrollView, ScrollablePositionedList, SingleChildScrollView) can read MediaQuery.paddingOf(context) and fold it in explicitly.

A docked (regular) bottom bar sits below the body and owns the bottom safe-area inset; the scaffold removePaddings it from the body so scrollables don't reserve it twice.

StreamAppBar — floating-aware slots

StreamAppBar resolves its behavior (floating/regular), but previously only its own auto-implied back button knew about it. A caller-provided slot that resolves floating from context — e.g. a channel header's avatar or a custom button calling isFloatingAppBar(context) — couldn't see a behavior that was set via style passed only to the bar.

The bar now republishes its resolved behavior into an ambient StreamAppBarTheme scoped to its slot subtree — the same way it already republishes button and title styles to its slots, and the way Material's AppBar republishes IconTheme / DefaultTextStyle to its toolbar. Behaviour only; padding, colours, and spacing stay the bar's own chrome.

This fixes a live bug in the chat SDK (motivating consumer: stream-chat-flutter#2748), where a floating channel header rendered a flat avatar + ghost back button because those slots resolved behavior from the ambient theme, which the style prop never reached.

StreamBottomAppBar — floating variant

StreamBottomAppBar already had a behavior field, but only the scaffold read it (for layout) — the component itself always rendered a solid bar, so a scaffold-floated bottom app bar would sit opaque over the content behind it. It now mirrors StreamAppBar:

  • Resolves effectiveBehavior from style.behavior → ambient StreamBottomAppBarThemeStreamAppStyle.
  • When floating, drops the solid background + top border and fades into the content behind it with an upward gradient (new StreamBottomAppBarStyle.floatingBackgroundColor, defaulting to backgroundElevation0), mirroring the nav bar's fade direction.
  • Republishes its resolved behavior to its leading / title / trailing slots, symmetric with StreamAppBar.

Two notes for reviewers:

  • The bottom-app-bar slot republish is infrastructure ahead of its consumer — nothing reads StreamBottomAppBarTheme behaviour yet (StreamButton.isFloating is prop-only, so floating slot buttons are still wired explicitly, as the gallery shows). It's pinned by tests, ready for when a reader lands (a StreamButton ambient-resolution or an isFloatingBottomAppBar helper). This differs from the app-bar republish, which has a live reader today.
  • Bar-floats and body-extends-behind are set independently: a StreamBottomAppBar(style: floating) floats the bar, while StreamScaffold decides extendBody/padding from its own bottomBarBehavior / the ambient StreamBottomAppBarTheme. Set both consistently for the full effect (same shape as the app bar).

StreamSafeArea

A shared safe-area primitive (exported from core.dart) — a SafeArea variant that insets its child by max(systemInset, minimum) + margin per edge, so a pinned or floating surface keeps a controlled gap from the status bar / navigation bar / home indicator instead of sitting flush.

  • minimum floors an edge (like SafeArea.minimum); margin adds beyond the safe area.
  • maintainBottomViewPadding defaults to false, matching Flutter's SafeArea (set true to read viewPadding so a pinned surface doesn't move when the keyboard opens).
  • Static StreamSafeArea.resolveInsets returns the same insets as a value — e.g. to size a gradient painted behind the child.
  • StreamSafeArea.driven interpolates the inset toward a target (default EdgeInsets.zero) driven by any ValueListenable<double>, for a floating surface that releases its space as a panel slides in beneath it.

StreamBottomNavBar fix

The floating pill derived its bottom gap solely from SafeArea, so it sat flush on devices reporting no bottom inset (emulators / non-edge-to-edge 3-button nav). It now insets via StreamSafeArea: the sides and top floor at spacing.xl (the pill's Figma side margins), and the bottom floors at spacing.md. Flooring (rather than adding a margin) means a larger system inset — an iOS home indicator, an Android navigation bar — is used as-is so the pill sits flush above it without lifting an extra margin, while a device that reserves nothing still gets the spacing.md floor.

StreamToolbarButton — type override

StreamToolbarButton gains an optional type that overrides the shape it otherwise resolves from the enclosing bar (outline when floating, ghost when docked) — so a slot can render e.g. a solid primary action while keeping the bar-driven elevation.

Tests & gallery

  • Full inset-injection suite: mode matrix (regular/floating × app-bar/bottom), measured-height tracking, auto-inset behaviour, keyboard interaction, and "documented surprises" (e.g. a non-top-level ListView re-consuming the top inset).
  • StreamAppBar slot-behaviour tests (republish + ambient-theme precedence).
  • StreamBottomAppBar floating-visual + slot-behaviour tests.
  • StreamSafeArea test suite mirroring Flutter's own SafeArea (edge flags, minimums, nesting, keyboard / maintainBottomViewPadding, zero-area), plus resolveInsets, .driven, RTL, and debugFillProperties.
  • StreamToolbarButton type-override test.
  • Regenerated the floating nav-bar golden for the pill fix.
  • Added StreamScaffold and StreamBottomAppBar (floating knob) gallery use-cases.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a StreamScaffold gallery with playground and real-world examples, including configurable drawers, app bars, bottom navigation, gestures, and restoration.
    • Added toolbar-aware buttons and shared toolbar behavior support.
    • Added floating and regular presentation options for app bars, bottom bars, navigation bars, and media viewer chrome.
    • Added configurable floating navigation elevation and styling.
  • Updates

    • Renamed the app-wide surface style API to StreamSurfaceStyle.
    • Improved safe-area inset handling and floating layout behavior.
    • Increased default floating avatar elevation and expanded theme configuration.

@xsahil03x
xsahil03x requested a review from a team as a code owner August 4, 2026 09:52
@xsahil03x xsahil03x changed the title feat(scaffold): add StreamScaffold with floating app-bar / bottom-bar support feat(ui): add StreamScaffold with floating app-bar / bottom-bar support Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR standardizes toolbar behavior on StreamSurfaceStyle, adds toolbar scope and button APIs, updates floating chrome and scaffold inset handling, expands drawer configuration, adds media viewer and color-scheme behavior, and updates gallery, tests, changelog, and platform integration.

Changes

Toolbar contracts and styles

Layer / File(s) Summary
Shared toolbar contracts and theme styles
packages/stream_core_flutter/lib/src/theme/..., packages/stream_core_flutter/lib/src/components/toolbar/stream_toolbar_*.dart, packages/stream_core_flutter/lib/core.dart
Renames StreamAppStyle to StreamSurfaceStyle, removes component-specific behavior enums, adds StreamToolbarScope and StreamToolbarButton, and extends floating style properties.
Toolbar rendering and floating chrome
packages/stream_core_flutter/lib/src/components/toolbar/*
Updates app bars, bottom app bars, and bottom navigation bars to resolve shared styles and render floating chrome with gradients, elevation, measured dimensions, and safe-area spacing.

Scaffold, media, and validation

Layer / File(s) Summary
Scaffold drawers and MediaQuery inset layout
packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart, packages/stream_core_flutter/lib/src/components/media_viewer/stream_media_viewer.dart
Adds drawer forwarding and restoration, replaces scaffold-specific insets with MediaQuery.padding, and applies media insets only to docked chrome.
Validation and design-system gallery
packages/stream_core_flutter/test/..., apps/design_system_gallery/lib/components/..., apps/design_system_gallery/lib/app/gallery_app.directories.g.dart
Adds scaffold, toolbar, media viewer, golden, and scope tests. Adds scaffold examples and updates gallery toolbar and media viewer controls.

Platform integration

Layer / File(s) Summary
Generated platform build integration
apps/design_system_gallery/macos/Runner.xcodeproj/*, packages/stream_thumbnail/example/linux/flutter/*
Links the generated Flutter Swift package for macOS and adds generated Linux plugin registration and CMake files.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: renefloor

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the primary change: adding StreamScaffold with floating app-bar and bottom-bar support.
Description check ✅ Passed The description explains the feature, inset behavior, implementation details, testing, and gallery updates, but omits the template metadata and screenshot table.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/stream-scaffold

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart (1)

274-281: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Give the body tight constraints so a shrink-wrapping body still fills the scaffold.

_BodyBoxConstraints sets only maxWidth and maxHeight, so the body slot receives loose constraints. A body that shrink-wraps (for example a Column with mainAxisSize.min, or a bare Text) then sizes to its intrinsic extent instead of the scaffold area, and is positioned at Offset.zero with that smaller size. The regular (non-floating) path does not have this behaviour, because Scaffold gives its body tight constraints. The existing tests all use expanding bodies (SizedBox.expand, ListView), so the difference is not covered.

Set the minimum extents as well to match Scaffold body semantics.

🐛 Proposed fix: tight body constraints
     layoutChild(
       _Slot.body,
       _BodyBoxConstraints(
+        minWidth: size.width,
         maxWidth: size.width,
+        minHeight: size.height,
         maxHeight: size.height,
         bottomHeight: bottomHeight,
       ),
     );

Also widen the forwarded constructor parameters:

 class _BodyBoxConstraints extends BoxConstraints {
   const _BodyBoxConstraints({
+    super.minWidth,
     super.maxWidth,
+    super.minHeight,
     super.maxHeight,
     required this.bottomHeight,
   }) : assert(bottomHeight >= 0, 'bottomHeight must be non-negative');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart`
around lines 274 - 281, Update the floating scaffold body layout around
_BodyBoxConstraints to provide tight constraints by forwarding minimum width and
height equal to the available size, alongside the existing maximum extents.
Widen the relevant _BodyBoxConstraints constructor parameters as needed, while
preserving bottomHeight handling and the existing layoutChild flow.
🧹 Nitpick comments (6)
packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart (5)

12-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding an Alchemist golden test for StreamScaffold.

The repository guideline for packages/stream_core_flutter/test/components/**/*.dart asks for golden tests with Alchemist, stored in goldens/ci/ and goldens/macos/, and tagged golden. This file covers layout and inset behaviour only. A golden for the floating app-bar plus floating bottom-bar combination would lock in the visual result of the new layout path.

As per coding guidelines: "Flutter components should have golden tests using Alchemist, with goldens stored in the component's goldens/ci/ and goldens/macos/ directories; tag golden tests with golden."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart`
around lines 12 - 49, Add an Alchemist golden test to the StreamScaffold tests
covering the floating app-bar and floating bottom-bar combination, using the
repository’s established golden-test conventions. Store generated goldens under
the component’s goldens/ci and goldens/macos directories and tag the test with
golden; preserve the existing inset and layout tests.

Source: Coding guidelines


573-575: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the exact bar position instead of an upper bound.

lessThanOrEqualTo passes for any position at or above the keyboard top, including a bar collapsed to the top of the screen. When resizeToAvoidBottomInset is true, the Scaffold body ends exactly at the keyboard top, so the bar bottom is surfaceHeight - keyboard. Use the same moreOrLessEquals style already used in the resizeToAvoidBottomInset: false case. The same applies to the regular-bottom assertion at Line 630.

♻️ Proposed assertion
-      expect(barBottom, lessThanOrEqualTo(surfaceHeight - keyboard + 0.5));
+      expect(barBottom, moreOrLessEquals(surfaceHeight - keyboard, epsilon: 0.5));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart`
around lines 573 - 575, Update the keyboard-visible bar assertion in the
resizeToAvoidBottomInset: true test to require barBottom moreOrLessEquals
surfaceHeight - keyboard, matching the exact-position style used by the false
case. Apply the same exact moreOrLessEquals assertion to the regular-bottom
assertion near the later test assertion, replacing the permissive
lessThanOrEqualTo checks while preserving the existing tolerance.

246-247: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tighten this assertion and cover the bottom edge.

lessThan(_kBarHeight) passes for any value from 0 to 55. With a regular app bar the Scaffold consumes the system top inset, so padding.top is 0. The test also configures a regular bottom of 64 with devicePadding.bottom of 34 but asserts nothing about padding.bottom, so this matrix cell does not verify the docked-bottom half of its own name.

♻️ Proposed assertions
-      // A regular app bar consumes the system top; nothing enlarges it.
-      expect(captured.padding!.top, lessThan(_kBarHeight));
+      // A regular app bar consumes the system top; nothing enlarges it, and the
+      // docked bottom owns the home-indicator inset.
+      expect(captured.padding!.top, 0);
+      expect(captured.padding!.bottom, 0);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart`
around lines 246 - 247, In the regular app bar test case, replace the broad
padding.top less-than assertion with an exact zero assertion, then add an
assertion that captured.padding!.bottom equals the expected bottom inset
behavior for a regular 64-height bottom with devicePadding.bottom of 34. Keep
the existing scaffold configuration and verify both top and bottom edges in this
matrix cell.

481-484: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the dependency on an external consumer in this comment.

The comment explains the scenario by referring to "the channel list's bottomPadding > 0 ? … : null". That code is not in stream_core_flutter, so a reader of this package cannot resolve the reference, and the comment goes stale if the consumer changes. Describe the pattern itself: a nested scrollable whose padding is conditional on a non-zero bottom inset re-consumes the injected top inset when the bottom inset is zero.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart`
around lines 481 - 484, Update the mixed-mode comment to remove the reference to
the external channel list and its implementation. Describe the behavior
generically: when a nested scrollable conditionally applies padding only for a
non-zero bottom inset, a zero bottom inset causes it to re-consume the injected
top inset, producing a gap after the header.

94-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extend the forwarding test to the newly added Scaffold properties.

This test asserts only drawer and endDrawer. The PR forwards nine more values: onDrawerChanged, onEndDrawerChanged, drawerScrimColor, drawerEdgeDragWidth, drawerEnableOpenDragGesture, endDrawerEnableOpenDragGesture, drawerDragStartBehavior, drawerBarrierDismissible, and restorationId. A missing or mis-wired forward in StreamScaffold.build would not fail any test. Assert the full set in one pass over the resolved Scaffold.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart`
around lines 94 - 111, Extend the test named “forwards drawer/endDrawer to the
underlying Scaffold” to configure and assert all newly forwarded Scaffold
properties: onDrawerChanged, onEndDrawerChanged, drawerScrimColor,
drawerEdgeDragWidth, drawerEnableOpenDragGesture,
endDrawerEnableOpenDragGesture, drawerDragStartBehavior,
drawerBarrierDismissible, and restorationId. Resolve the underlying Scaffold
once and verify each property matches the corresponding StreamScaffold input
alongside drawer and endDrawer.
apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart (1)

86-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a standard Widgetbook use-case name.

Drawers does not follow the gallery use-case naming convention. It also sorts before Playground in the generated directory. Rename this contextual example to Real-world Example, then regenerate the directory.

  • apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart#L86-L90: Rename the Drawers annotation value to Real-world Example.
  • apps/design_system_gallery/lib/app/gallery_app.directories.g.dart#L1144-L1149: Regenerate this file with dart run build_runner build --delete-conflicting-outputs; do not edit it manually.

As per coding guidelines, component use cases should use Playground, Type/Size Variants, or Real-world Example.

Proposed source change
-  name: 'Drawers',
+  name: 'Real-world Example',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart`
around lines 86 - 90, Rename the Widgetbook use-case name in
stream_scaffold.dart from Drawers to Real-world Example. Then regenerate
apps/design_system_gallery/lib/app/gallery_app.directories.g.dart using dart run
build_runner build --delete-conflicting-outputs; do not edit the generated file
manually.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart`:
- Around line 270-272: Update the documentation comment for StreamBottomNavBar
so its measured height is described as becoming the body’s bottom inset only
when floating is true; clarify that regular docked bars remain below the body,
matching the StreamScaffold contract.

In
`@packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart`:
- Around line 450-452: Update the comment above the first-item gap assertion in
the scaffold test to state that the inner ListView injects a top padding/inset
of 56 pixels, matching _kBarHeight, rather than describing the gap as bar height
plus system-top.

---

Outside diff comments:
In
`@packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart`:
- Around line 274-281: Update the floating scaffold body layout around
_BodyBoxConstraints to provide tight constraints by forwarding minimum width and
height equal to the available size, alongside the existing maximum extents.
Widen the relevant _BodyBoxConstraints constructor parameters as needed, while
preserving bottomHeight handling and the existing layoutChild flow.

---

Nitpick comments:
In `@apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart`:
- Around line 86-90: Rename the Widgetbook use-case name in stream_scaffold.dart
from Drawers to Real-world Example. Then regenerate
apps/design_system_gallery/lib/app/gallery_app.directories.g.dart using dart run
build_runner build --delete-conflicting-outputs; do not edit the generated file
manually.

In
`@packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart`:
- Around line 12-49: Add an Alchemist golden test to the StreamScaffold tests
covering the floating app-bar and floating bottom-bar combination, using the
repository’s established golden-test conventions. Store generated goldens under
the component’s goldens/ci and goldens/macos directories and tag the test with
golden; preserve the existing inset and layout tests.
- Around line 573-575: Update the keyboard-visible bar assertion in the
resizeToAvoidBottomInset: true test to require barBottom moreOrLessEquals
surfaceHeight - keyboard, matching the exact-position style used by the false
case. Apply the same exact moreOrLessEquals assertion to the regular-bottom
assertion near the later test assertion, replacing the permissive
lessThanOrEqualTo checks while preserving the existing tolerance.
- Around line 246-247: In the regular app bar test case, replace the broad
padding.top less-than assertion with an exact zero assertion, then add an
assertion that captured.padding!.bottom equals the expected bottom inset
behavior for a regular 64-height bottom with devicePadding.bottom of 34. Keep
the existing scaffold configuration and verify both top and bottom edges in this
matrix cell.
- Around line 481-484: Update the mixed-mode comment to remove the reference to
the external channel list and its implementation. Describe the behavior
generically: when a nested scrollable conditionally applies padding only for a
non-zero bottom inset, a zero bottom inset causes it to re-consume the injected
top inset, producing a gap after the header.
- Around line 94-111: Extend the test named “forwards drawer/endDrawer to the
underlying Scaffold” to configure and assert all newly forwarded Scaffold
properties: onDrawerChanged, onEndDrawerChanged, drawerScrimColor,
drawerEdgeDragWidth, drawerEnableOpenDragGesture,
endDrawerEnableOpenDragGesture, drawerDragStartBehavior,
drawerBarrierDismissible, and restorationId. Resolve the underlying Scaffold
once and verify each property matches the corresponding StreamScaffold input
alongside drawer and endDrawer.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0efbffda-12f9-4913-b395-4d2523b69aaf

📥 Commits

Reviewing files that changed from the base of the PR and between 6dd2942 and ab3feea.

⛔ Files ignored due to path filters (1)
  • packages/stream_core_flutter/test/components/toolbar/goldens/ci/stream_bottom_nav_bar_floating.png is excluded by !**/*.png
📒 Files selected for processing (6)
  • apps/design_system_gallery/lib/app/gallery_app.directories.g.dart
  • apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart
  • packages/stream_core_flutter/CHANGELOG.md
  • packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_nav_bar.dart
  • packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart

Comment thread apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart Outdated
Comment thread packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart Outdated
@xsahil03x
xsahil03x force-pushed the feat/stream-scaffold branch from ab3feea to 57d2fab Compare August 4, 2026 10:07
@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.49%. Comparing base (d79db82) to head (c4308b3).

Files with missing lines Patch % Lines
...b/src/components/toolbar/stream_toolbar_scope.dart 83.33% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #146      +/-   ##
==========================================
+ Coverage   58.17%   59.49%   +1.32%     
==========================================
  Files         187      190       +3     
  Lines        7615     7725     +110     
==========================================
+ Hits         4430     4596     +166     
+ Misses       3185     3129      -56     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@xsahil03x
xsahil03x force-pushed the feat/stream-scaffold branch from 57d2fab to df0d10c Compare August 4, 2026 10:24
… support

Composes appBar/body/bottom slots and, when a bar floats, enlarges the body's MediaQuery.padding by the bar's height so standard scrollables and SafeArea auto-inset. Floors the floating StreamBottomNavBar pill margin so it never sits flush when the device reports no bottom inset. Adds a gallery use-case and an inset-injection test suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xsahil03x
xsahil03x force-pushed the feat/stream-scaffold branch from df0d10c to 229720f Compare August 4, 2026 10:35
),
),
if (hasBottom) LayoutId(id: _Slot.bottom, child: bottom!),
LayoutId(id: _Slot.bottom, child: bottom!),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bottom can be null here right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be but we have an assertion which throws before we reach this point.

  }) : assert(!floating || bottom != null, 'A floating body requires a bottom widget.');

…tton

Merge StreamAppBarBehavior and StreamBottomAppBarBehavior into a single
StreamToolbarBehavior { regular, floating } (breaking, unreleased). Each bar
publishes its resolved behaviour through a StreamToolbarScope so slot widgets
match the bar, and StreamToolbarButton adapts its outline/ghost look to that
scope.

Align the floating-fade solidFraction with the painted box height in all three
bars. Give StreamBottomNavBar a public kStreamBottomNavBarHeight (72), a single
SafeArea for its pill insets, and a themeable floatingElevation. Standardise
floating Material elevations on StreamElevation.level3 (avatar + nav bar).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
apps/design_system_gallery/lib/components/media_viewer/stream_media_viewer.dart (1)

44-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a dropdown knob for the behavior enum.

The knob selects between StreamToolbarBehavior.floating and StreamToolbarBehavior.regular. Use context.knobs.object.dropdown with the enum values so the control matches the modelled type and the gallery conventions.

♻️ Proposed refactor
-  final floating = context.knobs.boolean(
-    label: 'Floating chrome',
-    initialValue: true,
+  final behavior = context.knobs.object.dropdown(
+    label: 'Chrome behavior',
+    options: StreamToolbarBehavior.values,
+    initialOption: StreamToolbarBehavior.floating,
     description:
         'Floating chrome overlays full-bleed media with a gradient fade; '
         'regular chrome insets the media between opaque bars.',
   );

Then pass behavior directly to both styles instead of floating ? .floating : .regular.

As per path instructions: "Use `context.knobs.object.dropdown` for enums".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/design_system_gallery/lib/components/media_viewer/stream_media_viewer.dart`
around lines 44 - 50, Replace the boolean floating knob with a
context.knobs.object.dropdown control using both StreamToolbarBehavior.floating
and StreamToolbarBehavior.regular as enum options. Store the selected behavior
and pass it directly to both relevant styles, removing the floating conditional
conversion.

Source: Path instructions

packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_nav_bar.dart (1)

107-118: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚖️ Poor tradeoff

Address the remaining StreamBottomNavBarStyle.behavior usages.

packages/stream_core_flutter/CHANGELOG.md documents the public migration. The only remaining direct uses live in generated Widgetbook factory code; regenerate the Widgetbook directory after the public StreamBottomNavBarProps/DefaultStreamBottomNavBar API is migrated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_nav_bar.dart`
around lines 107 - 118, Regenerate the Widgetbook directory after migrating the
public StreamBottomNavBarProps and DefaultStreamBottomNavBar APIs, removing all
remaining direct StreamBottomNavBarStyle.behavior usages from generated
Widgetbook factory code. Ensure the regenerated factories use the migrated API
while preserving existing toolbar behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart`:
- Around line 86-90: Update the Widgetbook use-case annotation for
StreamScaffold to rename the use case from “Drawers” to the required “Real-world
Example” name, leaving the type and path unchanged.

In
`@apps/design_system_gallery/lib/components/toolbar/stream_bottom_app_bar.dart`:
- Line 106: Update the floating toolbar margin in the preview content to use the
Figma-defined context.streamSpacing token instead of the hardcoded horizontal
value 32, preserving the existing symmetric horizontal margin behavior.

In
`@packages/stream_core_flutter/lib/src/components/media_viewer/stream_media_viewer.dart`:
- Around line 149-163: Update DefaultStreamMediaViewer’s headerFloating and
footerFloating resolution to use the same merged bar styles as
DefaultStreamAppBar and DefaultStreamBottomAppBar, merging each ambient bar
theme style with the corresponding local props.style before reading
behavior.isFloating. Preserve the StreamAppStyle fallback only when the merged
bar style has no pinned behavior, and keep the existing inset calculations
unchanged.

In
`@packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart`:
- Around line 195-199: Update the behavior resolution in the scaffold’s app-bar
and bottom-bar flow to account for per-instance bar styles, or explicitly
document that `StreamAppBarStyle`/bottom-bar instance styles with a pinned
`behavior` require matching `appBarBehavior`/`bottomBarBehavior` values on the
scaffold. Ensure the behavior used for `extendBodyBehindAppBar` and published
insets matches the behavior rendered by each bar.

---

Nitpick comments:
In
`@apps/design_system_gallery/lib/components/media_viewer/stream_media_viewer.dart`:
- Around line 44-50: Replace the boolean floating knob with a
context.knobs.object.dropdown control using both StreamToolbarBehavior.floating
and StreamToolbarBehavior.regular as enum options. Store the selected behavior
and pass it directly to both relevant styles, removing the floating conditional
conversion.

In
`@packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_nav_bar.dart`:
- Around line 107-118: Regenerate the Widgetbook directory after migrating the
public StreamBottomNavBarProps and DefaultStreamBottomNavBar APIs, removing all
remaining direct StreamBottomNavBarStyle.behavior usages from generated
Widgetbook factory code. Ensure the regenerated factories use the migrated API
while preserving existing toolbar behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 10523aa0-44e2-4d5f-8ffb-550101886e96

📥 Commits

Reviewing files that changed from the base of the PR and between 6dd2942 and 5f94eb1.

⛔ Files ignored due to path filters (7)
  • packages/stream_core_flutter/test/components/avatar/goldens/ci/stream_avatar_group_shadow_dark.png is excluded by !**/*.png
  • packages/stream_core_flutter/test/components/avatar/goldens/ci/stream_avatar_group_shadow_light.png is excluded by !**/*.png
  • packages/stream_core_flutter/test/components/avatar/goldens/ci/stream_avatar_shadow_light.png is excluded by !**/*.png
  • packages/stream_core_flutter/test/components/avatar/goldens/ci/stream_avatar_stack_shadow_dark.png is excluded by !**/*.png
  • packages/stream_core_flutter/test/components/avatar/goldens/ci/stream_avatar_stack_shadow_light.png is excluded by !**/*.png
  • packages/stream_core_flutter/test/components/toolbar/goldens/ci/stream_bottom_nav_bar_floating.png is excluded by !**/*.png
  • packages/stream_core_flutter/test/components/toolbar/goldens/ci/stream_bottom_nav_bar_regular.png is excluded by !**/*.png
📒 Files selected for processing (32)
  • apps/design_system_gallery/lib/app/gallery_app.directories.g.dart
  • apps/design_system_gallery/lib/components/media_viewer/stream_media_viewer.dart
  • apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart
  • apps/design_system_gallery/lib/components/toolbar/stream_app_bar.dart
  • apps/design_system_gallery/lib/components/toolbar/stream_bottom_app_bar.dart
  • apps/design_system_gallery/lib/components/toolbar/stream_bottom_nav_bar.dart
  • packages/stream_core_flutter/CHANGELOG.md
  • packages/stream_core_flutter/lib/core.dart
  • packages/stream_core_flutter/lib/src/components/avatar/stream_avatar.dart
  • packages/stream_core_flutter/lib/src/components/media_viewer/stream_media_viewer.dart
  • packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_app_bar.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_app_bar.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_nav_bar.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_toolbar_button.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_toolbar_scope.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_app_bar_theme.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_app_bar_theme.g.theme.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_avatar_theme.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_bottom_app_bar_theme.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_bottom_app_bar_theme.g.theme.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_bottom_nav_bar_theme.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_bottom_nav_bar_theme.g.theme.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_toolbar_behavior.dart
  • packages/stream_core_flutter/lib/src/theme/stream_app_style.dart
  • packages/stream_core_flutter/test/components/media_viewer/stream_media_viewer_test.dart
  • packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_app_bar_golden_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_app_bar_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_bottom_app_bar_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_bottom_nav_bar_golden_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_bottom_nav_bar_test.dart
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/design_system_gallery/lib/app/gallery_app.directories.g.dart
  • packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart

Comment thread apps/design_system_gallery/lib/components/toolbar/stream_bottom_app_bar.dart Outdated
Comment thread packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart Outdated
…rough generated code

StreamColorScheme.brightness (added in #131) was never regenerated into the theme
extension, so the generated copyWith, lerp, merge, ==, and hashCode silently dropped
it — copyWith(brightness:) was impossible, a light/dark lerp resolved brightness to
the default, merge lost it, and two schemes differing only in brightness compared
equal. Regenerate to sync.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@renefloor renefloor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed with a fan-out of specialized checks (general correctness, silent-failure/error-handling, test coverage, type design, comment accuracy). Summary below; inline comments point at the specific lines.

Critical: StreamScaffold resolves floating/regular behaviour for a slot from a different source than the bar/nav-bar widget itself resolves from, so the two can silently disagree — see the inline comment on stream_scaffold.dart. This affects both a StreamAppBar/StreamBottomAppBar with a per-instance style and, more concretely, StreamBottomNavBar (which is resolved from an entirely different theme/enum that the scaffold never reads at all).

Other notable gaps not tied to one line:

  • StreamToolbarButton and StreamToolbarScope (new public API) have no dedicated test file — only indirect coverage via the app bar's auto-implied back button.
  • StreamBottomAppBar's new floating visual mode has no golden test, and the bottom-nav-bar pill-margin fix (the headline bug fix in this PR) has no unit assertion on the margin math, only golden images at zero device inset.
  • Nice test coverage overall on the scaffold's inset-injection matrix and the "documented surprises" regression group — that pattern is worth extending to pin the behaviour-mismatch issue above.

Nothing here should block merge outright if the team's comfortable following up, but I'd suggest at least fixing the critical resolution mismatch or adding a debug-mode assertion that surfaces it loudly, since StreamScaffold + StreamBottomNavBar is the documented, expected combination.

Comment on lines +191 to +204
final appStyle = context.streamTheme.appStyle;
final effectiveStreamAppBarBehavior =
appBarBehavior ??
context.streamAppBarTheme.style?.behavior ??
(appStyle.isFloating ? StreamAppBarBehavior.floating : StreamAppBarBehavior.regular);
final effectiveStreamBottomAppBarBehavior =
bottomBarBehavior ??
context.streamBottomAppBarTheme.style?.behavior ??
(appStyle.isFloating ? StreamBottomAppBarBehavior.floating : StreamBottomAppBarBehavior.regular);
final effectiveBackgroundColor = backgroundColor ?? context.streamColorScheme.backgroundApp;

final appBarFloating = effectiveStreamAppBarBehavior == StreamAppBarBehavior.floating;
final bottomFloating = effectiveStreamBottomAppBarBehavior == StreamBottomAppBarBehavior.floating && bottom != null;

final topInset = appBarFloating ? (appBar?.preferredSize.height ?? 0) + MediaQuery.paddingOf(context).top : 0.0;

// When neither slot is floating, use a plain Scaffold for maximum
// compatibility (e.g. keyboard avoidance, Scaffold.of, etc.).
// The bottom widget lives inside the body Column (not bottomNavigationBar)
// because bottomNavigationBar is not repositioned above the keyboard on
// Android, which causes text-input composers to be hidden behind the IME.
if (!appBarFloating && !bottomFloating) {
return Scaffold(
backgroundColor: effectiveBackgroundColor,
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
appBar: appBar,
drawer: drawer,
endDrawer: endDrawer,
body: Column(
children: [
Expanded(
child: StreamScaffoldInsets(
topPadding: 0,
bottomPadding: 0,
child: body,
),
),
?bottom,
],
),
);
}
final appBarStyle = context.streamAppBarTheme.style;
final bottomAppBarStyle = context.streamBottomAppBarTheme.style;

var effectiveAppBarBehavior = appBarBehavior ?? appBarStyle?.behavior;
effectiveAppBarBehavior ??= appStyle.isFloating ? .floating : .regular;

var effectiveBottomBarBehavior = bottomBarBehavior ?? bottomAppBarStyle?.behavior;
effectiveBottomBarBehavior ??= appStyle.isFloating ? .floating : .regular;

final effectiveBackgroundColor = backgroundColor ?? colorScheme.backgroundApp;

final appBarFloating = effectiveAppBarBehavior == .floating;
final bottomFloating = effectiveBottomBarBehavior == .floating && bottom != null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical — resolved behaviour can diverge from what the bar/nav-bar actually renders.

This only consults the ambient StreamAppBarTheme/StreamBottomAppBarTheme (appBarStyle/bottomAppBarStyle above) and the global StreamAppStyle. It never looks at a style passed directly to the appBar:/bottom: widget instance — but DefaultStreamAppBar and StreamBottomAppBar both let their own props.style win over the ambient theme (style.merge(...) pattern). So e.g.

StreamScaffold(
  appBar: StreamAppBar(style: StreamAppBarStyle(behavior: .floating)),
  body: ListView(...),
)

with no ambient override renders a floating (translucent/gradient) bar while the scaffold computes .regular here — extendBodyBehindAppBar: false and no top inset injected. The bar ends up in a non-extended slot with floating-style chrome fading into nothing.

More concretely: the documented, gallery-recommended bottom widget is StreamBottomNavBar, but it resolves its own floating look from context.streamBottomNavBarTheme / StreamBottomNavBarBehavior — a completely different theme and enum than bottomAppBarStyle here. Wrapping a StreamBottomNavBarTheme(style: behavior: .floating) around this scaffold makes the pill render floating while this code still computes .regular (no ambient StreamBottomAppBarTheme, non-floating StreamAppStyle) — wrong insets, no overlap, and the pill's own margin fix loses its paired layout. This combination isn't tested anywhere (no test builds a StreamBottomNavBar inside StreamScaffold).

Suggest making propagation one-directional (scaffold resolves once and pushes the result down via the ambient themes) rather than each side resolving independently from divergent sources, and unifying StreamBottomNavBarBehavior into the shared StreamToolbarBehavior so there's one source of truth for the bottom slot.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xsahil03x should we maybe unify the themes for the behavior to make this scaffold work?

Comment on lines +35 to +39
static StreamToolbarBehavior of(BuildContext context) {
final scope = context.dependOnInheritedWidgetOfExactType<StreamToolbarScope>();
assert(scope != null, 'StreamToolbarScope.of() called outside a Stream toolbar.');
return scope!.behavior;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a release build (asserts stripped), calling of() outside a toolbar throws a bare null-check TypeError instead of the descriptive message here. Since StreamToolbarButton (public API, exported from core.dart) calls this asserting variant, dropping it into a slot that doesn't publish a scope — e.g. StreamSheetHeader, which is another toolbar-height widget in this same directory but doesn't wrap its slots in StreamToolbarScope — is an easy crash for downstream SDKs with a confusing error.

Consider having StreamToolbarButton use maybeOf(context) ?? StreamToolbarBehavior.regular instead, degrading to the docked look rather than crashing.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2beebd3. StreamToolbarScope.of() now throws a descriptive FlutterError (same shape as StreamChat.of / Scaffold.of) instead of a bare null-check. And StreamToolbarButton now reads via maybeOf(context) ?? .regular, so it degrades to the docked style outside a toolbar rather than crashing — with a debug assert to flag the likely misuse (e.g. dropping it into StreamSheetHeader). Added stream_toolbar_button_test.dart covering the scope contract and the button styling in both modes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you revert that again later? 65b30b1

/// carry this value per component.
/// * [StreamToolbarScope], which publishes the resolved value to a bar's slots.
/// * [StreamAppStyle], the global app-wide style that acts as fallback.
enum StreamToolbarBehavior {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is structurally identical to the pre-existing StreamBottomNavBarBehavior (stream_bottom_nav_bar_theme.dart:18-24) — same two-value "docked vs floating" concept, just scoped to a different component. Every call site converts between the two via the same appStyle.isFloating ? .floating : .regular idiom.

This PR was a natural point to unify them (it already touches StreamBottomNavBarTheme to add floatingElevation), and not doing so is part of why the nav-bar case in stream_scaffold.dart falls through the cracks — StreamScaffold has no single enum/theme it can check for "is the bottom slot floating" across both bar types. Worth a follow-up to rename StreamBottomNavBarBehavior → this enum and drop the duplicate before a third toolbar-shaped component copies the pattern again.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in c49bf29. Since StreamBottomNavBarBehavior is unreleased (not in v0.4.1), replaced it outright with StreamToolbarBehavior — no deprecation needed. StreamBottomNavBarStyle.behavior now uses the shared enum, so a toolbar-shaped component's floating state has one source of truth. Regenerated the theme; gallery and CHANGELOG updated.

Comment on lines +149 to +155
// Resolve the chrome's floating state the same way the bars (and
// StreamScaffold) do, so the media extends full-bleed behind floating
// chrome and is inset under docked (regular) chrome. Falls back to the
// ambient StreamAppStyle when the chrome style doesn't pin a behavior.
final fallbackFloating = context.streamTheme.appStyle.isFloating;
final headerFloating = effectiveAppBarStyle?.behavior?.isFloating ?? fallbackFloating;
final footerFloating = effectiveBottomAppBarStyle?.behavior?.isFloating ?? fallbackFloating;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"the same way the bars ... do" isn't quite accurate: effectiveAppBarStyle/effectiveBottomAppBarStyle here only come from the media-viewer's theme (theme.appBarStyle) and the ambient StreamAppStyle fallback — never from props.header's/props.footer's own per-instance style. But DefaultStreamAppBar itself does let a per-instance style.behavior override win over its ambient theme.

Concretely: header: StreamAppBar(style: StreamAppBarStyle(behavior: .regular)) while the media-viewer theme/ambient style says floating — the bar correctly renders opaque/regular, but headerInset still computes 0.0 (floating branch), so the media renders full-bleed underneath an opaque header. Worth narrowing the comment to say it mirrors only the theme/app-style fallback levels, not any per-instance override on the header/footer widget itself.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2beebd3 — narrowed the comment to say it mirrors only the theme/app-style fallback levels, not a per-instance style on the header/footer widget itself.

/// [appBar] (e.g. `StreamChannelListHeader`) can find the drawer via
/// `Scaffold.maybeOf(context)?.openDrawer()`.
/// Provide a [drawer] and/or [endDrawer] to add slide-in side panels; a widget
/// in [appBar] can open them (as `StreamChannelListHeader` does for its menu).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StreamChannelListHeader doesn't exist anywhere in this repo (it's written in plain backticks rather than a dartdoc [...] link, which suggests it wasn't expected to resolve). Either point at a component that actually exists here, or make clear this is an example from an external SDK (e.g. stream_chat_flutter).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2beebd3 — dropped the non-existent StreamChannelListHeader reference. The doc now points at Scaffold.of(context).openDrawer() and frames it as an example from an external chat SDK.

drawerDragStartBehavior: drawerDragStartBehavior,
drawerBarrierDismissible: drawerBarrierDismissible,
extendBodyBehindAppBar: appBarFloating,
extendBody: bottomFloating,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extendBody: bottomFloating looks like a no-op: Scaffold.bottomNavigationBar/persistentFooterButtons are never set on this Scaffold, so Flutter's own extendBody handling has nothing to extend around — the actual bottom-slot overlap is done entirely by _StreamScaffoldBody below. Worth double-checking whether this line does anything, since a test currently asserts on it (stream_scaffold_test.dart), which cements a possibly-misleading signal if it's dead.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — confirmed it was dead. extendBody only affects a Scaffold that sets bottomNavigationBar / persistentFooterButtons, and this one sets neither (the bottom bar lives in the body), so the flag did nothing; the overlap is all _StreamScaffoldBody. Removed it in 69feed0, and re-pointed the two tests that asserted on it at observable outcomes instead (the bottomNavigationBar slot stays empty; no bottom inset is injected when there's no bottom widget). The full inset/layout matrix still passes unchanged, which confirms it was inert.

Comment on lines +480 to +483
// The gradient spans the whole chrome — top margin, pill, and bottom inset.
// Keep it solid across the bottom inset and fade up through the pill into
// the content behind the bar.
final totalHeight = topInset + kStreamBottomNavBarHeight + bottomInset;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads as an exact geometric description, but kStreamBottomNavBarHeight is only a minHeight on the tiles' ConstrainedBox, not the guaranteed rendered height. A pre-diff version of this code had an explicit "approximate" caveat that seems to have been dropped in the rewrite. A long/wrapped localized label pushing a tile taller than the constant would make solidFraction under-represent the real bottom-inset proportion, drifting the gradient's fade boundary off the pill edge — probably fine in practice, but worth restoring the caveat so a future reader doesn't treat this as exact.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2beebd3 — restored the "approximate" caveat noting kStreamBottomNavBarHeight is the tiles' ConstrainedBox minHeight, not the guaranteed rendered height, so a taller wrapped label drifts the fade boundary slightly.

xsahil03x and others added 3 commits August 6, 2026 18:46
- StreamToolbarButton: read the scope via maybeOf and degrade to the docked
  style outside a toolbar (assert flags the misuse in debug) instead of
  crashing on a hard StreamToolbarScope.of().
- StreamToolbarScope.of(): throw a descriptive FlutterError.fromParts when
  no scope is in context, matching Flutter's *.of() convention.
- StreamScaffold: give the body tight min/max constraints so a shrink-
  wrapping child fills the slot; clarify the behavior-resolution and drawer
  docs.
- StreamBottomNavBar / media viewer: doc-only clarifications.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cessor

- kStreamBottomNavBarHeight 72 -> 64 per the design system.
- Regenerate the CI bottom-nav-bar goldens for the new height.
- Fix StreamToolbarButton test to read type/isFloating via .props (the
  committed accessor did not compile).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/stream_core_flutter/test/components/toolbar/stream_toolbar_button_test.dart (2)

34-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test the complete no-scope toolbar contract.

Lines 34-47 only verify the FlutterError type. Verify that the error identifies StreamToolbarScope.
Lines 72-102 do not verify the StreamToolbarButton fallback outside a scope. Add a test that expects the regular, ghost, non-floating button in that case.

Also applies to: 72-102

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_core_flutter/test/components/toolbar/stream_toolbar_button_test.dart`
around lines 34 - 47, The no-scope toolbar tests do not verify the full error
and fallback behavior. Update the test for StreamToolbarScope.of to assert that
the FlutterError identifies StreamToolbarScope, and add coverage for
StreamToolbarButton outside a scope confirming it renders the regular, ghost,
non-floating button variant.

72-102: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add golden coverage for regular and floating toolbar buttons.

These tests inspect StreamButton.props. They cannot detect rendering regressions in the outlined floating and ghost docked variants. Add tagged Alchemist golden tests and regenerate their CI and macOS goldens.

As per coding guidelines, Flutter components must have Alchemist golden tests tagged golden, and visual changes require golden regeneration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_core_flutter/test/components/toolbar/stream_toolbar_button_test.dart`
around lines 72 - 102, Add Alchemist golden widget tests for both
StreamToolbarButton variants in the StreamToolbarButton test group, tagging each
test with golden and capturing the outlined floating and ghost docked renderings
rather than only inspecting StreamButton.props. Regenerate and commit the
corresponding CI and macOS golden artifacts.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@packages/stream_core_flutter/test/components/toolbar/stream_toolbar_button_test.dart`:
- Around line 34-47: The no-scope toolbar tests do not verify the full error and
fallback behavior. Update the test for StreamToolbarScope.of to assert that the
FlutterError identifies StreamToolbarScope, and add coverage for
StreamToolbarButton outside a scope confirming it renders the regular, ghost,
non-floating button variant.
- Around line 72-102: Add Alchemist golden widget tests for both
StreamToolbarButton variants in the StreamToolbarButton test group, tagging each
test with golden and capturing the outlined floating and ghost docked renderings
rather than only inspecting StreamButton.props. Regenerate and commit the
corresponding CI and macOS golden artifacts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 048df629-38e2-4d27-af0c-c35d28b16e84

📥 Commits

Reviewing files that changed from the base of the PR and between 5f94eb1 and 805804d.

⛔ Files ignored due to path filters (2)
  • packages/stream_core_flutter/test/components/toolbar/goldens/ci/stream_bottom_nav_bar_floating.png is excluded by !**/*.png
  • packages/stream_core_flutter/test/components/toolbar/goldens/ci/stream_bottom_nav_bar_regular.png is excluded by !**/*.png
📒 Files selected for processing (18)
  • apps/design_system_gallery/lib/app/gallery_app.directories.g.dart
  • apps/design_system_gallery/lib/components/media_viewer/stream_media_viewer.dart
  • apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart
  • apps/design_system_gallery/lib/components/toolbar/stream_bottom_app_bar.dart
  • apps/design_system_gallery/macos/Runner.xcodeproj/project.pbxproj
  • apps/design_system_gallery/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  • packages/stream_core_flutter/CHANGELOG.md
  • packages/stream_core_flutter/lib/src/components/media_viewer/stream_media_viewer.dart
  • packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_nav_bar.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_toolbar_button.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_toolbar_scope.dart
  • packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.g.theme.dart
  • packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_toolbar_button_test.dart
  • packages/stream_thumbnail/example/linux/flutter/generated_plugin_registrant.cc
  • packages/stream_thumbnail/example/linux/flutter/generated_plugin_registrant.h
  • packages/stream_thumbnail/example/linux/flutter/generated_plugins.cmake
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/stream_core_flutter/CHANGELOG.md
  • apps/design_system_gallery/lib/app/gallery_app.directories.g.dart
  • apps/design_system_gallery/lib/components/media_viewer/stream_media_viewer.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_toolbar_button.dart
  • apps/design_system_gallery/lib/components/toolbar/stream_bottom_app_bar.dart
  • packages/stream_core_flutter/lib/src/components/media_viewer/stream_media_viewer.dart
  • packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_nav_bar.dart
  • apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart
  • packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart

xsahil03x and others added 5 commits August 6, 2026 19:11
Replace imperative phrasing with the "Consider" / non-imperative form the
style guide calls for in dartdoc prose.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop how-it-resolves details from StreamToolbarScope's public dartdoc and
reword the toolbar-button knob note in contract terms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ssertions

- Golden coverage for the new floating StreamBottomAppBar chrome (gradient
  fade, no border) in light/dark.
- Unit assertions on the floating StreamBottomNavBar pill margin: floors the
  bottom gap at spacing.xl with no device inset, grows with the inset otherwise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
extendBody only affects a Scaffold that sets bottomNavigationBar /
persistentFooterButtons; this scaffold sets neither (the bottom bar lives in
the body), so the flag did nothing — the body-behind-bar overlap is done by
_StreamScaffoldBody. Removing it, and re-pointing the two tests that asserted
on the flag at observable outcomes (empty bottomNavigationBar slot; no bottom
inset injected without a bottom widget).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removed 9 tests that assert framework behavior, are trivially true, or
duplicate existing coverage (cross-checked via three independent reviews):

- scaffold: the "documented surprises" group (4 tests exercising Flutter's
  MediaQuery-consumption and downstream consumer padding patterns, not our
  contract) and one redundant bottomNavigationBar-null cell.
- nav bar: a BottomNavigationBar-absence assert (trivially true in both
  chromes) and a floating onTap test duplicating the shared-tile path.
- bottom app bar: a title-only render whose load-bearing assert cannot fail.
- app bar: a heading-default semantics test that is a strict subset of the
  Android namesRoute test.

Also drop the stray extendBody comment. Suites stay green (99 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/stream_core_flutter/test/components/toolbar/stream_bottom_nav_bar_test.dart`:
- Around line 249-252: Update gapBelowPill to measure the aligned nav-bar render
box rather than the StreamBottomNavBar widget. Add a unique Key to the Align
wrapping the nav bar, create or reuse a finder for that key, and pass it to
tester.getRect when calculating barBottom; leave the pill measurement unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 675ce64b-1665-4cd8-846e-1a05335adfcd

📥 Commits

Reviewing files that changed from the base of the PR and between 805804d and 11883b3.

⛔ Files ignored due to path filters (2)
  • packages/stream_core_flutter/test/components/toolbar/goldens/ci/stream_bottom_app_bar_floating.png is excluded by !**/*.png
  • packages/stream_core_flutter/test/components/toolbar/goldens/ci/stream_bottom_app_bar_regular.png is excluded by !**/*.png
📒 Files selected for processing (8)
  • packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_toolbar_button.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_toolbar_scope.dart
  • packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_app_bar_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_bottom_app_bar_golden_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_bottom_app_bar_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_bottom_nav_bar_test.dart
💤 Files with no reviewable changes (2)
  • packages/stream_core_flutter/test/components/toolbar/stream_bottom_app_bar_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_app_bar_test.dart
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_toolbar_scope.dart
  • packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart
  • packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart

xsahil03x and others added 2 commits August 6, 2026 20:22
…rBehavior

StreamBottomNavBarBehavior duplicated StreamToolbarBehavior (same regular /
floating). Replace it with the shared enum so a toolbar-shaped component's
floating state has one source of truth. Unreleased API, so no deprecation.
Regenerated the theme; gallery and CHANGELOG updated (also corrects the stale
nav-bar height in the changelog to 64).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reamSurfaceStyle

Both were structurally identical {regular, floating} enums — the app-wide
default and the per-component override — which forced constant conversions
between them. Merge into one StreamSurfaceStyle used at both scopes:

- StreamTheme.appStyle and every component's `behavior` field share the type.
- Drops the `isFloating ? .floating : .regular` conversion boilerplate.
- Named for the surface (not "toolbar"/"app"): triangulated with Material 3
  (docked/floating toolbars) and iOS 26 (inline/expanded placement + a separate
  Glass material), leaving room for a future material axis (e.g. liquid glass)
  as its own property rather than a value here.

Unreleased API, so no deprecation. Regenerated themes; tests, gallery, and
CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/stream_core_flutter/lib/src/theme/stream_surface_style.dart`:
- Around line 1-32: Document the StreamAppStyle to StreamSurfaceStyle rename in
CHANGELOG.md with migration guidance for existing consumers, or add a deprecated
StreamAppStyle compatibility alias that forwards to StreamSurfaceStyle. Preserve
the single export of StreamSurfaceStyle from core.dart and ensure the old public
API remains discoverable through either documentation or compatibility support.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e62718be-74c2-4162-9a57-670d038ac244

📥 Commits

Reviewing files that changed from the base of the PR and between 11883b3 and 959383c.

📒 Files selected for processing (30)
  • apps/design_system_gallery/lib/components/media_viewer/stream_media_viewer.dart
  • apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart
  • apps/design_system_gallery/lib/components/toolbar/stream_app_bar.dart
  • apps/design_system_gallery/lib/components/toolbar/stream_bottom_app_bar.dart
  • packages/stream_core_flutter/CHANGELOG.md
  • packages/stream_core_flutter/lib/core.dart
  • packages/stream_core_flutter/lib/src/components/media_viewer/stream_media_viewer.dart
  • packages/stream_core_flutter/lib/src/components/scaffold/stream_scaffold.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_app_bar.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_app_bar.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_nav_bar.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_toolbar_scope.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_app_bar_theme.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_app_bar_theme.g.theme.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_bottom_app_bar_theme.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_bottom_app_bar_theme.g.theme.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_bottom_nav_bar_theme.dart
  • packages/stream_core_flutter/lib/src/theme/components/stream_bottom_nav_bar_theme.g.theme.dart
  • packages/stream_core_flutter/lib/src/theme/stream_surface_style.dart
  • packages/stream_core_flutter/lib/src/theme/stream_theme.dart
  • packages/stream_core_flutter/lib/src/theme/stream_theme.g.theme.dart
  • packages/stream_core_flutter/test/components/media_viewer/stream_media_viewer_test.dart
  • packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_app_bar_golden_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_app_bar_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_bottom_app_bar_golden_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_bottom_app_bar_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_bottom_nav_bar_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_toolbar_button_test.dart
  • packages/stream_core_flutter/test/theme/stream_theme_test.dart
🚧 Files skipped from review as they are similar to previous changes (17)
  • packages/stream_core_flutter/lib/src/theme/components/stream_app_bar_theme.g.theme.dart
  • packages/stream_core_flutter/lib/core.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_bottom_app_bar_golden_test.dart
  • packages/stream_core_flutter/lib/src/components/media_viewer/stream_media_viewer.dart
  • apps/design_system_gallery/lib/components/toolbar/stream_bottom_app_bar.dart
  • apps/design_system_gallery/lib/components/toolbar/stream_app_bar.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_app_bar_golden_test.dart
  • apps/design_system_gallery/lib/components/scaffold/stream_scaffold.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_toolbar_button_test.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_bottom_app_bar_test.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_app_bar.dart
  • apps/design_system_gallery/lib/components/media_viewer/stream_media_viewer.dart
  • packages/stream_core_flutter/test/components/toolbar/stream_app_bar_test.dart
  • packages/stream_core_flutter/test/components/media_viewer/stream_media_viewer_test.dart
  • packages/stream_core_flutter/test/components/scaffold/stream_scaffold_test.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_app_bar.dart
  • packages/stream_core_flutter/lib/src/components/toolbar/stream_bottom_nav_bar.dart

Comment thread packages/stream_core_flutter/lib/src/theme/stream_surface_style.dart Outdated
xsahil03x and others added 21 commits August 7, 2026 01:27
Make StreamSafeArea a general, app-agnostic SafeArea + margin widget:
maintainBottomViewPadding is now a parameter (on the widget and
resolveInsets), defaulting to true so the floating case works out of the
box while any app can flip it to SafeArea's standard behaviour. No
Stream-specific dependencies — the margin value stays at the call site.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite the doc comments to describe observable behaviour (a fixed margin
kept beyond the system insets, keyboard-stable bottom) instead of the
implementation (no "composes SafeArea", inner Padding, or which MediaQuery
field is read), and add a usage snippet.

Replace the two pill-margin tests with a matrix over the real bottom
insets — iOS no-inset (0) and home indicator (34), Android gesture (24)
and 2-/3-button (48) — asserting the pill floats spacing.xl above each.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match SafeArea's dartdoc shape — "A widget that insets its child…" opener,
a one-line note on the margin, a {@tool snippet}, a "### MediaQuery impact"
section, and a See also list — with terse field docs in SafeArea's voice
("Whether to avoid system intrusions on the left"). Reorder the edge flags
to left/top/right/bottom to match SafeArea too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prove the widget is direction-agnostic — SafeArea's physical left/right
plus a physical EdgeInsets margin, so an asymmetric inset lands on the
same physical edges in LTR and RTL (no swap).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pill floated spacing.xl (24) above the bottom system inset, which sat
too high. Drop the bottom margin to spacing.xs (8) while keeping the
sides/top at xl, so it hugs the inset more closely. Regenerates the
floating CI golden and updates the navigation-mode matrix test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the Figma spec: the pill's bottom gap is now max(systemInset, 2xl)
— a 32 floor that a larger inset absorbs, landing in a 32→48 band across
iOS (34 portrait) and Android (24 gesture, 48 button nav) instead of the
too-tight inset+8. Adds a `minimum` (floor) param to StreamSafeArea and
resolveInsets — each edge is now max(systemInset, minimum) + margin — and
switches the nav bar to a minimum-only floor. Regenerates the CI golden.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the bottom floor with max(inset, xl) + xs: a uniform spacing.xs (8)
above whatever the system reserves, floored so small-inset devices land on
spacing.xxl (24 + 8 = 32, matching Figma). The pill no longer touches the
opaque 3-button bar (48 -> 56) and never over-floats, with no fragile
gesture-vs-button detection. StreamSafeArea is unchanged — this just uses
its existing minimum (floor) + margin (beyond-inset). Golden unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the spacing.xs bottom gap conditionally — only when the bottom inset
looks like a tappable 2-/3-button bar (>= 40), which fills its whole
extent. Thin overlays (gesture ~24, iOS indicator ~34) already have space
around them and sit flush. Flutter exposes no nav-mode API, so this keys
off the inset height (40 cleanly splits the tallest overlay from a button
bar). Gaps: gesture 24, iOS 34, 3-button 56. Regenerates the CI golden
(no-inset pill drops from 32 to 24 — no opaque bar there).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The inset >= 40 threshold was a hack (Flutter exposes no nav-mode signal;
systemGestureInsets is unreliable on iOS). Material sidesteps this by
docking its nav bar rather than floating it, so there's no clean
precedent. Go back to a uniform max(inset, xl) + xs: the pill floats a
consistent spacing.xs above whatever the system reserves — never flush,
never guessing. Restores the no-inset golden to 32.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Describe the behavioural difference (insets only to the safe area, no
margin beyond) instead of "which this builds on".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A named constructor (Flutter's idiom for "same widget, alternate mode",
like ListView.builder) that lerps the resolved inset between full (0) and
nothing (1) by an Animation<double>. Lets a floating surface release its
safe area full-bleed as a panel slides in beneath it, while keeping the
same StreamSafeArea call shape as the static/nav-bar usage. The default
constructor is unchanged (SafeArea-based, consumes); collapsing applies a
lerped Padding and does not consume.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
….driven

Rework the collapsing constructor into a publishable, generic one:
- StreamSafeArea.driven (not .collapsing/.lerp — .lerp reads as a static
  interpolation fn, not a widget constructor).
- Driven by any ValueListenable<double>, not just Animation<double>.
- Interpolates toward a configurable `to` target (default EdgeInsets.zero),
  not hardcoded zero.

At 0 the full inset applies; at 1 it is `to`. Values clamp to [0, 1].
Rebuilds via ValueListenableBuilder. Test covers the default (collapse to
zero) and a non-zero target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The driven path only applied a Padding, leaving the child's MediaQuery
untouched — so it didn't consume the inset like the default (SafeArea)
path, and a nested safe area could double-apply it. Wrap the child in
MediaQuery.removePadding on the avoided edges (binary, independent of the
interpolation), matching the default. Adds a test that a descendant sees
the bottom inset removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nits

Review follow-ups:
- Note the default behaves like SafeArea *with maintainBottomViewPadding on*
  (SafeArea's default is off), so the bottom diverges under a keyboard.
- Generalize the appliedInsets helper comment (driven uses Padding, not SafeArea).
- Add a test that .driven rebuilds when a live ValueListenable changes (was
  only ever pumped with fresh AlwaysStoppedAnimation).
- Add a default-constructor consume test to mirror the .driven one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both constructors now apply resolveInsets via Padding and consume via
MediaQuery.removePadding — the default no longer composes Flutter's
SafeArea. This removes the two-implementation split (SafeArea for the
static path, hand-rolled for the driven path, which could drift) and makes
resolveInsets the single source of truth, so default == driven-at-0 by
construction. Behaviour is identical (SafeArea is Padding(max(inset,
minimum)) + removePadding); nav-bar tests + golden unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… value

Clarify that resolveInsets is the endpoint StreamSafeArea.driven interpolates
from, so it isn't mistaken for the current interpolated inset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cover the cases Flutter's SafeArea tests that we were missing:
- add debugFillProperties (edge flags + minimum/margin/maintainBottomViewPadding
  + driven listenable/to) and a test asserting the "avoid X padding" flags,
- nested StreamSafeArea doesn't double-inset (removePadding),
- rebuilds when the MediaQuery padding changes,
- doesn't crash at zero area.

(We already exceed the suite on margin, RTL, and the driven lerp/consume/
reactivity; no sliver variant, so SliverSafeArea's cases don't apply.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… false

Match Flutter's SafeArea, which defaults it to false. Reading the bottom gap
from padding (not viewPadding) lets it collapse with the keyboard and lets a
parent that injects the inset via padding (e.g. StreamScaffold) flow through
unchanged. Callers that pin a surface the keyboard should slide under can opt
into viewPadding with maintainBottomViewPadding: true.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use max(inset, spacing.md) on the pill's bottom instead of adding a margin.
A larger system inset (iOS home indicator, Android nav bar) is used as-is so
the pill sits flush above it rather than lifting an extra margin higher, while
a device that reserves nothing still gets the spacing.md floor. Behaviour is
unchanged on zero-inset devices (still spacing.md); it only stops over-lifting
where the inset already exceeds the floor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an optional `type` that overrides the shape the toolbar otherwise resolves
from the enclosing bar (outline when floating, ghost when docked), so a slot
can render e.g. a solid primary action while keeping the bar-driven elevation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These generated_plugin_registrant.* / generated_plugins.cmake files under the
stream_thumbnail example's linux/ folder are regenerated on every Linux build
and were committed by mistake in 2beebd3. Remove them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xsahil03x
xsahil03x force-pushed the feat/stream-scaffold branch from d9c1ebe to 401b891 Compare August 7, 2026 07:07
The design_system_gallery's macOS Runner.xcodeproj/project.pbxproj and
Runner.xcscheme were auto-modified by Xcode (build-setting / scheme churn)
and committed by mistake. Restore them to their base (main) version so they
drop out of the PR diff.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
final footer = props.footer?.preferredSize;

final headerInset = (header == null || headerFloating) ? 0.0 : header.height + mediaQueryPadding.top;
final footerInset = (footer == null || footerFloating) ? 0.0 : footer.height + mediaQueryPadding.bottom;

@renefloor renefloor Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes the header/footer bar self-insets the safe area, which only happens when the bar's own primary is true (the default on StreamAppBar/StreamBottomAppBar). Pass header: StreamAppBar(primary: false, ...) — a supported, documented option on the bar itself — and the rendered bar height stays at the fixed toolbar height (no device padding added), but this still reserves header.height + mediaQueryPadding.top / footer.height + mediaQueryPadding.bottom. That leaves a visible gap between the chrome's edge and where the media content actually starts.

stream_media_viewer_test.dart's _FakeBar fixture (used in the "regular chrome adds the device inset to the bar heights" case) is a bare SizedBox(height: height) that doesn't self-inset either — i.e. it behaves like a primary: false bar — but the test only asserts the abstract AnimatedPadding.padding value, never the actual rendered position of the header vs. the content's first pixel, so it doesn't catch the gap.

Suggest gating the + mediaQueryPadding.top / + mediaQueryPadding.bottom addition on the chrome's own resolved primary state (mirroring how headerFloating/footerFloating are already resolved above), plus a test that pairs a real StreamAppBar(primary: false) / StreamBottomAppBar(primary: false) with the media viewer.

xsahil03x and others added 2 commits August 7, 2026 16:09
The bottom slot holds an arbitrary widget (composer, nav bar, bottom app
bar), so name the override after the slot rather than a bar, and resolve it
from the per-instance value then the ambient StreamSurfaceStyle only. Drop
the StreamBottomAppBarTheme rung, which wrongly applied bottom-app-bar
semantics to whatever occupied the slot; the app bar slot keeps its own
theme chain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Introduces a `StreamSpacing.safeAreaBottom()` extension that provides platform-specific minimum insets (xxl on iOS/macOS, xxxl elsewhere) for floating surfaces.
- Updates `StreamBottomNavBar` to use this adaptive spacing for its minimum bottom inset instead of a hardcoded `spacing.md`.
- Corrects a documentation reference from `bottomBarSurfaceStyle` to `bottomSurfaceStyle`.
- Updates widget tests and golden images to reflect the platform-aware spacing logic.
final StreamElevation _elevation;

@override
double get floatingElevation => _elevation.level2;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we made it level2 because level3 was a bit too much on group avatars.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2026-08-07 at 16 58 27

its the same in figma

/// a docked bar.
///
/// In a [StreamScaffold] `bottom` slot, drive floating through the ambient
/// [StreamSurfaceStyle] (or the scaffold's `bottomBarSurfaceStyle`) so the

@renefloor renefloor Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// [StreamSurfaceStyle] (or the scaffold's `bottomBarSurfaceStyle`) so the
/// [StreamSurfaceStyle] (or the scaffold's `bottomSurfaceStyle`) so the

/// system inset (home indicator, navigation bar) is honored as-is.
///
/// Adapts to [platform], defaulting to the current platform.
double safeAreaBottom({TargetPlatform? platform}) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"The recommended minimum bottom inset for a floating surface"

I would also put that in the method name, something like

double safeAreaFloatingBottom({TargetPlatform? platform}) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In figma the name of the token is device/safe-area-bottom.

@renefloor renefloor Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but in Figma it's actually supposed to be the safe area, now this is a custom value that is not in any way really coming from the safe area the device specifies. This is only supposed to be used for floating components right? In every other case I would expect the safearea on the bottom to be exactly the same as the official safe area from the device.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants