From 1558806cc36b520a0e2adf95df883ab27d02f772 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Jul 2025 11:18:08 +0300 Subject: [PATCH] Refactor SpaceDetailsForm to use StatefulWidget for form validation and improve user experience with dynamic save button state, and added space name validation to not be longer than 50 characters. --- .../widgets/space_details_form.dart | 74 +++++++++++++------ .../widgets/space_name_text_field.dart | 47 ++++++------ 2 files changed, 74 insertions(+), 47 deletions(-) diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_form.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_form.dart index 886a792d..f90d1d57 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_form.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_form.dart @@ -10,7 +10,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/update_space/prese import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -class SpaceDetailsForm extends StatelessWidget { +class SpaceDetailsForm extends StatefulWidget { const SpaceDetailsForm({ required this.title, required this.space, @@ -22,24 +22,52 @@ class SpaceDetailsForm extends StatelessWidget { final SpaceDetailsModel space; final void Function(SpaceDetailsModel space) onSave; + @override + State createState() => _SpaceDetailsFormState(); +} + +class _SpaceDetailsFormState extends State { + final _formKey = GlobalKey(); + bool _isFormValid = false; + + @override + void initState() { + super.initState(); + final initialName = widget.space.spaceName; + _isFormValid = initialName.isNotEmpty && + initialName.length <= 50 && + !widget.space.subspaces.any((subspace) => subspace.name == initialName); + } + @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => SpaceDetailsModelBloc(initialState: space), + create: (context) => SpaceDetailsModelBloc(initialState: widget.space), child: BlocBuilder( - buildWhen: (previous, current) => previous != current, - builder: (context, space) { - return AlertDialog( - title: DefaultTextStyle( - style: context.textTheme.titleLarge!.copyWith( - fontSize: 30, - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor, - ), - child: title, + buildWhen: (previous, current) => previous != current, + builder: (context, space) { + return AlertDialog( + title: DefaultTextStyle( + style: context.textTheme.titleLarge!.copyWith( + fontSize: 30, + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, ), - backgroundColor: ColorsManager.white, - content: SizedBox( + child: widget.title, + ), + backgroundColor: ColorsManager.white, + content: Form( + key: _formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + onChanged: () { + final isValid = _formKey.currentState?.validate() ?? false; + if (_isFormValid != isValid) { + setState(() { + _isFormValid = isValid; + }); + } + }, + child: SizedBox( height: context.screenHeight * 0.3, width: context.screenWidth * 0.4, child: Row( @@ -70,14 +98,16 @@ class SpaceDetailsForm extends StatelessWidget { ], ), ), - actions: [ - SpaceDetailsActionButtons( - onSave: () => onSave(space), - onCancel: Navigator.of(context).pop, - ), - ], - ); - }), + ), + actions: [ + SpaceDetailsActionButtons( + onSave: _isFormValid ? () => widget.onSave(space) : null, + onCancel: Navigator.of(context).pop, + ), + ], + ); + }, + ), ); } } diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_name_text_field.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_name_text_field.dart index 0c62490f..7ddbb07c 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_name_text_field.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_name_text_field.dart @@ -33,12 +33,13 @@ class _SpaceNameTextFieldState extends State { super.dispose(); } - final _formKey = GlobalKey(); - String? _validateName(String? value) { if (value == null || value.isEmpty) { return '*Space name should not be empty.'; } + if (value.length > 50) { + return '*Space name cannot be longer than 50 characters.'; + } if (widget.isNameFieldExist(value)) { return '*Name already exists'; } @@ -47,30 +48,26 @@ class _SpaceNameTextFieldState extends State { @override Widget build(BuildContext context) { - return Form( - key: _formKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: TextFormField( - controller: _controller, - onChanged: (value) => context.read().add( - UpdateSpaceDetailsName(value), - ), - validator: _validateName, - style: context.textTheme.bodyMedium, - decoration: InputDecoration( - hintText: 'Please enter the name', - hintStyle: context.textTheme.bodyMedium!.copyWith( - color: ColorsManager.lightGrayColor, - ), - filled: true, - fillColor: ColorsManager.boxColor, - enabledBorder: _buildBorder(context, ColorsManager.vividBlue), - focusedBorder: _buildBorder(context, ColorsManager.primaryColor), - errorBorder: _buildBorder(context, context.theme.colorScheme.error), - focusedErrorBorder: _buildBorder(context, context.theme.colorScheme.error), - errorStyle: context.textTheme.bodySmall?.copyWith( - color: context.theme.colorScheme.error, + return TextFormField( + controller: _controller, + onChanged: (value) => context.read().add( + UpdateSpaceDetailsName(value), ), + validator: _validateName, + style: context.textTheme.bodyMedium, + decoration: InputDecoration( + hintText: 'Please enter the name', + hintStyle: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.lightGrayColor, + ), + filled: true, + fillColor: ColorsManager.boxColor, + enabledBorder: _buildBorder(context, ColorsManager.vividBlue), + focusedBorder: _buildBorder(context, ColorsManager.primaryColor), + errorBorder: _buildBorder(context, context.theme.colorScheme.error), + focusedErrorBorder: _buildBorder(context, context.theme.colorScheme.error), + errorStyle: context.textTheme.bodySmall?.copyWith( + color: context.theme.colorScheme.error, ), ), );