Skip to content

Conversation

@xsahil03x
Copy link
Member

@xsahil03x xsahil03x commented Sep 18, 2025

Fixes: #2369

Submit a pull request

This PR fixes an issue where GradientAvatars for users with same-length IDs would have identical colors.

The GradientAvatarPainter now uses Random(userId.hashCode) instead of Random(userId.length) to generate colors and transform points. This ensures that different user IDs, even if they have the same length, will produce visually distinct avatars.

A regression test (gradient_avatar_issue_2369) has been added to verify this fix, including the specific scenario reported in GitHub issue #2369.

Additionally, the golden test theme in flutter_test_config.dart has been updated with a new background color, border color, name text style, and padding for improved visual clarity in golden tests.

Summary by CodeRabbit

  • New Features

    • Avatars now render deterministic polygon gradients, offer configurable jitter intensity, and show improved initials extraction and sizing.
    • Added a reusable jitter utility for deterministic, seeded displacement (re-exported for use).
  • Bug Fixes / Improvements

    • Fixed identical-gradient issue for users with same-length IDs; avatar colors now vary and remain stable.
    • Enforced exact avatar sizing in channel info for consistent display.
  • Tests

    • Added regression/golden tests for avatar colors and unit tests for jitter; test helpers updated.
  • Documentation

    • Changelog updated with the gradient avatar fix.

This commit fixes an issue where GradientAvatars for users with same-length IDs would have identical colors.

The `GradientAvatarPainter` now uses `Random(userId.hashCode)` instead of `Random(userId.length)` to generate colors and transform points. This ensures that different user IDs, even if they have the same length, will produce visually distinct avatars.

A regression test (`gradient_avatar_issue_2369`) has been added to verify this fix, including the specific scenario reported in GitHub issue #2369.

