Skip to content

Conversation

dballance
Copy link
Contributor

@dballance dballance commented Aug 25, 2025

CLA

  • I have signed the Stream CLA (required).
  • The code changes follow best practices
  • Code changes are tested (add some information if not applicable)

Description of the pull request

Adds optionsBuilder to mobileAttachmentPickerBuilder, webOrDesktopAttachmentPickerBuilder, and showStreamAttachmentPickerModalBottomSheet so that the order of the options can be fully controlled.

Today, options are strictly prepended, with no option to control ordering custom options after the default options.

Screenshots

Before After
image image

Would also allow interspersing, or putting an exact picker in an exact index location, or reversing the list, etc. Below, the red indicate dummy options interspersed with defaultOptions
image

Summary by CodeRabbit

  • New Features

    • Attachment picker now supports full customization of option ordering, visibility, and replacement on mobile, web, and desktop via a new options builder; legacy custom options remain supported but cannot be combined with the new builder.
  • Documentation

    • CHANGELOG updated with guidance on using the new attachment picker options customization.
  • Tests

    • Added tests for customization, reordering, filtering, legacy behavior, and conflict assertion.

Copy link
Contributor

coderabbitai bot commented Aug 25, 2025

Walkthrough

Adds an optionsBuilder API for attachment picker (mobile and web/desktop), wires it through the modal bottom sheet, introduces two typedefs, enforces exclusivity with customOptions, refactors default/final options computation, updates builder signatures, adds tests, and documents the change in the CHANGELOG.

Changes

Cohort / File(s) Summary of Changes
API: Option builders & signatures
packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart
Adds typedefs AttachmentPickerOptionsBuilder and WebOrDesktopAttachmentPickerOptionsBuilder; adds optional optionsBuilder parameters to mobileAttachmentPickerBuilder and webOrDesktopAttachmentPickerBuilder; asserts exclusivity with customOptions; refactors default-options construction and computes finalOptions via optionsBuilder or merged customOptions+defaults.
Modal bottom sheet integration
packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart
Adds named parameter AttachmentPickerOptionsBuilder? optionsBuilder to showStreamAttachmentPickerModalBottomSheet; asserts not both customOptions and optionsBuilder; when provided, obtains default AttachmentPickerOption list, calls optionsBuilder, maps results for web/desktop, and forwards computed options to mobile/web builders; preserves legacy behavior when null.
Tests
packages/stream_chat_flutter/test/src/message_input/attachment_picker/options_builder_test.dart
New test suite validating optionsBuilder behavior: ordering/combining with defaults, reordering/filtering, typedef usage, customOptions behavior when optionsBuilder absent, and assertion when both provided.
Docs
packages/stream_chat_flutter/CHANGELOG.md
Adds Upcoming entry documenting the new optionsBuilder for showStreamAttachmentPickerModalBottomSheet, mobileAttachmentPickerBuilder, and webOrDesktopAttachmentPickerBuilder (documentation-only change).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App
  participant Modal as showStreamAttachmentPickerModalBottomSheet
  participant MobileBuilder as mobileAttachmentPickerBuilder
  participant WebBuilder as webOrDesktopAttachmentPickerBuilder
  participant Sheet as AttachmentPickerBottomSheet

  App->>Modal: open(..., customOptions?, optionsBuilder?)
  Note right of Modal: assert(!(customOptions && optionsBuilder))

  alt optionsBuilder provided
    Modal->>MobileBuilder: call(..., optionsBuilder: optionsBuilder)
    Modal->>WebBuilder: call(..., optionsBuilder: wrapper(mapping default→webOption))
    Note right of MobileBuilder: build defaultOptions\nfinalOptions = optionsBuilder(context, defaultOptions)
    Note right of WebBuilder: build defaultOptions\nmapped = defaultOptions→webOptions\nfinalOptions = optionsBuilder(context, mapped)
  else legacy path
    Modal->>MobileBuilder: call(..., customOptions?)
    Modal->>WebBuilder: call(..., customOptions?)
    Note over MobileBuilder,WebBuilder: finalOptions = (customOptions? + defaultOptions)
  end

  MobileBuilder->>Sheet: present(finalOptions)
  WebBuilder->>Sheet: present(finalOptions)
  Sheet-->>App: selection or dismiss
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • xsahil03x
  • renefloor

Poem

I hopped through options, nudged defaults in line,
A builder to reorder — pickers now shine.
Gallery, file, poll — placed where I please,
One tiny assert keeps conflicts at ease.
I twitch my whiskers: choices sorted with glee. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title clearly conveys the primary feature addition by stating that an optionsBuilder has been added for customizing attachment options, which directly matches the main changes described in the diff. It is concise, follows a conventional commit style prefix, and avoids unnecessary detail or noise. A teammate scanning the history will immediately understand the nature and scope of the update without ambiguity. The title accurately reflects the PR’s intent and focuses on the core enhancement.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart (1)

