Skip to content

fix(ui): iPhone Duo polish: picker height, list states, full-width suggestions - #3021

Open
xsahil03x wants to merge 14 commits into
sahil/flu-820-respect-asymmetric-safe-area-insets-on-iphone-duo-composerfrom
sahil/flu-823-iphone-duo-polish-viewer-fit-thumbnails-autocomplete-height
Open

xsahil03x wants to merge 14 commits into
sahil/flu-820-respect-asymmetric-safe-area-insets-on-iphone-duo-composerfrom
sahil/flu-823-iphone-duo-polish-viewer-fit-thumbnails-autocomplete-height

Conversation

@xsahil03x

@xsahil03x xsahil03x commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Part of FLU-823. Stacked on #3017 (FLU-820); review only the commits on top of it.

  • Attachment picker on short screens: below 480pt of height (Material's compact-height class, e.g. a phone in landscape), the tabbed picker's options area is 158pt instead of 260pt. With the 72pt tabs the picker then takes the height of the landscape keyboard it replaces (230pt on iPhone Duo's outer display), instead of overflowing and squeezing the message list to nothing.
  • Message list states: the default loading, empty and error states scroll instead of overflowing when the list has little room (e.g. landscape with the picker open). The empty and error states fill the space with SliverFillRemaining, so they stay centred when they fit; the error UI moves into an internal StreamMessageListErrorState, leaving the list to decide only the safe-area insets.
  • Autocomplete: the composer's suggestions were anchored to the inset input, so under a side inset their background stopped at the rail while the composer ran edge to edge. The autocomplete now wraps the inset input, and StreamAutocompleteOptions keeps its options clear of the side insets. Suggestions from customAutocompleteTriggers span the full width too and handle the insets themselves (noted under Changed).
  • The message list's safe-area helper now uses StreamSafeArea.

Verified on the iPhone Duo simulator (outer display, portrait and landscape): no overflow with the picker open, the empty state scrolls in the remaining space, and the suggestions span the full width with their rows clear of the rail. The semantics of the list states are unchanged apart from the scroll container.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Autocomplete suggestions now span the composer’s full width while staying clear of side safe-area insets.
    • Attachment pickers use less vertical space on short screens.
    • Empty, loading, and error states in message lists can scroll when space is limited, preventing content from overflowing.
    • Message-list error states display an error message and offer an option to retry loading messages.
    • Message-list layouts respect safe-area insets more consistently.

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 21a0f585-bb09-4aa6-8d15-1c7114a71709
📥 Commits

Reviewing files that changed from the base of the PR and between c33f86f and c91ba57.

📒 Files selected for processing (2)
  • packages/stream_chat_flutter/CHANGELOG.md
  • packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

📝 Walkthrough

The composer and autocomplete options now handle safe-area insets separately. The attachment picker uses a reduced height on short screens. Default message-list loading, empty, and error states now use scrollable layouts.

Changes: Constrained Layouts

Layer / File(s) Summary
Composer and autocomplete insets
packages/stream_chat_flutter/lib/src/message_input/stream_message_composer.dart, packages/stream_chat_flutter/lib/src/autocomplete/stream_autocomplete.dart, packages/stream_chat_flutter/CHANGELOG.md
The composer applies safe-area insets to its input and no-permission message. Autocomplete options use StreamSafeArea with top and bottom insets disabled. The changelog records the autocomplete layout update.
Compact attachment picker
packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart
The tabbed picker uses a height of 158 pixels when screen height is below 480 pixels. It retains the 260-pixel height at or above that breakpoint.
Scrollable message-list states
packages/stream_chat_flutter/lib/src/message_list_view/*, packages/stream_chat_flutter/CHANGELOG.md
The empty and error states use CustomScrollView and SliverFillRemaining. The skeleton uses SingleChildScrollView. The default error builder uses StreamMessageListErrorState with a channel-reload retry callback, and enabled safe areas use StreamSafeArea. The changelog records the message-list layout fixes.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Low

Suggested reviewers: renefloor

Merge Risk: ⚪ Minimal · up to c91ba

The changes shorten the picker on compact screens and let default message states scroll in limited space while preserving composer side insets. The inspected code shows no concrete user-facing regression or merge-blocking risk beyond normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to d2e6a

The change affects a public UI customization point, but the inspected permission checks and message-retry behavior remain in place. No security issue was verified. Custom suggestion content may need to account for side insets.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The identified change in reachability is to suggestion presentation in applications using custom autocomplete triggers; the inspected path does not add a channel capability or a new reload authority.

Trust Boundaries and Controls

  • observed — The inspected composer keeps its capability decision outside the new presentation wrapper, while autocomplete receives the same effective controller and focus node.
Pre-merge checks | Passed 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly summarizes the main UI changes: reduced picker height, improved list states, and full-width autocomplete suggestions.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

@codecov

codecov Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.71429% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.67%. Comparing base (ebce125) to head (d9e226a).

Files with missing lines Patch % Lines
...r/lib/src/message_list_view/message_list_view.dart 25.00% 3 Missing ⚠️
Additional details and impacted files
@@                                             Coverage Diff                                              @@
##           sahil/flu-820-respect-asymmetric-safe-area-insets-on-iphone-duo-composer    #3021      +/-   ##
============================================================================================================
+ Coverage                                                                     79.13%   79.67%   +0.54%     
============================================================================================================
  Files                                                                           452      453       +1     
  Lines                                                                         29459    29476      +17     
============================================================================================================
+ Hits                                                                          23312    23486     +174     
+ Misses                                                                         6147     5990     -157     

☔ 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 added this pull request to stack #3022 September 29, 2026 20:48
xsahil03x and others added 5 commits September 29, 2026 22:49
On short screens, such as a phone in landscape, the tabbed picker left no
room under the app bar and overflowed. Below 480pt of height its options area
is now 158pt, so with the 72pt tabs the picker takes the height of the
landscape keyboard it replaces (230pt on iPhone Duo's outer display).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When the list had less room than its default loading, empty or error state
needed, such as in landscape with the attachment picker open, the state
overflowed. Each state now scrolls instead: the empty and error states fill
the space with SliverFillRemaining, so they stay centred when they fit, and
the skeleton scrolls. The error state moves into its own internal widget,
StreamMessageListErrorState, so the list only decides the safe-area insets.

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

The suggestions were anchored to the inset input, so under a side safe-area
inset their background and border stopped short of the screen edge while the
composer and app bar ran edge to edge. The autocomplete now wraps the inset
input, so its suggestions span the composer, and StreamAutocompleteOptions
keeps its options clear of the side insets. Custom trigger suggestions span
the full width too and handle the insets themselves.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@xsahil03x
xsahil03x force-pushed the sahil/flu-823-iphone-duo-polish-viewer-fit-thumbnails-autocomplete-height branch from 085dfd9 to d2e6a78 Compare September 29, 2026 20:49
xsahil03x and others added 3 commits October 1, 2026 12:01
…hone-duo-composer' into sahil/flu-823-iphone-duo-polish-viewer-fit-thumbnails-autocomplete-height
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AP6bV4L7FQYjHoHZeomLxr
xsahil03x added a commit that referenced this pull request Oct 2, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AP6bV4L7FQYjHoHZeomLxr
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AP6bV4L7FQYjHoHZeomLxr
xsahil03x added a commit that referenced this pull request Oct 12, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AP6bV4L7FQYjHoHZeomLxr

@renefloor renefloor left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed this layer on its own. The SliverFillRemaining(hasScrollBody: false) approach for the list states and the _insetContent extraction both look like the right shape.

Main concern: there are no test changes in this layer, despite behaviour changes in 7 lib files. Tests I'd expect:

  • List states: pump StreamMessageListEmptyState, StreamMessageListErrorState and StreamMessageListSkeletonLoading in a ~120pt-tall box; expect no exception and a Scrollable. On revert these fail with a RenderFlex overflow.
  • Error state retry: tap "Try again" and verify reloadChannel is called.
  • Autocomplete width: composer with a side inset, type /; the options card spans the full width and an option tile stays clear of the inset. Same with a custom trigger.
  • No-permission message: with a side and bottom inset, the message stays clear of both.
  • Picker height: StreamTabbedAttachmentPicker at heights 479 and 480 → 158 and 260, selecting every tab, with expect(tester.takeException(), isNull). That would have caught the overflow below.


// The height of the attachment picker when using the tabbed interface on
// compact-height screens.
const _kCompactTabbedAttachmentPickerHeight = 158.0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think 158 is too short for every tab except the gallery. The file, camera, video and poll tabs wrap their content in OptionDrawer, which defaults to vertical: spacing.xxxl (40) margins, which leaves 78pt. Their column is a 32 icon + 8 gap + ~20 text + 16 gap + 40 button ≈ 116pt, and it doesn't scroll.

So on a phone in landscape, switching to Files/Camera/Video/Poll should still hit a ~38px RenderFlex overflow, which is the overflow this PR is fixing. Only the gallery tab (margin: .zero) fits. I worked this out from the code and haven't run it, so it's worth checking on the simulator with each tab. Fix: either reduce the OptionDrawer vertical margin at compact height, or make the drawer content scrollable.

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.

Confirmed: on the iPhone Duo in landscape the file, camera, video and poll tabs overflowed by 45px at 158. Fixed in d9e226a by giving those four tabs only the side margin. Their prompt is wrapped in a Center, so the 40pt top and bottom margin never moved it; it only took away the room it needed. The regular 260 layout is unchanged, and OptionDrawer's default margin is untouched, so custom drawers don't move. The new stream_tabbed_attachment_picker_height_test.dart covers every tab at 479 and 480 tall and fails on the old margin.

focusNode: _effectiveFocusNode,
messageComposerController: _effectiveController,
fieldViewBuilder: _buildMessageInput,
// Inset inside the autocomplete so its suggestions span the full width.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Moving the inset into fieldViewBuilder means a customer's customAutocompleteTriggers overlay now runs under the notch/rail instead of being clipped to the inset width. It's in the changelog, which is good, but under CLAUDE.md's rules it's a behavioural break (no compile error, rendering changes), so I think it needs a ! in the title and a migration hint, e.g. "wrap custom options in SafeArea(top: false, bottom: false)".

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.

Intentional. The suggestions area now matches the composer and app bar: the background runs under the side insets, and our own StreamAutocompleteOptions keeps its rows clear of them. Customer content is laid out as built rather than wrapped, the same as the paged-list states in #3017. It's documented under 🔄 Changed with the one-line migration (a552d1d). I'm not marking it !: it's layout-only and affects only fully custom options views, like the composer border change. a5f716e adds tests for both the command and custom-trigger suggestions under the insets.

),
],
),
child: StreamSafeArea(top: false, bottom: false, child: content),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

StreamAutocompleteOptions is exported, and it now always adds side padding, which changes any custom overlay built on it. This isn't in the changelog; could it get its own 🔄 Changed line?

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.

Added in a552d1d: the 🔄 Changed line now names StreamAutocompleteOptions and how other options views clear the insets. a5f716e checks its content stays clear of them.

/// A widget that is used to display the error state of the message list.
///
/// Scrolls instead of overflowing when it has less room than it needs.
class StreamMessageListErrorState extends StatelessWidget {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: this isn't exported (consistent with the empty/skeleton states), but the Stream prefix and "Creates a new instance" dartdoc make it look like public API. If customers are meant to reuse it in builders.error, export it and add a changelog Added line; otherwise, fine as is.

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.

Leaving it internal for now. It matches StreamMessageListEmptyState and StreamMessageListSkeletonLoading, which have the same prefix and doc style and aren't exported either. Exporting the three would be new public API, so I'd do that as its own feat when there's a use for them in builders.

xsahil03x and others added 5 commits October 15, 2026 16:00
# Conflicts:
#	packages/stream_chat_flutter/CHANGELOG.md
#	packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart
… entry

Options built on StreamAutocompleteOptions keep clear of the side insets on
their own, so only other options views need to wrap their content.

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

Command and custom suggestions run under the side insets while their content
stays clear of them, the no-permission message clears the side and bottom
insets, and the message list's default states fit a 120pt-tall list.

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

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The camera, video, file and poll tabs centred their prompt inside a 40pt top and bottom margin, which moved nothing but left too little room in the compact picker on a short screen, so the prompt's button overflowed. Those tabs now keep only the side margin.

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

This branch has not been deployed

No deployments
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