Additionally, the golden test theme in `flutter_test_config.dart` has been updated with a new background color, border color, name text style, and padding for improved visual clarity in golden tests.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (20)
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_issue_2369.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_light.png is excluded by !**/*.png

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Converted gradient avatar rendering to a deterministic, stateless implementation using userId.hashCode-based palette selection; added Jitter utility, PolygonCell and refactored PolygonGradientPainter; added String.initials, tests (golden and unit), test fakes for connectivity/path provider, and updated changelog for the avatar color bugfix.

Changes

Cohort / File(s) Summary
Gradient avatar implementation
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart
Converted StreamGradientAvatar to StatelessWidget with public jitterIntensity; deterministic gradient selection from private _palettes using userId.hashCode; added _Initials widget; refactored PolygonGradientPainter API (named params: rows, columns, jitterSeed, jitterIntensity, required gradient); removed Offset4 and exported colorGradients; added PolygonCell.
Jitter utility & exports
packages/stream_chat_flutter/lib/src/utils/jitter.dart
packages/stream_chat_flutter/lib/src/utils/utils.dart
Added public Jitter class (seeded/deterministic RNG, intensity clamping, presets, applyTo(Offset,maxDx,maxDy)); re-exported jitter.dart via utils.dart.
String extension
packages/stream_chat_flutter/lib/src/utils/extensions.dart
Added String? get initials (Unicode-aware, returns 1–2 uppercase initials or null).
Tests: gradient avatar & helpers
packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart
Added regression/golden test for #2369; new test widgets AvatarComparisonRow, _AvatarItem, AvatarComparisonTestWidget to assert color variation and consistency across ID patterns.
Tests: jitter unit tests
packages/stream_chat_flutter/test/src/utils/jitter_test.dart
New comprehensive unit tests for Jitter covering clamping, presets, deterministic seeding, applyTo bounds and behavior.
Tests: connectivity/path provider fakes & updates
packages/stream_chat_flutter/test/src/fakes.dart
packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart
packages/stream_chat_flutter/test/src/channel/channel_header_test.dart
Added FakeConnectivityPlatform; imported connectivity/path-provider fakes; tests call TestWidgetsFlutterBinding.ensureInitialized() and override ConnectivityPlatform and PathProviderPlatform with fakes; group avatar test simplified to use concrete Member/User instances.
Dialog tweak
packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart
Changed second avatar sizing from max constraints to exact BoxConstraints.tightFor(height:64,width:64).
Changelog
packages/stream_chat_flutter/CHANGELOG.md
Added Upcoming bugfix entry: "Fixed GradientAvatars for users with same-length IDs would have identical colors (#2369)"; removed an unrelated entry.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App
  participant Widget as StreamGradientAvatar (Stateless)
  participant Palette as _palettes
  participant Jitter as Jitter (seeded)
  participant Painter as PolygonGradientPainter
  participant Canvas

  App->>Widget: build(name, userId, jitterIntensity)
  Widget->>Palette: select gradient (index = userId.hashCode % palettes.length)
  Widget->>Jitter: Jitter(seed: userId.hashCode, intensity: jitterIntensity)
  Widget->>Painter: paint(size, rows, cols, jitter, gradient)
  Painter->>Jitter: sample jittered grid points
  Painter->>Canvas: draw PolygonCell tiles with gradient
  Canvas-->>Widget: background rendered
  Widget->>Widget: overlay `_Initials` centered
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • renefloor
  • Brazol

Poem

A hop, a seed, a patterned trail,
Hashes nudge petals, never fail.
Each user blooms in a different hue,
Polygons hum — initials peek through.
Rabbit claps paws, deterministic and true. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning Although the seed fix is present, the changeset includes numerous unrelated public API and behavior changes—StreamGradientAvatar converted to StatelessWidget with a new jitterIntensity param, PolygonGradientPainter constructor and public fields were refactored, Offset4 and exported colorGradients were removed, PolygonCell and a private palettes list were added, a new Jitter utility (and export) and a String.initials extension were introduced, plus multiple test harness and UI tweaks—which go beyond the narrow scope of fixing the avatar seed and may introduce breaking changes for downstream users. Split the work: isolate the seed change and its regression test into a focused PR and move the refactor/API changes (PolygonGradientPainter signature change, new Jitter and PolygonCell, removals of Offset4/colorGradients, public API surface changes) into a separate, documented breaking-change PR, or revert the unrelated public API changes and update the changelog and versioning if the refactor must be merged now.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "fix(ui): use userId.hashCode for GradientAvatar colors" is a concise, single-sentence summary that accurately describes the primary change in the PR (switching the GradientAvatar seed to userId.hashCode) and aligns with the linked issue #2369 and the PR objectives.
Linked Issues Check ✅ Passed The PR implements the linked issue #2369 by replacing length-based randomness with a per-user deterministic seed (Random(userId.hashCode)), and it adds a regression golden test that verifies distinct avatar colors for same-length IDs, meeting the core objectives of ensuring per-user visual variation and providing a reproducible verification path.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

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

🧹 Nitpick comments (5)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (4)

176-181: Guard nextInt() against zero/negative bounds for tiny avatar sizes.

When size.width/height < grid counts, ~/ can yield 0 causing RangeError in nextInt(0). Clamp to at least 1.

-      final dx = sign1 * 0.6 * _rand.nextInt(size.width ~/ columnCount);
-      final dy = sign2 * 0.6 * _rand.nextInt(size.height ~/ rowCount);
+      final dxRange = max(1, (size.width / columnCount).floor());
+      final dyRange = max(1, (size.height / rowCount).floor());
+      final dx = sign1 * 0.6 * _rand.nextInt(dxRange);
+      final dy = sign2 * 0.6 * _rand.nextInt(dyRange);

156-158: Repaint logic should depend on inputs; always-false can skip updates.

If name/userId/font changes, the painter should repaint.

-  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
+  bool shouldRepaint(covariant PolygonGradientPainter old) =>
+      old.userId != userId ||
+      old.username != username ||
+      old.fontFamily != fontFamily;

123-126: Naming nit: smallerSide is actually the larger side.

Minor readability fix.

-    final smallerSide = size.width > size.height ? size.width : size.height;
-    final textSize = smallerSide / 3;
+    final longerSide = size.width > size.height ? size.width : size.height;
+    final textSize = longerSide / 3;

94-117: Micro-alloc/perf: avoid toList/indexOf in the inner loop.

Not hot with 5x5, but trivial to tidy: maintain a Map<Offset,int> as you insert into points, or build the full list once after the loops and compute indices from arithmetic instead of indexOf.

packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart (1)

188-228: Drop Expanded — redundant with Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly)

Expanded is unnecessary here; return Padding directly as shown.

-      children: users.map((userData) {
+      children: users.map((userData) {
         final (userId, userName) = userData;
-        return Expanded(
-          child: Padding(
-            padding: EdgeInsets.symmetric(horizontal: spacing / 2),
-            child: _AvatarItem(
-              userId: userId,
-              userName: userName,
-              avatarSize: avatarSize,
-            ),
-          ),
-        );
+        return Padding(
+          padding: EdgeInsets.symmetric(horizontal: spacing / 2),
+          child: _AvatarItem(
+            userId: userId,
+            userName: userName,
+            avatarSize: avatarSize,
+          ),
+        );
       }).toList(),

Verified repo SDK: ^3.6.2 — Dart 3 records are supported.

📜 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 ee78069 and a57b315.

⛔ Files ignored due to path filters (4)
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (3 hunks)
  • packages/stream_chat_flutter/test/flutter_test_config.dart (2 hunks)
  • packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart (2 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). (10)
  • GitHub Check: update_goldens
  • GitHub Check: build (android)
  • GitHub Check: test
  • GitHub Check: analyze
  • GitHub Check: stream_chat
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: stream_chat_persistence
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat_localizations
  • GitHub Check: analyze_legacy_versions
🔇 Additional comments (3)
packages/stream_chat_flutter/test/flutter_test_config.dart (2)

5-5: Import addition is correct.

Required for Color/EdgeInsets/TextStyle usage in the theme.


16-25: Global golden theme change — confirm baseline updates and CI stability.

This alters all golden snapshots. Ensure all affected baselines were regenerated and that CI (with PlatformGoldens disabled) still renders consistently on your goldens runner.

packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart (1)

124-178: Regression goldens for issue #2369 — LGTM.

Good coverage: same-length numeric/alphabetic IDs, mixed lengths, and identical IDs.

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

📜 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 a57b315 and 5b98b23.

⛔ Files ignored due to path filters (133)
  • packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_idle_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playing_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/attachment/goldens/ci/stream_voice_recording_attachment_playlist_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_issue_2369.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/attachment_modal_sheet_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/edit_message_sheet_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/bottom_sheets/goldens/ci/error_alert_sheet_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/channel/goldens/ci/channel_header_bottom_widget.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/context_menu_items/goldens/ci/download_menu_item_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/context_menu_items/goldens/ci/stream_chat_context_menu_item_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/dialogs/goldens/ci/confirmation_dialog_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/dialogs/goldens/ci/delete_message_dialog_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/dialogs/goldens/ci/message_dialog_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_footer_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/gallery/goldens/ci/gallery_header_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/icons/goldens/ci/stream_svg_icon_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/indicators/goldens/ci/sending_indicator_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/indicators/goldens/ci/upload_progress_indicator_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_idle_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_hold_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_locked_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/audio_recorder/goldens/ci/stream_audio_recorder_button_recording_stopped_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/goldens/ci/attachment_button_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/goldens/ci/clear_input_item_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/goldens/ci/command_button_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/goldens/ci/countdown_button_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/goldens/ci/dm_checkbox_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/goldens/ci/dm_checkbox_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_input/goldens/ci/dm_checkbox_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_custom.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/goldens/ci/deleted_message_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/message_widget/goldens/ci/message_text.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/misc/goldens/ci/stream_audio_waveform_slider_custom_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_custom_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_empty_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_inverted_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_less_data_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_progress_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_audio_waveform_slider_progress_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/stream_timestamp_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/system_message_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/misc/goldens/ci/system_message_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_error_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_option_reorderable_list_view_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_error_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/poll_question_text_field_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_full_screen_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/creator/goldens/ci/stream_poll_creator_full_screen_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_error.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_option_reorderable_list_view_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_error.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/poll_question_text_field_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_full_screen_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_creator_full_screen_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_with_initial_value_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_add_comment_dialog_with_initial_value_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_end_vote_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_long_question_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_all_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_disabled_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_limited_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_header_subtitle_voting_mode_unique_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/poll_suggest_option_dialog_with_initial_option_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/draft_scroll_view/goldens/ci/stream_draft_list_tile_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_unread_threads_banner_light.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • packages/stream_chat_flutter/CHANGELOG.md (1 hunks)
  • packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/stream_chat_flutter/test/src/avatars/gradient_avatar_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). (9)
  • GitHub Check: build (android)
  • GitHub Check: test
  • GitHub Check: analyze
  • GitHub Check: update_goldens
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat_persistence
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: analyze_legacy_versions

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

🧹 Nitpick comments (2)
packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart (2)

294-309: Nit: mainAxisAlignment is redundant with Expanded children.

With all children wrapped in Expanded, MainAxisAlignment.spaceEvenly has no visible effect. Remove for clarity.

   return Row(
-      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
       children: users.map((userData) {

364-371: Default brightness for deterministic goldens (optional).

Setting a non-null default avoids accidental theme drift in screenshots.

-Widget _wrapWithMaterialApp(
-  Widget widget, {
-  Brightness? brightness,
-}) {
+Widget _wrapWithMaterialApp(
+  Widget widget, {
+  Brightness brightness = Brightness.light,
+}) {
📜 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 8823c72 and 0d515a9.

📒 Files selected for processing (1)
  • packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart (2 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). (6)
  • GitHub Check: test
  • GitHub Check: build (ios)
  • GitHub Check: build (android)
  • GitHub Check: analyze_legacy_versions
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat_localizations
🔇 Additional comments (3)
packages/stream_chat_flutter/test/src/avatars/gradient_avatar_test.dart (3)

1-1: No action needed.

File-level lint ignore is reasonable for test descriptions.


271-285: Dart 3 SDK constraint confirmed — no action required.

packages/stream_chat_flutter/pubspec.yaml sets environment.sdk = ^3.6.2 (>=3.6.2 <4.0.0), so Dart 3 features like records are supported.


118-132: Golden present — verify it's up-to-date and CI fonts/theme are fixed

  • Found golden: test/src/avatars/goldens/ci/gradient_avatar_issue_2369.png — ensure this image was generated from the current test output and updated if needed.
  • Found test config: test/flutter_test_config.dart — confirm CI loads this config (locks fonts and theme) to avoid cross-platform golden flakiness.

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

🧹 Nitpick comments (1)
packages/stream_chat_flutter/test/src/channel/channel_header_test.dart (1)

13-18: Optional: switch to setUpAll/tearDownAll to reduce per-test churn.

Functionally fine as-is. Using per-file setup keeps the override stable across tests and avoids repeated writes.

Apply:

-  TestWidgetsFlutterBinding.ensureInitialized();
-
-  final originalPathProviderPlatform = PathProviderPlatform.instance;
-  setUp(() => PathProviderPlatform.instance = FakePathProviderPlatform());
-  tearDown(() => PathProviderPlatform.instance = originalPathProviderPlatform);
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  late final PathProviderPlatform originalPathProviderPlatform;
+  setUpAll(() {
+    originalPathProviderPlatform = PathProviderPlatform.instance;
+    PathProviderPlatform.instance = FakePathProviderPlatform();
+  });
+  tearDownAll(() {
+    PathProviderPlatform.instance = originalPathProviderPlatform;
+  });
📜 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 0d515a9 and 18b5077.

⛔ Files ignored due to path filters (18)
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_issue_2369.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_light.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • packages/stream_chat_flutter/test/src/channel/channel_header_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). (6)
  • GitHub Check: build (android)
  • GitHub Check: build (ios)
  • GitHub Check: test
  • GitHub Check: analyze_legacy_versions
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat_flutter
🔇 Additional comments (2)
packages/stream_chat_flutter/test/src/channel/channel_header_test.dart (2)

8-8: No action required — FakePathProviderPlatform mixes in MockPlatformInterfaceMixin.
Verified in packages/stream_chat_flutter/test/src/fakes.dart; token checks will pass.


5-5: Declare path_provider_platform_interface as a direct dev dependency.

Importing a transitive package in tests can trigger the depend_on_referenced_packages lint; add path_provider_platform_interface to dev_dependencies in packages/stream_chat_flutter/pubspec.yaml (and in example/pubspec.yaml if the example tests import it).

@codecov
Copy link

codecov bot commented Sep 18, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 63.77%. Comparing base (ee78069) to head (e97777a).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #2381   +/-   ##
=======================================
  Coverage   63.76%   63.77%           
=======================================
  Files         412      413    +1     
  Lines       25821    25814    -7     
=======================================
- Hits        16466    16462    -4     
+ Misses       9355     9352    -3     

☔ 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 and others added 4 commits September 18, 2025 22:21
This commit introduces significant enhancements to the `StreamGradientAvatar` and its underlying rendering logic:

- **Jitter Implementation:** A new `Jitter` class has been added to introduce controlled randomness to the polygon grid in `StreamGradientAvatar`. This creates more visually diverse and dynamic avatars while maintaining deterministic output based on the user ID (seed). The `Jitter` class offers configurable intensity levels (none, light, medium, heavy, max) and allows for custom `Random` instances.

- **Refined Gradient Avatar:**
    - `StreamGradientAvatar` now utilizes the `Jitter` class for its background polygon grid.
    - The number of rows and columns in the polygon grid is now configurable (defaulting to 4x4).
    - Initials display has been improved with responsive font sizing based on available space and the number of characters.
    - The avatar uses a predefined list of color palettes for gradients, ensuring visually appealing combinations.
    - The `PolygonGradientPainter` has been rewritten to be more efficient and customizable, incorporating the `Jitter` logic and handling cell painting.
    - The old `Offset4` class and related logic have been removed in favor of the new `PolygonCell` and improved painter.

- **String Extension for Initials:** A new `initials` getter has been added to `StringX` to reliably extract up to two initials from a string.

- **Test Updates:**
    - Golden tests for gradient avatars have been updated to reflect the new visual style.
    - New tests for the `Jitter` class have been added to ensure its functionality.
    - `GroupAvatar` tests have been updated to use a `FakeConnectivityPlatform` for better test isolation.

Overall, these changes result in more aesthetically pleasing and dynamic gradient avatars with improved performance and maintainability.
This commit addresses an issue where `GradientAvatar` would generate different avatars for the same `userId` due to the non-deterministic nature of `Random()`.

The fix involves the following changes:

- The `Random` instance is now seeded with `userId.hashCode` to ensure that the same user ID always produces the same sequence of random numbers.
- `Jitter.custom` has been replaced with `Jitter(seed: seed)` to leverage the seeded `Random` instance for jitter generation.
- The gradient selection now uses `seed.abs() % _palettes.length` to ensure a deterministic choice from the available palettes.

These changes guarantee that `GradientAvatar` will consistently render the same avatar for a given `userId` across different runs and platforms.
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

♻️ Duplicate comments (2)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (2)

52-65: Avoid String.hashCode for seeding; use a stable hash and decouple palette choice from RNG state.

  • String.hashCode is not stable across runs/platforms. Use a stable 32‑bit hash.
  • Picking the palette via the same RNG instance advances the stream before jittering, causing pattern shifts if reused.
  • Pass a stable seed into the painter and re-seed a local RNG per paint.

Apply this diff:

@@
   @override
   Widget build(BuildContext context) {
-    final random = Random(userId.hashCode);
-    final gradient = _palettes[random.nextInt(_palettes.length)];
-    final jitter = Jitter.custom(random: random, intensity: jitterIntensity);
+    final seed = _stableHash32(userId);
+    final gradient = _palettes[seed % _palettes.length];
+    final jitter = Jitter(seed: seed, intensity: jitterIntensity);
 
     return RepaintBoundary(
       key: Key(userId),
       child: CustomPaint(
         painter: PolygonGradientPainter(
-          jitter: jitter,
+          seed: seed,
+          jitter: jitter,
           gradient: gradient,
         ),
         child: Center(child: _Initials(username: name)),
       ),
     );
   }
 }
+
+// Stable FNV-1a 32-bit hash for deterministic seeding
+int _stableHash32(String s) {
+  var hash = 0x811C9DC5;
+  for (final cu in s.codeUnits) {
+    hash ^= cu;
+    hash = (hash * 0x01000193) & 0xFFFFFFFF;
+  }
+  return hash & 0x7FFFFFFF;
+}

139-180: Re-seed RNG per paint to keep visuals deterministic across repaints.

Create a local Random(seed) and derive jitter from it instead of consuming jitter's internal RNG.

   void paint(Canvas canvas, Size size) {
     if (size.isEmpty) return;
@@
-    // Build jittered grid points
+    // Build jittered grid points using a per-paint RNG
+    final rng = Random(seed);
     final points = List<Offset>.filled(cols1 * rows1, Offset.zero);
@@
-        if (isBorder) {
-          points[rowBase + c] = Offset(x, y);
-        } else {
-          points[rowBase + c] = jitter.applyTo(Offset(x, y), maxDx, maxDy);
-        }
+        if (isBorder) {
+          points[rowBase + c] = Offset(x, y);
+        } else {
+          final dx = (rng.nextDouble() * 2 - 1) * maxDx * jitter.intensity;
+          final dy = (rng.nextDouble() * 2 - 1) * maxDy * jitter.intensity;
+          points[rowBase + c] = Offset(x + dx, y + dy);
+        }
🧹 Nitpick comments (4)
packages/stream_chat_flutter/lib/src/utils/extensions.dart (1)

60-70: Initials: double-check characters import and grapheme handling.

  • This getter relies on .characters. Ensure package:characters/characters.dart is imported (directly or via a re-export), otherwise builds can fail depending on import paths.
  • Minor: For consistent UI sizing later, prefer using initials.characters.length (not .length) when deciding font size in the avatar widget.
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (2)

82-101: Use grapheme count for font sizing and avoid withValues for wider SDK compatibility.

  • .length can miscount multi-codepoint initials; use .characters.length.
  • withValues may require newer Flutter. withOpacity(0.7) is broadly compatible.
-      final fontSize = initials.length == 2 ? side / 3 : side / 2;
+      final fontSize =
+          initials.characters.length == 2 ? side / 3 : side / 2;
@@
-          // ignore: deprecated_member_use
-          color: Colors.white.withValues(alpha: 0.7),
+          color: Colors.white.withOpacity(0.7),

182-188: Account for seed changes in shouldRepaint.

Add seed to the comparison to ensure proper invalidation when the user changes.

   bool shouldRepaint(covariant PolygonGradientPainter old) =>
       old.rows != rows ||
       old.columns != columns ||
+      old.seed != seed ||
       old.jitter.intensity != jitter.intensity ||
       !const ListEquality().equals(old.gradient, gradient);
packages/stream_chat_flutter/test/src/fakes.dart (1)

146-158: LGTM: minimal fake for connectivity.

The fake returns a single WIFI event; adequate for tests. Consider exposing a controller if future tests need to emit multiple changes.

📜 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 18b5077 and 987fe95.

⛔ Files ignored due to path filters (2)
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png is excluded by !**/*.png
📒 Files selected for processing (7)
  • packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/utils/extensions.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/utils/jitter.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/utils/utils.dart (1 hunks)
  • packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart (1 hunks)
  • packages/stream_chat_flutter/test/src/fakes.dart (2 hunks)
  • packages/stream_chat_flutter/test/src/utils/jitter_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: test
  • GitHub Check: build (ios)
  • GitHub Check: build (android)
  • GitHub Check: analyze
  • GitHub Check: analyze_legacy_versions
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: stream_chat_persistence
🔇 Additional comments (3)
packages/stream_chat_flutter/lib/src/utils/utils.dart (1)

