Skip to content

refactor!(ui): add support for customising reaction picker #2248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 45 commits into
base: v10.0.0
Choose a base branch
from

Conversation

xsahil03x
Copy link
Member

@xsahil03x xsahil03x commented May 12, 2025

Resolves: #2225, #2216

Description of the pull request

This pull request Introduces a reactionPickerBuilder parameter in StreamMessageWidget, enabling developers to provide custom reaction picker implementations. Also fixed the issue where overriding the reaction icons in a subtree was not getting used in the reaction picker.

Screen.Recording.2025-05-26.at.02.29.35.mov

Summary by CodeRabbit

  • New Features

    • Added a customizable reaction picker builder to message widgets and modals, allowing for tailored reaction picker UIs.
    • Introduced a unified builder for desktop and web platforms, simplifying platform-specific widget creation.
    • Provided a static list of default reaction icons for easier reaction customization.
    • Added new widgets for displaying user reactions and reaction indicators.
  • Refactor

    • Improved reaction UI components and modularized reaction-related widgets for better maintainability and flexibility.
    • Enhanced platform-specific UI handling for messages on desktop and web.
  • Bug Fixes

    • Corrected and streamlined reaction icon and picker handling for more consistent behavior.
  • Tests

    • Added and updated tests to cover new reaction picker features and platform widget builder behavior.
  • Chores

    • Updated documentation and reorganized exports for reaction-related components.

@xsahil03x xsahil03x changed the title refactor(ui): add support for customising reaction picker refactor!(ui): add support for customising reaction picker May 12, 2025
xsahil03x and others added 11 commits May 12, 2025 17:15
…eat/reactions-v2

# Conflicts:
#	packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart
#	packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart
#	packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart
This reverts commit 9551f18.
@xsahil03x xsahil03x marked this pull request as ready for review May 14, 2025 13:09
Copy link

codecov bot commented May 14, 2025

Codecov Report

Attention: Patch coverage is 71.37809% with 81 lines in your changes missing coverage. Please review.

Please upload report for BASE (v10.0.0@08600ee). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...flutter/lib/src/message_widget/message_widget.dart 58.87% 44 Missing ⚠️
..._flutter/lib/src/reactions/reaction_indicator.dart 4.34% 22 Missing ⚠️
...r/lib/src/message_list_view/message_list_view.dart 6.66% 14 Missing ⚠️
...r/lib/src/reactions/desktop_reactions_builder.dart 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             v10.0.0    #2248   +/-   ##
==========================================
  Coverage           ?   63.44%           
==========================================
  Files              ?      399           
  Lines              ?    25104           
  Branches           ?        0           
==========================================
  Hits               ?    15927           
  Misses             ?     9177           
  Partials           ?        0           

☔ 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.

Base automatically changed from refactor/message-actions to v10.0.0 May 22, 2025 13:37
xsahil03x and others added 9 commits May 22, 2025 16:31
# Conflicts:
#	packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart
#	packages/stream_chat_flutter/lib/src/message_action/message_action_type.dart
#	packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart
#	packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart
#	packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart
#	packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart
#	packages/stream_chat_flutter/lib/src/message_widget/reactions/reaction_picker.dart
#	packages/stream_chat_flutter/lib/src/message_widget/reactions/reaction_picker_icon_list.dart
#	packages/stream_chat_flutter/lib/src/message_widget/reactions/reactions_align.dart
#	packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart
@xsahil03x xsahil03x changed the base branch from v10.0.0 to refactor/sync-theme-colors May 26, 2025 12:54
Base automatically changed from refactor/sync-theme-colors to v10.0.0 May 26, 2025 13:18
xsahil03x and others added 2 commits May 26, 2025 15:22
# Conflicts:
#	packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png
#	packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png
#	packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png
#	packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png
#	packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/reaction_icon_button_selected_light.png
#	packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/reaction_icon_button_unselected_light.png
#	packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/reaction_picker_icon_list_light.png
#	packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/reaction_picker_icon_list_selected_light.png
#	packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/stream_reaction_picker_light.png
#	packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/stream_reaction_picker_selected_light.png
#	packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_2.png
#	packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_dark.png
#	packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_light.png
#	packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_dark.png
#	packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_light.png
# Conflicts:
#	packages/stream_chat_flutter/lib/src/message_action/message_action_type.dart
#	packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart
#	packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart
#	packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart
#	packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart
#	packages/stream_chat_flutter/lib/src/message_widget/message_widget_content_components.dart
#	packages/stream_chat_flutter/lib/src/message_widget/reactions/reaction_picker.dart
#	packages/stream_chat_flutter/lib/src/message_widget/reactions/reactions_align.dart
#	packages/stream_chat_flutter/lib/src/reactions/reaction_picker_icon_list.dart
#	packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png
#	packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png
#	packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png
#	packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png
#	packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart
#	packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_2.png
#	packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_dark.png
#	packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_light.png
#	packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_dark.png
#	packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_light.png
@xsahil03x xsahil03x force-pushed the feat/reactions-v2 branch from 9718aae to 3a60f29 Compare May 26, 2025 15:09
@xsahil03x
Copy link
Member Author

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented May 29, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

