Skip to content

refactor(ui): decouple StreamComponentFactory from StreamTheme#47

Merged
xsahil03x merged 3 commits intomain-design-systemfrom
refactor/component-factory
Feb 6, 2026
Merged

refactor(ui): decouple StreamComponentFactory from StreamTheme#47
xsahil03x merged 3 commits intomain-design-systemfrom
refactor/component-factory

Conversation

@xsahil03x
Copy link
Member

@xsahil03x xsahil03x commented Feb 5, 2026

Description of the pull request

  • Convert StreamComponentFactory to InheritedWidget for subtree overrides.
  • Remove componentFactory from StreamTheme.
  • Introduce StreamComponentBuilders for component customization.
  • Update StreamButton and StreamFileTypeIcon to use the new factory lookup.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a component factory system enabling flexible customization of avatar, badge, button, indicator, and icon rendering.
  • Refactor

    • Restructured component configuration architecture for improved extensibility and customization support.
    • Refined public API exports to streamline component interfaces while maintaining backward compatibility.

- Convert `StreamComponentFactory` to `InheritedWidget` for subtree overrides.
- Remove `componentFactory` from `StreamTheme`.
- Introduce `StreamComponentBuilders` for component customization.
- Update `StreamButton` and `StreamFileTypeIcon` to use the new factory lookup.
@xsahil03x xsahil03x requested a review from a team as a code owner February 5, 2026 10:40
@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

This PR refactors the component customization architecture by introducing an InheritedWidget-based StreamComponentFactory that replaces theme-integrated factory management. Multiple components adopt a props-based configuration pattern with dedicated Default* implementations, decoupling rendering from configuration and enabling factory-based builder injection.

Changes

Cohort / File(s) Summary
Factory Architecture
factory/stream_component_factory.dart, factory/stream_component_factory.g.theme.dart
Replaces parameterized factory API with InheritedWidget-based StreamComponentFactory exposing StreamComponentBuilders. Adds theme extension helpers (copyWith, lerp, equality) via generated mixin. Introduces StreamComponentBuilder typedef and BuildContext extension for convenient factory access.
Component Exports & Theme
components.dart, theme.dart, theme/stream_theme.dart, theme/stream_theme.g.theme.dart
Updates barrel exports to hide Default* variants while adding factory export. Removes componentFactory from StreamTheme public API and generated theme extension, decoupling factory from theme configuration. Exposes button theme component export.
Avatar Components
components/avatar/stream_avatar.dart, components/avatar/stream_avatar_group.dart, components/avatar/stream_avatar_stack.dart
Refactors all three avatar components to use dedicated props classes (StreamAvatarProps, StreamAvatarGroupProps, StreamAvatarStackProps) with corresponding Default* implementations. Introduces factory-based builder injection via StreamComponentFactory.maybeOf(context) with fallback rendering. Converts constructors from const to regular and moves rendering logic to Default* classes.
Badge & Indicator Components
components/badge/stream_badge_count.dart, components/indicator/stream_online_indicator.dart
Refactors badge count and online indicator components with props-based configuration (StreamBadgeCountProps, StreamOnlineIndicatorProps) and Default* implementations. Wires factory-based builder delegation with fallback to default rendering. Introduces helper classes for theme defaults.
Accessory Components
components/accessories/stream_file_type_icon.dart
Adds public StreamFileTypeIconSize, StreamFileType enums, and StreamFileTypeIconProps configuration class. Replaces StreamTheme factory lookup with StreamComponentFactory.maybeOf(context) pattern. Removes static factory getter from DefaultStreamFileTypeIcon.
Button Component
components/buttons/stream_button.dart
Updates builder lookup to use StreamComponentFactory.maybeOf(context)?.button with fallback to DefaultStreamButton. Removes static factory getter from DefaultStreamButton. Refines imports to include color scheme and theme extensions.

Sequence Diagram

sequenceDiagram
    participant Widget as Component Widget
    participant Factory as StreamComponentFactory
    participant Builder as Custom Builder
    participant Default as Default Implementation
    
    Widget->>Widget: build(context)
    Widget->>Factory: maybeOf(context)
    alt Factory Found & Builder Available
        Factory-->>Widget: StreamComponentBuilders
        Widget->>Builder: builder(context, props)
        Builder-->>Widget: Custom Widget
    else Factory Not Found or No Builder
        Factory-->>Widget: null / no builder
        Widget->>Default: DefaultComponent(props)
        Default-->>Widget: Default Rendered Widget
    end
    Default-->>Widget: Widget Tree
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • renefloor

Poem

🐰 Factories now bloom in inherited light,
Props dance through avatars, badges held tight,
Defaults stand ready when builders take flight,
The component garden grows nested, yet bright!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The description covers the main changes but lacks detail on testing and the CLA checkbox requirement from the template. Add information about how changes were tested and verify CLA has been signed, as per the repository template.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately describes the primary change: decoupling StreamComponentFactory from StreamTheme.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/component-factory

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.