4-4: Good addition to the utils barrel.

Re-exporting jitter.dart makes the API cohesive and simplifies imports.

packages/stream_chat_flutter/test/src/utils/jitter_test.dart (1)

6-193: Solid coverage and determinism checks.

Tests cover clamping, presets, determinism with seeds, and bounds—nice.

packages/stream_chat_flutter/test/src/avatars/group_avatar_test.dart (1)

13-21: LGTM: simpler, deterministic test setup.

Using real Member/User objects and a fake connectivity platform improves stability and removes flaky platform channel handling.

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

♻️ Duplicate comments (1)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (1)

52-55: Avoid seeding with String.hashCode; use a stable 32‑bit hash for determinism across platforms/runs.

String.hashCode isn’t guaranteed stable between executions or platforms. This can break cross‑platform consistency and golden tests. Seed both the palette selection and jitter with a stable hash.

Apply this diff:

-    final hash = userId.hashCode;
-    final gradient = _palettes[hash.abs() % _palettes.length];
-    final jitter = Jitter(seed: hash, intensity: jitterIntensity);
+    final stableSeed = _stableHash32(userId);
+    final gradient = _palettes[stableSeed % _palettes.length];
+    final jitter = Jitter(seed: stableSeed, intensity: jitterIntensity);

Add this helper (place near the top of the file):

