From d9219b2b5596a208a15ea57022b72d97e85508dc Mon Sep 17 00:00:00 2001 From: nilesh sahu Date: Fri, 19 Apr 2024 12:23:42 +0530 Subject: [PATCH 1/2] feat:select-card-form-field --- example/lib/sources/complete_form.dart | 119 ++++++-- lib/flutter_form_builder.dart | 2 + .../alippo_selection_card_group.dart | 288 +++++++++++++++++ .../comp/selection_card.dart | 289 ++++++++++++++++++ .../comp/selection_card_options.dart | 40 +++ 5 files changed, 711 insertions(+), 27 deletions(-) create mode 100644 lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/alippo_selection_card_group.dart create mode 100644 lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card.dart create mode 100644 lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card_options.dart diff --git a/example/lib/sources/complete_form.dart b/example/lib/sources/complete_form.dart index 3c41c7ad9..a151f0cef 100644 --- a/example/lib/sources/complete_form.dart +++ b/example/lib/sources/complete_form.dart @@ -26,6 +26,34 @@ class _CompleteFormState extends State { @override Widget build(BuildContext context) { + InfoModalConfig config = InfoModalConfig( + leadingIcon: const Icon( + Icons.post_add_outlined, + size: 40, + ), + description: const Opacity( + opacity: 0.79, + child: Text( + 'Don\'t know the skill? It\'s okay, we will teach you market-style skills too. and you can earn up to 5000 - 6000 per month.', + style: TextStyle( + color: Color(0xFF003B67), + fontSize: 14, + fontFamily: 'PP Pangram Sans', + fontWeight: FontWeight.w700, + ), + ), + ), + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1.30, color: Color(0x1915749D)), + borderRadius: BorderRadius.circular(13), + ), + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0x00FAF9F9), Color(0xFF94B8D2)], + ), + ); + return SingleChildScrollView( child: Column( children: [ @@ -286,37 +314,74 @@ class _CompleteFormState extends State { FormBuilderValidators.maxLength(3), ]), ), - FormBuilderChoiceChip( - autovalidateMode: AutovalidateMode.onUserInteraction, - decoration: const InputDecoration( - labelText: - 'Ok, if I had to choose one language, it would be:'), - name: 'languages_choice', - initialValue: 'Dart', - options: const [ - FormBuilderChipOption( - value: 'Dart', - avatar: CircleAvatar(child: Text('D')), + Builder(builder: (context) { + return AlippoSelectionCardGroups( + autovalidateMode: AutovalidateMode.onUserInteraction, + name: 'languages_choice', + initialValue: 'Dart', + padding: + const EdgeInsets.only(top: 20, bottom: 20, left: 50), + expanded: true, + spacing: 20, + selectedLabelStyle: const TextStyle( + color: Color(0xFFFAF9F9), + fontSize: 18, + fontFamily: 'PP Pangram Sans Rounded', + fontWeight: FontWeight.w700, + letterSpacing: -0.36, ), - FormBuilderChipOption( - value: 'Kotlin', - avatar: CircleAvatar(child: Text('K')), + unselectedLabelStyle: const TextStyle( + color: Color(0xFF1A4F76), + fontSize: 18, + fontFamily: 'PP Pangram Sans Rounded', + fontWeight: FontWeight.w400, + letterSpacing: -0.36, ), - FormBuilderChipOption( - value: 'Java', - avatar: CircleAvatar(child: Text('J')), - ), - FormBuilderChipOption( - value: 'Swift', - avatar: CircleAvatar(child: Text('S')), + selectedColor: const Color(0xFF1A4F76), + selectedShape: RoundedRectangleBorder( + side: BorderSide( + width: 2, + color: Colors.black.withOpacity(0.36000001430511475), + ), + borderRadius: BorderRadius.circular(17), ), - FormBuilderChipOption( - value: 'Objective-C', - avatar: CircleAvatar(child: Text('O')), + unselectedShape: RoundedRectangleBorder( + side: BorderSide( + width: 2, + color: Colors.black.withOpacity(0.07000000029802322), + ), + borderRadius: BorderRadius.circular(17), ), - ], - onChanged: _onChanged, - ), + options: [ + SelectionCardOption( + value: '5000 - 6000', + avatar: const Icon(Icons.monetization_on_outlined), + infoModalConfig: config, + ), + SelectionCardOption( + value: 'Kotlin', + avatar: const Icon(Icons.monetization_on_outlined), + infoModalConfig: config, + ), + SelectionCardOption( + value: 'Java', + avatar: const Icon(Icons.monetization_on_outlined), + infoModalConfig: config, + ), + SelectionCardOption( + value: 'Swift', + avatar: const Icon(Icons.monetization_on_outlined), + infoModalConfig: config, + ), + SelectionCardOption( + value: 'Objective-C', + avatar: const Icon(Icons.monetization_on_outlined), + infoModalConfig: config, + ), + ], + onChanged: _onChanged, + ); + }), ], ), ), diff --git a/lib/flutter_form_builder.dart b/lib/flutter_form_builder.dart index de3a7c521..ee5d973c4 100644 --- a/lib/flutter_form_builder.dart +++ b/lib/flutter_form_builder.dart @@ -20,3 +20,5 @@ export 'src/widgets/grouped_checkbox.dart'; export 'src/widgets/grouped_radio.dart'; export 'src/options/form_builder_chip_option.dart'; export 'src/options/display_values_enum.dart'; +export 'src/alippo_custom_form_components/fields/alippo_selection_card_group/alippo_selection_card_group.dart'; +export 'src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card_options.dart'; diff --git a/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/alippo_selection_card_group.dart b/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/alippo_selection_card_group.dart new file mode 100644 index 000000000..6cc7a77cd --- /dev/null +++ b/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/alippo_selection_card_group.dart @@ -0,0 +1,288 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; + +import 'comp/selection_card.dart'; + +/// A list of `Chip`s that acts like radio buttons +class AlippoSelectionCardGroups extends FormBuilderFieldDecoration { + /// The list of items the user can select. + final List> options; + + // FilterChip Settings + /// Elevation to be applied on the chip relative to its parent. + /// + /// This controls the size of the shadow below the chip. + /// + /// Defaults to 0. The value is always non-negative. + final double? elevation; + + /// Elevation to be applied on the chip relative to its parent during the + /// press motion. + /// + /// This controls the size of the shadow below the chip. + /// + /// Defaults to 8. The value is always non-negative. + final double? pressElevation; + + /// Color to be used for the chip's background, indicating that it is + /// selected. + final Color? selectedColor; + + final Color? unselectedColor; + + /// Color to be used for the chip's background indicating that it is disabled. + /// + /// The chip is disabled when [isEnabled] is false, or all three of + /// [SelectableChipAttributes.onSelected], [TappableChipAttributes.onPressed], + /// and [DeletableChipAttributes.onDelete] are null. + /// + /// It defaults to [Colors.black38]. + final Color? disabledColor; + + /// Color to be used for the unselected, enabled chip's background. + /// + /// The default is light grey. + final Color? backgroundColor; + + /// Color of the chip's shadow when the elevation is greater than 0 and the + /// chip is selected. + /// + /// The default is [Colors.black]. + final Color? selectedShadowColor; + + /// Color of the chip's shadow when the elevation is greater than 0. + /// + /// The default is [Colors.black]. + final Color? shadowColor; + + /// The [OutlinedBorder] to draw around the chip. + /// + /// Defaults to the shape in the ambient [ChipThemeData]. If the theme + /// shape resolves to null, the default is [StadiumBorder]. + /// + /// This shape is combined with [side] to create a shape decorated with an + /// outline. If it is a [MaterialStateOutlinedBorder], + /// [MaterialStateProperty.resolve] is used for the following + /// [MaterialState]s: + /// + /// * [MaterialState.disabled]. + /// * [MaterialState.selected]. + /// * [MaterialState.hovered]. + /// * [MaterialState.focused]. + /// * [MaterialState.pressed]. + final OutlinedBorder? selectedShape; + + final OutlinedBorder? unselectedShape; + + /// The padding around the [label] widget. + /// + /// By default, this is 4 logical pixels at the beginning and the end of the + /// label, and zero on top and bottom. + final EdgeInsets? labelPadding; + + /// The style to be applied to the chip's label. + /// + /// If null, the value of the [ChipTheme]'s [ChipThemeData.labelStyle] is used. + // + /// This only has an effect on widgets that respect the [DefaultTextStyle], + /// such as [Text]. + /// + /// If [labelStyle.color] is a [MaterialStateProperty], [MaterialStateProperty.resolve] + /// is used for the following [MaterialState]s: + /// + /// * [MaterialState.disabled]. + /// * [MaterialState.selected]. + /// * [MaterialState.hovered]. + /// * [MaterialState.focused]. + /// * [MaterialState.pressed]. + final TextStyle? selectedLabelStyle; + + final TextStyle? unselectedLabelStyle; + + /// The padding between the contents of the chip and the outside [selectedShape]. + /// + /// Defaults to 4 logical pixels on all sides. + final EdgeInsets? padding; + + // Wrap Settings + /// The direction to use as the main axis when wrapping chips. + /// + /// For example, if [direction] is [Axis.horizontal], the default, the + /// children are placed adjacent to one another in a horizontal run until the + /// available horizontal space is consumed, at which point a subsequent + /// children are placed in a new run vertically adjacent to the previous run. + final Axis direction; + + /// How the children within a run should be placed in the main axis. + /// + /// For example, if [alignment] is [WrapAlignment.center], the children in + /// each run are grouped together in the center of their run in the main axis. + /// + /// Defaults to [WrapAlignment.start]. + /// + /// See also: + /// + /// * [runAlignment], which controls how the runs are placed relative to each + /// other in the cross axis. + /// * [crossAxisAlignment], which controls how the children within each run + /// are placed relative to each other in the cross axis. + final WrapAlignment alignment; + + /// How much space to place between children in a run in the main axis. + /// + /// For example, if [spacing] is 10.0, the children will be spaced at least + /// 10.0 logical pixels apart in the main axis. + /// + /// If there is additional free space in a run (e.g., because the wrap has a + /// minimum size that is not filled or because some runs are longer than + /// others), the additional free space will be allocated according to the + /// [alignment]. + /// + /// Defaults to 0.0. + final double spacing; + + /// How the runs themselves should be placed in the cross axis. + /// + /// For example, if [runAlignment] is [WrapAlignment.center], the runs are + /// grouped together in the center of the overall [Wrap] in the cross axis. + /// + /// Defaults to [WrapAlignment.start]. + /// + /// See also: + /// + /// * [alignment], which controls how the children within each run are placed + /// relative to each other in the main axis. + /// * [crossAxisAlignment], which controls how the children within each run + /// are placed relative to each other in the cross axis. + final WrapAlignment runAlignment; + + /// How much space to place between the runs themselves in the cross axis. + /// + /// For example, if [runSpacing] is 10.0, the runs will be spaced at least + /// 10.0 logical pixels apart in the cross axis. + /// + /// If there is additional free space in the overall [Wrap] (e.g., because + /// the wrap has a minimum size that is not filled), the additional free space + /// will be allocated according to the [runAlignment]. + /// + /// Defaults to 0.0. + final double runSpacing; + + /// How the children within a run should be aligned relative to each other in + /// the cross axis. + /// + /// For example, if this is set to [WrapCrossAlignment.end], and the + /// [direction] is [Axis.horizontal], then the children within each + /// run will have their bottom edges aligned to the bottom edge of the run. + /// + /// Defaults to [WrapCrossAlignment.start]. + /// + /// See also: + /// + /// * [alignment], which controls how the children within each run are placed + /// relative to each other in the main axis. + /// * [runAlignment], which controls how the runs are placed relative to each + /// other in the cross axis. + final WrapCrossAlignment crossAxisAlignment; + + final ShapeBorder avatarBorder; + + final bool expanded; + + /// Creates a list of `Chip`s that acts like radio buttons + AlippoSelectionCardGroups({ + super.autovalidateMode = AutovalidateMode.disabled, + super.enabled, + super.focusNode, + super.onSaved, + super.validator, + super.decoration, + super.key, + required super.name, + required this.options, + super.initialValue, + super.restorationId, + super.onChanged, + super.valueTransformer, + super.onReset, + this.alignment = WrapAlignment.start, + this.avatarBorder = const CircleBorder(), + this.backgroundColor, + this.crossAxisAlignment = WrapCrossAlignment.start, + this.direction = Axis.horizontal, + this.disabledColor, + this.elevation, + this.labelPadding, + this.selectedLabelStyle, + this.unselectedLabelStyle, + this.padding, + this.pressElevation, + this.runAlignment = WrapAlignment.start, + this.runSpacing = 0.0, + this.selectedColor, + this.unselectedColor, + this.selectedShadowColor, + this.shadowColor, + this.selectedShape, + this.unselectedShape, + this.spacing = 0.0, + this.expanded = false, + }) : super( + builder: (FormFieldState field) { + final state = field as _AlippoSelectionCardGroupsState; + + return InputDecorator( + decoration: state.decoration, + child: SingleChildScrollView( + child: Column( + children: [ + for (SelectionCardOption option in options) + Column( + children: [ + SelectionCard( + label: option, + selected: field.value == option.value, + onSelected: state.enabled + ? (selected) { + final choice = + selected ? option.value : null; + state.didChange(choice); + } + : null, + avatar: option.avatar, + selectedIconColor: unselectedColor, + unselectedIconColor: selectedColor, + selectedCardColor: selectedColor, + disabledColor: disabledColor, + backgroundColor: backgroundColor, + shadowColor: shadowColor, + selectedShadowColor: selectedShadowColor, + elevation: elevation, + pressElevation: pressElevation, + selectedLabelStyle: selectedLabelStyle, + unselectedLabelStyle: unselectedLabelStyle, + labelPadding: labelPadding, + padding: padding, + selectedShape: selectedShape, + unselectedShape: unselectedShape, + expanded: expanded, + infoModalConfig: option.infoModalConfig, + infoModalHeight: 100, + ), + if (options.last != option) SizedBox(height: spacing), + ], + ), + ], + ), + ), + ); + }, + ); + + @override + FormBuilderFieldDecorationState, T> + createState() => _AlippoSelectionCardGroupsState(); +} + +class _AlippoSelectionCardGroupsState + extends FormBuilderFieldDecorationState, T> {} diff --git a/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card.dart b/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card.dart new file mode 100644 index 000000000..4dbab256a --- /dev/null +++ b/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card.dart @@ -0,0 +1,289 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card_options.dart'; + +class SelectionCard extends StatefulWidget { + final Widget? avatar; + + final Widget label; + + final TextStyle? selectedLabelStyle; + + final TextStyle? unselectedLabelStyle; + + final EdgeInsetsGeometry? labelPadding; + + final ValueChanged? onSelected; + + final double? pressElevation; + + final bool selected; + + final Color? disabledColor; + + final Color? selectedCardColor; + + final Color? defaultCardColor; + + final OutlinedBorder? selectedShape; + + final OutlinedBorder? unselectedShape; + + final FocusNode? focusNode; + + final bool autofocus; + + final Color? backgroundColor; + + final EdgeInsetsGeometry? padding; + + final double? elevation; + + final Color? shadowColor; + + final Color? selectedShadowColor; + + final Color? selectedIconColor; + + final Color? unselectedIconColor; + + final bool expanded; + + final bool disabled; + + final Duration? animationDuration; + + final Duration? reverseAnimationDuration; + + final Curve? animationCurve; + + final Curve? reverseAnimationCurve; + + final double infoModalHeight; + + /// Configuration for the info modal + final InfoModalConfig? infoModalConfig; + + /// Creates an option for fields with selection options + const SelectionCard({ + super.key, + required this.label, + this.selectedLabelStyle, + this.unselectedLabelStyle, + this.labelPadding, + this.onSelected, + this.pressElevation, + this.selected = false, + this.disabledColor, + this.selectedCardColor, + this.defaultCardColor, + this.selectedShape, + this.unselectedShape, + this.focusNode, + this.autofocus = false, + this.backgroundColor, + this.padding, + this.elevation, + this.shadowColor, + this.selectedShadowColor, + this.avatar, + this.selectedIconColor, + this.unselectedIconColor, + this.expanded = false, + this.disabled = false, + this.infoModalConfig, + this.animationDuration, + this.reverseAnimationDuration, + this.animationCurve, + this.reverseAnimationCurve, + this.infoModalHeight = 0, + }); + + @override + State createState() => _SelectionCardState(); +} + +class _SelectionCardState extends State + with SingleTickerProviderStateMixin { + bool isSelected = false; + AnimationController? _controller; + Animation? _animation; + + Duration get animationDuration => + widget.animationDuration ?? const Duration(milliseconds: 300); + + Duration get reverseAnimationDuration => + widget.reverseAnimationDuration ?? animationDuration; + + Curve get animationCurve => widget.animationCurve ?? Curves.easeInOut; + + Curve get reverseAnimationCurve => + widget.reverseAnimationCurve ?? animationCurve; + + @override + void initState() { + super.initState(); + isSelected = widget.selected; + _controller = AnimationController( + duration: animationDuration, + reverseDuration: reverseAnimationDuration, + vsync: this, + ); + _animation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: _controller!, + curve: animationCurve, + reverseCurve: reverseAnimationCurve, + )); + + if (isSelected) { + _controller!.value = 1.0; // If initially selected, show expanded + } + } + + @override + void didUpdateWidget(covariant SelectionCard oldWidget) { + handleAnimation(oldWidget); + super.didUpdateWidget(oldWidget); + } + + Future handleAnimation(SelectionCard oldWidget) async { + if (widget.selected != oldWidget.selected) { + if (oldWidget.selected && !widget.selected) { + await Future.delayed( + reverseAnimationDuration, + ); + } + isSelected = widget.selected; + if (isSelected) { + _controller!.forward(); + } else { + _controller!.reverse(); + } + } + } + + @override + void dispose() { + _controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final Widget content = AnimatedBuilder( + animation: _animation!, + builder: (_, __) { + return Padding( + padding: widget.padding ?? const EdgeInsets.all(8.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.avatar != null) + IconTheme( + data: IconThemeData( + color: ColorTween( + begin: widget.unselectedIconColor ?? + Theme.of(context) + .colorScheme + .onSurface, // Light theme color + end: (widget.selectedIconColor ?? + Theme.of(context) + .colorScheme + .onPrimary), // Dark theme color + ).evaluate(_animation!)), + child: Padding( + padding: const EdgeInsets.only(right: 12.0), + child: widget.avatar!, + ), + ), + DefaultTextStyle( + style: TextStyleTween( + begin: widget.unselectedLabelStyle, + end: widget.selectedLabelStyle, + ).evaluate(_animation!), + child: widget.label, + ), + ], + ), + ); + }, + ); + final Widget child = widget.expanded + ? Row( + children: [ + Expanded(child: content), + ], + ) + : content; + return AnimatedBuilder( + animation: _animation!, + builder: (_, __) { + return Stack( + children: [ + if (widget.infoModalConfig != null) + Padding( + padding: const EdgeInsets.only(top: 50), + child: Visibility( + visible: _animation!.value > 0, + maintainSize: false, + child: Transform( + transform: + Matrix4.diagonal3Values(1.0, _animation!.value, 1.0), + alignment: Alignment.topCenter, + child: Opacity( + opacity: _animation!.value, + child: Container( + decoration: ShapeDecoration( + gradient: widget.infoModalConfig!.gradient, + shape: widget.infoModalConfig!.shape, + ), + child: Padding( + padding: const EdgeInsets.only( + top: 32, + left: 20.0, + right: 20.0, + bottom: 20.0, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + widget.infoModalConfig!.leadingIcon, + const SizedBox(width: 20), + Expanded( + child: widget.infoModalConfig!.description, + ), + ], + ), + ), + ), + ), + ), + ), + ), + Card( + color: ColorTween( + begin: widget.backgroundColor, // Light theme color + end: widget.selectedCardColor, // Dark theme color + ).evaluate(_animation!) ?? + widget.backgroundColor, + elevation: widget.elevation, + shadowColor: widget.shadowColor, + shape: widget.selected + ? widget.selectedShape + : widget.unselectedShape ?? widget.selectedShape, + margin: EdgeInsets.zero, + child: InkWell( + onTap: () { + if (widget.onSelected != null) { + widget.onSelected!(!widget.selected); + } + }, + child: child, + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card_options.dart b/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card_options.dart new file mode 100644 index 000000000..dc3ac2ee1 --- /dev/null +++ b/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card_options.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/src/form_builder_field_option.dart'; + + + +class InfoModalConfig { + final Widget leadingIcon; + final Widget description; + final ShapeBorder shape; + final Gradient? gradient; + + const InfoModalConfig({ + required this.leadingIcon, + required this.description, + required this.shape, + this.gradient, + }); +} + + +class SelectionCardOption extends FormBuilderFieldOption { + final Widget? avatar; + + final InfoModalConfig? infoModalConfig; + + + /// Creates an option for fields with selection options + const SelectionCardOption({ + super.key, + required super.value, + this.avatar, + this.infoModalConfig, + super.child, + }); + + @override + Widget build(BuildContext context) { + return child ?? Text(value.toString()); + } +} From c660bad2169a45ff57361d390f04511e01badb2a Mon Sep 17 00:00:00 2001 From: nilesh sahu Date: Mon, 22 Apr 2024 08:36:12 +0530 Subject: [PATCH 2/2] save changes --- example/lib/sources/complete_form.dart | 57 +++++++++++-- .../alippo_selection_card_group.dart | 80 +++++-------------- .../comp/selection_card.dart | 17 ++-- 3 files changed, 76 insertions(+), 78 deletions(-) diff --git a/example/lib/sources/complete_form.dart b/example/lib/sources/complete_form.dart index a151f0cef..007f2fb84 100644 --- a/example/lib/sources/complete_form.dart +++ b/example/lib/sources/complete_form.dart @@ -53,7 +53,32 @@ class _CompleteFormState extends State { colors: [Color(0x00FAF9F9), Color(0xFF94B8D2)], ), ); - + InfoModalConfig config2 = InfoModalConfig( + leadingIcon: const Icon( + Icons.post_add_outlined, + size: 40, + ), + description: const Opacity( + opacity: 0.79, + child: Text( + 'Don\'t know the skill? It\'s okay, we will teach you market-style skills too. ', + style: TextStyle( + color: Color(0xFF003B67), + fontSize: 14, + fontFamily: 'PP Pangram Sans', + fontWeight: FontWeight.w700, + ), + ), + ), + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1.30, color: Color(0x1915749D)), + borderRadius: BorderRadius.circular(13), + ), + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0x00EB467A), Color(0xFFFF976E)]), + ); return SingleChildScrollView( child: Column( children: [ @@ -70,12 +95,28 @@ class _CompleteFormState extends State { 'best_language': 'Dart', 'age': '13', 'gender': 'Male', - 'languages_filter': ['Dart'] + 'languages_filter': ['Dart'], + 'languages_choice': '5000 - 6000', }, skipDisabled: true, child: Column( children: [ const SizedBox(height: 15), + FormBuilderTextField( + autovalidateMode: AutovalidateMode.onUserInteraction, + name: 'text_field', + decoration: const InputDecoration( + //labelText: 'Text Field', + hintText: 'Hint Text', + filled: true, + ), + onChanged: _onChanged, + // valueTransformer: (text) => num.tryParse(text), + keyboardType: TextInputType.multiline, + textInputAction: TextInputAction.next, + minLines: 1, + maxLines: null, + ), FormBuilderDateTimePicker( name: 'date', initialEntryMode: DatePickerEntryMode.calendar, @@ -318,7 +359,7 @@ class _CompleteFormState extends State { return AlippoSelectionCardGroups( autovalidateMode: AutovalidateMode.onUserInteraction, name: 'languages_choice', - initialValue: 'Dart', + // initialValue: 'Java', padding: const EdgeInsets.only(top: 20, bottom: 20, left: 50), expanded: true, @@ -337,7 +378,8 @@ class _CompleteFormState extends State { fontWeight: FontWeight.w400, letterSpacing: -0.36, ), - selectedColor: const Color(0xFF1A4F76), + selectedCardColor: const Color(0xFF1A4F76), + defaultCardColor: const Color(0xFFFAF9F9), selectedShape: RoundedRectangleBorder( side: BorderSide( width: 2, @@ -361,7 +403,7 @@ class _CompleteFormState extends State { SelectionCardOption( value: 'Kotlin', avatar: const Icon(Icons.monetization_on_outlined), - infoModalConfig: config, + infoModalConfig: config2, ), SelectionCardOption( value: 'Java', @@ -371,7 +413,7 @@ class _CompleteFormState extends State { SelectionCardOption( value: 'Swift', avatar: const Icon(Icons.monetization_on_outlined), - infoModalConfig: config, + infoModalConfig: config2, ), SelectionCardOption( value: 'Objective-C', @@ -380,6 +422,9 @@ class _CompleteFormState extends State { ), ], onChanged: _onChanged, + validator: FormBuilderValidators.compose([ + FormBuilderValidators.minLength(1), + ]), ); }), ], diff --git a/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/alippo_selection_card_group.dart b/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/alippo_selection_card_group.dart index 6cc7a77cd..606885380 100644 --- a/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/alippo_selection_card_group.dart +++ b/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/alippo_selection_card_group.dart @@ -26,9 +26,7 @@ class AlippoSelectionCardGroups extends FormBuilderFieldDecoration { /// Color to be used for the chip's background, indicating that it is /// selected. - final Color? selectedColor; - - final Color? unselectedColor; + final Color? selectedCardColor; /// Color to be used for the chip's background indicating that it is disabled. /// @@ -39,10 +37,7 @@ class AlippoSelectionCardGroups extends FormBuilderFieldDecoration { /// It defaults to [Colors.black38]. final Color? disabledColor; - /// Color to be used for the unselected, enabled chip's background. - /// - /// The default is light grey. - final Color? backgroundColor; + final Color? defaultCardColor; /// Color of the chip's shadow when the elevation is greater than 0 and the /// chip is selected. @@ -141,53 +136,17 @@ class AlippoSelectionCardGroups extends FormBuilderFieldDecoration { /// Defaults to 0.0. final double spacing; - /// How the runs themselves should be placed in the cross axis. - /// - /// For example, if [runAlignment] is [WrapAlignment.center], the runs are - /// grouped together in the center of the overall [Wrap] in the cross axis. - /// - /// Defaults to [WrapAlignment.start]. - /// - /// See also: - /// - /// * [alignment], which controls how the children within each run are placed - /// relative to each other in the main axis. - /// * [crossAxisAlignment], which controls how the children within each run - /// are placed relative to each other in the cross axis. - final WrapAlignment runAlignment; + final ShapeBorder avatarBorder; - /// How much space to place between the runs themselves in the cross axis. - /// - /// For example, if [runSpacing] is 10.0, the runs will be spaced at least - /// 10.0 logical pixels apart in the cross axis. - /// - /// If there is additional free space in the overall [Wrap] (e.g., because - /// the wrap has a minimum size that is not filled), the additional free space - /// will be allocated according to the [runAlignment]. - /// - /// Defaults to 0.0. - final double runSpacing; + final bool expanded; - /// How the children within a run should be aligned relative to each other in - /// the cross axis. - /// - /// For example, if this is set to [WrapCrossAlignment.end], and the - /// [direction] is [Axis.horizontal], then the children within each - /// run will have their bottom edges aligned to the bottom edge of the run. - /// - /// Defaults to [WrapCrossAlignment.start]. - /// - /// See also: - /// - /// * [alignment], which controls how the children within each run are placed - /// relative to each other in the main axis. - /// * [runAlignment], which controls how the runs are placed relative to each - /// other in the cross axis. - final WrapCrossAlignment crossAxisAlignment; + final Duration? animationDuration; - final ShapeBorder avatarBorder; + final Duration? reverseAnimationDuration; - final bool expanded; + final Curve? animationCurve; + + final Curve? reverseAnimationCurve; /// Creates a list of `Chip`s that acts like radio buttons AlippoSelectionCardGroups({ @@ -207,8 +166,7 @@ class AlippoSelectionCardGroups extends FormBuilderFieldDecoration { super.onReset, this.alignment = WrapAlignment.start, this.avatarBorder = const CircleBorder(), - this.backgroundColor, - this.crossAxisAlignment = WrapCrossAlignment.start, + this.defaultCardColor, this.direction = Axis.horizontal, this.disabledColor, this.elevation, @@ -217,16 +175,17 @@ class AlippoSelectionCardGroups extends FormBuilderFieldDecoration { this.unselectedLabelStyle, this.padding, this.pressElevation, - this.runAlignment = WrapAlignment.start, - this.runSpacing = 0.0, - this.selectedColor, - this.unselectedColor, + this.selectedCardColor, this.selectedShadowColor, this.shadowColor, this.selectedShape, this.unselectedShape, this.spacing = 0.0, this.expanded = false, + this.animationDuration, + this.reverseAnimationDuration, + this.animationCurve, + this.reverseAnimationCurve, }) : super( builder: (FormFieldState field) { final state = field as _AlippoSelectionCardGroupsState; @@ -250,11 +209,11 @@ class AlippoSelectionCardGroups extends FormBuilderFieldDecoration { } : null, avatar: option.avatar, - selectedIconColor: unselectedColor, - unselectedIconColor: selectedColor, - selectedCardColor: selectedColor, + selectedIconColor: defaultCardColor, + unselectedIconColor: selectedCardColor, + selectedCardColor: selectedCardColor, + defaultCardColor: defaultCardColor, disabledColor: disabledColor, - backgroundColor: backgroundColor, shadowColor: shadowColor, selectedShadowColor: selectedShadowColor, elevation: elevation, @@ -267,7 +226,6 @@ class AlippoSelectionCardGroups extends FormBuilderFieldDecoration { unselectedShape: unselectedShape, expanded: expanded, infoModalConfig: option.infoModalConfig, - infoModalHeight: 100, ), if (options.last != option) SizedBox(height: spacing), ], diff --git a/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card.dart b/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card.dart index 4dbab256a..277dc0275 100644 --- a/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card.dart +++ b/lib/src/alippo_custom_form_components/fields/alippo_selection_card_group/comp/selection_card.dart @@ -32,8 +32,6 @@ class SelectionCard extends StatefulWidget { final bool autofocus; - final Color? backgroundColor; - final EdgeInsetsGeometry? padding; final double? elevation; @@ -58,8 +56,6 @@ class SelectionCard extends StatefulWidget { final Curve? reverseAnimationCurve; - final double infoModalHeight; - /// Configuration for the info modal final InfoModalConfig? infoModalConfig; @@ -80,7 +76,6 @@ class SelectionCard extends StatefulWidget { this.unselectedShape, this.focusNode, this.autofocus = false, - this.backgroundColor, this.padding, this.elevation, this.shadowColor, @@ -95,7 +90,6 @@ class SelectionCard extends StatefulWidget { this.reverseAnimationDuration, this.animationCurve, this.reverseAnimationCurve, - this.infoModalHeight = 0, }); @override @@ -222,9 +216,9 @@ class _SelectionCardState extends State if (widget.infoModalConfig != null) Padding( padding: const EdgeInsets.only(top: 50), - child: Visibility( - visible: _animation!.value > 0, - maintainSize: false, + child: SizeTransition( + sizeFactor: _animation!, + axis: Axis.vertical, child: Transform( transform: Matrix4.diagonal3Values(1.0, _animation!.value, 1.0), @@ -262,10 +256,10 @@ class _SelectionCardState extends State ), Card( color: ColorTween( - begin: widget.backgroundColor, // Light theme color + begin: widget.defaultCardColor, // Light theme color end: widget.selectedCardColor, // Dark theme color ).evaluate(_animation!) ?? - widget.backgroundColor, + widget.defaultCardColor, elevation: widget.elevation, shadowColor: widget.shadowColor, shape: widget.selected @@ -273,6 +267,7 @@ class _SelectionCardState extends State : widget.unselectedShape ?? widget.selectedShape, margin: EdgeInsets.zero, child: InkWell( + splashColor: Colors.transparent, onTap: () { if (widget.onSelected != null) { widget.onSelected!(!widget.selected);