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, ), ); }