// Stable FNV-1a 32-bit hash for seeding PRNG deterministically.
int _stableHash32(String s) {
  var hash = 0x811C9DC5; // offset basis
  for (final cu in s.codeUnits) {
    hash ^= cu;
    hash = (hash * 0x01000193) & 0xFFFFFFFF; // FNV prime
  }
  return hash & 0x7FFFFFFF; // non-negative (Random seed requirement)
}
Is Dart's String.hashCode guaranteed to be stable across different executions and platforms?
🧹 Nitpick comments (5)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (5)

31-36: Enforce the documented jitterIntensity range with an assert.

Prevents accidental out‑of‑range values.

-  const StreamGradientAvatar({
+  const StreamGradientAvatar({
     super.key,
     required this.name,
     required this.userId,
     this.jitterIntensity = 0.4,
-  });
+  }) : assert(jitterIntensity >= 0.0 && jitterIntensity <= 1.0);

58-58: Prefer ValueKey for clarity.

Explicit ValueKey<String> reads clearer and avoids dynamic Key factories.

-      key: Key(userId),
+      key: ValueKey<String>(userId),

96-98: Use withOpacity for wider SDK compatibility; drop the deprecated ignore.

withOpacity(0.7) is broadly supported; the lint suppression is unnecessary.

-          // ignore: deprecated_member_use
-          color: Colors.white.withValues(alpha: 0.7),
+          color: Colors.white.withOpacity(0.7),

