-
-
Notifications
You must be signed in to change notification settings - Fork 73
Stac Framework Enhancements: Navigation, Borders, Colors & Architecture Improvements #361
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
WalkthroughRefactors 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
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
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]
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches🧪 Generate unit tests
Comment |
### 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
42778d6
to
030f125
Compare
- 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
030f125
to
1641cb0
Compare
There was a problem hiding this 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 ArgumentErrorMessages 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 typedaction
.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 assertsSmall 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 assertsSame 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
definesparse
forStacAction?
or that callers never rely onaction == null
.
23-25
: Stricter input validation forvalues
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 asStacAction?
— 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, setincludeIfNull: 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
vschildren
- 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
📒 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 optionalalignment
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 goodConstructor 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 typedStacAction
— looks good.
fromJson
routesaction
throughStacAction.fromJson
, andtoJson
callsaction?.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 fromstac_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 optionalStacWidget
fields likeseparator
.
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 theitemTemplate
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 alongsidechildren
; 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 prematureInheritedWidget
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 nestedchild
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.
/// 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, | ||
); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
/// 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.
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'; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
const StacDefaultBottomNavigationController({ | ||
required this.length, | ||
this.initialIndex, | ||
required this.child, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
/// The child widget that will be wrapped by this controller. | ||
/// | ||
/// Type: StacWidget | ||
final StacWidget child; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ 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.
/// 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.
BottomNavigationController({this.initialIndex = 0, required this.length}) | ||
: _index = initialIndex; | ||
|
||
/// The initial index when the controller is created. | ||
final int initialIndex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
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 toStacColor
StacColors.primary.withOpacity(0.8)
→"primary@80"
Added factory constructors to
StacBorder
StacBorder.all()
- uniform borders on all sidesStacBorder.symmetric()
- horizontal/vertical symmetric bordersAdded factory constructors to
StacBorderRadius
StacBorderRadius.only()
- individual corner stylingStacBorderRadius.horizontal()
- left/right symmetric cornersStacBorderRadius.vertical()
- top/bottom symmetric cornersStacBorderRadius.circular()
- uniform circular bordersMigrated
StacDefaultBottomNavigationController
to stac_coreBug Fixes
Fixed
BottomNavigationScope
timing issuesInheritedWidget
access to build timeFixed
StacSetValueAction
type safetyaction
field fromMap<String, dynamic>?
toStacAction?
Added
itemTemplate
support toStacColumn
StacListView
Added
itemTemplate
support toStacListView
Examples
Color System Extension
Border Factory Methods
Type of Change
Summary by CodeRabbit
New Features
Changes