coderabbitai bot commented May 29, 2025

Walkthrough

This update introduces enhanced customization for reaction pickers in the stream_chat_flutter package. It adds a reactionPickerBuilder parameter to key widgets, enables a unified builder for desktop and web platforms, and exposes a default set of reaction icons. Internal refactoring and new widgets support these features, and related tests are updated accordingly.

Changes

File(s) Change Summary
platform_widget_builder.dart, platform_widget_builder_test.dart Added desktopOrWeb builder parameter and corresponding test for unified desktop/web widget building.
message_actions_modal.dart, message_reactions_modal.dart, message_widget.dart Added reactionPickerBuilder parameter to allow custom reaction picker injection in modals and message widgets.
reaction_icon.dart, stream_chat_configuration.dart Introduced StreamReactionIcon.defaultReactions and updated configuration to use it as the default.
reaction_picker.dart, reaction_picker_icon_list.dart Added ReactionPickerBuilder typedef and modularized reaction picker icon logic for customization.
reactions_card.dart, reactions/reaction_indicator.dart, message_widget/reactions/reaction_indicator.dart Deleted old reaction card/indicator widgets and introduced new or reorganized equivalents.
user_reactions.dart, reactions/desktop_reactions_builder.dart Added new StreamUserReactions widget and updated usages to replace ReactionsCard.
message_list_view.dart, message_widget_content.dart, message_widget_content_components.dart Refactored internal logic for thread tap handling, reaction indicator layout, and removed obsolete exports.
reactions/reaction_bubble.dart Refactored reaction bubble layout and icon lookup logic for flexibility and fallback.
message_modal.dart Wrapped modal content in Flexible for improved layout.
stream_chat_flutter.dart Reorganized exports for reaction-related components into a dedicated reactions directory.
message_reactions_modal_test.dart, reaction_picker_icon_list_test.dart, reaction_picker_test.dart Updated tests to align with new reaction picker and user reactions widget structure.
app.dart Cached router instance to avoid unnecessary recreation.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant StreamMessageWidget
    participant reactionPickerBuilder (custom or default)
    participant Modal (Actions/Reactions)

    User->>StreamMessageWidget: Long press / context menu on message
    StreamMessageWidget->>Modal: Show actions/reactions modal with reactionPickerBuilder
    Modal->>reactionPickerBuilder: Build custom reaction picker UI
    reactionPickerBuilder->>Modal: Return picker widget
    User->>reactionPickerBuilder: Select reaction
    reactionPickerBuilder->>Modal: onReactionPicked callback
    Modal->>StreamMessageWidget: Update message with new reaction
Loading

Assessment against linked issues

