From f1cf8d88d3a9166ec481d9f29ddbcfea568d2fa1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 24 Jul 2025 16:40:03 +0300 Subject: [PATCH 1/6] Matched design of duplicate space dialog, with the design language of the system. --- .../views/duplicate_space_dialog.dart | 1 + .../widgets/duplicate_space_dialog_form.dart | 30 +++++++++++++------ .../widgets/duplicate_space_text_field.dart | 9 +++--- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart index dcda4a58..2083bf62 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart @@ -31,6 +31,7 @@ class DuplicateSpaceDialog extends StatelessWidget { child: BlocListener( listener: _listener, child: DuplicateSpaceDialogForm( + initialNameSuffix: '(1)', initialName: initialName, selectedSpaceUuid: selectedSpaceUuid, selectedCommunityUuid: selectedCommunityUuid, diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart index 7f4ec966..bac1cecd 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart @@ -3,18 +3,22 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_action_buttons.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; class DuplicateSpaceDialogForm extends StatefulWidget { const DuplicateSpaceDialogForm({ required this.initialName, required this.selectedSpaceUuid, required this.selectedCommunityUuid, + required this.initialNameSuffix, super.key, }); final String initialName; final String selectedSpaceUuid; final String selectedCommunityUuid; + final String initialNameSuffix; @override State createState() => _DuplicateSpaceDialogFormState(); @@ -27,7 +31,9 @@ class _DuplicateSpaceDialogFormState extends State { @override void initState() { super.initState(); - _nameController = TextEditingController(text: '${widget.initialName}(1)'); + _nameController = TextEditingController( + text: '${widget.initialName}${widget.initialNameSuffix}', + ); _nameController.addListener(_validateName); } @@ -44,9 +50,17 @@ class _DuplicateSpaceDialogFormState extends State { @override Widget build(BuildContext context) { return AlertDialog( - title: const SelectableText('Duplicate Space'), + title: const SelectableText( + 'Duplicate Space', + style: TextStyle( + fontSize: 30, + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + ), + ), content: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, spacing: 16, children: [ const SelectableText('Enter a new name for the duplicated space:'), @@ -58,13 +72,11 @@ class _DuplicateSpaceDialogFormState extends State { ], ), actions: [ - TextButton( - onPressed: Navigator.of(context).pop, - child: const Text('Cancel'), - ), - TextButton( - onPressed: _isNameValid ? () => _submit(context) : null, - child: const Text('Duplicate'), + SpaceDetailsActionButtons( + spacerFlex: 2, + onSave: _isNameValid ? () => _submit(context) : null, + onCancel: Navigator.of(context).pop, + saveButtonLabel: 'Duplicate', ), ], ); diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart index 19b0201a..828e72f0 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart @@ -24,9 +24,10 @@ class DuplicateSpaceTextField extends StatelessWidget { color: ColorsManager.blackColor, ), decoration: InputDecoration( - label: const Text('Space Name'), - border: _border(), - enabledBorder: _border(), + filled: true, + fillColor: ColorsManager.boxColor, + border: _border(ColorsManager.transparentColor), + enabledBorder: _border(ColorsManager.transparentColor), focusedBorder: _border(ColorsManager.primaryColor), errorBorder: _border(context.theme.colorScheme.error), focusedErrorBorder: _border(context.theme.colorScheme.error), @@ -41,7 +42,7 @@ class DuplicateSpaceTextField extends StatelessWidget { OutlineInputBorder _border([Color? color]) { return OutlineInputBorder( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: color ?? ColorsManager.blackColor, width: 0.5, From 83895d3ddab7031ca32e15b9df549f9e754dba80 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Jul 2025 10:51:53 +0300 Subject: [PATCH 2/6] Diallows naming duplication when duplicating a space, and adds a proper suffix to the name in case of another spaces having a name match. --- .../helpers/spaces_recursive_helper.dart | 16 ++++ ...ucture_header_action_buttons_composer.dart | 4 +- .../views/duplicate_space_dialog.dart | 82 +++++++++++++++++-- .../widgets/duplicate_space_dialog_form.dart | 33 +++++--- .../widgets/duplicate_space_text_field.dart | 8 +- 5 files changed, 118 insertions(+), 25 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart index de5ce34f..96d019ae 100644 --- a/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart +++ b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart @@ -68,4 +68,20 @@ abstract final class SpacesRecursiveHelper { return space; }).toList(); } + + static SpaceModel? findParent( + List spaces, + String targetUuid, + ) { + for (final space in spaces) { + if (space.children.any((child) => child.uuid == targetUuid)) { + return space; + } + final parent = findParent(space.children, targetUuid); + if (parent != null) { + return parent; + } + } + return null; + } } diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.dart index b2a41274..0cb56bc5 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.dart @@ -49,8 +49,8 @@ class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget { context: context, builder: (_) => DuplicateSpaceDialog( initialName: space.spaceName, - selectedSpaceUuid: space.uuid, - selectedCommunityUuid: selectedCommunity.uuid, + selectedSpace: space, + selectedCommunity: selectedCommunity, onSuccess: (spaces) { final updatedCommunity = selectedCommunity.copyWith( spaces: spaces, diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart index 2083bf62..8f4ddf90 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/common/widgets/app_loading_indicator.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart'; @@ -12,15 +13,15 @@ class DuplicateSpaceDialog extends StatelessWidget { const DuplicateSpaceDialog({ required this.initialName, required this.onSuccess, - required this.selectedSpaceUuid, - required this.selectedCommunityUuid, + required this.selectedSpace, + required this.selectedCommunity, super.key, }); final String initialName; final void Function(List spaces) onSuccess; - final String selectedSpaceUuid; - final String selectedCommunityUuid; + final SpaceModel selectedSpace; + final CommunityModel selectedCommunity; @override Widget build(BuildContext context) { @@ -31,10 +32,10 @@ class DuplicateSpaceDialog extends StatelessWidget { child: BlocListener( listener: _listener, child: DuplicateSpaceDialogForm( - initialNameSuffix: '(1)', - initialName: initialName, - selectedSpaceUuid: selectedSpaceUuid, - selectedCommunityUuid: selectedCommunityUuid, + initialName: _getInitialName(), + selectedSpaceUuid: selectedSpace.uuid, + selectedCommunityUuid: selectedCommunity.uuid, + siblingNames: _getSiblingNames(), ), ), ); @@ -67,4 +68,69 @@ class DuplicateSpaceDialog extends StatelessWidget { break; } } + + List _findSiblings( + List spaces, + String targetUuid, + ) { + final parent = _findParent(spaces, targetUuid); + + if (parent != null) { + return parent.children.where((s) => s.uuid != targetUuid).toList(); + } else { + if (spaces.any((s) => s.uuid == targetUuid)) { + return spaces.where((s) => s.uuid != targetUuid).toList(); + } + } + return []; + } + + SpaceModel? _findParent(List allSpaces, String childUuid) { + for (final space in allSpaces) { + if (space.children.any((child) => child.uuid == childUuid)) { + return space; + } + final parent = _findParent(space.children, childUuid); + if (parent != null) { + return parent; + } + } + return null; + } + + List _getSiblingNames() { + final siblings = _findSiblings(selectedCommunity.spaces, selectedSpace.uuid); + final names = siblings.map((s) => s.spaceName).toList(); + names.add(initialName); + return names; + } + + String _getInitialName() { + final allRelevantNames = _getSiblingNames(); + final nameRegex = RegExp(r'^(.*?) ?\((\d+)\)$'); + + final baseNameMatch = nameRegex.firstMatch(initialName); + final baseName = baseNameMatch?.group(1)?.trim() ?? initialName; + + var maxSuffix = 0; + + for (final name in allRelevantNames) { + if (name == baseName) { + continue; + } + + final match = nameRegex.firstMatch(name); + if (match != null) { + final currentBaseName = match.group(1)!.trim(); + if (currentBaseName == baseName) { + final suffix = int.parse(match.group(2)!); + if (suffix > maxSuffix) { + maxSuffix = suffix; + } + } + } + } + + return '$baseName(${maxSuffix + 1})'; + } } diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart index bac1cecd..db3cc878 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart @@ -11,14 +11,14 @@ class DuplicateSpaceDialogForm extends StatefulWidget { required this.initialName, required this.selectedSpaceUuid, required this.selectedCommunityUuid, - required this.initialNameSuffix, + required this.siblingNames, super.key, }); final String initialName; final String selectedSpaceUuid; final String selectedCommunityUuid; - final String initialNameSuffix; + final List siblingNames; @override State createState() => _DuplicateSpaceDialogFormState(); @@ -26,20 +26,33 @@ class DuplicateSpaceDialogForm extends StatefulWidget { class _DuplicateSpaceDialogFormState extends State { late final TextEditingController _nameController; - bool _isNameValid = true; + String? _errorText; @override void initState() { super.initState(); _nameController = TextEditingController( - text: '${widget.initialName}${widget.initialNameSuffix}', + text: widget.initialName, ); _nameController.addListener(_validateName); } - void _validateName() => setState( - () => _isNameValid = _nameController.text.trim() != widget.initialName, - ); + void _validateName() { + final name = _nameController.text.trim(); + if (name.isEmpty) { + setState(() { + _errorText = 'Name cannot be empty'; + }); + } else if (widget.siblingNames.contains(name)) { + setState(() { + _errorText = 'Name must be unique'; + }); + } else { + setState(() { + _errorText = null; + }); + } + } @override void dispose() { @@ -66,15 +79,15 @@ class _DuplicateSpaceDialogFormState extends State { const SelectableText('Enter a new name for the duplicated space:'), DuplicateSpaceTextField( nameController: _nameController, - isNameValid: _isNameValid, - initialName: widget.initialName, + isNameValid: _errorText == null, + errorText: _errorText, ), ], ), actions: [ SpaceDetailsActionButtons( spacerFlex: 2, - onSave: _isNameValid ? () => _submit(context) : null, + onSave: _errorText == null ? () => _submit(context) : null, onCancel: Navigator.of(context).pop, saveButtonLabel: 'Duplicate', ), diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart index 828e72f0..8d8bfe64 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart @@ -6,15 +6,13 @@ class DuplicateSpaceTextField extends StatelessWidget { const DuplicateSpaceTextField({ required this.nameController, required this.isNameValid, - required this.initialName, + this.errorText, super.key, }); final TextEditingController nameController; final bool isNameValid; - final String initialName; - - String get _errorText => 'Name must be different from "$initialName"'; + final String? errorText; @override Widget build(BuildContext context) { @@ -35,7 +33,7 @@ class DuplicateSpaceTextField extends StatelessWidget { color: context.theme.colorScheme.error, fontSize: 8, ), - errorText: isNameValid ? null : _errorText, + errorText: isNameValid ? null : errorText, ), ); } From cd8ffc99ea163732b1fbfbef2d54d436853fbffc Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Jul 2025 10:56:54 +0300 Subject: [PATCH 3/6] removed unnecessary flag, that can be replaced with checking if the value equals `null`. --- .../presentation/widgets/duplicate_space_dialog_form.dart | 1 - .../presentation/widgets/duplicate_space_text_field.dart | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart index db3cc878..5c2566fe 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart @@ -79,7 +79,6 @@ class _DuplicateSpaceDialogFormState extends State { const SelectableText('Enter a new name for the duplicated space:'), DuplicateSpaceTextField( nameController: _nameController, - isNameValid: _errorText == null, errorText: _errorText, ), ], diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart index 8d8bfe64..c12414e1 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart @@ -5,13 +5,11 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; class DuplicateSpaceTextField extends StatelessWidget { const DuplicateSpaceTextField({ required this.nameController, - required this.isNameValid, - this.errorText, + required this.errorText, super.key, }); final TextEditingController nameController; - final bool isNameValid; final String? errorText; @override @@ -33,7 +31,7 @@ class DuplicateSpaceTextField extends StatelessWidget { color: context.theme.colorScheme.error, fontSize: 8, ), - errorText: isNameValid ? null : errorText, + errorText: errorText, ), ); } From 2d69e3c72f0d4cc8dfeb3f78198096cad18a31a2 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Jul 2025 11:01:22 +0300 Subject: [PATCH 4/6] Made `SpaceSubSpacesDialog` scrollable, for a better UX, and to account for a very long list of subspaces. --- .../widgets/space_sub_spaces_dialog.dart | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart index 924a96e6..02a5c3d2 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart @@ -89,28 +89,35 @@ class _SpaceSubSpacesDialogState extends State { ), child: const SelectableText('Create Sub-Space'), ), - content: Column( - spacing: 12, - mainAxisSize: MainAxisSize.min, - children: [ - SubSpacesInput( - subSpaces: _subspaces, - onSubspaceAdded: _handleSubspaceAdded, - onSubspaceDeleted: _handleSubspaceDeleted, - controller: _subspaceNameController, - ), - AnimatedSwitcher( - duration: const Duration(milliseconds: 100), - child: Visibility( - key: ValueKey(_hasDuplicateNames), - visible: _hasDuplicateNames, - child: const SelectableText( - 'Error: Duplicate subspace names are not allowed.', - style: TextStyle(color: Colors.red), + content: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: context.screenHeight * 0.4, + ), + child: SingleChildScrollView( + child: Column( + spacing: 12, + mainAxisSize: MainAxisSize.min, + children: [ + SubSpacesInput( + subSpaces: _subspaces, + onSubspaceAdded: _handleSubspaceAdded, + onSubspaceDeleted: _handleSubspaceDeleted, + controller: _subspaceNameController, ), - ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 100), + child: Visibility( + key: ValueKey(_hasDuplicateNames), + visible: _hasDuplicateNames, + child: const SelectableText( + 'Error: Duplicate subspace names are not allowed.', + style: TextStyle(color: Colors.red), + ), + ), + ), + ], ), - ], + ), ), actions: [ SpaceDetailsActionButtons( From a4391aa73ed1bd5b1c58444555e114bf95c1c4a5 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Jul 2025 11:05:30 +0300 Subject: [PATCH 5/6] Made `AssignTagsDialog` scrollable to account for a long list of product allocations, for a better UX. --- .../widgets/assign_tags_dialog.dart | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart b/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart index d14a3923..08e9fac2 100644 --- a/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart +++ b/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart @@ -182,15 +182,20 @@ class _AssignTagsDialogState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - width: double.infinity, - child: AssignTagsTable( - productAllocations: allProductAllocations, - subspaces: _space.subspaces, - productLocations: productLocations, - onTagSelected: _handleTagChange, - onLocationSelected: _handleLocationChange, - onProductDeleted: _handleProductDelete, + ConstrainedBox( + constraints: BoxConstraints( + minWidth: double.infinity, + maxHeight: context.screenHeight * 0.6, + ), + child: SingleChildScrollView( + child: AssignTagsTable( + productAllocations: allProductAllocations, + subspaces: _space.subspaces, + productLocations: productLocations, + onTagSelected: _handleTagChange, + onLocationSelected: _handleLocationChange, + onProductDeleted: _handleProductDelete, + ), ), ), if (hasErrors) From 1558806cc36b520a0e2adf95df883ab27d02f772 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Jul 2025 11:18:08 +0300 Subject: [PATCH 6/6] 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, ), ), );