119-125: Assert nonzero rows/columns and valid gradient length.

Protects against division by zero and shader errors if misused.

-  PolygonGradientPainter({
+  PolygonGradientPainter({
     this.rows = 5,
     this.columns = 5,
     required this.jitter,
     required this.gradient,
-  });
+  })  : assert(rows > 0),
+        assert(columns > 0),
+        assert(gradient.length >= 2);

150-166: Minor perf: reuse a Path instance in the loop (optional).

Allocation is small here (default 5×5), but reusing a Path avoids churn.

-    // Build cells from jittered points and draw them
+    // Build cells from jittered points and draw them
+    final reusablePath = Path();
     for (var r = 0; r < rows; r++) {
@@
-        PolygonCell(a, b, e, d).paint(canvas, gradient);
+        PolygonCell(a, b, e, d).paint(canvas, gradient, reusablePath: reusablePath);

And update PolygonCell.paint:

-  void paint(
-    Canvas canvas,
-    List<Color> gradient,
-  ) {
+  void paint(
+    Canvas canvas,
+    List<Color> gradient, {
+    Path? reusablePath,
+  }) {
     final shader = ui.Gradient.linear(pointA, pointC, gradient);
     final paint = Paint()..shader = shader;
-
-    final path = Path()
+    final path = (reusablePath?..reset()) ?? Path()
       ..moveTo(pointA.dx, pointA.dy)

Also applies to: 168-178

📜 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 987fe95 and e85ef45.

⛔ Files ignored due to path filters (20)
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_issue_2369.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_light.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.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). (3)
  • GitHub Check: test
  • GitHub Check: build (android)
  • GitHub Check: build (ios)

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

♻️ Duplicate comments (1)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (1)

52-55: Avoid seeding with String.hashCode; not stable across runs/platforms

String.hashCode is not guaranteed to be stable between executions or platforms, which can break determinism and golden stability. Use a stable 32‑bit hash for both palette selection and jitter seeding.

Apply:

@@
-    final hash = userId.hashCode;
-    final gradient = _palettes[hash.abs() % _palettes.length];
-    final jitter = Jitter(seed: hash, intensity: jitterIntensity);
+    final hash = _stableHash32(userId);
+    final gradient = _palettes[hash % _palettes.length];
+    final jitter = Jitter(seed: hash, intensity: jitterIntensity);

Add a stable hash helper after imports:

@@
 import 'package:stream_chat_flutter/src/utils/utils.dart';
 
+// Stable FNV-1a 32-bit hash for deterministic seeding across platforms/runs.
+int _stableHash32(String s) {
+  var h = 0x811C9DC5; // offset basis
+  for (final cu in s.codeUnits) {
+    h ^= cu;
+    h = (h * 0x01000193) & 0xFFFFFFFF; // FNV prime
+  }
+  return h & 0x7FFFFFFF; // non-negative
+}
🧹 Nitpick comments (5)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (5)

56-58: Prefer ValueKey over Key(String)

Use ValueKey<String> for clarity and to match Flutter conventions.

-    return RepaintBoundary(
-      key: Key(userId),
+    return RepaintBoundary(
+      key: ValueKey(userId),

31-36: Enforce documented bounds for jitterIntensity

The docs say 0.0–1.0; add an assert to catch misuse early.

   const StreamGradientAvatar({
     super.key,
     required this.name,
     required this.userId,
     this.jitterIntensity = 0.4,
-  });
+  }) : assert(jitterIntensity >= 0.0 && jitterIntensity <= 1.0);

83-101: Use withOpacity for broader SDK compatibility; clamp font size to a sane minimum

Color.withValues requires recent Flutter; withOpacity is universally supported. Also guard against tiny layouts producing unreadable text.

       final side = min(constraints.maxWidth, constraints.maxHeight);
-      final fontSize = initials.length == 2 ? side / 3 : side / 2;
+      final fontSize = initials.length == 2 ? side / 3 : side / 2;
+      final effectiveFontSize = max(8.0, fontSize);
@@
         style: TextStyle(
-          fontSize: fontSize,
+          fontSize: effectiveFontSize,
           fontWeight: FontWeight.w500,
-          // ignore: deprecated_member_use
-          color: Colors.white.withValues(alpha: 0.7),
+          color: Colors.white.withOpacity(0.7),
         ),

If the project’s minimum Flutter SDK guarantees Color.withValues, keep it and ignore this change. Please confirm your min SDK.


119-125: Constructor should validate painter invariants

Rows/columns must be positive; gradients should have at least 2 colors.

   PolygonGradientPainter({
     this.rows = 5,
     this.columns = 5,
     required this.jitter,
     required this.gradient,
-  });
+  })  : assert(rows > 0),
+        assert(columns > 0),
+        assert(gradient.length >= 2);

