From bb7562380fee6b4b282ffb6dba5d9b69e402fc23 Mon Sep 17 00:00:00 2001 From: Divyanshu Bhargava Date: Tue, 4 Nov 2025 18:47:13 +0530 Subject: [PATCH 1/4] refactor: Simplify StacRadio and StacRadioGroup implementations - Refactored StacRadioParser and StacRadioGroupParser to remove unnecessary dependencies and streamline widget structure. - Converted StacRadioWidget to a StatefulWidget for better state management. - Removed StacRadioGroupScope in favor of direct state management within StacRadioGroup. - Added new properties to StacRadio for enhanced customization: enabled, backgroundColor, side, and innerRadius. - Updated JSON serialization for new properties in StacRadio and StacRadioGroup. --- .../widgets/stac_radio/stac_radio_parser.dart | 191 +++++++++--------- .../stac_radio_group_parser.dart | 64 +++--- .../stac_radio_group_scope.dart | 32 --- .../lib/widgets/radio/stac_radio.dart | 21 ++ .../lib/widgets/radio/stac_radio.g.dart | 10 + .../widgets/radio_group/stac_radio_group.dart | 6 +- .../radio_group/stac_radio_group.g.dart | 4 + 7 files changed, 168 insertions(+), 160 deletions(-) delete mode 100644 packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_scope.dart diff --git a/packages/stac/lib/src/parsers/widgets/stac_radio/stac_radio_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_radio/stac_radio_parser.dart index 608e76d5a..9158a4012 100644 --- a/packages/stac/lib/src/parsers/widgets/stac_radio/stac_radio_parser.dart +++ b/packages/stac/lib/src/parsers/widgets/stac_radio/stac_radio_parser.dart @@ -1,9 +1,9 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:stac/src/parsers/foundation/borders/stac_border_side_parser.dart'; import 'package:stac/src/parsers/foundation/geometry/stac_visual_density_parser.dart'; import 'package:stac/src/parsers/foundation/interaction/stac_mouse_cursor_parser.dart'; import 'package:stac/src/parsers/foundation/layout/stac_material_tap_target_size_parser.dart'; -import 'package:stac/src/parsers/widgets/stac_radio_group/stac_radio_group_scope.dart'; import 'package:stac/src/utils/color_utils.dart'; import 'package:stac_core/stac_core.dart'; import 'package:stac_framework/stac_framework.dart'; @@ -19,116 +19,125 @@ class StacRadioParser extends StacParser { @override Widget parse(BuildContext context, StacRadio model) { - return _RadioWidget( - model: model, - radioGroupScope: StacRadioGroupScope.of(context), - ); + return _RadioWidget(model: model); } } -class _RadioWidget extends StatelessWidget { - const _RadioWidget({ - required this.radioGroupScope, - required this.model, - }); +class _RadioWidget extends StatefulWidget { + const _RadioWidget({required this.model}); - final StacRadioGroupScope? radioGroupScope; final StacRadio model; @override - Widget build(BuildContext context) { - final FocusNode focusNode = FocusNode(); + State<_RadioWidget> createState() => _RadioWidgetState(); +} + +class _RadioWidgetState extends State<_RadioWidget> { + late final FocusNode _focusNode; + + @override + void initState() { + super.initState(); + _focusNode = FocusNode(); + } + + @override + void dispose() { + _focusNode.dispose(); + super.dispose(); + } - switch (model.radioType ?? StacRadioType.material) { + @override + Widget build(BuildContext context) { + switch (widget.model.radioType ?? StacRadioType.material) { case StacRadioType.cupertino: - return _buildCupertinoRadio(context, model, focusNode); + return _buildCupertinoRadio(context); case StacRadioType.adaptive: - return _buildAdaptiveRadio(context, model, focusNode); + return _buildAdaptiveRadio(context); case StacRadioType.material: - return _buildMaterialRadio(context, model, focusNode); + return _buildMaterialRadio(context); } } - Widget _buildCupertinoRadio( - BuildContext context, - StacRadio model, - FocusNode focusNode, - ) { - return ValueListenableBuilder( - valueListenable: radioGroupScope!.radioGroupValue, - builder: (context, value, child) { - return CupertinoRadio( - value: model.value, - mouseCursor: model.mouseCursor?.parse, - toggleable: model.toggleable ?? false, - activeColor: model.activeColor?.toColor(context), - inactiveColor: model.inactiveColor?.toColor(context), - fillColor: model.fillColor?.toColor(context), - focusColor: model.focusColor?.toColor(context), - focusNode: focusNode, - autofocus: model.autofocus ?? false, - useCheckmarkStyle: model.useCheckmarkStyle ?? false, - ); - }, + Widget _buildCupertinoRadio(BuildContext context) { + return CupertinoRadio( + value: widget.model.value, + mouseCursor: widget.model.mouseCursor?.parse, + toggleable: widget.model.toggleable ?? false, + activeColor: widget.model.activeColor?.toColor(context), + inactiveColor: widget.model.inactiveColor?.toColor(context), + fillColor: widget.model.fillColor?.toColor(context), + focusColor: widget.model.focusColor?.toColor(context), + focusNode: _focusNode, + autofocus: widget.model.autofocus ?? false, + useCheckmarkStyle: widget.model.useCheckmarkStyle ?? false, + enabled: widget.model.enabled, ); } - Widget _buildAdaptiveRadio( - BuildContext context, - StacRadio model, - FocusNode focusNode, - ) { - return ValueListenableBuilder( - valueListenable: radioGroupScope!.radioGroupValue, - builder: (context, value, child) { - return Radio.adaptive( - value: model.value, - mouseCursor: model.mouseCursor?.parse, - toggleable: model.toggleable ?? false, - activeColor: model.activeColor?.toColor(context), - fillColor: WidgetStateProperty.all(model.fillColor?.toColor(context)), - focusColor: model.focusColor?.toColor(context), - hoverColor: model.hoverColor?.toColor(context), - overlayColor: WidgetStateProperty.all( - model.overlayColor?.toColor(context), - ), - splashRadius: model.splashRadius, - materialTapTargetSize: model.materialTapTargetSize?.parse, - visualDensity: model.visualDensity?.parse, - focusNode: focusNode, - autofocus: model.autofocus ?? false, - useCupertinoCheckmarkStyle: model.useCupertinoCheckmarkStyle ?? false, - ); - }, + Widget _buildAdaptiveRadio(BuildContext context) { + return Radio.adaptive( + value: widget.model.value, + mouseCursor: widget.model.mouseCursor?.parse, + toggleable: widget.model.toggleable ?? false, + activeColor: widget.model.activeColor?.toColor(context), + fillColor: WidgetStateProperty.all( + widget.model.fillColor?.toColor(context), + ), + focusColor: widget.model.focusColor?.toColor(context), + hoverColor: widget.model.hoverColor?.toColor(context), + overlayColor: WidgetStateProperty.all( + widget.model.overlayColor?.toColor(context), + ), + splashRadius: widget.model.splashRadius, + materialTapTargetSize: widget.model.materialTapTargetSize?.parse, + visualDensity: widget.model.visualDensity?.parse, + focusNode: _focusNode, + autofocus: widget.model.autofocus ?? false, + useCupertinoCheckmarkStyle: + widget.model.useCupertinoCheckmarkStyle ?? false, + enabled: widget.model.enabled, + backgroundColor: widget.model.backgroundColor != null + ? WidgetStateProperty.all( + widget.model.backgroundColor!.toColor(context), + ) + : null, + side: widget.model.side?.parse(context), + innerRadius: widget.model.innerRadius != null + ? WidgetStateProperty.all(widget.model.innerRadius) + : null, ); } - Widget _buildMaterialRadio( - BuildContext context, - StacRadio model, - FocusNode focusNode, - ) { - return ValueListenableBuilder( - valueListenable: radioGroupScope!.radioGroupValue, - builder: (context, value, child) { - return Radio( - value: model.value, - mouseCursor: model.mouseCursor?.parse, - toggleable: model.toggleable ?? false, - activeColor: model.activeColor?.toColor(context), - fillColor: WidgetStateProperty.all(model.fillColor?.toColor(context)), - focusColor: model.focusColor?.toColor(context), - hoverColor: model.hoverColor?.toColor(context), - overlayColor: WidgetStateProperty.all( - model.overlayColor?.toColor(context), - ), - splashRadius: model.splashRadius, - materialTapTargetSize: model.materialTapTargetSize?.parse, - visualDensity: model.visualDensity?.parse, - focusNode: focusNode, - autofocus: model.autofocus ?? false, - ); - }, + Widget _buildMaterialRadio(BuildContext context) { + return Radio( + value: widget.model.value, + mouseCursor: widget.model.mouseCursor?.parse, + toggleable: widget.model.toggleable ?? false, + activeColor: widget.model.activeColor?.toColor(context), + fillColor: WidgetStateProperty.all( + widget.model.fillColor?.toColor(context), + ), + focusColor: widget.model.focusColor?.toColor(context), + hoverColor: widget.model.hoverColor?.toColor(context), + overlayColor: WidgetStateProperty.all( + widget.model.overlayColor?.toColor(context), + ), + splashRadius: widget.model.splashRadius, + materialTapTargetSize: widget.model.materialTapTargetSize?.parse, + visualDensity: widget.model.visualDensity?.parse, + focusNode: _focusNode, + autofocus: widget.model.autofocus ?? false, + enabled: widget.model.enabled, + backgroundColor: widget.model.backgroundColor != null + ? WidgetStateProperty.all( + widget.model.backgroundColor!.toColor(context), + ) + : null, + side: widget.model.side?.parse(context), + innerRadius: widget.model.innerRadius != null + ? WidgetStateProperty.all(widget.model.innerRadius) + : null, ); } } diff --git a/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_parser.dart index 0d3412ba1..693d9ea66 100644 --- a/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_parser.dart +++ b/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_parser.dart @@ -1,8 +1,7 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stac/src/parsers/core/stac_action_parser.dart'; import 'package:stac/src/parsers/core/stac_widget_parser.dart'; import 'package:stac/src/parsers/widgets/stac_form/stac_form_scope.dart'; -import 'package:stac/src/parsers/widgets/stac_radio_group/stac_radio_group_scope.dart'; import 'package:stac_core/stac_core.dart'; import 'package:stac_framework/stac_framework.dart'; @@ -18,63 +17,56 @@ class StacRadioGroupParser extends StacParser { @override Widget parse(BuildContext context, StacRadioGroup model) { - return _RadioGroupWidget( - model: model, - formScope: StacFormScope.of(context), - ); + return _RadioGroupWidget(model, StacFormScope.of(context)); } } class _RadioGroupWidget extends StatefulWidget { - const _RadioGroupWidget({ - required this.model, - required this.formScope, - }); + const _RadioGroupWidget(this.model, this.formScope); final StacRadioGroup model; final StacFormScope? formScope; @override - State<_RadioGroupWidget> createState() => __RadioGroupWidgetState(); + State<_RadioGroupWidget> createState() => _RadioGroupWidgetState(); } -class __RadioGroupWidgetState extends State<_RadioGroupWidget> { - late ValueNotifier groupValue; +class _RadioGroupWidgetState extends State<_RadioGroupWidget> { + dynamic _groupValue; @override void initState() { super.initState(); - groupValue = ValueNotifier(widget.model.groupValue); - _saveValueInFormData(); - } + _groupValue = widget.model.groupValue; - @override - void dispose() { - groupValue.dispose(); - super.dispose(); + // Initialize form data if id is provided + if (widget.model.id != null && widget.formScope != null) { + widget.formScope!.formData[widget.model.id!] = widget.model.groupValue; + } } - void _updateGroupValue(dynamic value) { - groupValue.value = value; - _saveValueInFormData(); - } + void _onChanged(dynamic value) { + setState(() { + _groupValue = value; + }); + + // Save to form data if id is provided + if (widget.model.id != null && widget.formScope != null) { + widget.formScope!.formData[widget.model.id!] = value; + } - void _saveValueInFormData() { - if (widget.model.id != null) { - widget.formScope?.formData[widget.model.id!] = groupValue.value; + // Call the onChanged action if provided + if (widget.model.onChanged != null) { + widget.model.onChanged!.parse(context); } } @override Widget build(BuildContext context) { - final StacRadioGroup model = widget.model; - - return StacRadioGroupScope( - radioGroupValue: groupValue, - onSelect: _updateGroupValue, - child: Builder(builder: (context) { - return model.child?.parse(context) ?? const SizedBox(); - }), + return RadioGroup( + groupValue: _groupValue, + onChanged: _onChanged, + child: widget.model.child?.parse(context) ?? const SizedBox.shrink(), ); } } diff --git a/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_scope.dart b/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_scope.dart deleted file mode 100644 index 533e9865d..000000000 --- a/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_scope.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:stac_logger/stac_logger.dart'; - -class StacRadioGroupScope extends InheritedWidget { - const StacRadioGroupScope({ - super.key, - required this.radioGroupValue, - required this.onSelect, - required super.child, - }); - - final ValueNotifier radioGroupValue; - final Function(dynamic value) onSelect; - - static StacRadioGroupScope? of(BuildContext context) { - final StacRadioGroupScope? result = - context.dependOnInheritedWidgetOfExactType(); - - if (result != null) { - return result; - } else { - Log.e( - "StacRadioGroupScope.of() called with a context that does not contain a StacRadioGroupScope."); - return null; - } - } - - @override - bool updateShouldNotify(covariant InheritedWidget oldWidget) { - return oldWidget.child != child; - } -} diff --git a/packages/stac_core/lib/widgets/radio/stac_radio.dart b/packages/stac_core/lib/widgets/radio/stac_radio.dart index 89015fb96..07697283e 100644 --- a/packages/stac_core/lib/widgets/radio/stac_radio.dart +++ b/packages/stac_core/lib/widgets/radio/stac_radio.dart @@ -62,6 +62,10 @@ class StacRadio extends StacWidget { this.autofocus, this.useCheckmarkStyle, this.useCupertinoCheckmarkStyle, + this.enabled, + this.backgroundColor, + this.side, + this.innerRadius, }); /// Which platform style of radio to render. @@ -134,6 +138,23 @@ class StacRadio extends StacWidget { /// Whether to use a Cupertino checkmark style when using adaptive radios. final bool? useCupertinoCheckmarkStyle; + /// Whether this radio is enabled for user interaction. + final bool? enabled; + + /// The background color of the radio. + /// + /// Type: [StacColor] + final StacColor? backgroundColor; + + /// The border side of the radio. + /// + /// Type: [StacBorderSide] + final StacBorderSide? side; + + /// The inner radius of the radio in logical pixels. + @DoubleConverter() + final double? innerRadius; + /// Widget type identifier. @override String get type => WidgetType.radio.name; diff --git a/packages/stac_core/lib/widgets/radio/stac_radio.g.dart b/packages/stac_core/lib/widgets/radio/stac_radio.g.dart index 70e436f3b..34912e961 100644 --- a/packages/stac_core/lib/widgets/radio/stac_radio.g.dart +++ b/packages/stac_core/lib/widgets/radio/stac_radio.g.dart @@ -37,6 +37,12 @@ StacRadio _$StacRadioFromJson(Map json) => StacRadio( autofocus: json['autofocus'] as bool?, useCheckmarkStyle: json['useCheckmarkStyle'] as bool?, useCupertinoCheckmarkStyle: json['useCupertinoCheckmarkStyle'] as bool?, + enabled: json['enabled'] as bool?, + backgroundColor: json['backgroundColor'] as String?, + side: json['side'] == null + ? null + : StacBorderSide.fromJson(json['side'] as Map), + innerRadius: const DoubleConverter().fromJson(json['innerRadius']), ); Map _$StacRadioToJson(StacRadio instance) => { @@ -59,6 +65,10 @@ Map _$StacRadioToJson(StacRadio instance) => { 'autofocus': instance.autofocus, 'useCheckmarkStyle': instance.useCheckmarkStyle, 'useCupertinoCheckmarkStyle': instance.useCupertinoCheckmarkStyle, + 'enabled': instance.enabled, + 'backgroundColor': instance.backgroundColor, + 'side': instance.side?.toJson(), + 'innerRadius': const DoubleConverter().toJson(instance.innerRadius), 'type': instance.type, }; diff --git a/packages/stac_core/lib/widgets/radio_group/stac_radio_group.dart b/packages/stac_core/lib/widgets/radio_group/stac_radio_group.dart index 7dcc30402..f88f0d32c 100644 --- a/packages/stac_core/lib/widgets/radio_group/stac_radio_group.dart +++ b/packages/stac_core/lib/widgets/radio_group/stac_radio_group.dart @@ -1,4 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:stac_core/core/stac_action.dart'; import 'package:stac_core/core/stac_widget.dart'; import 'package:stac_core/foundation/specifications/widget_type.dart'; @@ -47,7 +48,7 @@ part 'stac_radio_group.g.dart'; @JsonSerializable() class StacRadioGroup extends StacWidget { /// Creates a [StacRadioGroup]. - const StacRadioGroup({this.id, this.groupValue, this.child}); + const StacRadioGroup({this.id, this.groupValue, this.child, this.onChanged}); /// The identifier under which the selected value will be saved in a [StacFormScope]'s form data. final String? id; @@ -58,6 +59,9 @@ class StacRadioGroup extends StacWidget { /// The widget subtree to render within the radio group scope. final StacWidget? child; + /// The function to call when the group value changes. + final StacAction? onChanged; + /// Widget type identifier. @override String get type => WidgetType.radioGroup.name; diff --git a/packages/stac_core/lib/widgets/radio_group/stac_radio_group.g.dart b/packages/stac_core/lib/widgets/radio_group/stac_radio_group.g.dart index 8f3aa8839..73684b1fa 100644 --- a/packages/stac_core/lib/widgets/radio_group/stac_radio_group.g.dart +++ b/packages/stac_core/lib/widgets/radio_group/stac_radio_group.g.dart @@ -13,6 +13,9 @@ StacRadioGroup _$StacRadioGroupFromJson(Map json) => child: json['child'] == null ? null : StacWidget.fromJson(json['child'] as Map), + onChanged: json['onChanged'] == null + ? null + : StacAction.fromJson(json['onChanged'] as Map), ); Map _$StacRadioGroupToJson(StacRadioGroup instance) => @@ -20,5 +23,6 @@ Map _$StacRadioGroupToJson(StacRadioGroup instance) => 'id': instance.id, 'groupValue': instance.groupValue, 'child': instance.child?.toJson(), + 'onChanged': instance.onChanged?.toJson(), 'type': instance.type, }; From e6700af5ceab6fb145cc42092966b86b8a84b52e Mon Sep 17 00:00:00 2001 From: Divyanshu Bhargava Date: Tue, 4 Nov 2025 18:49:59 +0530 Subject: [PATCH 2/4] docs: Update radio_group documentation with new properties --- docs/widgets/radio_group.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/widgets/radio_group.mdx b/docs/widgets/radio_group.mdx index 0e05fc65f..1fc76de1a 100644 --- a/docs/widgets/radio_group.mdx +++ b/docs/widgets/radio_group.mdx @@ -34,6 +34,10 @@ To know more about the Radio widget in Flutter, refer to the [official documenta | autofocus | `bool` | True if this widget will be selected as the initial focus when no other node in its scope is currently focused. | | useCheckmarkStyle | `bool` | Controls whether the radio displays in a checkbox style or the default iOS radio style. | | useCupertinoCheckmarkStyle | `bool` | Controls whether the checkmark style is used in an iOS-style radio. | +| enabled | `bool` | Whether this radio is enabled for user interaction. | +| backgroundColor | `String` | The background color of the radio. | +| side | `StacBorderSide` | The border side of the radio. | +| innerRadius | `double` | The inner radius of the radio in logical pixels. | ## Example JSON From 9ed168a97ec59ef7d4fe06d00a9e8d07948599e6 Mon Sep 17 00:00:00 2001 From: Divyanshu Bhargava Date: Tue, 4 Nov 2025 19:02:09 +0530 Subject: [PATCH 3/4] feat: Add state management for groupValue updates in StacRadioGroup --- .../stac_radio_group/stac_radio_group_parser.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_parser.dart index 693d9ea66..5fdcaedb8 100644 --- a/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_parser.dart +++ b/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_parser.dart @@ -45,6 +45,19 @@ class _RadioGroupWidgetState extends State<_RadioGroupWidget> { } } + @override + void didUpdateWidget(covariant _RadioGroupWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.model.groupValue != widget.model.groupValue) { + _groupValue = widget.model.groupValue; + + // Save to form data if id is provided + if (widget.model.id != null && widget.formScope != null) { + widget.formScope!.formData[widget.model.id!] = widget.model.groupValue; + } + } + } + void _onChanged(dynamic value) { setState(() { _groupValue = value; From 90201d404586c103e193efddd4fca129db052ee5 Mon Sep 17 00:00:00 2001 From: Divyanshu Bhargava Date: Tue, 4 Nov 2025 19:06:43 +0530 Subject: [PATCH 4/4] fix: Ensure groupValue is set within setState for StacRadioGroup initialization --- .../widgets/stac_radio_group/stac_radio_group_parser.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_parser.dart index 5fdcaedb8..0b113faf6 100644 --- a/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_parser.dart +++ b/packages/stac/lib/src/parsers/widgets/stac_radio_group/stac_radio_group_parser.dart @@ -37,7 +37,9 @@ class _RadioGroupWidgetState extends State<_RadioGroupWidget> { @override void initState() { super.initState(); - _groupValue = widget.model.groupValue; + setState(() { + _groupValue = widget.model.groupValue; + }); // Initialize form data if id is provided if (widget.model.id != null && widget.formScope != null) {