Objective Addressed Explanation
Allow providing a custom reaction picker in message widgets and modals (#2225)

Poem

A rabbit hopped through code one night,
And found new ways to pick delight—
With custom pickers, icons bright,
Reactions now are just your type!
On every platform, web or desk,
The chat feels fun and picturesque.
🐇✨


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 2

🔭 Outside diff range comments (3)
sample_app/lib/app.dart (1)

448-472: ⚠️ Potential issue

Fix observer inconsistency with router caching.

The router caching optimization introduces a potential issue: the LocalNotificationObserver is recreated on every _setupRouter() call, but the cached router retains the original observer reference. This means navigation events may not be properly observed after the first router creation.

Consider one of these solutions:

Solution 1: Cache the observer along with the router

  GoRouter _setupRouter() {
+   if (router != null) {
+     return router!;
+   }
+   
    if (localNotificationObserver != null) {
      localNotificationObserver!.dispose();
    }
    localNotificationObserver = LocalNotificationObserver(
        _initNotifier.initData!.client, _navigatorKey);

    return router ??= GoRouter(
      refreshListenable: _initNotifier,
      initialLocation: Routes.CHANNEL_LIST_PAGE.path,
      navigatorKey: _navigatorKey,
      observers: [localNotificationObserver!],
      redirect: (context, state) {
        // ... rest of the method
      },
      routes: appRoutes,
    );
  }

Solution 2: Reset router when observer needs to be recreated

  GoRouter _setupRouter() {
    if (localNotificationObserver != null) {
      localNotificationObserver!.dispose();
+     router = null; // Force router recreation when observer changes
    }
    localNotificationObserver = LocalNotificationObserver(
        _initNotifier.initData!.client, _navigatorKey);

    return router ??= GoRouter(
      // ... rest unchanged
    );
  }
packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart (1)

123-136: 🛠️ Refactor suggestion

Guard against accidental mutation of the default reaction icon list

StreamReactionIcon.defaultReactions is now used as the global default.
Because lists are mutable, any downstream code could inadvertently add / remove
entries and thereby affect the whole application at runtime.

A small defensive copy (wrapped in List.unmodifiable) makes the intent
explicit and protects against hard-to-trace bugs.

-      reactionIcons: reactionIcons ?? StreamReactionIcon.defaultReactions,
+      reactionIcons:
+          List.unmodifiable(reactionIcons ?? StreamReactionIcon.defaultReactions),
packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart (1)

125-145: 🛠️ Refactor suggestion

Re-initialising animations only when the length changes can miss updates
didUpdateWidget recreates the _iconAnimations list only when reactionIcons.length changes.
If a caller swaps the list with icons of the same length (e.g. reordered or replaced with theme-specific builders), the old animations persist and are now out of sync with the new icons.

-if (oldWidget.reactionIcons.length != widget.reactionIcons.length) {
+if (!const ListEquality().equals(
+      oldWidget.reactionIcons.map((e) => e.type).toList(),
+      widget.reactionIcons.map((e) => e.type).toList(),
+    )) {

Using collection’s ListEquality (already imported) keeps the cost low and guarantees the animations align with the actual icon list.

🧹 Nitpick comments (8)
packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart (1)

59-66: Consider adding empty state handling.

While the current implementation handles null reactions gracefully, consider adding explicit handling for when message.latestReactions is empty to provide better user feedback.

You could add an empty state message:

children: [
  if (message.latestReactions?.isEmpty ?? true)
    Text(
      context.translations.noReactionsLabel,
      style: textTheme.body,
      textAlign: TextAlign.center,
    )
  else
    ...message.latestReactions!.map((reaction) {
      return _UserReactionItem(
        key: Key('${reaction.userId}-${reaction.type}'),
        reaction: reaction,
        onTap: onUserAvatarTap,
      );
    }),
],
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart (1)

1477-1513: Excellent refactor using modern Dart pattern matching!

The refactor from nested if-else statements to a switch expression with tuple pattern matching significantly improves code readability and maintainability. The logic is well-structured with clear cases:

  1. Case 1: Uses provided onThreadTap callback
  2. Case 2: Creates default navigation with StreamChatConfiguration wrapper
  3. Case 3: Falls back to null

The addition of StreamChatConfiguration wrapper in Case 2 (lines 1491-1505) is particularly important as it ensures reaction icons and other configuration are properly propagated to the thread page, which aligns with the PR's reaction picker customization objectives.

Consider adding tests for the new switch expression cases to improve coverage, though this refactor preserves existing behavior and the logic is straightforward.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 1481-1482: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1481-L1482
Added lines #L1481 - L1482 were not covered by tests


[warning] 1484-1484: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1484
Added line #L1484 was not covered by tests


[warning] 1490-1491: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1490-L1491
Added lines #L1490 - L1491 were not covered by tests


[warning] 1494-1497: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1494-L1497
Added lines #L1494 - L1497 were not covered by tests


[warning] 1499-1500: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1499-L1500
Added lines #L1499 - L1500 were not covered by tests


[warning] 1502-1502: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1502
Added line #L1502 was not covered by tests


[warning] 1507-1508: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1507-L1508
Added lines #L1507 - L1508 were not covered by tests

packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart (1)

180-189: Nit: prefer const where possible

Center and StreamGradientAvatar (with only compile-time literals) can be
marked const, enabling a tiny performance gain and clearer intent.

-    return Center(
-      child: StreamGradientAvatar(
+    return const Center(
+      child: StreamGradientAvatar(
packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart (1)

108-118: Avoid repeated O(N²) look-ups when rendering many reactions

firstWhere is executed once per reaction, scanning the full
reactionIcons list each time.
With many, custom reaction sets this becomes unnecessarily expensive.

Consider building a Map<String, StreamReactionIcon> once and doing O(1)
look-ups inside the loop.

-    final reactionIcon = reactionIcons.firstWhere(
-      (it) => it.type == reaction.type,
-      orElse: () => const StreamReactionIcon.unknown(),
-    );
+    final reactionIcon =
+        _iconCache.putIfAbsent(reaction.type, () {
+          return reactionIcons.firstWhere(
+            (it) => it.type == reaction.type,
+            orElse: () => const StreamReactionIcon.unknown(),
+          );
+        });

Where _iconCache is a small final _iconCache = <String, StreamReactionIcon>{};
kept in the widget (or passed in).

This keeps the hot build path lean without altering behaviour.

packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart (1)

885-891: Context-menu reaction picker lacks width constraints

On desktop/web the picker widget is inserted directly into the context-menu
column. A custom picker with a fixed width could overflow or push the standard
action items off-screen.

Consider wrapping the builder result in a ConstrainedBox or allowing the menu
to scroll.

-        widget.reactionPickerBuilder(
+        ConstrainedBox(
+          constraints: const BoxConstraints(maxWidth: 280),
+          child: widget.reactionPickerBuilder(
             context,
             message,
             (reaction) => onActionTap(
               SelectReaction(message: message, reaction: reaction),
             ),
           ),
+        ),
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 885-885: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L885
Added line #L885 was not covered by tests


[warning] 888-888: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L888
Added line #L888 was not covered by tests

packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart (3)

163-170: Avoid O(m × n) look-ups when determining selected icons
Inside the mapIndexed loop we rebuild ownReactions and firstWhereOrNull for every icon. For long reaction sets this quickly becomes quadratic.

-        ...widget.reactionIcons.mapIndexed((index, icon) {
+        final ownTypes = {
+          for (final r in widget.message.ownReactions ?? const []) r.type
+        };
+        ...widget.reactionIcons.mapIndexed((index, icon) {
           bool reactionCheck(Reaction reaction) => reaction.type == icon.type;
-          final ownReactions = [...?widget.message.ownReactions];
-          final reaction = ownReactions.firstWhereOrNull(reactionCheck);
+          final isSelected = ownTypes.contains(icon.type);

This flattens the complexity to O(m + n) and eliminates repeated list allocations.

Also applies to: 166-168


262-266: Hard-coded size limits customisability
icon.builder(context, icon.isSelected, 24) locks the icon to 24 dp. Consider forwarding the IconButton.iconSize or exposing a size property on ReactionPickerIcon so theme builders can resize consistently with text scale factors.


270-276: Minor: the arrow-function needn’t return a void result
onPressed ultimately expects a void callback, yet the closure returns the (void) result of onPressed(type). Omitting the return avoids an unnecessary expression and silences the “value of type ‘void’ used” lint in strict analysis.

-final onPressed? => () {
-    final type = icon.type;
-    return onPressed(type);
-  },
+final onPressed? => () => onPressed(icon.type),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08600ee and 1c8a86f.

⛔ Files ignored due to path filters (33)
  • packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_modal/goldens/ci/stream_message_reactions_modal_reversed_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/reaction_icon_button_selected_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/reaction_icon_button_selected_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/reaction_icon_button_unselected_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/reaction_icon_button_unselected_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/reaction_picker_icon_list_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/reaction_picker_icon_list_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/reaction_picker_icon_list_selected_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/reaction_picker_icon_list_selected_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/stream_reaction_picker_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/stream_reaction_picker_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/stream_reaction_picker_selected_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/reactions/goldens/ci/stream_reaction_picker_selected_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_3_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/reaction_bubble_like_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/reactions/goldens/ci/reaction_icon_button_selected_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/reactions/goldens/ci/reaction_icon_button_selected_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/reactions/goldens/ci/reaction_icon_button_unselected_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/reactions/goldens/ci/reaction_icon_button_unselected_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/reactions/goldens/ci/reaction_picker_icon_list_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/reactions/goldens/ci/reaction_picker_icon_list_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/reactions/goldens/ci/reaction_picker_icon_list_selected_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/reactions/goldens/ci/reaction_picker_icon_list_selected_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/reactions/goldens/ci/stream_reaction_picker_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/reactions/goldens/ci/stream_reaction_picker_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/reactions/goldens/ci/stream_reaction_picker_selected_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/reactions/goldens/ci/stream_reaction_picker_selected_light.png is excluded by !**/*.png
📒 Files selected for processing (26)
  • packages/stream_chat_flutter/CHANGELOG.md (1 hunks)
  • packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_builder.dart (2 hunks)
  • packages/stream_chat_flutter/lib/src/message_action/message_action_type.dart (2 hunks)
  • packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart (4 hunks)
  • packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart (5 hunks)
  • packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart (8 hunks)
  • packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart (2 hunks)
  • packages/stream_chat_flutter/lib/src/message_widget/message_widget_content_components.dart (0 hunks)
  • packages/stream_chat_flutter/lib/src/message_widget/reactions/reaction_indicator.dart (0 hunks)
  • packages/stream_chat_flutter/lib/src/message_widget/reactions/reactions_card.dart (0 hunks)
  • packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart (2 hunks)
  • packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart (2 hunks)
  • packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart (5 hunks)
  • packages/stream_chat_flutter/lib/src/reactions/reaction_bubble.dart (2 hunks)
  • packages/stream_chat_flutter/lib/src/reactions/reaction_indicator.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/stream_chat_configuration.dart (2 hunks)
  • packages/stream_chat_flutter/lib/stream_chat_flutter.dart (1 hunks)
  • packages/stream_chat_flutter/test/platform_widget_builder/platform_widget_builder_test.dart (1 hunks)
  • packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart (2 hunks)
  • packages/stream_chat_flutter/test/src/reactions/reaction_picker_icon_list_test.dart (5 hunks)
  • packages/stream_chat_flutter/test/src/reactions/reaction_picker_test.dart (1 hunks)
  • sample_app/lib/app.dart (2 hunks)
💤 Files with no reviewable changes (3)
  • packages/stream_chat_flutter/lib/src/message_widget/message_widget_content_components.dart
  • packages/stream_chat_flutter/lib/src/message_widget/reactions/reaction_indicator.dart
  • packages/stream_chat_flutter/lib/src/message_widget/reactions/reactions_card.dart
🧰 Additional context used
🪛 LanguageTool
packages/stream_chat_flutter/CHANGELOG.md

[style] ~50-~50: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...nable custom reaction picker widgets. - Added StreamReactionIcon.defaultReactions p...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🪛 GitHub Check: codecov/patch
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart

[warning] 1481-1482: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1481-L1482
Added lines #L1481 - L1482 were not covered by tests


[warning] 1484-1484: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1484
Added line #L1484 was not covered by tests


[warning] 1490-1491: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1490-L1491
Added lines #L1490 - L1491 were not covered by tests


[warning] 1494-1497: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1494-L1497
Added lines #L1494 - L1497 were not covered by tests


[warning] 1499-1500: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1499-L1500
Added lines #L1499 - L1500 were not covered by tests


[warning] 1502-1502: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1502
Added line #L1502 was not covered by tests


[warning] 1507-1508: packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart#L1507-L1508
Added lines #L1507 - L1508 were not covered by tests

packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart

[warning] 547-547: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L547
Added line #L547 was not covered by tests


[warning] 679-679: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L679
Added line #L679 was not covered by tests


[warning] 683-683: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L683
Added line #L683 was not covered by tests


[warning] 688-688: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L688
Added line #L688 was not covered by tests


[warning] 693-695: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L693-L695
Added lines #L693 - L695 were not covered by tests


[warning] 699-699: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L699
Added line #L699 was not covered by tests


[warning] 701-701: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L701
Added line #L701 was not covered by tests


[warning] 703-704: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L703-L704
Added lines #L703 - L704 were not covered by tests


[warning] 752-756: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L752-L756
Added lines #L752 - L756 were not covered by tests


[warning] 885-885: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L885
Added line #L885 was not covered by tests


[warning] 888-888: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L888
Added line #L888 was not covered by tests


[warning] 908-910: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L908-L910
Added lines #L908 - L910 were not covered by tests


[warning] 916-916: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L916
Added line #L916 was not covered by tests


[warning] 919-920: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L919-L920
Added lines #L919 - L920 were not covered by tests


[warning] 922-923: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L922-L923
Added lines #L922 - L923 were not covered by tests


[warning] 925-925: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L925
Added line #L925 was not covered by tests


[warning] 927-927: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L927
Added line #L927 was not covered by tests


[warning] 929-929: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L929
Added line #L929 was not covered by tests


[warning] 931-931: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L931
Added line #L931 was not covered by tests


[warning] 940-942: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L940-L942
Added lines #L940 - L942 were not covered by tests


[warning] 1035-1035: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L1035
Added line #L1035 was not covered by tests


[warning] 1038-1039: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L1038-L1039
Added lines #L1038 - L1039 were not covered by tests


[warning] 1041-1041: packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart#L1041
Added line #L1041 was not covered by tests

🔇 Additional comments (34)
packages/stream_chat_flutter/lib/src/message_action/message_action_type.dart (2)

75-78: Consistent multi-line constructor formatting for MuteUser
This change brings the MuteUser constructor in line with the existing style (multi-line params with a trailing comma), improving readability and diff clarity.


87-90: Consistent multi-line constructor formatting for UnmuteUser
Similarly, updating UnmuteUser to use the same parameter-format style ensures consistency across message action types.

sample_app/lib/app.dart (1)

437-437: Good addition of router caching field.

The nullable GoRouter? router field enables router caching to improve performance by avoiding unnecessary recreations.

packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_builder.dart (3)

28-28: LGTM: Well-designed parameter addition.

The desktopOrWeb parameter provides a clean way to specify a unified widget for both desktop and web platforms while maintaining backward compatibility.


43-47: Excellent documentation and design.

The documentation clearly explains the precedence behavior, ensuring developers understand that explicit desktop or web builders will override the desktopOrWeb fallback.


51-58: Clean fallback implementation.

The null-aware operator usage provides clean fallback logic that correctly prioritizes specific platform builders over the unified desktopOrWeb builder.

packages/stream_chat_flutter/lib/src/message_modal/message_modal.dart (1)

144-144: Good layout improvement.

Wrapping the content in Flexible prevents potential overflow issues and allows the modal content to adapt flexibly to available space within the Column layout.

packages/stream_chat_flutter/test/src/reactions/reaction_picker_test.dart (1)

186-186: Consistent theming update.

The background color change from appBg to overlay aligns the test environment with the modal/overlay theming used in the actual application, ensuring visual consistency.

packages/stream_chat_flutter/lib/src/reactions/desktop_reactions_builder.dart (1)

103-103: Good component modernization.

The replacement of ReactionsCard with StreamUserReactions simplifies the API by removing explicit currentUser and messageTheme parameters, suggesting better internal encapsulation and cleaner component interfaces.

packages/stream_chat_flutter/test/src/message_modal/message_reactions_modal_test.dart (1)

70-70: LGTM! Test updates correctly reflect widget refactoring.

The replacement of ReactionsCard with StreamUserReactions in test expectations properly aligns with the main codebase refactoring described in the AI summary.

Also applies to: 96-96

packages/stream_chat_flutter/test/platform_widget_builder/platform_widget_builder_test.dart (1)

75-98: Good test coverage for the new desktopOrWeb feature.

The test properly verifies that PlatformWidgetBuilder correctly handles the new desktopOrWeb builder parameter across multiple desktop platforms and web (using the Fuchsia hack). The test structure is consistent with existing tests.

packages/stream_chat_flutter/test/src/reactions/reaction_picker_icon_list_test.dart (2)

57-63: Well-implemented test updates for ReactionPickerIcon abstraction.

The changes correctly implement the new ReactionPickerIcon abstraction, properly extracting type and builder properties from the original reactionIcons.first. The updated callback signature with the type parameter aligns with the new OnReactionPickerIconPressed typedef.

Also applies to: 80-86, 109-114, 125-131


385-385: Background color change aligns with UI updates.

The change from theme.colorTheme.appBg to theme.colorTheme.overlay for the scaffold background is consistent with UI theming updates mentioned in the AI summary.

packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker.dart (2)

4-19: Excellent API design with comprehensive documentation.

The ReactionPickerBuilder typedef provides a clean, well-documented interface for customizing reaction pickers. The template documentation clearly explains usage in modal components and parameter purposes.


111-114: Good improvement from DecoratedBox to Material widget.

Changing from DecoratedBox to Material follows Flutter best practices and provides better platform integration, touch feedback, and accessibility while maintaining the same visual properties (border radius, color, clip behavior).

packages/stream_chat_flutter/lib/stream_chat_flutter.dart (1)

106-110: LGTM! Well-organized export restructuring.

The reorganization of reaction-related exports from message_widget/reactions to a dedicated reactions directory improves code organization and makes the reaction functionality more discoverable. The export paths are correctly structured and follow Flutter package conventions.

packages/stream_chat_flutter/CHANGELOG.md (1)

46-50: Clear and comprehensive feature documentation.

The changelog entries effectively document the new features being introduced. The descriptions are concise yet informative, properly explaining the purpose and scope of each addition. The formatting is consistent with existing changelog conventions.

🧰 Tools
🪛 LanguageTool

[style] ~50-~50: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...nable custom reaction picker widgets. - Added StreamReactionIcon.defaultReactions p...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart (4)

4-4: Import path correctly updated for reorganized directory structure.

The import path has been properly updated to reflect the movement of desktop_reactions_builder.dart from message_widget/reactions to the new reactions directory structure.


288-294: Simplified reaction indicator configuration.

The portalFollower is now always assigned a ReactionIndicator instance, removing the previous conditional logic. This simplification appears to streamline the reaction display logic while maintaining functionality.


296-300: Enhanced anchor positioning with improved alignment controls.

The anchor configuration has been restructured with more explicit alignment properties and added offset and shiftToWithinBound parameters. This provides better control over reaction indicator positioning and boundary handling.


306-306: Verify visual impact of increased padding.

The top padding has been increased from 18 to 28 pixels when reactions are shown. This change affects the visual spacing around the reaction indicator.

Please review the visual impact of this padding change to ensure it doesn't create excessive spacing or alignment issues with the reaction indicator layout, especially across different screen sizes and orientations.

packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart (2)

15-75: Well-structured widget implementation with good Material Design principles.

The StreamUserReactions widget is well-implemented with:

  • Proper theme integration using StreamChatTheme
  • Responsive layout with Flexible and SingleChildScrollView
  • Appropriate use of Material Design with rounded corners and theme-based colors
  • Good accessibility with proper text styling and localized strings

92-92: Good null safety handling for reaction users.

The null check for reactionUser properly handles cases where reaction data might be incomplete, returning an empty widget instead of causing runtime errors.

packages/stream_chat_flutter/lib/src/message_modal/message_actions_modal.dart (3)

2-2: Import path updated for folder reorganization.

The import path change from src/message_widget/reactions/reactions_align.dart to src/reactions/reactions_align.dart aligns with the broader package reorganization to consolidate reaction-related components into a dedicated reactions folder.


24-24: Well-implemented builder pattern for reaction picker customization.

The addition of the reactionPickerBuilder parameter with a sensible default (StreamReactionPicker.builder) enables customization while maintaining backward compatibility. This follows the established builder pattern conventions in the Flutter ecosystem.

Also applies to: 62-63


100-100: Clean abstraction using the injected builder.

Replacing the direct StreamReactionPicker instantiation with the injected reactionPickerBuilder function call properly abstracts the reaction picker creation, allowing for flexible customization while maintaining the same interface.

packages/stream_chat_flutter/lib/src/reactions/reaction_indicator.dart (2)

38-57: Robust reaction filtering and sorting logic.

The implementation correctly handles reaction deduplication and sorting:

  1. Filtering (lines 38-45): Ensures only one reaction per type, prioritizing the current user's reactions when duplicates exist
  2. Sorting (lines 47-57): Properly handles null user IDs and places current user's reactions last for better UX

The logic handles edge cases well, including null user IDs and ensures consistent behavior.


59-72: Clean widget composition with proper theming.

The widget structure is well-designed:

  • GestureDetector for interaction handling
  • StreamReactionBubble for consistent visual presentation
  • Proper integration with messageTheme properties for styling consistency

The use of ValueKey with message ID ensures proper widget rebuilding when reactions change.

packages/stream_chat_flutter/lib/src/misc/reaction_icon.dart (3)

26-29: Useful unknown reaction constructor for fallback scenarios.

The unknown constructor provides a sensible fallback for unrecognized reaction types, using a help icon which clearly indicates an unknown state to users.


37-54: Well-documented default reactions list.

The defaultReactions static list provides a comprehensive set of common reactions (love, like, sad, haha, wow) with excellent documentation. This serves as both a ready-to-use default and a clear example for developers implementing custom reactions.


56-162: Consistent and well-structured reaction builders.

All reaction builders follow a consistent pattern:

  • Proper theme integration using StreamChatTheme.of(context)
  • Clean color logic with switch expressions for highlighted/unhighlighted states
  • Consistent use of StreamSvgIcon for SVG assets and standard Icon for the unknown fallback
  • Appropriate parameterization (context, highlighted state, size)

The color switching between accentPrimary and primaryIconTheme.color provides clear visual feedback for user interaction states.

packages/stream_chat_flutter/lib/src/message_modal/message_reactions_modal.dart (1)

93-96: Null-safety check for onReactionPicked

reactionPickerBuilder is invoked with onReactionPicked, which can be null
(line 84). If a custom builder assumes a non-null callback it will throw at
runtime.

Either:

  1. Document that the third parameter may be null, or
  2. Pass a default no-op callback when showReactionPicker == true.
-              child: reactionPickerBuilder(context, message, onReactionPicked),
+              child: reactionPickerBuilder(
+                context,
+                message,
+                onReactionPicked ?? (_) {},
+              ),

Please pick one approach so custom builders have a clear contract.

packages/stream_chat_flutter/lib/src/reactions/picker/reaction_picker_icon_list.dart (2)

54-66: Great addition – open-ended iconBuilder unlocks theming flexibility
The new iconBuilder parameter (with a sensible default) decouples rendering from business logic, allowing callers to plug in fully-custom widgets – nice!


213-238: Documentation ✨
Clear, concise docs for ReactionPickerIcon make the new data object self-explanatory – nicely done.

),
const SizedBox(height: 8),
Text(
reactionUser.name.split(' ')[0],
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve robustness of name parsing logic.

The current implementation uses reactionUser.name.split(' ')[0] to extract the first name, which assumes the name contains spaces and could fail for single-word names or names with different formatting.

Consider using a more robust approach:

-        Text(
-          reactionUser.name.split(' ')[0],
+        Text(
+          reactionUser.name.split(' ').firstOrNull ?? reactionUser.name,
           style: theme.textTheme.footnoteBold,
           textAlign: TextAlign.center,
           overflow: TextOverflow.ellipsis,
           maxLines: 1,
         ),

Alternatively, you could use a helper method to safely extract the first name:

String _getFirstName(String fullName) {
  final parts = fullName.trim().split(' ');
  return parts.isNotEmpty ? parts.first : fullName;
}
🤖 Prompt for AI Agents
In packages/stream_chat_flutter/lib/src/reactions/user_reactions.dart at line
137, the current code uses reactionUser.name.split(' ')[0] to get the first
name, which can fail if the name has no spaces or unusual formatting. Replace
this with a helper method that trims the name, splits by spaces, and returns the
first part if available, otherwise returns the full name. Then use this helper
method to safely extract the first name.

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.

Custom Reaction picker Reactions per channel like in React Native
2 participants