240-257: Optional: verify text contrast over lighter palettes

Some palettes are pastel; with 70% white text, contrast may be marginal on small avatars. Consider full white (1.0) or dynamic foreground based on luminance if accessibility is a goal.

📜 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 987fe95 and e85ef45.

⛔ Files ignored due to path filters (20)
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_issue_2369.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_light.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.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). (1)
  • GitHub Check: build (android)

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 (2)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (2)

142-150: LGTM: Re-seeding Jitter per paint avoids RNG drift.

Constructing Jitter inside paint() with a seed ensures frame-to-frame determinism.


52-62: Avoid String.hashCode for seeding; use a stable 32-bit hash.

hashCode isn’t stable across runs/platforms and can break determinism/goldens. Seed and palette index should come from a stable hash.

Apply this diff within the shown lines:

-    final jitterSeed = userId.hashCode;
-    final gradient = _palettes[jitterSeed.abs() % _palettes.length];
+    final stableSeed = _stableHash32(userId);
+    final gradient = _palettes[stableSeed % _palettes.length];
@@
-        painter: PolygonGradientPainter(
+        painter: PolygonGradientPainter(
           gradient: gradient,
-          jitterSeed: jitterSeed,
+          jitterSeed: stableSeed,
           jitterIntensity: jitterIntensity,
         ),

And add this helper (top-level, near imports):

// Stable FNV-1a 32-bit hash for deterministic seeding (non-negative).
int _stableHash32(String s) {
  var hash = 0x811C9DC5;
  for (final cu in s.codeUnits) {
    hash ^= cu;
    hash = (hash * 0x01000193) & 0xFFFFFFFF;
  }
  return hash & 0x7FFFFFFF;
}
🧹 Nitpick comments (5)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (5)

55-56: Keying the RepaintBoundary by userId is optional.

Unless you rely on keyed element identity, this key can be dropped (or use ValueKey(userId)) to avoid unnecessary element churn.


119-125: Harden constructor with asserts (rows/cols > 0, gradient >= 2, intensity in [0,1]).

Prevents silent misuse and aids debug.

   PolygonGradientPainter({
     this.rows = 5,
     this.columns = 5,
     this.jitterSeed,
     this.jitterIntensity = 0.4,
     required this.gradient,
-  });
+  })  : assert(rows > 0),
+        assert(columns > 0),
+        assert(jitterIntensity >= 0 && jitterIntensity <= 1),
+        assert(gradient.length >= 2);

93-99: Remove unnecessary deprecated_member_use ignore.

withValues isn’t deprecated; drop the ignore (or use withOpacity if desired).

-          // ignore: deprecated_member_use
           color: Colors.white.withValues(alpha: 0.7),

176-188: Tiny allocation nit: reuse Paint/Path inside loops.

Hoist Paint/Path per frame and reuse; set shader per cell and reset the path.