279-282: Null key will crash Set/hash operations

AttachmentPickerOption.hashCode calls key.hashCode unconditionally, but key is nullable. Several code paths convert lists to Set<AttachmentPickerOption>; if a developer returns an option without a key via optionsBuilder, this will throw.

Make hashCode null-safe. This is critical given the new extension points.

-  @override
-  int get hashCode =>
-      key.hashCode ^ const IterableEquality().hash(supportedTypes);
+  @override
+  int get hashCode =>
+      (key?.hashCode ?? 0) ^ const IterableEquality().hash(supportedTypes);
🧹 Nitpick comments (10)
packages/stream_chat_flutter/CHANGELOG.md (1)

5-7: Clarify mutual exclusivity and tighten wording

Please call out that optionsBuilder and customOptions are mutually exclusive (asserted in code) and that optionsBuilder takes precedence if both are provided in release mode. Also prefer “ordering and display” over the slashed phrasing.

Proposed edit:

- - Added `optionsBuilder` to `showStreamAttachmentPickerModalBottomSheet`, 
-   `mobileAttachmentPickerBuilder`, and `webOrDesktopAttachmentPickerBuilder` 
-   to allow full control over the attachment picker options ordering / display.
+ - Added `optionsBuilder` to `showStreamAttachmentPickerModalBottomSheet`,
+   `mobileAttachmentPickerBuilder`, and `webOrDesktopAttachmentPickerBuilder`,
+   allowing full control over the attachment picker options ordering and display.
+   Note: `optionsBuilder` and `customOptions` are mutually exclusive. If both
+   are supplied (e.g., in release where asserts are disabled), `optionsBuilder`
+   takes precedence and `customOptions` are ignored.
packages/stream_chat_flutter/test/src/message_input/attachment_picker/options_builder_test.dart (4)

65-89: Assert coverage on reordering is solid; consider adding a guard on key assumptions

The test hard-codes 'gallery-picker'. That's fine for stability, but if keys ever change this test will fail for unrelated reasons. Consider verifying by type (supportedTypes) as a fallback in an additional assertion.


97-114: Typedef smoke test is fine; prefer a compile-time use site in real widget context

This ensures the typedef compiles. If you want stronger guarantees, add a tiny widget that accepts AttachmentPickerOptionsBuilder and invokes it once. Not required here.


116-136: Web/Desktop typedef test exists; add an integration test for the web/desktop builder path

You validate the typedef, but there’s no integration case that exercises webOrDesktopAttachmentPickerBuilder(optionsBuilder: ...). Adding one will guard the mapping logic introduced in the modal and the builder.

Happy to draft a test that pumps a minimal app, calls webOrDesktopAttachmentPickerBuilder with an optionsBuilder that filters to a single option, and asserts the bottom sheet options and titles.


174-200: Assertion test is good; consider documenting release-mode behavior

This asserts the mutual exclusivity in debug. In release, both can be passed and optionsBuilder wins silently. Consider a doc comment or an error log to make this explicit.

packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart (5)

223-231: Docstring nit: spacing and clarity

Minor grammar/punctuation issues in the typedef docs.

-/// The [defaultOptions] parameter contains the standard attachment picker
-/// options(gallery, file, image, video, poll pickers). Developers can use
-/// these as-is, reorder them, or combine them with custom options.
+/// The [defaultOptions] parameter contains the standard attachment picker
+/// options (gallery, file, image, video, poll pickers). Developers can use
+/// these as-is, reorder them, or combine them with custom options.

236-244: Docstring nit: spacing

Minor spacing fix.

-/// picker options (image, video, file, poll pickers). Developers can use these
-/// as-is,reorder them, or combine them with custom options.
+/// picker options (image, video, file, poll pickers). Developers can use these
+/// as-is, reorder them, or combine them with custom options.

915-921: Consider reapplying allowedTypes after optionsBuilder

A user-provided optionsBuilder can introduce options whose supportedTypes are not in allowedTypes. If allowedTypes should be authoritative, re-filter finalOptions post-build. If it’s purely an advisory default and customization should override it, leave as-is, but document the precedence.

-  final finalOptions = optionsBuilder != null
-      ? optionsBuilder(context, defaultOptions).toSet()
-      : {
-          if (customOptions != null) ...customOptions,
-          ...defaultOptions,
-        };
+  final built = optionsBuilder != null
+      ? optionsBuilder(context, defaultOptions)
+      : [
+          if (customOptions != null) ...customOptions,
+          ...defaultOptions,
+        ];
+  final finalOptions = built
+      .where((o) => o.supportedTypes.every(allowedTypes.contains))
+      .toSet();