@codecov
Copy link

codecov bot commented Feb 5, 2026

Codecov Report

❌ Patch coverage is 6.06061% with 93 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main-design-system@97a34d2). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...utter/lib/src/components/avatar/stream_avatar.dart 0.00% 18 Missing ⚠️
...lib/src/components/avatar/stream_avatar_group.dart 0.00% 16 Missing ⚠️
.../components/indicator/stream_online_indicator.dart 0.00% 16 Missing ⚠️
...lib/src/components/avatar/stream_avatar_stack.dart 0.00% 15 Missing ⚠️
...tter/lib/src/factory/stream_component_factory.dart 12.50% 14 Missing ⚠️
...r/lib/src/components/badge/stream_badge_count.dart 0.00% 10 Missing ⚠️
.../components/accessories/stream_file_type_icon.dart 0.00% 3 Missing ⚠️
...tter/lib/src/components/buttons/stream_button.dart 80.00% 1 Missing ⚠️

❌ Your patch status has failed because the patch coverage (6.06%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@                  Coverage Diff                  @@
##             main-design-system      #47   +/-   ##
=====================================================
  Coverage                      ?   37.69%           
=====================================================
  Files                         ?       78           
  Lines                         ?     2056           
  Branches                      ?        0           
=====================================================
  Hits                          ?      775           
  Misses                        ?     1281           
  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.

Refactors StreamAvatar, StreamAvatarGroup, StreamAvatarStack, StreamBadgeCount, and StreamOnlineIndicator to use StreamComponentFactory.
@xsahil03x
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

✅ 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

@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

Caution

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

⚠️ Outside diff range comments (2)
packages/stream_core_flutter/lib/src/components/accessories/stream_file_type_icon.dart (1)

188-188: ⚠️ Potential issue | 🟡 Minor

Inconsistent file type mapping for tar archives.

'application/x-tar' is mapped to .code with extension 'tar', but tar is a compression/archive format, not a code format. This appears to be a copy-paste error given the surrounding compression format mappings.

🐛 Proposed fix
-    'application/x-tar' => (.code, 'tar'),
+    'application/x-tar' => (.compression, 'tar'),
packages/stream_core_flutter/lib/src/components/avatar/stream_avatar_stack.dart (1)

221-227: ⚠️ Potential issue | 🟡 Minor

Badge positioning may be slightly off when badge size differs from avatar size.

The positioning loop uses i * visiblePortion for all children including the overflow badge (line 224). However, visiblePortion is derived from the avatar diameter, while the badge may have a different diameter (badgeDiameter). This means the badge might not align perfectly with the expected overlap when badge and avatar sizes differ.

Currently badgeVisiblePortion (line 203) is only used for totalWidth calculation but not for positioning the badge itself.

💡 Suggested approach

If precise alignment is needed, calculate the badge position separately:

           for (var i = 0; i < displayChildren.length; i++)
             Positioned(
-              left: i * visiblePortion,
+              left: i < visible.length
+                  ? i * visiblePortion
+                  : (visible.length - 1) * visiblePortion + badgeVisiblePortion,
               child: displayChildren[i],
             ),

However, if the current visual result is acceptable, this can be deferred.

🧹 Nitpick comments (2)
packages/stream_core_flutter/lib/src/factory/stream_component_factory.g.theme.dart (1)

87-107: Function equality comparison relies on reference identity.

The == operator compares builder functions directly. In Dart, function equality uses reference identity, so two identical anonymous functions created separately will not be equal. This is standard behavior but worth noting: widgets using this equality check may rebuild more often than expected if builders are recreated on each build.

packages/stream_core_flutter/lib/src/components/avatar/stream_avatar_group.dart (1)

150-185: Materialize children once to avoid repeated Iterable traversal.

Iterable.length/first/last/elementAt can re-traverse or re-evaluate lazy iterables. Converting once avoids extra work and ensures consistent ordering.

♻️ Proposed refactor
-    if (props.children.isEmpty) return const SizedBox.shrink();
+    final children = props.children.toList(growable: false);
+    if (children.isEmpty) return const SizedBox.shrink();
@@
-              builder: (context) => switch (props.children.length) {
-                1 => _buildForOne(context, props.children),
-                2 => _buildForTwo(context, props.children),
-                3 => _buildForThree(context, props.children),
-                4 => _buildForFour(context, props.children),
-                _ => _buildForFourOrMore(context, props.children),
+              builder: (context) => switch (children.length) {
+                1 => _buildForOne(context, children),
+                2 => _buildForTwo(context, children),
+                3 => _buildForThree(context, children),
+                4 => _buildForFour(context, children),
+                _ => _buildForFourOrMore(context, children),
               },

@xsahil03x xsahil03x merged commit 983218e into main-design-system Feb 6, 2026
3 of 6 checks passed
@xsahil03x xsahil03x deleted the refactor/component-factory branch February 6, 2026 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants