Skip to content

Conversation

Potatomonsta
Copy link
Contributor

@Potatomonsta Potatomonsta commented Sep 30, 2025

Stac Framework Enhancements: Navigation, Borders, Colors & Architecture Improvements

Overview

This PR introduces significant enhancements to the Stac framework, focusing on improved navigation functionality, enhanced border and color systems, and architectural improvements. The changes span across multiple packages and include both new features and critical bug fixes.

New Features

  • Added withOpacity() extension method to StacColor

    • Enables easy opacity manipulation for any color
    • Supports both hex colors and theme color names
    • Format: StacColors.primary.withOpacity(0.8)"primary@80"
    • Automatic clamping and percentage conversion
  • Added factory constructors to StacBorder

    • StacBorder.all() - uniform borders on all sides
    • StacBorder.symmetric() - horizontal/vertical symmetric borders
    • Comprehensive documentation with Dart and JSON examples
  • Added factory constructors to StacBorderRadius

    • StacBorderRadius.only() - individual corner styling
    • StacBorderRadius.horizontal() - left/right symmetric corners
    • StacBorderRadius.vertical() - top/bottom symmetric corners
    • StacBorderRadius.circular() - uniform circular borders
  • Migrated StacDefaultBottomNavigationController to stac_core

    • Moved from legacy Freezed model to JsonSerializable
    • Improved type safety and architecture consistency
    • Better separation of concerns

Bug Fixes

  • Fixed BottomNavigationScope timing issues

    • Deferred InheritedWidget access to build time
    • Wrapped navigation parsers in separate widgets
    • Ensures proper controller communication between bar and view
  • Fixed StacSetValueAction type safety

    • Changed action field from Map<String, dynamic>? to StacAction?
    • Improved JSON serialization handling
    • Better type safety for action chaining
  • Added itemTemplate support to StacColumn

    • Enables dynamic child generation similar to StacListView
    • Maintains consistency across layout widgets
    • Supports data-driven column layouts
  • Added itemTemplate support to StacListView

    • Enables dynamic child generation for list views
    • Supports data-driven list layouts
    • Maintains consistency with other layout widgets

Examples

Color System Extension

// Before
String color = "primary";

// After  
String color = StacColors.primary.withOpacity(0.8); // "primary@80"

Border Factory Methods

// Uniform borders
StacBorder.all(color: StacColors.blue, width: 2.0)

// Symmetric borders
StacBorder.symmetric(horizontal: topBottomSide, vertical: leftRightSide)

// Individual corner radius
StacBorderRadius.only(topLeft: 8.0, bottomRight: 4.0)

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Code refactor
  • Build configuration change
  • Documentation
  • Chore

Summary by CodeRabbit

  • New Features

    • Added Default Bottom Navigation Controller widget with JSON support.
    • ListView and Column now support itemTemplate for dynamic item generation.
    • Added Border factories: all() and symmetric().
    • Added BorderRadius constructors: only, horizontal, vertical, circular.
    • Added StacColor.withOpacity(...) helper.
  • Changes

    • Bottom navigation rendering and state handling improved for stability.
    • Align alignment is now nullable (no implicit center default).
    • Set-Value actions now accept typed actions for more reliable follow-up execution.

Copy link

coderabbitai bot commented Sep 30, 2025

Walkthrough

Refactors bottom navigation to a parser-driven controller and encapsulated widgets, removing the old freezed model. Adjusts action execution to use typed StacAction.parse. Adds new core widgets and APIs: DefaultBottomNavigationController model, itemTemplate for Column/ListView, border/border-radius constructors, color opacity extension, and updates Align JSON handling.

Changes

Cohort / File(s) Summary
Set Value Action parsing
packages/stac/lib/src/parsers/actions/stac_set_value/stac_set_value_action_parser.dart, packages/stac_core/lib/actions/set_value/stac_set_value_action.dart, packages/stac_core/lib/actions/set_value/stac_set_value_action.g.dart
Switches action execution from map-based to typed StacAction.parse; updates field type and JSON (de)serialization accordingly.
Bottom navigation parser refactor
packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_bar/stac_bottom_navigation_bar_parser.dart, packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_view/stac_bottom_navigation_view_parser.dart
Moves build logic into private widgets; uses parser-based controller import; computes/display current tab via controller.
Bottom navigation controller: old removed (stac) / new added (stac_core)
packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart (removed), .../stac_default_bottom_navigation_controller.freezed.dart (removed), .../stac_default_bottom_navigation_controller.g.dart (removed), .../stac_default_bottom_navigation_controller_parser.dart (updated), packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart (added), .../stac_default_bottom_navigation_controller.g.dart (added), packages/stac/lib/src/parsers/widgets/widgets.dart (export updated), packages/stac_core/lib/widgets/widgets.dart (export added)
Deletes freezed data model in stac; introduces parser-scoped BottomNavigationScope/Controller; adds typed core widget model with JSON support; updates exports to point to parser (stac) and new core widget.
List/Column item templates
packages/stac_core/lib/widgets/list_view/stac_list_view.dart, .../stac_list_view.g.dart, packages/stac_core/lib/widgets/column/stac_column.dart, .../stac_column.g.dart, packages/stac/lib/src/parsers/widgets/stac_list_view/stac_list_view_parser.dart
Adds optional itemTemplate to ListView and Column with JSON handling; parser import adjusted for WidgetType source.
Border APIs
packages/stac_core/lib/foundation/borders/stac_border/stac_border.dart, packages/stac_core/lib/foundation/borders/stac_border_radius/stac_border_radius.dart
Adds StacBorder.all/symmetric and StacBorderRadius.only/horizontal/vertical/circular constructors.
Color extension
packages/stac_core/lib/foundation/colors/stac_color/stac_colors.dart
Adds StacColorExtension.withOpacity(double) producing “@”.
Align JSON behavior
packages/stac_core/lib/widgets/align/stac_align.g.dart
Removes default-to-center for missing alignment; alignment now nullable in JSON round-trip.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Parser as StacSetValueActionParser
  participant Model as StacSetValueAction
  participant Action as StacAction
  participant Ctx as Context

  User->>Parser: parse(json, context)
  Parser->>Model: set value
  alt action provided
    Parser->>Action: Model.action.parse(Ctx)
    Action-->>Parser: result
  else no action
    Parser-->>User: done
  end
Loading
sequenceDiagram
  participant ViewParser as BottomNavigationViewParser
  participant Scope as BottomNavigationScope
  participant Ctrl as BottomNavigationController
  participant Bar as _BottomNavigationBarWidget
  participant Child as Current Tab Content

  ViewParser->>Ctrl: create (length, initialIndex)
  ViewParser->>Scope: provide (length, controller)
  Bar->>Scope: of(context)
  Scope-->>Bar: controller
  Bar->>Ctrl: listen index
  ViewParser->>Child: render children[Ctrl.index]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • divyanshub024

Poem

Hop, hop—tabs now glide in tune,
A parser’s paw rewires the lune.
Actions parse, not maps that sprawl,
Borders round, colors softly fall.
Lists and columns template bright—
I thump approval, code takes flight!
(_/)<3

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The provided title enumerates multiple enhancement areas—navigation, borders, colors, and architecture improvements—but does not clearly identify the single primary change and omits other significant updates such as action chaining safety and layout templating. By listing broad categories, it reads as a generic summary rather than a focused, single‐sentence description of the most impactful change. A pull request title should succinctly highlight the main objective so that teammates scanning history immediately understand the core contribution. Consider refining the title to emphasize the central change—such as migrating the default bottom navigation controller into stac_core with a new BottomNavigationScope and controller—while optionally mentioning key secondary enhancements like the color opacity extension or border constructors. This will make the title more specific and immediately informative.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mn/DSL-bugfixes

Comment @coderabbitai help to get the list of available commands and usage tips.

### Changes:
- Add `itemTemplate` field of type `StacWidget?` to StacListView class
- Update constructor to include itemTemplate parameter
- Migrate StacDefaultBottomNavigationController from legacy Freezed model to new stac_models system
- Move model definition from packages/stac to packages/stac_core/lib/widgets/
- Replace Freezed-generated code with JsonSerializable-based implementation
- Update parser to use new StacWidget base class and parse method
- Add comprehensive documentation with Dart and JSON examples
- Remove legacy Freezed files (.freezed.dart) and update imports
- Update widget exports in both packages

This migration follows the established pattern of moving widget models to the
stac_models package while maintaining backward compatibility. The new
implementation provides better type safety and follows the current architecture
standards.

