diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 715e8b55..50d73fff 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -344,10 +344,21 @@ class CreateSpaceDialogState extends State { runSpacing: 8.0, children: [ if (subspaces != null) - ...subspaces!.map( - (subspace) => SubspaceNameDisplayWidget( - text: subspace.subspaceName, - )), + ...subspaces!.map((subspace) => + SubspaceNameDisplayWidget( + validateName: (updatedName) { + return !subspaces!.any((s) => + s != subspace && + s.subspaceName == updatedName); + }, + text: subspace.subspaceName, + onNameChanged: (updatedName) { + setState(() { + subspace.subspaceName = + updatedName; + }); + }, + )), EditChip( onTap: () async { _showSubSpaceDialog(context, enteredName, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index a825e868..e172231e 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -123,7 +123,6 @@ class CreateSpaceModelDialog extends StatelessWidget { ), const SizedBox(height: 16), SubspaceModelCreate( - context, subspaces: state.space.subspaceModels ?? [], onSpaceModelUpdate: (updatedSubspaces) { context diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 8dc981da..5fddfe6e 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -6,20 +6,36 @@ import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class SubspaceModelCreate extends StatelessWidget { +class SubspaceModelCreate extends StatefulWidget { final List subspaces; final void Function(List newSubspaces)? onSpaceModelUpdate; - const SubspaceModelCreate(BuildContext context, - {Key? key, required this.subspaces, this.onSpaceModelUpdate}) - : super(key: key); + const SubspaceModelCreate({ + Key? key, + required this.subspaces, + this.onSpaceModelUpdate, + }) : super(key: key); + + @override + _SubspaceModelCreateState createState() => _SubspaceModelCreateState(); +} + +class _SubspaceModelCreateState extends State { + late List _subspaces; + String? errorSubspaceId; + + @override + void initState() { + super.initState(); + _subspaces = List.from(widget.subspaces); + } @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; return Container( - child: subspaces.isEmpty + child: _subspaces.isEmpty ? TextButton( style: TextButton.styleFrom( overlayColor: ColorsManager.transparentColor, @@ -41,16 +57,33 @@ class SubspaceModelCreate extends StatelessWidget { borderRadius: BorderRadius.circular(15), border: Border.all( color: ColorsManager.textFieldGreyColor, - width: 3.0, // Border width + width: 3.0, ), ), child: Wrap( spacing: 8.0, runSpacing: 8.0, children: [ - ...subspaces.map((subspace) => SubspaceNameDisplayWidget( - text: subspace.subspaceName, - )), + ..._subspaces.map((subspace) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SubspaceNameDisplayWidget( + text: subspace.subspaceName, + validateName: (updatedName) { + return !_subspaces.any((s) => + s != subspace && + s.subspaceName == updatedName); + }, + onNameChanged: (updatedName) { + setState(() { + subspace.subspaceName = updatedName; + }); + }, + ), + ], + ); + }), EditChip( onTap: () async { await _openDialog(context, 'Edit Sub-space'); @@ -71,9 +104,15 @@ class SubspaceModelCreate extends StatelessWidget { return CreateSubSpaceModelDialog( isEdit: true, dialogTitle: dialogTitle, - existingSubSpaces: subspaces, + existingSubSpaces: _subspaces, onUpdate: (subspaceModels) { - onSpaceModelUpdate!(subspaceModels); + setState(() { + _subspaces = subspaceModels; + errorSubspaceId = null; + }); + if (widget.onSpaceModelUpdate != null) { + widget.onSpaceModelUpdate!(subspaceModels); + } }, ); }, diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart index a2920b89..2ecbf0b1 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart @@ -1,13 +1,15 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class SubspaceNameDisplayWidget extends StatelessWidget { +class SubspaceNameDisplayWidget extends StatefulWidget { final String text; final TextStyle? textStyle; final Color backgroundColor; final Color borderColor; final EdgeInsetsGeometry padding; final BorderRadiusGeometry borderRadius; + final void Function(String updatedName) onNameChanged; + final bool Function(String updatedName) validateName; const SubspaceNameDisplayWidget({ Key? key, @@ -17,25 +19,112 @@ class SubspaceNameDisplayWidget extends StatelessWidget { this.borderColor = ColorsManager.transparentColor, this.padding = const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), this.borderRadius = const BorderRadius.all(Radius.circular(10)), + required this.onNameChanged, + required this.validateName, }) : super(key: key); + @override + _SubspaceNameDisplayWidgetState createState() => + _SubspaceNameDisplayWidgetState(); +} + +class _SubspaceNameDisplayWidgetState extends State { + bool isEditing = false; + late TextEditingController _controller; + late FocusNode _focusNode; + late String previousName; + String? errorText; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(text: widget.text); + _focusNode = FocusNode(); + previousName = widget.text; + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + void _handleValidationAndSave() { + final updatedName = _controller.text; + if (widget.validateName(updatedName)) { + setState(() { + errorText = null; + isEditing = false; + previousName = updatedName; + widget.onNameChanged(updatedName); + }); + } else { + setState(() { + errorText = 'Subspace name already exists.'; + }); + } + } + @override Widget build(BuildContext context) { - return Container( - padding: padding, - decoration: BoxDecoration( - color: backgroundColor, - borderRadius: borderRadius, - border: Border.all(color: borderColor), - ), - child: Text( - text, - style: textStyle ?? - Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.spaceColor), - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () { + setState(() { + isEditing = true; + _focusNode.requestFocus(); + }); + }, + child: Container( + padding: widget.padding, + decoration: BoxDecoration( + color: widget.backgroundColor, + borderRadius: widget.borderRadius, + border: Border.all(color: widget.borderColor), + ), + child: isEditing + ? TextField( + controller: _controller, + focusNode: _focusNode, + style: widget.textStyle ?? + Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + autofocus: true, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 8.0), + ), + onSubmitted: (value) { + _handleValidationAndSave(); + }, + ) + : Text( + widget.text, + style: widget.textStyle ?? + Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.spaceColor), + ), + ), + ), + if (errorText != null) + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Text( + errorText!, + style: const TextStyle( + color: ColorsManager.warningRed, + fontSize: 12, + ), + ), + ), + ], ); } }