diff --git a/packages/stac/lib/src/parsers/actions/stac_set_value/stac_set_value_action_parser.dart b/packages/stac/lib/src/parsers/actions/stac_set_value/stac_set_value_action_parser.dart index c7bfd4852..7da1801be 100644 --- a/packages/stac/lib/src/parsers/actions/stac_set_value/stac_set_value_action_parser.dart +++ b/packages/stac/lib/src/parsers/actions/stac_set_value/stac_set_value_action_parser.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:stac/src/parsers/core/stac_action_parser.dart'; import 'package:stac/stac.dart'; import 'package:stac_core/stac_core.dart'; @@ -22,6 +23,6 @@ class StacSetValueActionParser extends StacActionParser { for (final value in model.values ?? []) { StacRegistry.instance.setValue(value['key'] as String, value['value']); } - return Stac.onCallFromJson(model.action, context); + return model.action.parse(context); } } diff --git a/packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_bar/stac_bottom_navigation_bar_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_bar/stac_bottom_navigation_bar_parser.dart index 24b577d58..375933e63 100644 --- a/packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_bar/stac_bottom_navigation_bar_parser.dart +++ b/packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_bar/stac_bottom_navigation_bar_parser.dart @@ -3,7 +3,7 @@ import 'package:stac/src/parsers/core/stac_widget_parser.dart'; import 'package:stac/src/parsers/foundation/navigation/stac_bottom_navigation_bar_landscape_layout_parser.dart'; import 'package:stac/src/parsers/foundation/navigation/stac_bottom_navigation_bar_type_parser.dart'; import 'package:stac/src/parsers/foundation/text/stac_text_style_parser.dart'; -import 'package:stac/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart'; +import 'package:stac/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller_parser.dart'; import 'package:stac/src/utils/color_utils.dart'; import 'package:stac_core/stac_core.dart'; import 'package:stac_framework/stac_framework.dart'; @@ -21,6 +21,17 @@ class StacBottomNavigationBarParser @override Widget parse(BuildContext context, StacBottomNavigationBar model) { + return _BottomNavigationBarWidget(model: model); + } +} + +class _BottomNavigationBarWidget extends StatelessWidget { + const _BottomNavigationBarWidget({required this.model}); + + final StacBottomNavigationBar model; + + @override + Widget build(BuildContext context) { final controller = BottomNavigationScope.of(context)?.controller; return BottomNavigationBar( diff --git a/packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_view/stac_bottom_navigation_view_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_view/stac_bottom_navigation_view_parser.dart index 9e0ef8bc5..47ef71634 100644 --- a/packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_view/stac_bottom_navigation_view_parser.dart +++ b/packages/stac/lib/src/parsers/widgets/stac_bottom_navigation_view/stac_bottom_navigation_view_parser.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:stac/src/parsers/core/stac_widget_parser.dart'; +import 'package:stac/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller_parser.dart'; import 'package:stac_core/stac_core.dart'; import 'package:stac_framework/stac_framework.dart'; -import '../stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart'; - class StacBottomNavigationViewParser extends StacParser { const StacBottomNavigationViewParser(); @@ -18,6 +17,17 @@ class StacBottomNavigationViewParser @override Widget parse(BuildContext context, StacBottomNavigationView model) { + return _BottomNavigationViewWidget(model: model); + } +} + +class _BottomNavigationViewWidget extends StatelessWidget { + const _BottomNavigationViewWidget({required this.model}); + + final StacBottomNavigationView model; + + @override + Widget build(BuildContext context) { final controller = BottomNavigationScope.of(context)?.controller; if (model.children.isEmpty) return const SizedBox(); final index = controller?.index ?? 0; diff --git a/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart b/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart deleted file mode 100644 index 54359aa39..000000000 --- a/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -export 'stac_default_bottom_navigation_controller_parser.dart'; - -part 'stac_default_bottom_navigation_controller.freezed.dart'; -part 'stac_default_bottom_navigation_controller.g.dart'; - -@freezed -abstract class StacDefaultBottomNavigationController - with _$StacDefaultBottomNavigationController { - const factory StacDefaultBottomNavigationController({ - required int length, - int? initialIndex, - required Map child, - }) = _StacDefaultBottomNavigationController; - - factory StacDefaultBottomNavigationController.fromJson( - Map json) => - _$StacDefaultBottomNavigationControllerFromJson(json); -} diff --git a/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.freezed.dart b/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.freezed.dart deleted file mode 100644 index b74bdce61..000000000 --- a/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.freezed.dart +++ /dev/null @@ -1,372 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// coverage:ignore-file -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'stac_default_bottom_navigation_controller.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// dart format off -T _$identity(T value) => value; - -/// @nodoc -mixin _$StacDefaultBottomNavigationController { - int get length; - int? get initialIndex; - Map get child; - - /// Create a copy of StacDefaultBottomNavigationController - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @pragma('vm:prefer-inline') - $StacDefaultBottomNavigationControllerCopyWith< - StacDefaultBottomNavigationController> - get copyWith => _$StacDefaultBottomNavigationControllerCopyWithImpl< - StacDefaultBottomNavigationController>( - this as StacDefaultBottomNavigationController, _$identity); - - /// Serializes this StacDefaultBottomNavigationController to a JSON map. - Map toJson(); - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is StacDefaultBottomNavigationController && - (identical(other.length, length) || other.length == length) && - (identical(other.initialIndex, initialIndex) || - other.initialIndex == initialIndex) && - const DeepCollectionEquality().equals(other.child, child)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, length, initialIndex, - const DeepCollectionEquality().hash(child)); - - @override - String toString() { - return 'StacDefaultBottomNavigationController(length: $length, initialIndex: $initialIndex, child: $child)'; - } -} - -/// @nodoc -abstract mixin class $StacDefaultBottomNavigationControllerCopyWith<$Res> { - factory $StacDefaultBottomNavigationControllerCopyWith( - StacDefaultBottomNavigationController value, - $Res Function(StacDefaultBottomNavigationController) _then) = - _$StacDefaultBottomNavigationControllerCopyWithImpl; - @useResult - $Res call({int length, int? initialIndex, Map child}); -} - -/// @nodoc -class _$StacDefaultBottomNavigationControllerCopyWithImpl<$Res> - implements $StacDefaultBottomNavigationControllerCopyWith<$Res> { - _$StacDefaultBottomNavigationControllerCopyWithImpl(this._self, this._then); - - final StacDefaultBottomNavigationController _self; - final $Res Function(StacDefaultBottomNavigationController) _then; - - /// Create a copy of StacDefaultBottomNavigationController - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? length = null, - Object? initialIndex = freezed, - Object? child = null, - }) { - return _then(_self.copyWith( - length: null == length - ? _self.length - : length // ignore: cast_nullable_to_non_nullable - as int, - initialIndex: freezed == initialIndex - ? _self.initialIndex - : initialIndex // ignore: cast_nullable_to_non_nullable - as int?, - child: null == child - ? _self.child - : child // ignore: cast_nullable_to_non_nullable - as Map, - )); - } -} - -/// Adds pattern-matching-related methods to [StacDefaultBottomNavigationController]. -extension StacDefaultBottomNavigationControllerPatterns - on StacDefaultBottomNavigationController { - /// A variant of `map` that fallback to returning `orElse`. - /// - /// It is equivalent to doing: - /// ```dart - /// switch (sealedClass) { - /// case final Subclass value: - /// return ...; - /// case _: - /// return orElse(); - /// } - /// ``` - - @optionalTypeArgs - TResult maybeMap( - TResult Function(_StacDefaultBottomNavigationController value)? $default, { - required TResult orElse(), - }) { - final _that = this; - switch (_that) { - case _StacDefaultBottomNavigationController() when $default != null: - return $default(_that); - case _: - return orElse(); - } - } - - /// A `switch`-like method, using callbacks. - /// - /// Callbacks receives the raw object, upcasted. - /// It is equivalent to doing: - /// ```dart - /// switch (sealedClass) { - /// case final Subclass value: - /// return ...; - /// case final Subclass2 value: - /// return ...; - /// } - /// ``` - - @optionalTypeArgs - TResult map( - TResult Function(_StacDefaultBottomNavigationController value) $default, - ) { - final _that = this; - switch (_that) { - case _StacDefaultBottomNavigationController(): - return $default(_that); - case _: - throw StateError('Unexpected subclass'); - } - } - - /// A variant of `map` that fallback to returning `null`. - /// - /// It is equivalent to doing: - /// ```dart - /// switch (sealedClass) { - /// case final Subclass value: - /// return ...; - /// case _: - /// return null; - /// } - /// ``` - - @optionalTypeArgs - TResult? mapOrNull( - TResult? Function(_StacDefaultBottomNavigationController value)? $default, - ) { - final _that = this; - switch (_that) { - case _StacDefaultBottomNavigationController() when $default != null: - return $default(_that); - case _: - return null; - } - } - - /// A variant of `when` that fallback to an `orElse` callback. - /// - /// It is equivalent to doing: - /// ```dart - /// switch (sealedClass) { - /// case Subclass(:final field): - /// return ...; - /// case _: - /// return orElse(); - /// } - /// ``` - - @optionalTypeArgs - TResult maybeWhen( - TResult Function(int length, int? initialIndex, Map child)? - $default, { - required TResult orElse(), - }) { - final _that = this; - switch (_that) { - case _StacDefaultBottomNavigationController() when $default != null: - return $default(_that.length, _that.initialIndex, _that.child); - case _: - return orElse(); - } - } - - /// A `switch`-like method, using callbacks. - /// - /// As opposed to `map`, this offers destructuring. - /// It is equivalent to doing: - /// ```dart - /// switch (sealedClass) { - /// case Subclass(:final field): - /// return ...; - /// case Subclass2(:final field2): - /// return ...; - /// } - /// ``` - - @optionalTypeArgs - TResult when( - TResult Function(int length, int? initialIndex, Map child) - $default, - ) { - final _that = this; - switch (_that) { - case _StacDefaultBottomNavigationController(): - return $default(_that.length, _that.initialIndex, _that.child); - case _: - throw StateError('Unexpected subclass'); - } - } - - /// A variant of `when` that fallback to returning `null` - /// - /// It is equivalent to doing: - /// ```dart - /// switch (sealedClass) { - /// case Subclass(:final field): - /// return ...; - /// case _: - /// return null; - /// } - /// ``` - - @optionalTypeArgs - TResult? whenOrNull( - TResult? Function( - int length, int? initialIndex, Map child)? - $default, - ) { - final _that = this; - switch (_that) { - case _StacDefaultBottomNavigationController() when $default != null: - return $default(_that.length, _that.initialIndex, _that.child); - case _: - return null; - } - } -} - -/// @nodoc -@JsonSerializable() -class _StacDefaultBottomNavigationController - implements StacDefaultBottomNavigationController { - const _StacDefaultBottomNavigationController( - {required this.length, - this.initialIndex, - required final Map child}) - : _child = child; - factory _StacDefaultBottomNavigationController.fromJson( - Map json) => - _$StacDefaultBottomNavigationControllerFromJson(json); - - @override - final int length; - @override - final int? initialIndex; - final Map _child; - @override - Map get child { - if (_child is EqualUnmodifiableMapView) return _child; - // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(_child); - } - - /// Create a copy of StacDefaultBottomNavigationController - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - @pragma('vm:prefer-inline') - _$StacDefaultBottomNavigationControllerCopyWith< - _StacDefaultBottomNavigationController> - get copyWith => __$StacDefaultBottomNavigationControllerCopyWithImpl< - _StacDefaultBottomNavigationController>(this, _$identity); - - @override - Map toJson() { - return _$StacDefaultBottomNavigationControllerToJson( - this, - ); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _StacDefaultBottomNavigationController && - (identical(other.length, length) || other.length == length) && - (identical(other.initialIndex, initialIndex) || - other.initialIndex == initialIndex) && - const DeepCollectionEquality().equals(other._child, _child)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, length, initialIndex, - const DeepCollectionEquality().hash(_child)); - - @override - String toString() { - return 'StacDefaultBottomNavigationController(length: $length, initialIndex: $initialIndex, child: $child)'; - } -} - -/// @nodoc -abstract mixin class _$StacDefaultBottomNavigationControllerCopyWith<$Res> - implements $StacDefaultBottomNavigationControllerCopyWith<$Res> { - factory _$StacDefaultBottomNavigationControllerCopyWith( - _StacDefaultBottomNavigationController value, - $Res Function(_StacDefaultBottomNavigationController) _then) = - __$StacDefaultBottomNavigationControllerCopyWithImpl; - @override - @useResult - $Res call({int length, int? initialIndex, Map child}); -} - -/// @nodoc -class __$StacDefaultBottomNavigationControllerCopyWithImpl<$Res> - implements _$StacDefaultBottomNavigationControllerCopyWith<$Res> { - __$StacDefaultBottomNavigationControllerCopyWithImpl(this._self, this._then); - - final _StacDefaultBottomNavigationController _self; - final $Res Function(_StacDefaultBottomNavigationController) _then; - - /// Create a copy of StacDefaultBottomNavigationController - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $Res call({ - Object? length = null, - Object? initialIndex = freezed, - Object? child = null, - }) { - return _then(_StacDefaultBottomNavigationController( - length: null == length - ? _self.length - : length // ignore: cast_nullable_to_non_nullable - as int, - initialIndex: freezed == initialIndex - ? _self.initialIndex - : initialIndex // ignore: cast_nullable_to_non_nullable - as int?, - child: null == child - ? _self._child - : child // ignore: cast_nullable_to_non_nullable - as Map, - )); - } -} - -// dart format on diff --git a/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.g.dart b/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.g.dart deleted file mode 100644 index 940d86cb6..000000000 --- a/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.g.dart +++ /dev/null @@ -1,24 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'stac_default_bottom_navigation_controller.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_StacDefaultBottomNavigationController - _$StacDefaultBottomNavigationControllerFromJson( - Map json) => - _StacDefaultBottomNavigationController( - length: (json['length'] as num).toInt(), - initialIndex: (json['initialIndex'] as num?)?.toInt(), - child: json['child'] as Map, - ); - -Map _$StacDefaultBottomNavigationControllerToJson( - _StacDefaultBottomNavigationController instance) => - { - 'length': instance.length, - 'initialIndex': instance.initialIndex, - 'child': instance.child, - }; diff --git a/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller_parser.dart index 85d2e5065..a501bea6e 100644 --- a/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller_parser.dart +++ b/packages/stac/lib/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller_parser.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stac/src/framework/framework.dart'; -import 'package:stac/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart'; +import 'package:stac/src/parsers/core/stac_widget_parser.dart'; import 'package:stac_core/stac_core.dart'; import 'package:stac_framework/stac_framework.dart'; import 'package:stac_logger/stac_logger.dart'; @@ -31,6 +30,7 @@ class _DefaultBottomNavigationControllerWidget extends StatefulWidget { }); final StacDefaultBottomNavigationController model; + @override State<_DefaultBottomNavigationControllerWidget> createState() => _DefaultBottomNavigationControllerWidgetState(); @@ -61,15 +61,18 @@ class _DefaultBottomNavigationControllerWidgetState return BottomNavigationScope( length: widget.model.length, controller: _controller, - child: Builder( - builder: (context) => - Stac.fromJson(widget.model.child, context) ?? const SizedBox(), - ), + child: widget.model.child.parse(context) ?? const SizedBox(), ); } } +/// An inherited widget that provides bottom navigation state to descendant widgets. +/// +/// This widget is typically created by [StacDefaultBottomNavigationController] +/// and provides access to the [BottomNavigationController] and navigation length +/// to child widgets like [StacBottomNavigationBar] and [StacBottomNavigationView]. class BottomNavigationScope extends InheritedWidget { + /// Creates a [BottomNavigationScope] with the specified properties. const BottomNavigationScope({ super.key, required super.child, @@ -77,9 +80,15 @@ class BottomNavigationScope extends InheritedWidget { required this.controller, }); + /// The number of bottom navigation items. final int length; + + /// The controller that manages the current navigation index. final BottomNavigationController controller; + /// Returns the [BottomNavigationScope] from the widget tree. + /// + /// Returns null if no [BottomNavigationScope] is found in the widget tree. static BottomNavigationScope? of(BuildContext context) { final BottomNavigationScope? result = context.dependOnInheritedWidgetOfExactType(); @@ -88,7 +97,8 @@ class BottomNavigationScope extends InheritedWidget { return result; } else { Log.e( - "BottomNavigationScope.of() called with a context that does not contain a BottomNavigationScope."); + "BottomNavigationScope.of() called with a context that does not contain a BottomNavigationScope.", + ); return null; } } @@ -99,17 +109,28 @@ class BottomNavigationScope extends InheritedWidget { } } +/// A controller that manages the state of a bottom navigation bar. +/// +/// This controller tracks the current selected index and notifies listeners +/// when the index changes. It is used by [BottomNavigationScope] to coordinate +/// between [StacBottomNavigationBar] and [StacBottomNavigationView]. class BottomNavigationController extends ChangeNotifier { - BottomNavigationController({ - this.initialIndex = 0, - required this.length, - }) : _index = initialIndex; + /// Creates a [BottomNavigationController] with the specified properties. + BottomNavigationController({this.initialIndex = 0, required this.length}) + : _index = initialIndex; + /// The initial index when the controller is created. final int initialIndex; + + /// The number of navigation items. final int length; int _index = 0; + + /// The current selected index. int get index => _index; + + /// Sets the current selected index. set index(int value) => _changeIndex(value); void _changeIndex(int value) { diff --git a/packages/stac/lib/src/parsers/widgets/widgets.dart b/packages/stac/lib/src/parsers/widgets/widgets.dart index cb3957838..bb0347f00 100644 --- a/packages/stac/lib/src/parsers/widgets/widgets.dart +++ b/packages/stac/lib/src/parsers/widgets/widgets.dart @@ -21,7 +21,7 @@ export 'package:stac/src/parsers/widgets/stac_column/stac_column_parser.dart'; export 'package:stac/src/parsers/widgets/stac_conditional/stac_conditional_parser.dart'; export 'package:stac/src/parsers/widgets/stac_container/stac_container_parser.dart'; export 'package:stac/src/parsers/widgets/stac_custom_scroll_view/stac_custom_scroll_view_parser.dart'; -export 'package:stac/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart'; +export 'package:stac/src/parsers/widgets/stac_default_bottom_navigation_controller/stac_default_bottom_navigation_controller_parser.dart'; export 'package:stac/src/parsers/widgets/stac_default_tab_controller/stac_default_tab_controller_parser.dart'; export 'package:stac/src/parsers/widgets/stac_divider/stac_divider_parser.dart'; export 'package:stac/src/parsers/widgets/stac_drawer/stac_drawer_parser.dart'; diff --git a/packages/stac_core/lib/actions/set_value/stac_set_value_action.dart b/packages/stac_core/lib/actions/set_value/stac_set_value_action.dart index 141d56d4d..79d0e6e78 100644 --- a/packages/stac_core/lib/actions/set_value/stac_set_value_action.dart +++ b/packages/stac_core/lib/actions/set_value/stac_set_value_action.dart @@ -47,7 +47,7 @@ class StacSetValueAction extends StacAction { final List>? values; /// An optional action to execute after the values are written. - final Map? action; + final StacAction? action; /// Action type identifier. @override diff --git a/packages/stac_core/lib/actions/set_value/stac_set_value_action.g.dart b/packages/stac_core/lib/actions/set_value/stac_set_value_action.g.dart index d61bf187f..82603e7b7 100644 --- a/packages/stac_core/lib/actions/set_value/stac_set_value_action.g.dart +++ b/packages/stac_core/lib/actions/set_value/stac_set_value_action.g.dart @@ -11,12 +11,14 @@ StacSetValueAction _$StacSetValueActionFromJson(Map json) => values: (json['values'] as List?) ?.map((e) => e as Map) .toList(), - action: json['action'] as Map?, + action: json['action'] == null + ? null + : StacAction.fromJson(json['action'] as Map), ); Map _$StacSetValueActionToJson(StacSetValueAction instance) => { 'values': instance.values, - 'action': instance.action, + 'action': instance.action?.toJson(), 'actionType': instance.actionType, }; diff --git a/packages/stac_core/lib/foundation/borders/stac_border/stac_border.dart b/packages/stac_core/lib/foundation/borders/stac_border/stac_border.dart index b50379084..66327c28e 100644 --- a/packages/stac_core/lib/foundation/borders/stac_border/stac_border.dart +++ b/packages/stac_core/lib/foundation/borders/stac_border/stac_border.dart @@ -83,6 +83,92 @@ class StacBorder implements StacElement { /// The left border side with individual styling. final StacBorderSide? left; + /// Creates a uniform border applied to all sides. + /// + /// This factory method creates a border with the same styling applied + /// to all four sides (top, right, bottom, left). + /// + /// {@tool snippet} + /// Dart Example: + /// ```dart + /// StacBorder.all( + /// color: StacColors.blue, + /// width: 2.0, + /// borderStyle: StacBorderStyle.solid, + /// ) + /// ``` + /// {@end-tool} + /// + /// {@tool snippet} + /// JSON Example: + /// ```json + /// { + /// "color": "#2196F3", + /// "width": 2.0, + /// "borderStyle": "solid" + /// } + /// ``` + /// {@end-tool} + factory StacBorder.all({ + StacColor? color, + StacBorderStyle? borderStyle, + double? width, + double? strokeAlign, + }) { + return StacBorder( + color: color, + borderStyle: borderStyle, + width: width, + strokeAlign: strokeAlign, + ); + } + + /// Creates a symmetric border with different styling for horizontal and vertical sides. + /// + /// This factory method creates a border where horizontal sides (left, right) + /// have the same styling, and vertical sides (top, bottom) 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": "#F44336", "width": 1.0, "borderStyle": "solid"}, + /// "bottom": {"color": "#F44336", "width": 1.0, "borderStyle": "solid"}, + /// "left": {"color": "#2196F3", "width": 2.0, "borderStyle": "solid"}, + /// "right": {"color": "#2196F3", "width": 2.0, "borderStyle": "solid"} + /// } + /// ``` + /// {@end-tool} + factory StacBorder.symmetric({ + StacBorderSide? horizontal, + StacBorderSide? vertical, + }) { + return StacBorder( + top: vertical, + bottom: vertical, + left: horizontal, + right: horizontal, + ); + } + /// Creates a [StacBorder] from a JSON map. factory StacBorder.fromJson(Map json) => _$StacBorderFromJson(json); diff --git a/packages/stac_core/lib/foundation/borders/stac_border_radius/stac_border_radius.dart b/packages/stac_core/lib/foundation/borders/stac_border_radius/stac_border_radius.dart index 58ef5d784..b5f0b85b1 100644 --- a/packages/stac_core/lib/foundation/borders/stac_border_radius/stac_border_radius.dart +++ b/packages/stac_core/lib/foundation/borders/stac_border_radius/stac_border_radius.dart @@ -65,6 +65,146 @@ class StacBorderRadius implements StacElement { bottomRight: radius, ); + /// Creates a border radius with individual values for each corner. + /// + /// This factory method allows you to specify different radius values + /// for each corner individually. + /// + /// {@tool snippet} + /// Dart Example: + /// ```dart + /// StacBorderRadius.only( + /// topLeft: 8.0, + /// topRight: 4.0, + /// bottomLeft: 4.0, + /// bottomRight: 8.0, + /// ) + /// ``` + /// {@end-tool} + /// + /// {@tool snippet} + /// JSON Example: + /// ```json + /// { + /// "topLeft": 8.0, + /// "topRight": 4.0, + /// "bottomLeft": 4.0, + /// "bottomRight": 8.0 + /// } + /// ``` + /// {@end-tool} + const StacBorderRadius.only({ + double? topLeft, + double? topRight, + double? bottomLeft, + double? bottomRight, + }) : this( + topLeft: topLeft, + topRight: topRight, + bottomLeft: bottomLeft, + bottomRight: bottomRight, + ); + + /// Creates a border radius with symmetric horizontal corners. + /// + /// This factory method creates a border radius where left corners + /// have the same radius and right corners have the same radius. + /// + /// {@tool snippet} + /// Dart Example: + /// ```dart + /// StacBorderRadius.horizontal( + /// left: 8.0, + /// right: 4.0, + /// ) + /// ``` + /// {@end-tool} + /// + /// {@tool snippet} + /// JSON Example: + /// ```json + /// { + /// "topLeft": 8.0, + /// "topRight": 4.0, + /// "bottomLeft": 8.0, + /// "bottomRight": 4.0 + /// } + /// ``` + /// {@end-tool} + const StacBorderRadius.horizontal({double? left, double? right}) + : this( + topLeft: left, + topRight: right, + bottomLeft: left, + bottomRight: right, + ); + + /// Creates a border radius with symmetric vertical corners. + /// + /// This factory method creates a border radius where top corners + /// have the same radius and bottom corners have the same radius. + /// + /// {@tool snippet} + /// Dart Example: + /// ```dart + /// StacBorderRadius.vertical( + /// top: 8.0, + /// bottom: 4.0, + /// ) + /// ``` + /// {@end-tool} + /// + /// {@tool snippet} + /// JSON Example: + /// ```json + /// { + /// "topLeft": 8.0, + /// "topRight": 8.0, + /// "bottomLeft": 4.0, + /// "bottomRight": 4.0 + /// } + /// ``` + /// {@end-tool} + const StacBorderRadius.vertical({double? top, double? bottom}) + : this( + topLeft: top, + topRight: top, + bottomLeft: bottom, + bottomRight: bottom, + ); + + /// Creates a circular border radius. + /// + /// This factory method creates a border radius that forms a perfect circle + /// when applied to a square widget. For non-square widgets, it creates + /// an elliptical shape. + /// + /// {@tool snippet} + /// Dart Example: + /// ```dart + /// StacBorderRadius.circular(20.0) + /// ``` + /// {@end-tool} + /// + /// {@tool snippet} + /// JSON Example: + /// ```json + /// { + /// "topLeft": 20.0, + /// "topRight": 20.0, + /// "bottomLeft": 20.0, + /// "bottomRight": 20.0 + /// } + /// ``` + /// {@end-tool} + const StacBorderRadius.circular(double radius) + : this( + topLeft: radius, + topRight: radius, + bottomLeft: radius, + bottomRight: radius, + ); + /// The radius for the top-left corner in logical pixels. /// /// If null, no radius will be applied to this corner. diff --git a/packages/stac_core/lib/foundation/colors/stac_color/stac_colors.dart b/packages/stac_core/lib/foundation/colors/stac_color/stac_colors.dart index 015fc1200..d17b40db5 100644 --- a/packages/stac_core/lib/foundation/colors/stac_color/stac_colors.dart +++ b/packages/stac_core/lib/foundation/colors/stac_color/stac_colors.dart @@ -4,6 +4,49 @@ /// or theme color names (e.g., 'primary', 'secondary'). typedef StacColor = String; +/// Extension on [StacColor] to provide additional functionality. +extension StacColorExtension on StacColor { + /// Creates a new color with the specified opacity. + /// + /// This method appends the opacity value to the color string in the format + /// `@` where opacity is expressed as a percentage (0-100). + /// + /// {@tool snippet} + /// Dart Example: + /// ```dart + /// StacColors.primary.withOpacity(0.8) // Returns "primary@80" + /// StacColors.blue.withOpacity(0.5) // Returns "blue@50" + /// '#FF0000'.withOpacity(0.3) // Returns "#FF0000@30" + /// ``` + /// {@end-tool} + /// + /// {@tool snippet} + /// JSON Example: + /// ```json + /// { + /// "color": "primary@80", + /// "backgroundColor": "secondary@50" + /// } + /// ``` + /// {@end-tool} + /// + /// The opacity value should be between 0.0 (completely transparent) and 1.0 + /// (completely opaque). Values outside this range will be clamped. + 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(); + + // Remove any existing opacity suffix by splitting on '@' and taking the first part + final baseColor = split('@').first; + + // Return the base color string with new opacity suffix + return '$baseColor@$opacityPercentage'; + } +} + /// A collection of predefined colors for the Stac framework. /// /// This class provides a comprehensive set of Material Design colors, diff --git a/packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart b/packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart new file mode 100644 index 000000000..110449498 --- /dev/null +++ b/packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart @@ -0,0 +1,136 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:stac_core/core/stac_widget.dart'; +import 'package:stac_core/foundation/specifications/widget_type.dart'; + +part 'stac_default_bottom_navigation_controller.g.dart'; + +/// A Stac model representing Flutter's DefaultTabController widget for bottom navigation. +/// +/// This widget provides a controller for managing bottom navigation state and +/// establishes a BottomNavigationScope that can be accessed by child widgets +/// like StacBottomNavigationView and StacBottomNavigationBar. +/// +/// {@tool snippet} +/// Dart Example: +/// ```dart +/// StacDefaultBottomNavigationController( +/// length: 3, +/// initialIndex: 0, +/// child: StacScaffold( +/// appBar: StacAppBar( +/// title: StacText('Bottom Navigation Screen'), +/// ), +/// body: StacBottomNavigationView( +/// children: const [ +/// StacCenter(child: StacText('Home')), +/// StacCenter(child: StacText('Search')), +/// StacCenter(child: StacText('Profile')), +/// ], +/// ), +/// bottomNavigationBar: StacBottomNavigationBar( +/// items: [ +/// StacBottomNavigationBarItem( +/// icon: StacIcon(icon: 'home'), +/// label: 'Home', +/// ), +/// StacBottomNavigationBarItem( +/// icon: StacIcon(icon: 'search'), +/// label: 'Search', +/// ), +/// StacBottomNavigationBarItem( +/// icon: StacIcon(icon: 'account_circle'), +/// label: 'Profile', +/// ), +/// ], +/// ), +/// ), +/// ) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +/// JSON Example: +/// ```json +/// { +/// "type": "defaultBottomNavigationController", +/// "length": 3, +/// "initialIndex": 0, +/// "child": { +/// "type": "scaffold", +/// "appBar": { +/// "type": "appBar", +/// "title": {"type": "text", "data": "Bottom Navigation Screen"} +/// }, +/// "body": { +/// "type": "bottomNavigationView", +/// "children": [ +/// {"type": "center", "child": {"type": "text", "data": "Home"}}, +/// {"type": "center", "child": {"type": "text", "data": "Search"}}, +/// {"type": "center", "child": {"type": "text", "data": "Profile"}} +/// ] +/// }, +/// "bottomNavigationBar": { +/// "type": "bottomNavigationBar", +/// "items": [ +/// { +/// "type": "navigationBarItem", +/// "label": "Home", +/// "icon": {"type": "icon", "icon": "home"} +/// }, +/// { +/// "type": "navigationBarItem", +/// "label": "Search", +/// "icon": {"type": "icon", "icon": "search"} +/// }, +/// { +/// "type": "navigationBarItem", +/// "label": "Profile", +/// "icon": {"type": "icon", "icon": "account_circle"} +/// } +/// ] +/// } +/// } +/// } +/// ``` +/// {@end-tool} +/// +/// See also: +/// * Flutter's DefaultTabController docs (https://api.flutter.dev/flutter/material/DefaultTabController-class.html) +@JsonSerializable() +class StacDefaultBottomNavigationController extends StacWidget { + /// Creates a [StacDefaultBottomNavigationController] with the specified properties. + const StacDefaultBottomNavigationController({ + required this.length, + this.initialIndex, + required this.child, + }); + + /// The number of tabs/bottom navigation items. + /// + /// Type: int + final int length; + + /// The initial index of the selected tab. + /// + /// Type: int? + final int? initialIndex; + + /// The child widget that will be wrapped by this controller. + /// + /// Type: StacWidget + final StacWidget child; + + /// Widget type identifier. + @override + String get type => WidgetType.defaultBottomNavigationController.name; + + /// Creates a [StacDefaultBottomNavigationController] from JSON. + factory StacDefaultBottomNavigationController.fromJson( + Map json, + ) => _$StacDefaultBottomNavigationControllerFromJson(json); + + /// Converts this widget to JSON. + @override + Map toJson() => + _$StacDefaultBottomNavigationControllerToJson(this); +} diff --git a/packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.g.dart b/packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.g.dart new file mode 100644 index 000000000..c4b8ca4eb --- /dev/null +++ b/packages/stac_core/lib/widgets/default_bottom_navigation_controller/stac_default_bottom_navigation_controller.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'stac_default_bottom_navigation_controller.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +StacDefaultBottomNavigationController +_$StacDefaultBottomNavigationControllerFromJson(Map json) => + StacDefaultBottomNavigationController( + length: (json['length'] as num).toInt(), + initialIndex: (json['initialIndex'] as num?)?.toInt(), + child: StacWidget.fromJson(json['child'] as Map), + ); + +Map _$StacDefaultBottomNavigationControllerToJson( + StacDefaultBottomNavigationController instance, +) => { + 'length': instance.length, + 'initialIndex': instance.initialIndex, + 'child': instance.child.toJson(), + 'type': instance.type, +}; diff --git a/packages/stac_core/lib/widgets/widgets.dart b/packages/stac_core/lib/widgets/widgets.dart index 8a20a77a6..9d54fa626 100644 --- a/packages/stac_core/lib/widgets/widgets.dart +++ b/packages/stac_core/lib/widgets/widgets.dart @@ -81,3 +81,4 @@ export 'text_form_field/stac_text_form_field.dart'; export 'vertical_divider/stac_vertical_divider.dart'; export 'visibility/stac_visibility.dart'; export 'wrap/stac_wrap.dart'; +export 'default_bottom_navigation_controller/stac_default_bottom_navigation_controller.dart';