-    for (var r = 0; r < rows; r++) {
+    final paint = Paint();
+    final path = Path();
+    for (var r = 0; r < rows; r++) {
@@
-        PolygonCell(a, b, e, d).paint(canvas, gradient);
+        // Inline painting to reuse paint/path.
+        paint.shader = ui.Gradient.linear(a, e, gradient);
+        path
+          ..reset()
+          ..moveTo(a.dx, a.dy)
+          ..lineTo(b.dx, b.dy)
+          ..lineTo(e.dx, e.dy)
+          ..lineTo(d.dx, d.dy)
+          ..close();
+        canvas.drawPath(path, paint);

Also applies to: 228-247


8-16: Docs claim deterministic across sessions/platforms—ensure after stable hash change.

Once you switch to _stableHash32, the docs will be accurate; otherwise this promise may not hold across VMs/architectures.

📜 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 e85ef45 and ec83875.

📒 Files selected for processing (1)
  • packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.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). (10)
  • GitHub Check: analyze_legacy_versions
  • GitHub Check: build (android)
  • GitHub Check: build (ios)
  • GitHub Check: test
  • GitHub Check: stream_chat_persistence
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat
  • GitHub Check: analyze
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat_flutter_core
🔇 Additional comments (1)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (1)

191-197: LGTM: shouldRepaint covers seed/intensity/shape/gradient.

Including jitterSeed and gradient equality is correct for deterministic repaints.

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/avatars/gradient_avatar.dart (1)

52-54: Avoid String.hashCode for deterministic seeding; use a stable 32‑bit hash.

String.hashCode isn’t stable across runs/platforms, which breaks the “deterministic across sessions” guarantee and can flake goldens. Seed and palette index should derive from a stable hash of userId.

Apply this diff:

-    final jitterSeed = userId.hashCode;
-    final gradient = _palettes[jitterSeed.abs() % _palettes.length];
+    final jitterSeed = _stableHash32(userId);
+    final gradient = _palettes[jitterSeed % _palettes.length];

Add this helper in this file (library‑private):

// Stable FNV-1a 32-bit hash; returns non-negative seed.
int _stableHash32(String s) {
  var hash = 0x811C9DC5;
  for (final cu in s.codeUnits) {
    hash ^= cu;
    hash = (hash * 0x01000193) & 0xFFFFFFFF;
  }
  return hash & 0x7FFFFFFF;
}
🧹 Nitpick comments (3)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (3)

31-36: Enforce documented jitter range at the widget boundary.

Doc says 0.0–1.0; add an assert to fail fast on invalid values.

Apply this diff:

   const StreamGradientAvatar({
     super.key,
     required this.name,
     required this.userId,
     this.jitterIntensity = 0.4,
-  });
+  }) : assert(jitterIntensity >= 0 && jitterIntensity <= 1);

118-124: Harden painter constructor with basic invariants.

Avoid silent bad inputs (e.g., zero rows/cols, <2 gradient colors).

Apply this diff:

   PolygonGradientPainter({
     this.rows = 5,
     this.columns = 5,
     this.jitterSeed,
     this.jitterIntensity = 0.4,
     required this.gradient,
-  });
+  })  : assert(rows > 0),
+        assert(columns > 0),
+        assert(jitterIntensity >= 0 && jitterIntensity <= 1),
+        assert(gradient.length >= 2);

90-98: Remove unnecessary lint suppression.

TextStyle.color and Colors.white.withOpacity aren’t deprecated; the // ignore: deprecated_member_use can be dropped.

Apply this diff:

           fontWeight: FontWeight.w500,
-          // ignore: deprecated_member_use
           color: Colors.white.withOpacity(0.7),
📜 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 ec83875 and 0d0387a.

⛔ Files ignored due to path filters (8)
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_1.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_issue_2369.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/group_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/user_avatar_1.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.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). (10)
  • GitHub Check: update_goldens
  • GitHub Check: test
  • GitHub Check: build (android)
  • GitHub Check: analyze
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat_persistence
  • GitHub Check: stream_chat
  • GitHub Check: analyze_legacy_versions
🔇 Additional comments (3)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (3)

55-63: Good: deterministic painter wiring and repaint boundary.

Passing the seed/intensity into the painter and wrapping with RepaintBoundary is solid.


145-149: Good: re-instantiating Jitter in paint avoids RNG drift across frames.

This keeps geometry stable per seed.


150-157: Verify jitter displacement bounds to prevent cell inversions at high intensity.

With maxDx = cellW and maxDy = cellH, interior points could cross edges if Jitter.applyTo uses full ranges, producing self‑intersecting quads. If not already bounded inside Jitter, consider limiting to a fraction (e.g., 0.3–0.5 of cell size) or clamping to keep A–B–E–D ordering intact.

Would you confirm Jitter.applyTo guarantees displacements that keep polygons non-inverted for jitterIntensity in [0,1]? If not, I can propose a clamp.

Also applies to: 170-171, 175-186

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/avatars/gradient_avatar.dart (1)

52-61: Don’t seed visuals with String.hashCode; use a stable, non‑negative 32‑bit hash.

hashCode isn’t guaranteed stable across runs/platforms and may be negative. This can cause cross‑device/golden flakiness. Derive a stable seed and normalize it before palette selection and jitter.

Apply:

-    final jitterSeed = userId.hashCode;
-    final gradient = _palettes[jitterSeed.abs() % _palettes.length];
+    final seed = _stableHash32(userId); // stable, non-negative
+    final gradient = _palettes[seed % _palettes.length];
@@
-        painter: PolygonGradientPainter(
-          gradient: gradient,
-          jitterSeed: jitterSeed,
-          jitterIntensity: jitterIntensity,
-        ),
+        painter: PolygonGradientPainter(
+          gradient: gradient,
+          jitterSeed: seed,
+          jitterIntensity: jitterIntensity,
+        ),

Add helper (top-level, outside classes):

// Stable FNV-1a 32-bit hash suitable for seeding PRNGs.
int _stableHash32(String s) {
  var h = 0x811C9DC5;
  for (final cu in s.codeUnits) {
    h ^= cu;
    h = (h * 0x01000193) & 0xFFFFFFFF;
  }
  return h & 0x7FFFFFFF; // non-negative
}
🧹 Nitpick comments (3)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (3)

31-36: Validate jitterIntensity at construction time.

Add a constructor assert to prevent out‑of‑range values.

-  const StreamGradientAvatar({
+  const StreamGradientAvatar({
     super.key,
     required this.name,
     required this.userId,
     this.jitterIntensity = 0.4,
-  });
+  }) : assert(jitterIntensity >= 0.0 && jitterIntensity <= 1.0);

