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 to the attachment picker (mobile and web/desktop), wires it through the modal bottom sheet, introduces typedefs, enforces exclusivity with customOptions, refactors default/final options computation, updates signatures, adds tests, and documents the change in CHANGELOG.md.

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 params to mobileAttachmentPickerBuilder and webOrDesktopAttachmentPickerBuilder; asserts exclusivity with customOptions; refactors default options and computes finalOptions via optionsBuilder or 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, derives default AttachmentPickerOption list, calls optionsBuilder, maps results to web/desktop option types, and forwards the computed options to mobile/web builders. Legacy behavior preserved when null.
Tests
packages/stream_chat_flutter/test/src/message_input/attachment_picker/options_builder_test.dart
New test suite validating optionsBuilder behavior: combining and ordering with defaults, reordering/filtering, typedef usability, customOptions precedence when optionsBuilder absent, and assertion failure 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
  participant App
  participant Modal as showStreamAttachmentPickerModalBottomSheet
  participant Builder as AttachmentPickerBuilder (Mobile/Web)
  participant Sheet as AttachmentPickerBottomSheet

  App->>Modal: open(..., customOptions?, optionsBuilder?)
  Note over Modal: Assert: not (customOptions && optionsBuilder)

  alt optionsBuilder provided
    Modal->>Builder: call with optionsBuilder
    Note over Builder: build defaultOptions → optionsBuilder(context, defaultOptions)
    Builder->>Builder: finalOptions = optionsBuilder(...)
  else legacy path
    Modal->>Builder: call with customOptions?
    Note over Builder: finalOptions = (customOptions? + defaultOptions)
  end

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

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • renefloor

Poem

I hopped through options, nudged defaults in line,
A builder to reorder — your picker looks fine.
Gallery, file, poll — chosen where they start,
One assert keeps conflicts from tearing apart.
I twitch my nose: choices, sorted with heart. 🐇✨

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 title accurately and concisely summarizes the primary change: adding an optionsBuilder to the attachment picker UI to enable custom ordering of attachment options. It follows conventional commit style, is specific to the change, and is clear for teammates scanning the project history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent 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 3bd377f and 6451b0f.

📒 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 as they are similar to previous changes (4)
  • packages/stream_chat_flutter/test/src/message_input/attachment_picker/options_builder_test.dart
  • packages/stream_chat_flutter/CHANGELOG.md
  • packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart
  • packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker_bottom_sheet.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). (4)
  • GitHub Check: build (android)
  • GitHub Check: build (ios)
  • GitHub Check: test
  • GitHub Check: analyze_legacy_versions

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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 63.85%. Comparing base (ee78069) to head (6451b0f).

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.76%   63.85%   +0.08%     
==========================================
  Files         412      412              
  Lines       25821    25834      +13     
==========================================
+ Hits        16466    16496      +30     
+ Misses       9355     9338      -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!

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