982-991: Optional: enforce allowedTypes after custom builder on web/desktop

Same consideration as mobile: if allowedTypes should remain authoritative even with a custom builder, re-filter after building the final list.

-  if (optionsBuilder != null) {
-    finalOptions = optionsBuilder(context, defaultOptions).toSet();
-  } else {
+  if (optionsBuilder != null) {
+    finalOptions = optionsBuilder(context, defaultOptions)
+        .where((o) => o.supportedTypes.every(allowedTypes.contains))
+        .toSet();
+  } else {
     // Legacy behavior: combine custom and default options
     finalOptions = {
       if (customOptions != null) ...customOptions,
       ...defaultOptions,
     };
   }

604-606: Typo in doc comment

Stray character in the docstring.

-  /// The callback that is called when the end of the frame is reached.x
+  /// The callback that is called when the end of the frame is reached.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 64b75d8 and 5ec19d3.

📒 Files selected for processing (4)
  • packages/stream_chat_flutter/CHANGELOG.md (1 hunks)
  • packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart (3 hunks)
  • packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart (4 hunks)
  • packages/stream_chat_flutter/test/src/message_input/attachment_picker/options_builder_test.dart (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat_persistence
  • GitHub Check: stream_chat_flutter
  • GitHub Check: analyze_legacy_versions
  • GitHub Check: build (ios)
  • GitHub Check: test
  • GitHub Check: build (android)
  • GitHub Check: analyze
🔇 Additional comments (10)
packages/stream_chat_flutter/test/src/message_input/attachment_picker/options_builder_test.dart (1)

22-33: Good coverage for mobile builder happy path

These tests validate ordering and inclusion semantics when optionsBuilder is provided and ensure the returned widget type. Nice.

Also applies to: 34-48

packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart (2)

95-101: Assert for mutual exclusivity: good guard

The assert preventing simultaneous optionsBuilder and customOptions is appropriate and consistent with the builders.


171-185: Forwarding optionsBuilder to the mobile path is correct

Passing through optionsBuilder keeps behavior consistent across platforms.

packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart (7)

911-913: Confirm intended filtering semantics for compound options (gallery)

gallery-picker supports both images and videos. With every(allowedTypes.contains), it is included only when both are allowed. If you want gallery shown when either images or videos are allowed (and internally restrict selection), change every to any and ensure the view enforces filtering. If the current behavior is intentional to avoid exposing disallowed media, keep as-is.

Would you like me to draft a small test to lock the intended behavior and avoid regressions?


951-979: Web/desktop default options look good and respect allowedTypes

The web/desktop defaults are localized and filtered.


996-1025: Error handling is consistent and user-friendly

Catching errors, popping the sheet, and delegating to onError maintains UX and developer control.


793-910: Mobile default option implementations: solid, with consistent error handling

The option view builders are cohesive and consistently guard errors. Nice work.


496-575: Options row UX logic is clean

Enablement based on filterEnabledTypes, selected color, and send button gating on isValueChanged reads well.


314-358: Equality and enablement helpers are helpful; just fix the null key hash noted above

The helpers reduce duplication and centralize semantics.


369-413: Web/desktop bottom sheet list rendering is correct

Using ListTile and enablement by type works well with the proposed title assert.

@dballance
Copy link
Contributor Author

@xsahil03x @renefloor - unsure if anything needs to be fixed here - the only CodeRabbit comment is outside of my diff and not a part of these changes.

@dballance dballance force-pushed the feat/enable-ordering-custom-attachment-handlers branch from c022b22 to 1ef2cb5 Compare September 17, 2025 15:18
@dballance
Copy link
Contributor Author

Rebased on the tip and fixed analyze nit.

Copy link

codecov bot commented Sep 17, 2025

Codecov Report

❌ Patch coverage is 25.97403% with 57 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.01%. Comparing base (dd43ac6) to head (ded8149).

Files with missing lines Patch % Lines
...ut/attachment_picker/stream_attachment_picker.dart 26.38% 53 Missing ⚠️
..._picker/stream_attachment_picker_bottom_sheet.dart 20.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2363      +/-   ##
==========================================
+ Coverage   63.92%   64.01%   +0.08%     
==========================================
  Files         413      413              
  Lines       25892    25905      +13     
==========================================
+ Hits        16552    16582      +30     
+ Misses       9340     9323      -17     

☔ View full report in Codecov by Sentry.
📢 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
Copy link
Member

Hey @dballance, I will review this PR this week. Can you also allow edits from maintainers if I had to do any change before merging it.

@dballance
Copy link
Contributor Author

dballance commented Sep 17, 2025

@xsahil03x - I do not see that option in my UI for this PR. Happy to turn it on - but I just don't see it anywhere.

I think it's because of this: https://github.com/orgs/community/discussions/5634

Our fork lives within our organization, so it isn't available. Best bet may be to simply copy these commits over into another branch you own.

Edit: Which, to be clear, would be totally fine with me. I've signed the CLA and certainly don't require that I'm the author of these commits if the overall approach works!

@dballance
Copy link
Contributor Author

@xsahil03x Anything needed from me here?

@dballance dballance force-pushed the feat/enable-ordering-custom-attachment-handlers branch from 6451b0f to ded8149 Compare October 3, 2025 14:41
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart (1)

146-153: Fix type mismatch and add null-title guard for web/desktop wrapper

  • You’re passing defaultOptions (List) into AttachmentPickerOptionsBuilder (expects List) without a cast. This is unsafe and can break type checks.
  • Also reintroduce a debug assert to ensure titles are non-null before mapping, to prevent runtime crashes when option.title is null on web/desktop.

Apply:

-              optionsBuilder: optionsBuilder == null
-                  ? null
-                  : (context, defaultOptions) {
-                      return optionsBuilder(context, defaultOptions)
-                          .map(WebOrDesktopAttachmentPickerOption
-                              .fromAttachmentPickerOption)
-                          .toList();
-                    },
+              optionsBuilder: optionsBuilder == null
+                  ? null
+                  : (context, defaultOptions) {
+                      final built = optionsBuilder(
+                        context,
+                        defaultOptions.cast<AttachmentPickerOption>(),
+                      );
+                      assert(
+                        built.every((o) => o.title != null),
+                        'Each option must provide a non-null title when using '
+                        'optionsBuilder in web/desktop or with '
+                        'useSystemAttachmentPicker=true.',
+                      );
+                      return built
+                          .map(WebOrDesktopAttachmentPickerOption
+                              .fromAttachmentPickerOption)
+                          .toList();
+                    },

Note: Consider the same assert when mapping customOptions to web/desktop options.

🧹 Nitpick comments (3)
packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart (3)

223-244: Typedefs LGTM; tiny doc nit

Add a space after the comma in “as-is,reorder” -> “as-is, reorder”.


915-921: Enforce allowedTypes on optionsBuilder results (mobile)

As-is, a custom optionsBuilder can reintroduce disallowed types. Filter the builder’s output to keep semantics aligned with allowedTypes.

-  final finalOptions = optionsBuilder != null
-      ? optionsBuilder(context, defaultOptions).toSet()
+  final finalOptions = optionsBuilder != null
+      ? optionsBuilder(context, defaultOptions)
+          .where((o) => o.supportedTypes.every(allowedTypes.contains))
+          .toSet()
       : {
           if (customOptions != null) ...customOptions,
           ...defaultOptions,
         };

981-991: Enforce allowedTypes on optionsBuilder results (web/desktop)

Keep options in sync with allowedTypes when optionsBuilder is used.

-  if (optionsBuilder != null) {
-    finalOptions = optionsBuilder(context, defaultOptions).toSet();
-  } else {
+  if (optionsBuilder != null) {
+    finalOptions = optionsBuilder(context, defaultOptions)
+        .where((o) => o.supportedTypes.every(allowedTypes.contains))
+        .toSet();
+  } else {
     // Legacy behavior: combine custom and default options
     finalOptions = {
       if (customOptions != null) ...customOptions,
       ...defaultOptions,
     };
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6451b0f and ded8149.

📒 Files selected for processing (4)
  • packages/stream_chat_flutter/CHANGELOG.md (1 hunks)
  • packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart (3 hunks)
  • packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart (4 hunks)
  • packages/stream_chat_flutter/test/src/message_input/attachment_picker/options_builder_test.dart (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/stream_chat_flutter/CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/stream_chat_flutter/test/src/message_input/attachment_picker/options_builder_test.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: analyze_legacy_versions
  • GitHub Check: build (android)
  • GitHub Check: test
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat_persistence
🔇 Additional comments (4)
packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.dart (2)

95-100: Good guard: optionsBuilder vs customOptions are mutually exclusive

Assert prevents ambiguous configuration. LGTM.


169-169: Mobile wiring looks correct

Passing optionsBuilder through to the mobile builder keeps behavior consistent.

packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart (2)

784-790: Good guard

Mutual exclusivity assert is appropriate.


944-950: Good guard

Same assert here is sound.

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