From 8dbc8cfd5b628b253fddf2cac0b9c08d957028f6 Mon Sep 17 00:00:00 2001 From: Naveen K Date: Sun, 6 Jul 2025 16:22:12 +0530 Subject: [PATCH] feat: Add Drawer widget support to Stac framework - Add StacDrawer model and parser for Flutter Drawer widget - Support all Drawer properties: backgroundColor, elevation, width, etc. - Add drawer to WidgetType enum and register in framework - Add drawer example to stac_gallery - Include complete drawer demo with header and menu items The drawer widget can now be used in Stac JSON with swipe gesture and tap actions on menu items. --- .../assets/json/drawer_example.json | 133 ++++++ .../stac_gallery/assets/json/home_screen.json | 23 ++ packages/stac/lib/src/framework/stac.dart | 1 + .../widgets/stac_drawer/stac_drawer.dart | 27 ++ .../stac_drawer/stac_drawer.freezed.dart | 378 ++++++++++++++++++ .../widgets/stac_drawer/stac_drawer.g.dart | 43 ++ .../stac_drawer/stac_drawer_parser.dart | 33 ++ .../stac/lib/src/parsers/widgets/widgets.dart | 1 + packages/stac/lib/src/utils/widget_type.dart | 1 + 9 files changed, 640 insertions(+) create mode 100644 examples/stac_gallery/assets/json/drawer_example.json create mode 100644 packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer.dart create mode 100644 packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer.freezed.dart create mode 100644 packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer.g.dart create mode 100644 packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer_parser.dart diff --git a/examples/stac_gallery/assets/json/drawer_example.json b/examples/stac_gallery/assets/json/drawer_example.json new file mode 100644 index 00000000..51c7ba7d --- /dev/null +++ b/examples/stac_gallery/assets/json/drawer_example.json @@ -0,0 +1,133 @@ +{ + "type": "scaffold", + "appBar": { + "type": "appBar", + "title": { + "type": "text", + "data": "Drawer Example", + "style": { + "color": "#ffffff", + "fontSize": 21 + } + }, + "backgroundColor": "#4D00E9" + }, + "drawerEnableOpenDragGesture": true, + "drawerEdgeDragWidth": 20.0, + "drawer": { + "type": "drawer", + "backgroundColor": "#f5f5f5", + "elevation": 16.0, + "width": 280.0, + "child": { + "type": "column", + "children": [ + { + "type": "container", + "height": 120, + "color": "#4D00E9", + "child": { + "type": "center", + "child": { + "type": "text", + "data": "Drawer Header", + "style": { + "color": "#ffffff", + "fontSize": 20, + "fontWeight": "bold" + } + } + } + }, + { + "type": "expanded", + "child": { + "type": "listView", + "children": [ + { + "type": "listTile", + "leading": { + "type": "icon", + "iconType": "material", + "icon": "home", + "size": 24 + }, + "title": { + "type": "text", + "data": "Home" + }, + "onTap": { + "actionType": "snackBar", + "content": "Home tapped!" + } + }, + { + "type": "listTile", + "leading": { + "type": "icon", + "iconType": "material", + "icon": "settings", + "size": 24 + }, + "title": { + "type": "text", + "data": "Settings" + }, + "onTap": { + "actionType": "snackBar", + "content": "Settings tapped!" + } + }, + { + "type": "listTile", + "leading": { + "type": "icon", + "iconType": "material", + "icon": "info", + "size": 24 + }, + "title": { + "type": "text", + "data": "About" + }, + "onTap": { + "actionType": "snackBar", + "content": "About tapped!" + } + } + ] + } + } + ] + } + }, + "body": { + "type": "center", + "child": { + "type": "column", + "mainAxisAlignment": "center", + "children": [ + { + "type": "text", + "data": "Welcome to Drawer Example", + "style": { + "fontSize": 24, + "fontWeight": "bold" + } + }, + { + "type": "sizedBox", + "height": 16 + }, + { + "type": "text", + "data": "Swipe from left edge or tap the menu icon to open the drawer", + "style": { + "fontSize": 16, + "color": "#666666" + } + } + ] + } + } +} \ No newline at end of file diff --git a/examples/stac_gallery/assets/json/home_screen.json b/examples/stac_gallery/assets/json/home_screen.json index 7808885d..07ae7df3 100644 --- a/examples/stac_gallery/assets/json/home_screen.json +++ b/examples/stac_gallery/assets/json/home_screen.json @@ -154,6 +154,29 @@ } } }, + { + "type": "listTile", + "leading": { + "type": "icon", + "iconType": "material", + "icon": "menu" + }, + "title": { + "type": "text", + "data": "Stac Drawer" + }, + "subtitle": { + "type": "text", + "data": "A Material Design panel that slides in horizontally from the edge of a Scaffold" + }, + "onTap": { + "actionType": "navigate", + "widgetJson": { + "type": "exampleScreen", + "assetPath": "assets/json/drawer_example.json" + } + } + }, { "type": "listTile", "leading": { diff --git a/packages/stac/lib/src/framework/stac.dart b/packages/stac/lib/src/framework/stac.dart index 41ef5104..e90f09a7 100644 --- a/packages/stac/lib/src/framework/stac.dart +++ b/packages/stac/lib/src/framework/stac.dart @@ -87,6 +87,7 @@ class Stac { const StacCarouselViewParser(), const StacColoredBoxParser(), const StacDividerParser(), + const StacDrawerParser(), const StacCircularProgressIndicatorParser(), const StacLinearProgressIndicatorParser(), const StacHeroParser(), diff --git a/packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer.dart b/packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer.dart new file mode 100644 index 00000000..e23a2ab7 --- /dev/null +++ b/packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:stac/src/parsers/widgets/stac_double/stac_double.dart'; +import 'package:stac/src/parsers/widgets/stac_shape_border/stac_shape_border.dart'; + +export 'stac_drawer_parser.dart'; + +part 'stac_drawer.freezed.dart'; +part 'stac_drawer.g.dart'; + +@freezed +abstract class StacDrawer with _$StacDrawer { + const factory StacDrawer({ + String? backgroundColor, + StacDouble? elevation, + String? shadowColor, + String? surfaceTintColor, + StacShapeBorder? shape, + StacDouble? width, + Map? child, + String? semanticLabel, + Clip? clipBehavior, + }) = _StacDrawer; + + factory StacDrawer.fromJson(Map json) => + _$StacDrawerFromJson(json); +} diff --git a/packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer.freezed.dart b/packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer.freezed.dart new file mode 100644 index 00000000..d99d2ecf --- /dev/null +++ b/packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer.freezed.dart @@ -0,0 +1,378 @@ +// dart format width=80 +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// 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_drawer.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$StacDrawer { + String? get backgroundColor; + StacDouble? get elevation; + String? get shadowColor; + String? get surfaceTintColor; + StacShapeBorder? get shape; + StacDouble? get width; + Map? get child; + String? get semanticLabel; + Clip? get clipBehavior; + + /// Create a copy of StacDrawer + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + $StacDrawerCopyWith get copyWith => + _$StacDrawerCopyWithImpl(this as StacDrawer, _$identity); + + /// Serializes this StacDrawer to a JSON map. + Map toJson(); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is StacDrawer && + (identical(other.backgroundColor, backgroundColor) || + other.backgroundColor == backgroundColor) && + (identical(other.elevation, elevation) || + other.elevation == elevation) && + (identical(other.shadowColor, shadowColor) || + other.shadowColor == shadowColor) && + (identical(other.surfaceTintColor, surfaceTintColor) || + other.surfaceTintColor == surfaceTintColor) && + (identical(other.shape, shape) || other.shape == shape) && + (identical(other.width, width) || other.width == width) && + const DeepCollectionEquality().equals(other.child, child) && + (identical(other.semanticLabel, semanticLabel) || + other.semanticLabel == semanticLabel) && + (identical(other.clipBehavior, clipBehavior) || + other.clipBehavior == clipBehavior)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + backgroundColor, + elevation, + shadowColor, + surfaceTintColor, + shape, + width, + const DeepCollectionEquality().hash(child), + semanticLabel, + clipBehavior); + + @override + String toString() { + return 'StacDrawer(backgroundColor: $backgroundColor, elevation: $elevation, shadowColor: $shadowColor, surfaceTintColor: $surfaceTintColor, shape: $shape, width: $width, child: $child, semanticLabel: $semanticLabel, clipBehavior: $clipBehavior)'; + } +} + +/// @nodoc +abstract mixin class $StacDrawerCopyWith<$Res> { + factory $StacDrawerCopyWith( + StacDrawer value, $Res Function(StacDrawer) _then) = + _$StacDrawerCopyWithImpl; + @useResult + $Res call( + {String? backgroundColor, + StacDouble? elevation, + String? shadowColor, + String? surfaceTintColor, + StacShapeBorder? shape, + StacDouble? width, + Map? child, + String? semanticLabel, + Clip? clipBehavior}); + + $StacShapeBorderCopyWith<$Res>? get shape; +} + +/// @nodoc +class _$StacDrawerCopyWithImpl<$Res> implements $StacDrawerCopyWith<$Res> { + _$StacDrawerCopyWithImpl(this._self, this._then); + + final StacDrawer _self; + final $Res Function(StacDrawer) _then; + + /// Create a copy of StacDrawer + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? backgroundColor = freezed, + Object? elevation = freezed, + Object? shadowColor = freezed, + Object? surfaceTintColor = freezed, + Object? shape = freezed, + Object? width = freezed, + Object? child = freezed, + Object? semanticLabel = freezed, + Object? clipBehavior = freezed, + }) { + return _then(_self.copyWith( + backgroundColor: freezed == backgroundColor + ? _self.backgroundColor + : backgroundColor // ignore: cast_nullable_to_non_nullable + as String?, + elevation: freezed == elevation + ? _self.elevation + : elevation // ignore: cast_nullable_to_non_nullable + as StacDouble?, + shadowColor: freezed == shadowColor + ? _self.shadowColor + : shadowColor // ignore: cast_nullable_to_non_nullable + as String?, + surfaceTintColor: freezed == surfaceTintColor + ? _self.surfaceTintColor + : surfaceTintColor // ignore: cast_nullable_to_non_nullable + as String?, + shape: freezed == shape + ? _self.shape + : shape // ignore: cast_nullable_to_non_nullable + as StacShapeBorder?, + width: freezed == width + ? _self.width + : width // ignore: cast_nullable_to_non_nullable + as StacDouble?, + child: freezed == child + ? _self.child + : child // ignore: cast_nullable_to_non_nullable + as Map?, + semanticLabel: freezed == semanticLabel + ? _self.semanticLabel + : semanticLabel // ignore: cast_nullable_to_non_nullable + as String?, + clipBehavior: freezed == clipBehavior + ? _self.clipBehavior + : clipBehavior // ignore: cast_nullable_to_non_nullable + as Clip?, + )); + } + + /// Create a copy of StacDrawer + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $StacShapeBorderCopyWith<$Res>? get shape { + if (_self.shape == null) { + return null; + } + + return $StacShapeBorderCopyWith<$Res>(_self.shape!, (value) { + return _then(_self.copyWith(shape: value)); + }); + } +} + +/// @nodoc +@JsonSerializable() +class _StacDrawer implements StacDrawer { + const _StacDrawer( + {this.backgroundColor, + this.elevation, + this.shadowColor, + this.surfaceTintColor, + this.shape, + this.width, + final Map? child, + this.semanticLabel, + this.clipBehavior}) + : _child = child; + factory _StacDrawer.fromJson(Map json) => + _$StacDrawerFromJson(json); + + @override + final String? backgroundColor; + @override + final StacDouble? elevation; + @override + final String? shadowColor; + @override + final String? surfaceTintColor; + @override + final StacShapeBorder? shape; + @override + final StacDouble? width; + final Map? _child; + @override + Map? get child { + final value = _child; + if (value == null) return null; + if (_child is EqualUnmodifiableMapView) return _child; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + final String? semanticLabel; + @override + final Clip? clipBehavior; + + /// Create a copy of StacDrawer + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$StacDrawerCopyWith<_StacDrawer> get copyWith => + __$StacDrawerCopyWithImpl<_StacDrawer>(this, _$identity); + + @override + Map toJson() { + return _$StacDrawerToJson( + this, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _StacDrawer && + (identical(other.backgroundColor, backgroundColor) || + other.backgroundColor == backgroundColor) && + (identical(other.elevation, elevation) || + other.elevation == elevation) && + (identical(other.shadowColor, shadowColor) || + other.shadowColor == shadowColor) && + (identical(other.surfaceTintColor, surfaceTintColor) || + other.surfaceTintColor == surfaceTintColor) && + (identical(other.shape, shape) || other.shape == shape) && + (identical(other.width, width) || other.width == width) && + const DeepCollectionEquality().equals(other._child, _child) && + (identical(other.semanticLabel, semanticLabel) || + other.semanticLabel == semanticLabel) && + (identical(other.clipBehavior, clipBehavior) || + other.clipBehavior == clipBehavior)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + backgroundColor, + elevation, + shadowColor, + surfaceTintColor, + shape, + width, + const DeepCollectionEquality().hash(_child), + semanticLabel, + clipBehavior); + + @override + String toString() { + return 'StacDrawer(backgroundColor: $backgroundColor, elevation: $elevation, shadowColor: $shadowColor, surfaceTintColor: $surfaceTintColor, shape: $shape, width: $width, child: $child, semanticLabel: $semanticLabel, clipBehavior: $clipBehavior)'; + } +} + +/// @nodoc +abstract mixin class _$StacDrawerCopyWith<$Res> + implements $StacDrawerCopyWith<$Res> { + factory _$StacDrawerCopyWith( + _StacDrawer value, $Res Function(_StacDrawer) _then) = + __$StacDrawerCopyWithImpl; + @override + @useResult + $Res call( + {String? backgroundColor, + StacDouble? elevation, + String? shadowColor, + String? surfaceTintColor, + StacShapeBorder? shape, + StacDouble? width, + Map? child, + String? semanticLabel, + Clip? clipBehavior}); + + @override + $StacShapeBorderCopyWith<$Res>? get shape; +} + +/// @nodoc +class __$StacDrawerCopyWithImpl<$Res> implements _$StacDrawerCopyWith<$Res> { + __$StacDrawerCopyWithImpl(this._self, this._then); + + final _StacDrawer _self; + final $Res Function(_StacDrawer) _then; + + /// Create a copy of StacDrawer + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? backgroundColor = freezed, + Object? elevation = freezed, + Object? shadowColor = freezed, + Object? surfaceTintColor = freezed, + Object? shape = freezed, + Object? width = freezed, + Object? child = freezed, + Object? semanticLabel = freezed, + Object? clipBehavior = freezed, + }) { + return _then(_StacDrawer( + backgroundColor: freezed == backgroundColor + ? _self.backgroundColor + : backgroundColor // ignore: cast_nullable_to_non_nullable + as String?, + elevation: freezed == elevation + ? _self.elevation + : elevation // ignore: cast_nullable_to_non_nullable + as StacDouble?, + shadowColor: freezed == shadowColor + ? _self.shadowColor + : shadowColor // ignore: cast_nullable_to_non_nullable + as String?, + surfaceTintColor: freezed == surfaceTintColor + ? _self.surfaceTintColor + : surfaceTintColor // ignore: cast_nullable_to_non_nullable + as String?, + shape: freezed == shape + ? _self.shape + : shape // ignore: cast_nullable_to_non_nullable + as StacShapeBorder?, + width: freezed == width + ? _self.width + : width // ignore: cast_nullable_to_non_nullable + as StacDouble?, + child: freezed == child + ? _self._child + : child // ignore: cast_nullable_to_non_nullable + as Map?, + semanticLabel: freezed == semanticLabel + ? _self.semanticLabel + : semanticLabel // ignore: cast_nullable_to_non_nullable + as String?, + clipBehavior: freezed == clipBehavior + ? _self.clipBehavior + : clipBehavior // ignore: cast_nullable_to_non_nullable + as Clip?, + )); + } + + /// Create a copy of StacDrawer + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $StacShapeBorderCopyWith<$Res>? get shape { + if (_self.shape == null) { + return null; + } + + return $StacShapeBorderCopyWith<$Res>(_self.shape!, (value) { + return _then(_self.copyWith(shape: value)); + }); + } +} + +// dart format on diff --git a/packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer.g.dart b/packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer.g.dart new file mode 100644 index 00000000..3438e255 --- /dev/null +++ b/packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'stac_drawer.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_StacDrawer _$StacDrawerFromJson(Map json) => _StacDrawer( + backgroundColor: json['backgroundColor'] as String?, + elevation: json['elevation'] == null + ? null + : StacDouble.fromJson(json['elevation']), + shadowColor: json['shadowColor'] as String?, + surfaceTintColor: json['surfaceTintColor'] as String?, + shape: json['shape'] == null + ? null + : StacShapeBorder.fromJson(json['shape'] as Map), + width: json['width'] == null ? null : StacDouble.fromJson(json['width']), + child: json['child'] as Map?, + semanticLabel: json['semanticLabel'] as String?, + clipBehavior: $enumDecodeNullable(_$ClipEnumMap, json['clipBehavior']), + ); + +Map _$StacDrawerToJson(_StacDrawer instance) => + { + 'backgroundColor': instance.backgroundColor, + 'elevation': instance.elevation, + 'shadowColor': instance.shadowColor, + 'surfaceTintColor': instance.surfaceTintColor, + 'shape': instance.shape, + 'width': instance.width, + 'child': instance.child, + 'semanticLabel': instance.semanticLabel, + 'clipBehavior': _$ClipEnumMap[instance.clipBehavior], + }; + +const _$ClipEnumMap = { + Clip.none: 'none', + Clip.hardEdge: 'hardEdge', + Clip.antiAlias: 'antiAlias', + Clip.antiAliasWithSaveLayer: 'antiAliasWithSaveLayer', +}; diff --git a/packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer_parser.dart new file mode 100644 index 00000000..e0e3033f --- /dev/null +++ b/packages/stac/lib/src/parsers/widgets/stac_drawer/stac_drawer_parser.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:stac/src/framework/framework.dart'; +import 'package:stac/src/parsers/widgets/stac_drawer/stac_drawer.dart'; +import 'package:stac/src/parsers/widgets/stac_double/stac_double.dart'; +import 'package:stac/src/parsers/widgets/stac_shape_border/stac_shape_border.dart'; +import 'package:stac/src/utils/color_utils.dart'; +import 'package:stac/src/utils/widget_type.dart'; +import 'package:stac_framework/stac_framework.dart'; + +class StacDrawerParser extends StacParser { + const StacDrawerParser(); + + @override + String get type => WidgetType.drawer.name; + + @override + StacDrawer getModel(Map json) => StacDrawer.fromJson(json); + + @override + Widget parse(BuildContext context, StacDrawer model) { + return Drawer( + backgroundColor: model.backgroundColor?.toColor(context), + elevation: model.elevation?.parse, + shadowColor: model.shadowColor?.toColor(context), + surfaceTintColor: model.surfaceTintColor?.toColor(context), + shape: model.shape?.parse(context), + width: model.width?.parse, + semanticLabel: model.semanticLabel, + clipBehavior: model.clipBehavior, + child: Stac.fromJson(model.child, context), + ); + } +} diff --git a/packages/stac/lib/src/parsers/widgets/widgets.dart b/packages/stac/lib/src/parsers/widgets/widgets.dart index cf6a782a..b831b0ff 100644 --- a/packages/stac/lib/src/parsers/widgets/widgets.dart +++ b/packages/stac/lib/src/parsers/widgets/widgets.dart @@ -34,6 +34,7 @@ export 'package:stac/src/parsers/widgets/stac_decoration_image/stac_decoration_i 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_tab_controller/stac_default_tab_controller.dart'; export 'package:stac/src/parsers/widgets/stac_divider/stac_divider.dart'; +export 'package:stac/src/parsers/widgets/stac_drawer/stac_drawer.dart'; export 'package:stac/src/parsers/widgets/stac_dropdown_menu/stac_dropdown_menu.dart'; export 'package:stac/src/parsers/widgets/stac_dropdown_menu_entry/stac_dropdown_menu_entry.dart'; export 'package:stac/src/parsers/widgets/stac_duration/stac_duration.dart'; diff --git a/packages/stac/lib/src/utils/widget_type.dart b/packages/stac/lib/src/utils/widget_type.dart index 0b8a57d3..d6b47fe1 100644 --- a/packages/stac/lib/src/utils/widget_type.dart +++ b/packages/stac/lib/src/utils/widget_type.dart @@ -19,6 +19,7 @@ enum WidgetType { column, conditional, container, + drawer, dropdownMenu, customScrollView, defaultBottomNavigationController,