Files changed:
- Deleted: packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/*.dart
- Added: packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart
- Updated: parser implementation and widget exports
- Add itemTemplate property to StacColumn model for dynamic child generation
- Include comprehensive documentation explaining itemTemplate functionality
- Regenerate model files to include JSON serialization for itemTemplate
- Follow same pattern as StacListView for consistency
- Change action field type from Map<String, dynamic>? to StacAction?
- Update JSON serialization to properly handle StacAction objects
- Regenerate model files to reflect the type change
- Remove trailing whitespace in actionType getter
- Add StacBorder.all() factory method for uniform borders on all sides
- Add StacBorder.symmetric() factory method for horizontal/vertical symmetric borders
- Include comprehensive documentation with Dart and JSON examples for both methods
- Provide convenient constructors for common border patterns
- Add StacBorderRadius.only() factory for individual side styling
- Add StacBorderRadius.horizontal() factory for top/bottom borders only
- Add StacBorderRadius.vertical() factory for left/right borders only
- Add StacBorderRadius.circular() factory for uniform circular borders
- Include comprehensive documentation with Dart and JSON examples
…arsers

- Wrap StacBottomNavigationBar in _BottomNavigationBarWidget to defer controller access
- Wrap StacBottomNavigationView in _BottomNavigationViewWidget to defer controller access
- Move BottomNavigationScope.of(context) calls from parse() to build() methods
- Fix timing issue where InheritedWidget was accessed before being created

The issue was that both navigation parsers were calling BottomNavigationScope.of(context)
during the parsing phase, before the BottomNavigationScope was actually created in the
widget tree by StacDefaultBottomNavigationController. This caused the controller to be
null and prevented proper communication between the navigation bar and view.

By wrapping the widgets and deferring the BottomNavigationScope access to build time,
the InheritedWidget is guaranteed to be available when needed, ensuring proper
bottom navigation functionality.
- Add StacColorExtension with withOpacity() method
- Support opacity values from 0.0 to 1.0 with automatic clamping
- Convert opacity to percentage format (@<opacity>) for consistency
- Include comprehensive documentation with Dart and JSON examples
- Enable easy opacity manipulation for both hex colors and theme colors
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: 5

Caution

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

⚠️ Outside diff range comments (6)
packages/stac_core/lib/foundation/borders/stac_border_radius/stac_border_radius.dart (1)

246-261: Fix error messages: incorrect type name in thrown ArgumentError

Messages say “StacEdgeInsets” but this is StacBorderRadius.

-        throw ArgumentError('Invalid input format for StacEdgeInsets');
+        throw ArgumentError('Invalid input format for StacBorderRadius');
...
-      throw ArgumentError('Invalid input format for StacEdgeInsets');
+      throw ArgumentError('Invalid input format for StacBorderRadius');
packages/stac_core/lib/actions/set_value/stac_set_value_action.dart (1)

14-23: Update documentation examples to reflect typed action.

The Dart example still shows a Map, which no longer matches the public API.

-/// const StacSetValueAction(
-///   values: [
-///     {"key": "isLoggedIn", "value": true},
-///     {"key": "token", "value": "abc123"},
-///   ],
-///   action: {"type": "navigate", "routeName": "/home"},
-/// )
+/// final action = StacAction.fromJson({
+///   "type": "navigate",
+///   "routeName": "/home",
+/// });
+/// StacSetValueAction(
+///   values: [
+///     {"key": "isLoggedIn", "value": true},
+///     {"key": "token", "value": "abc123"},
+///   ],
+///   action: action,
+/// );

JSON example can stay as-is since wire format remains a map.

Also applies to: 26-37

packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller_parser.dart (4)

41-51: Dispose controller and remove listener to avoid leaks and setState-after-dispose.

_state adds a listener but never removes/disposing the controller.

   @override
   void initState() {
     super.initState();

     _controller = BottomNavigationController(
       length: widget.model.length,
       initialIndex: widget.model.initialIndex ?? 0,
     );

     _controller.addListener(_onIndexChange);
   }
+
+  @override
+  void dispose() {
+    _controller.removeListener(_onIndexChange);
+    _controller.dispose();
+    super.dispose();
+  }

57-65: Handle model updates (length/initialIndex) in didUpdateWidget.

Without reconciliation, controller can hold stale length/index when the model changes, breaking bar/view coordination.

   @override
   Widget build(BuildContext context) {
     return BottomNavigationScope(
       length: widget.model.length,
       controller: _controller,
       child: widget.model.child.parse(context) ?? const SizedBox(),
     );
   }
+
+  @override
+  void didUpdateWidget(
+    covariant _DefaultBottomNavigationControllerWidget oldWidget,
+  ) {
+    super.didUpdateWidget(oldWidget);
+    final int newLength = widget.model.length;
+    if (newLength != oldWidget.model.length) {
+      final int proposed =
+          widget.model.initialIndex ?? _controller.index;
+      _controller.removeListener(_onIndexChange);
+      _controller.dispose();
+      final int clampedInitial =
+          (newLength > 0 && proposed >= 0 && proposed < newLength)
+              ? proposed
+              : 0;
+      _controller = BottomNavigationController(
+        length: newLength,
+        initialIndex: clampedInitial,
+      );
+      _controller.addListener(_onIndexChange);
+      setState(() {});
+    } else if (widget.model.initialIndex != null &&
+        widget.model.initialIndex != _controller.index) {
+      final idx = widget.model.initialIndex!;
+      if (idx >= 0 && idx < newLength) {
+        _controller.index = idx;
+      }
+    }
+  }

104-107: updateShouldNotify should depend on length/controller, not child identity.

Comparing child causes unnecessary rebuilds and may miss changes in controller/length.

-  @override
-  bool updateShouldNotify(covariant InheritedWidget oldWidget) {
-    return oldWidget.child != child;
-  }
+  @override
+  bool updateShouldNotify(covariant BottomNavigationScope oldWidget) {
+    return oldWidget.length != length || oldWidget.controller != controller;
+  }

134-143: Tighten index bounds and early-return logic.

Ensure illegal indices never pass asserts; keep early return first to avoid asserting on degenerate lengths.

-  void _changeIndex(int value) {
-    assert(value >= 0 && (value < length || length == 0));
-
-    if (value == _index || length < 2) {
-      return;
-    }
-
-    _index = value;
-    notifyListeners();
-  }
+  void _changeIndex(int value) {
+    if (value == _index || length < 2) return;
+    assert(value >= 0 && value < length);
+    _index = value;
+    notifyListeners();
+  }
🧹 Nitpick comments (10)
packages/stac_core/lib/foundation/borders/stac_border/stac_border.dart (1)

86-124: Border.all: add basic validation and doc parity for strokeAlign

  • Guard against negative width; optionally document expected strokeAlign range and include it in the JSON snippet for consistency.
 factory StacBorder.all({
   StacColor? color,
   StacBorderStyle? borderStyle,
   double? width,
   double? strokeAlign,
 }) {
+    assert(width == null || width >= 0, 'width cannot be negative');
     return StacBorder(
       color: color,
       borderStyle: borderStyle,
       width: width,
       strokeAlign: strokeAlign,
     );
 }
packages/stac_core/lib/foundation/borders/stac_border_radius/stac_border_radius.dart (3)

108-141: horizontal(): add simple non-negative radius asserts

Small guardrails improve early failure and parity with typical radius expectations.

-const StacBorderRadius.horizontal({double? left, double? right})
-  : this(
+const StacBorderRadius.horizontal({double? left, double? right})
+  : assert(left == null || left >= 0, 'left radius cannot be negative'),
+    assert(right == null || right >= 0, 'right radius cannot be negative'),
+    this(
       topLeft: left,
       topRight: right,
       bottomLeft: left,
       bottomRight: right,
     );

142-175: vertical(): add non-negative radius asserts

Same rationale as horizontal().

-const StacBorderRadius.vertical({double? top, double? bottom})
-  : this(
+const StacBorderRadius.vertical({double? top, double? bottom})
+  : assert(top == null || top >= 0, 'top radius cannot be negative'),
+    assert(bottom == null || bottom >= 0, 'bottom radius cannot be negative'),
+    this(
       topLeft: top,
       topRight: top,
       bottomLeft: bottom,
       bottomRight: bottom,
     );

176-207: circular(): add non-negative radius assert and cross-reference all()

  • Add an assert for clarity.
  • Optional: mention it’s equivalent to all(radius) in the docs.
-const StacBorderRadius.circular(double radius)
-  : this(
+const StacBorderRadius.circular(double radius)
+  : assert(radius >= 0, 'radius cannot be negative'),
+    this(
       topLeft: radius,
       topRight: radius,
       bottomLeft: radius,
       bottomRight: radius,
     );
packages/stac/lib/src/parsers/actions/stac_set_value/stac_set_value_action_parser.dart (2)

26-26: Guard nullable follow-up action when parsing/executing.

If action is optional, prefer a null-aware call to avoid surprising NPEs and make the intent explicit. Also lets the method be a no-op when no follow-up action is provided.

-    return model.action.parse(context);
+    return model.action?.parse(context);

Optionally remove async (no awaits remain) to avoid an extra microtask hop:

-  FutureOr<dynamic> onCall(
+  FutureOr<dynamic> onCall(
     BuildContext context,
     StacSetValueAction model,
-  ) async {
+  ) {

Please confirm stac_action_parser.dart defines parse for StacAction? or that callers never rely on action == null.


23-25: Stricter input validation for values keys.

Casting value['key'] as String throws a type error with little context if the payload is malformed. Add a clear check to fail fast with a helpful message.

-    for (final value in model.values ?? []) {
-      StacRegistry.instance.setValue(value['key'] as String, value['value']);
-    }
+    for (final value in model.values ?? const []) {
+      final key = value['key'];
+      if (key is! String || key.isEmpty) {
+        throw FormatException('StacSetValueAction.values[]: "key" must be a non-empty String.');
+      }
+      StacRegistry.instance.setValue(key, value['value']);
+    }
packages/stac_core/lib/actions/set_value/stac_set_value_action.dart (2)

50-50: API change: action now typed as StacAction? — verify downstream usage or add a legacy shim.

This is a source-level breaking change for callers passing a Map. Consider a deprecated convenience factory to ease migration.

 class StacSetValueAction extends StacAction {
   /// Creates a [StacSetValueAction] that writes [values] and optionally runs [action].
   const StacSetValueAction({this.values, this.action});
+
+  @Deprecated('Use the typed `action: StacAction` instead.')
+  factory StacSetValueAction.legacy({
+    List<Map<String, dynamic>>? values,
+    Map<String, dynamic>? action,
+  }) =>
+      StacSetValueAction(
+        values: values,
+        action: action == null ? null : StacAction.fromJson(action),
+      );

Run the verification script below to spot legacy call sites passing a map to action.


39-39: Consider omitting nulls in JSON output.

To avoid emitting "action": null and trim payloads, set includeIfNull: false.

Based on learnings

-@JsonSerializable()
+@JsonSerializable(includeIfNull: false)
 class StacSetValueAction extends StacAction {
packages/stac_core/lib/widgets/list_view/stac_list_view.dart (1)

122-123: Enhance itemTemplate documentation.

The documentation for itemTemplate is minimal. Consider adding details about:

  • How it differs from the children property
  • When to use itemTemplate vs children
  • Example JSON structure showing itemTemplate usage
  • How data binding works with itemTemplate
packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller_parser.dart (1)

97-101: Demote log severity or provide maybeOf().

Logging an error for missing scope can be noisy for optional composition. Prefer a warning, or add a nullable maybeOf() and reserve of() for non-null (assert).

-      Log.e(
+      Log.w(
         "BottomNavigationScope.of() called with a context that does not contain a BottomNavigationScope.",
       );

Optionally add:

  • static BottomNavigationScope? maybeOf(BuildContext context) => context.dependOnInheritedWidgetOfExactType();
  • Have of() call maybeOf() and assert non-null in debug.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6630f3b and 1641cb0.

📒 Files selected for processing (22)
  • packages/stac/lib/src/parsers/actions/stac_set_value/stac_set_value_action_parser.dart (2 hunks)
  • packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_bar/stac_bottom_navigation_bar_parser.dart (2 hunks)
  • packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_view/stac_bottom_navigation_view_parser.dart (2 hunks)
  • packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart (0 hunks)
  • packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.freezed.dart (0 hunks)
  • packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.g.dart (0 hunks)
  • packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller_parser.dart (6 hunks)
  • packages/stac/lib/src/parsers/widgets/stac_list_view/stac_list_view_parser.dart (1 hunks)
  • packages/stac/lib/src/parsers/widgets/widgets.dart (1 hunks)
  • packages/stac_core/lib/actions/set_value/stac_set_value_action.dart (1 hunks)
  • packages/stac_core/lib/actions/set_value/stac_set_value_action.g.dart (1 hunks)
  • packages/stac_core/lib/foundation/borders/stac_border/stac_border.dart (1 hunks)
  • packages/stac_core/lib/foundation/borders/stac_border_radius/stac_border_radius.dart (1 hunks)
  • packages/stac_core/lib/foundation/colors/stac_color/stac_colors.dart (1 hunks)
  • packages/stac_core/lib/widgets/align/stac_align.g.dart (2 hunks)
  • packages/stac_core/lib/widgets/column/stac_column.dart (2 hunks)
  • packages/stac_core/lib/widgets/column/stac_column.g.dart (2 hunks)
  • packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart (1 hunks)
  • packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.g.dart (1 hunks)
  • packages/stac_core/lib/widgets/list_view/stac_list_view.dart (2 hunks)
  • packages/stac_core/lib/widgets/list_view/stac_list_view.g.dart (2 hunks)
  • packages/stac_core/lib/widgets/widgets.dart (1 hunks)
💤 Files with no reviewable changes (3)
  • packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.g.dart
  • packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart
  • packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.freezed.dart
🔇 Additional comments (17)
packages/stac_core/lib/widgets/align/stac_align.g.dart (1)

9-13: Looks good — nullable alignment decode matches the widget contract.

Allowing JSON decode to propagate null keeps parity with the optional alignment field and lets higher layers decide on defaults. No action needed.

packages/stac_core/lib/foundation/borders/stac_border_radius/stac_border_radius.dart (1)

68-107: only(): API and implementation look good

Constructor mirrors common patterns and keeps const/redirecting form. No issues.

packages/stac_core/lib/actions/set_value/stac_set_value_action.g.dart (1)

14-16: Serialization aligns with typed StacAction — looks good.

fromJson routes action through StacAction.fromJson, and toJson calls action?.toJson(). This matches the new field type and preserves wire format.

Please confirm StacAction.fromJson correctly discriminates all supported action subtypes (incl. "navigate") and that generated code was rebuilt across packages.

Also applies to: 22-22

packages/stac_core/lib/widgets/widgets.dart (1)

82-82: LGTM!

The new export correctly exposes the default bottom navigation controller from stac_core, aligning with the architecture improvements mentioned in the PR objectives.

packages/stac/lib/src/parsers/widgets/stac_list_view/stac_list_view_parser.dart (1)

10-10: LGTM!

Centralizing the WidgetType import from stac_core improves consistency across the parser layer.

packages/stac_core/lib/widgets/list_view/stac_list_view.g.dart (2)

42-44: LGTM!

The generated JSON deserialization for itemTemplate correctly follows the same pattern as other optional StacWidget fields like separator.


69-69: LGTM!

The generated JSON serialization for itemTemplate correctly handles the optional field.

packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_bar/stac_bottom_navigation_bar_parser.dart (3)

6-6: LGTM!

The import change to the parser variant aligns with the architecture improvements for bottom navigation mentioned in the PR objectives.


23-26: LGTM!

Delegating to a private widget _BottomNavigationBarWidget is a clean encapsulation pattern that separates parser logic from rendering logic.


28-66: LGTM! Correctly fixes the timing issue.

The private widget correctly defers BottomNavigationScope.of(context) access to build time (line 35), which addresses the timing issue mentioned in the PR objectives. This ensures the InheritedWidget is accessed at the appropriate lifecycle phase.

packages/stac_core/lib/widgets/list_view/stac_list_view.dart (1)

65-65: No action needed: itemTemplate is fully supported by the parser.

The stac_dynamic_view_parser.dart includes logic to detect the itemTemplate JSON key, iterate over the data list, and generate child items accordingly—so the new field is already integrated.

packages/stac_core/lib/widgets/column/stac_column.dart (1)

54-104: Item template integration looks solid.

Constructor wiring and inline docs clearly align this widget with the new data-driven pattern used in StacListView, with no backwards-compatibility risk. Nicely scoped addition.

packages/stac/lib/src/parsers/widgets/widgets.dart (1)

24-24: Export swap matches the parser-based controller shift.

Routing consumers through the parser implementation keeps the public surface consistent with the new architecture—no issues spotted.

packages/stac_core/lib/widgets/column/stac_column.g.dart (1)

38-57: Generated serialization stays in sync.

itemTemplate now round-trips cleanly through JSON alongside children; generation matches the manual field addition.

packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_view/stac_bottom_navigation_view_parser.dart (1)

1-37: Deferred build solves the scope timing bug.

Handing parsing off to _BottomNavigationViewWidget so the controller lookup occurs during build neatly avoids the premature InheritedWidget access, while the clamp keeps indices safe. Looks good.

packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.g.dart (1)

9-24: New controller JSON wiring is consistent.

length/initialIndex coercion and nested child serialization mirror the rest of the widget models—no gaps found.

packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart (1)

123-126: LGTM on type wiring.

type maps cleanly to WidgetType.defaultBottomNavigationController; consistent with Stac conventions.

Comment on lines +126 to +171
/// Creates a symmetric border with different styling for horizontal and vertical sides.
///
/// This factory method creates a border where horizontal sides (top, bottom)
/// have the same styling, and vertical sides (left, right) have the same styling.
///
/// {@tool snippet}
/// Dart Example:
/// ```dart
/// StacBorder.symmetric(
/// horizontal: StacBorderSide(
/// color: StacColors.blue,
/// width: 2.0,
/// borderStyle: StacBorderStyle.solid,
/// ),
/// vertical: StacBorderSide(
/// color: StacColors.red,
/// width: 1.0,
/// borderStyle: StacBorderStyle.solid,
/// ),
/// )
/// ```
/// {@end-tool}
///
/// {@tool snippet}
/// JSON Example:
/// ```json
/// {
/// "top": {"color": "#2196F3", "width": 2.0, "borderStyle": "solid"},
/// "bottom": {"color": "#2196F3", "width": 2.0, "borderStyle": "solid"},
/// "left": {"color": "#F44336", "width": 1.0, "borderStyle": "solid"},
/// "right": {"color": "#F44336", "width": 1.0, "borderStyle": "solid"}
/// }
/// ```
/// {@end-tool}
factory StacBorder.symmetric({
StacBorderSide? horizontal,
StacBorderSide? vertical,
}) {
return StacBorder(
top: horizontal,
bottom: horizontal,
left: vertical,
right: vertical,
);
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Border.symmetric: horizontal/vertical semantics are inverted; align with common conventions

Current docs/code set top/bottom from “horizontal” and left/right from “vertical”. In Flutter-style APIs, vertical maps to top/bottom and horizontal maps to left/right. This inversion is likely to confuse users and lead to bugs.

-  /// This factory method creates a border where horizontal sides (top, bottom)
-  /// have the same styling, and vertical sides (left, right) have the same styling.
+  /// This factory method creates a border where vertical sides (top, bottom)
+  /// have the same styling, and horizontal sides (left, right) have the same styling.

 factory StacBorder.symmetric({
   StacBorderSide? horizontal,
   StacBorderSide? vertical,
 }) {
   return StacBorder(
-      top: horizontal,
-      bottom: horizontal,
-      left: vertical,
-      right: vertical,
+      top: vertical,
+      bottom: vertical,
+      left: horizontal,
+      right: horizontal,
   );
 }

If you intentionally prefer the current semantics, consider renaming the parameters to topBottom and leftRight to avoid ambiguity.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Creates a symmetric border with different styling for horizontal and vertical sides.
///
/// This factory method creates a border where horizontal sides (top, bottom)
/// have the same styling, and vertical sides (left, right) have the same styling.
///
/// {@tool snippet}
/// Dart Example:
/// ```dart
/// StacBorder.symmetric(
/// horizontal: StacBorderSide(
/// color: StacColors.blue,
/// width: 2.0,
/// borderStyle: StacBorderStyle.solid,
/// ),
/// vertical: StacBorderSide(
/// color: StacColors.red,
/// width: 1.0,
/// borderStyle: StacBorderStyle.solid,
/// ),
/// )
/// ```
/// {@end-tool}
///
/// {@tool snippet}
/// JSON Example:
/// ```json
/// {
/// "top": {"color": "#2196F3", "width": 2.0, "borderStyle": "solid"},
/// "bottom": {"color": "#2196F3", "width": 2.0, "borderStyle": "solid"},
/// "left": {"color": "#F44336", "width": 1.0, "borderStyle": "solid"},
/// "right": {"color": "#F44336", "width": 1.0, "borderStyle": "solid"}
/// }
/// ```
/// {@end-tool}
factory StacBorder.symmetric({
StacBorderSide? horizontal,
StacBorderSide? vertical,
}) {
return StacBorder(
top: horizontal,
bottom: horizontal,
left: vertical,
right: vertical,
);
}
/// Creates a symmetric border with different styling for horizontal and vertical sides.
///
/// This factory method creates a border where vertical sides (top, bottom)
/// have the same styling, and horizontal sides (left, right) have the same styling.
///
/// {@tool snippet}
/// Dart Example:
///
🤖 Prompt for AI Agents
In packages/stac_core/lib/foundation/borders/stac_border/stac_border.dart around
lines 126 to 171, the symmetric factory currently assigns horizontal to
top/bottom and vertical to left/right which is inverted versus common Flutter
conventions; update the factory so the vertical parameter is applied to top and
bottom and the horizontal parameter is applied to left and right (i.e., return
StacBorder(top: vertical, bottom: vertical, left: horizontal, right:
horizontal)); alternatively, if the original semantics are intentional, rename
parameters to topBottom and leftRight and update docs and usages accordingly.

Comment on lines +35 to +44
StacColor withOpacity(double opacity) {
// Clamp opacity to valid range (0.0 to 1.0)
final clampedOpacity = opacity.clamp(0.0, 1.0);

// Convert to percentage (0-100) and round to nearest integer
final opacityPercentage = (clampedOpacity * 100).round();

// Return the color string with opacity suffix
return '$this@$opacityPercentage';
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace existing opacity suffix before appending a new one.

Calling withOpacity on a color string that already contains an opacity suffix (e.g. "primary@80") produces invalid values like "primary@80@50". We need to strip any existing suffix before appending the new percentage so repeated calls remain stable.

-    return '$this@$opacityPercentage';
+    final separatorIndex = indexOf('@');
+    final baseColor =
+        separatorIndex == -1 ? this : substring(0, separatorIndex);
+    return '$baseColor@$opacityPercentage';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
StacColor withOpacity(double opacity) {
// Clamp opacity to valid range (0.0 to 1.0)
final clampedOpacity = opacity.clamp(0.0, 1.0);
// Convert to percentage (0-100) and round to nearest integer
final opacityPercentage = (clampedOpacity * 100).round();
// Return the color string with opacity suffix
return '$this@$opacityPercentage';
}
StacColor withOpacity(double opacity) {
// Clamp opacity to valid range (0.0 to 1.0)
final clampedOpacity = opacity.clamp(0.0, 1.0);
// Convert to percentage (0-100) and round to nearest integer
final opacityPercentage = (clampedOpacity * 100).round();
// Strip existing opacity suffix (if any) before appending new one
final separatorIndex = indexOf('@');
final baseColor = separatorIndex == -1
? this
: substring(0, separatorIndex);
// Return the color string with the new opacity suffix
return '$baseColor@$opacityPercentage';
}
🤖 Prompt for AI Agents
In packages/stac_core/lib/foundation/colors/stac_color/stac_colors.dart around
lines 35 to 44, the withOpacity method currently appends an opacity suffix
without removing any existing suffix, producing values like "primary@80@50";
modify it to first strip any trailing opacity suffix (match a trailing
"@<number>" optionally with whitespace) using a regex, then append the new
"@<percentage>" suffix; keep the clamping/rounding logic the same and return the
cleaned base color plus the new suffix.

Comment on lines +102 to +106
const StacDefaultBottomNavigationController({
required this.length,
this.initialIndex,
required this.child,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Enforce invariants for length and initialIndex.

Add constructor assertions to prevent invalid JSON/config from creating impossible states (negative length, out-of-range initialIndex). Keeps errors close to source.

   const StacDefaultBottomNavigationController({
     required this.length,
     this.initialIndex,
     required this.child,
-  });
+  })  : assert(length >= 0, 'length must be >= 0'),
+       assert(
+         initialIndex == null || initialIndex >= 0,
+         'initialIndex must be >= 0 when provided',
+       ),
+       assert(
+         initialIndex == null || length == 0 || initialIndex < length,
+         'initialIndex must be < length when length > 0',
+       );
🤖 Prompt for AI Agents
In
packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart
around lines 102–106, the constructor currently accepts length and optional
initialIndex without validating them; add constructor assertions to enforce
length > 0 and, if initialIndex != null, 0 <= initialIndex < length so invalid
JSON/config can't create impossible states. Place assertions on the initializer
list (e.g. assert(length > 0), assert(initialIndex == null || (initialIndex >= 0
&& initialIndex < length))) and keep existing required child parameter
unchanged.

Comment on lines +118 to +121
/// The child widget that will be wrapped by this controller.
///
/// Type: StacWidget
final StacWidget child;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

❓ Verification inconclusive

JSON (de)serialization for StacWidget child: ensure a converter or explicit JsonKey.

json_serializable can’t generate fromJson for interface/abstract types without guidance. If you haven’t registered a JsonConverter for StacWidget at the library/class level, add an explicit JsonKey for child.

-  /// Type: StacWidget
-  final StacWidget child;
+  /// Type: StacWidget
+  @JsonKey(fromJson: StacWidget.fromJson, toJson: _childToJson)
+  final StacWidget child;
+
+  static Map<String, dynamic> _childToJson(StacWidget value) => value.toJson();

Run this script to confirm a converter already exists (and skip the JsonKey change if it does):


🏁 Script executed:

#!/bin/bash
# Verify presence of a JsonConverter for StacWidget or field-level overrides
rg -nP 'JsonConverter<\s*StacWidget' -C2
rg -nP '@JsonKey\([^)]*fromJson:\s*StacWidget\.fromJson' -C2
rg -nP '@JsonSerializable\([^)]*converters:\s*\[' -C2

Length of output: 0


Add explicit JsonKey annotation for StacWidget child
File: packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart:118-121

-  /// Type: StacWidget
-  final StacWidget child;
+  /// Type: StacWidget
+  @JsonKey(fromJson: StacWidget.fromJson, toJson: _childToJson)
+  final StacWidget child;
+
+  static Map<String, dynamic> _childToJson(StacWidget value) => value.toJson();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// The child widget that will be wrapped by this controller.
///
/// Type: StacWidget
final StacWidget child;
/// The child widget that will be wrapped by this controller.
///
/// Type: StacWidget
@JsonKey(fromJson: StacWidget.fromJson, toJson: _childToJson)
final StacWidget child;
static Map<String, dynamic> _childToJson(StacWidget value) => value.toJson();
🤖 Prompt for AI Agents
In
packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart
around lines 118-121, the StacWidget child field needs an explicit JsonKey
annotation; add a @JsonKey annotation (for example @JsonKey(required: true,
disallowNullValue: true)) directly above the final StacWidget child; ensure
json_annotation is imported if missing and then run the code generator (flutter
pub run build_runner build or similar) to update the generated *.g.dart files.

Comment on lines +117 to 121
BottomNavigationController({this.initialIndex = 0, required this.length})
: _index = initialIndex;

/// The initial index when the controller is created.
final int initialIndex;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate controller constructor inputs.

Guard against negative length and out-of-range initialIndex.

-  BottomNavigationController({this.initialIndex = 0, required this.length})
-      : _index = initialIndex;
+  BottomNavigationController({this.initialIndex = 0, required this.length})
+      : assert(length >= 0, 'length must be >= 0'),
+        assert(initialIndex >= 0, 'initialIndex must be >= 0'),
+        assert(length == 0 || initialIndex < length,
+            'initialIndex must be < length when length > 0'),
+        _index = initialIndex;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
BottomNavigationController({this.initialIndex = 0, required this.length})
: _index = initialIndex;
/// The initial index when the controller is created.
final int initialIndex;
BottomNavigationController({this.initialIndex = 0, required this.length})
: assert(length >= 0, 'length must be >= 0'),
assert(initialIndex >= 0, 'initialIndex must be >= 0'),
assert(length == 0 || initialIndex < length,
'initialIndex must be < length when length > 0'),
_index = initialIndex;
/// The initial index when the controller is created.
final int initialIndex;
🤖 Prompt for AI Agents
packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller_parser.dart
around lines 117-121: the constructor currently does not validate inputs; add
guards to ensure length is positive (length > 0) and initialIndex is within
range (0 <= initialIndex < length). Implement these checks in the constructor
(use assert(...) for debug builds and throw ArgumentError or RangeError for
production) — e.g. assert(length > 0), assert(initialIndex >= 0 && initialIndex
< length) and throw a descriptive ArgumentError if validation fails — then keep
assigning _index = initialIndex.

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.

1 participant