120-126: Harden painter invariants with asserts.

Guard against invalid rows/columns, gradient length, and jitter range.

   PolygonGradientPainter({
     this.rows = 5,
     this.columns = 5,
     this.jitterSeed,
     this.jitterIntensity = 0.4,
     required this.gradient,
-  });
+  })  : assert(rows > 0),
+        assert(columns > 0),
+        assert(jitterIntensity >= 0.0 && jitterIntensity <= 1.0),
+        assert(gradient.length >= 2);

96-98: Remove stale deprecation ignore.

Colors.white.withOpacity isn’t deprecated; the ignore adds noise.

-            // ignore: deprecated_member_use
             color: Colors.white.withOpacity(0.7),
📜 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 0d0387a and 1eb79a2.

⛔ Files ignored due to path filters (16)
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_0.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_2.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_3.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/avatars/goldens/ci/gradient_avatar_issue_2369.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_options_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/goldens/ci/stream_poll_results_dialog_with_show_all_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_closed_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/poll/interactor/goldens/ci/stream_poll_interactor_light.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_dark.png is excluded by !**/*.png
  • packages/stream_chat_flutter/test/src/scroll_view/thread_scroll_view/goldens/ci/stream_thread_list_tile_light.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (1 hunks)
  • packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.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). (10)
  • GitHub Check: stream_chat
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: stream_chat_flutter
  • GitHub Check: stream_chat_persistence
  • GitHub Check: build (android)
  • GitHub Check: test
  • GitHub Check: build (ios)
  • GitHub Check: analyze
  • GitHub Check: analyze_legacy_versions
🔇 Additional comments (3)
packages/stream_chat_flutter/lib/src/dialogs/channel_info_dialog.dart (1)

58-61: Tight constraints may clip in narrow layouts; confirm this is safe across dialog breakpoints.

Switching to BoxConstraints.tightFor(64×64) forces an exact size. If parent constraints are smaller (e.g., small dialogs, large text scale), this can overflow/clamp. If that’s intended for visual consistency, LGTM—otherwise consider keeping max constraints or wrapping in a SizedBox with fit behavior.

packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (2)

147-151: Good: re‑seeding Jitter per paint avoids RNG drift.

Creating Jitter inside paint() with a seed keeps frames deterministic for the same input. Nice.


192-199: Good: shouldRepaint covers all determinants.

Comparing rows, columns, jitterSeed, jitterIntensity, and gradient via ListEquality is correct and avoids stale frames.

Comment on lines 96 to 97
// ignore: deprecated_member_use
color: Colors.white.withOpacity(0.7),
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't like ignoring this warning, as we should move away from it at some point and this completely removes the warning from our view.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

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

🧹 Nitpick comments (2)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (2)

93-98: Prefer Color.withOpacity for broader Flutter compatibility

withValues may not be available on older Flutter channels. Use withOpacity(0.7).

-            color: Colors.white.withValues(alpha: 0.7),
+            color: Colors.white.withOpacity(0.7),

If you intentionally target a Flutter version that supports withValues, ensure the SDK constraint reflects that.


119-125: Add constructor asserts to enforce documented invariants

Guarantee valid grid/jitter configuration at construction time.

   PolygonGradientPainter({
     this.rows = 5,
     this.columns = 5,
     this.jitterSeed,
     this.jitterIntensity = 0.4,
     required this.gradient,
-  });
+  })  : assert(rows > 0),
+        assert(columns > 0),
+        assert(gradient.length >= 2),
+        assert(jitterIntensity >= 0.0 && jitterIntensity <= 1.0);
📜 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 1eb79a2 and aa9f5c6.

📒 Files selected for processing (1)
  • packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.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). (10)
  • GitHub Check: stream_chat_flutter
  • GitHub Check: build (android)
  • GitHub Check: analyze
  • GitHub Check: stream_chat_localizations
  • GitHub Check: stream_chat_flutter_core
  • GitHub Check: build (ios)
  • GitHub Check: stream_chat
  • GitHub Check: stream_chat_persistence
  • GitHub Check: test
  • GitHub Check: analyze_legacy_versions
🔇 Additional comments (3)
packages/stream_chat_flutter/lib/src/avatars/gradient_avatar.dart (3)

146-150: Good: re-instantiating Jitter inside paint avoids RNG drift across frames


191-197: Good: shouldRepaint compares seed/intensity/gradient to prevent stale frames


52-60: Replace String.hashCode with a stable hash for deterministic visuals

String.hashCode isn’t stable across runs/platforms; seeds and palettes may change between sessions/devices. Use a stable 32‑bit hash.

Apply this diff:

-    final jitterSeed = userId.hashCode;
-    final gradient = _palettes[jitterSeed.abs() % _palettes.length];
+    final stableSeed = _stableHash32(userId);
+    final gradient = _palettes[stableSeed % _palettes.length];
@@
-        painter: PolygonGradientPainter(
+        painter: PolygonGradientPainter(
           gradient: gradient,
-          jitterSeed: jitterSeed,
+          jitterSeed: stableSeed,
           jitterIntensity: jitterIntensity,
         ),

Add this helper near the top of the file (outside any class):

// Stable FNV-1a 32-bit hash for deterministic seeding (non-negative).
int _stableHash32(String s) {
  var hash = 0x811C9DC5;
  for (final cu in s.codeUnits) {
    hash ^= cu;
    hash = (hash * 0x01000193) & 0xFFFFFFFF;
  }
  return hash & 0x7FFFFFFF;
}

@xsahil03x xsahil03x merged commit 3226700 into master Sep 23, 2025
17 checks passed
@xsahil03x xsahil03x deleted the fix/gradient-avatar-color branch September 23, 2025 12:41
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.

All Users have the same Gradient Avatar color

3 participants