From e6d9000ee2b2d5a30b7253b845bb65baf203c133 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 09:45:04 +0300 Subject: [PATCH 01/20] Implemented duplicate space domain layer. --- .../domain/params/duplicate_space_param.dart | 13 +++++++++++++ .../domain/services/duplicate_space_service.dart | 6 ++++++ 2 files changed, 19 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart create mode 100644 lib/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart diff --git a/lib/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart b/lib/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart new file mode 100644 index 00000000..5d25e707 --- /dev/null +++ b/lib/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart @@ -0,0 +1,13 @@ +class DuplicateSpaceParam { + final String communityUuid; + final String spaceUuid; + final String newSpaceName; + final String newSpaceIcon; + + DuplicateSpaceParam({ + required this.communityUuid, + required this.spaceUuid, + required this.newSpaceName, + required this.newSpaceIcon, + }); +} diff --git a/lib/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart b/lib/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart new file mode 100644 index 00000000..955d28de --- /dev/null +++ b/lib/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart @@ -0,0 +1,6 @@ +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/domain/params/duplicate_space_param.dart'; + +abstract interface class DuplicateSpaceService { + Future duplicateSpace(DuplicateSpaceParam param); +} From 71f0da9299683b5eb7da076cf6873343b2fe749f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 09:51:09 +0300 Subject: [PATCH 02/20] Created `duplicate_space` presentation layer. --- .../bloc/duplicate_space_bloc.dart | 36 +++++++++++++++++++ .../bloc/duplicate_space_event.dart | 10 ++++++ .../bloc/duplicate_space_state.dart | 34 ++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart create mode 100644 lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_event.dart create mode 100644 lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_state.dart diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart new file mode 100644 index 00000000..482bf399 --- /dev/null +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart @@ -0,0 +1,36 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.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/domain/params/duplicate_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart'; +import 'package:syncrow_web/services/api/api_exception.dart'; + +part 'duplicate_space_event.dart'; +part 'duplicate_space_state.dart'; + +class DuplicateSpaceBloc extends Bloc { + DuplicateSpaceBloc( + this._duplicateSpaceService, + ) : super(const DuplicateSpaceInitial()) { + on(_onDuplicateSpaceEvent); + } + + final DuplicateSpaceService _duplicateSpaceService; + + Future _onDuplicateSpaceEvent( + DuplicateSpaceEvent event, + Emitter emit, + ) async { + try { + emit(const DuplicateSpaceLoading()); + final result = await _duplicateSpaceService.duplicateSpace(event.param); + emit(DuplicateSpaceSuccess(result)); + } on APIException catch (e) { + emit(DuplicateSpaceFailure(e.message)); + } catch (e) { + emit(DuplicateSpaceFailure(e.toString())); + } finally { + emit(const DuplicateSpaceInitial()); + } + } +} diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_event.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_event.dart new file mode 100644 index 00000000..5a437831 --- /dev/null +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_event.dart @@ -0,0 +1,10 @@ +part of 'duplicate_space_bloc.dart'; + +final class DuplicateSpaceEvent extends Equatable { + const DuplicateSpaceEvent({required this.param}); + + final DuplicateSpaceParam param; + + @override + List get props => [param]; +} diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_state.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_state.dart new file mode 100644 index 00000000..d27a5b38 --- /dev/null +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_state.dart @@ -0,0 +1,34 @@ +part of 'duplicate_space_bloc.dart'; + +sealed class DuplicateSpaceState extends Equatable { + const DuplicateSpaceState(); + + @override + List get props => []; +} + +final class DuplicateSpaceInitial extends DuplicateSpaceState { + const DuplicateSpaceInitial(); +} + +final class DuplicateSpaceLoading extends DuplicateSpaceState { + const DuplicateSpaceLoading(); +} + +final class DuplicateSpaceSuccess extends DuplicateSpaceState { + const DuplicateSpaceSuccess(this.space); + + final SpaceModel space; + + @override + List get props => [space]; +} + +final class DuplicateSpaceFailure extends DuplicateSpaceState { + const DuplicateSpaceFailure(this.errorMessage); + + final String errorMessage; + + @override + List get props => [errorMessage]; +} From c59d2b7fd66d570a1ffd25099df0c290f33c4689 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 09:58:15 +0300 Subject: [PATCH 03/20] Implemented `toJson` method in `DuplicateSpaceParam`. --- .../duplicate_space/domain/params/duplicate_space_param.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart b/lib/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart index 5d25e707..0069faa9 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart @@ -10,4 +10,8 @@ class DuplicateSpaceParam { required this.newSpaceName, required this.newSpaceIcon, }); + + Map toJson() => { + 'spaceName': newSpaceName, + }; } From ef8c9efff099c1be4bc96ebc53ff6378132e4bb8 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 09:58:46 +0300 Subject: [PATCH 04/20] Added duplicate space endpoint to `ApiEndpoints`. --- lib/utils/constants/api_const.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index cf7ffbe5..bbf8cccd 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -141,7 +141,7 @@ abstract class ApiEndpoints { '/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}'; static const String saveSchedule = '/schedule/{deviceUuid}'; - + static const String duplicateSpace = '/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/duplicate'; ////booking System static const String bookableSpaces = '/bookable-spaces'; From 4241d11cb650f7cafc1acac4e2aed9abef39631d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 09:59:13 +0300 Subject: [PATCH 05/20] Implemented `duplicate_space` data layer. --- .../remote_duplicate_space_service.dart | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart diff --git a/lib/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart b/lib/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart new file mode 100644 index 00000000..bd0b21fa --- /dev/null +++ b/lib/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart @@ -0,0 +1,58 @@ +import 'package:dio/dio.dart'; +import 'package:syncrow_web/pages/common/bloc/project_manager.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/domain/params/duplicate_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart'; +import 'package:syncrow_web/services/api/api_exception.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +final class RemoteDuplicateSpaceService implements DuplicateSpaceService { + RemoteDuplicateSpaceService(this._httpService); + + final HTTPService _httpService; + + @override + Future duplicateSpace(DuplicateSpaceParam param) async { + try { + final response = await _httpService.post( + path: await _makeUrl(param), + body: param.toJson(), + expectedResponseModel: (json) { + final response = json as Map; + final data = response['data'] as Map; + return SpaceModel.fromJson(data); + }, + ); + + return response; + } on DioException catch (e) { + final message = e.response?.data as Map?; + final error = message?['error'] as Map?; + final errorMessage = error?['error'] as String? ?? ''; + throw APIException(errorMessage); + } catch (e) { + throw APIException(e.toString()); + } + } + + Future _makeUrl(DuplicateSpaceParam param) async { + final projectUuid = await ProjectManager.getProjectUUID(); + if (projectUuid == null) { + throw APIException('Project UUID is not set'); + } + + if (param.communityUuid.isEmpty) { + throw APIException('Community UUID is not set'); + } + + if (param.spaceUuid.isEmpty) { + throw APIException('Space UUID is not set'); + } + + return ApiEndpoints.duplicateSpace + ..replaceAll('{projectUuid}', projectUuid) + ..replaceAll('{communityUuid}', param.communityUuid) + ..replaceAll('{spaceUuid}', param.spaceUuid); + } +} From aed3004a31e82ebe818cc927618fdf6285e8f319 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 10:51:51 +0300 Subject: [PATCH 06/20] Added `DuplicateSpaceBloc` to `SpaceManagementPage` for managing duplicate space functionality. --- .../main_module/views/space_management_page.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pages/space_management_v2/main_module/views/space_management_page.dart b/lib/pages/space_management_v2/main_module/views/space_management_page.dart index 47a67c36..e6949df5 100644 --- a/lib/pages/space_management_v2/main_module/views/space_management_page.dart +++ b/lib/pages/space_management_v2/main_module/views/space_management_page.dart @@ -7,6 +7,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/data/s import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.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'; import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/data/services/remote_reorder_spaces_service.dart'; @@ -67,6 +69,11 @@ class _SpaceManagementPageState extends State { RemoteReorderSpacesService(_httpService), ), ), + BlocProvider( + create: (context) => DuplicateSpaceBloc( + RemoteDuplicateSpaceService(_httpService), + ), + ), ], child: WebScaffold( appBarTitle: Text( From 7c5bca35fca4f1afceec8964caec1044e1e46e62 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 10:52:07 +0300 Subject: [PATCH 07/20] Add `DuplicateSpaceTextField` widget for user input in duplicate space management. --- .../widgets/duplicate_space_text_field.dart | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart 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 new file mode 100644 index 00000000..19b0201a --- /dev/null +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class DuplicateSpaceTextField extends StatelessWidget { + const DuplicateSpaceTextField({ + required this.nameController, + required this.isNameValid, + required this.initialName, + super.key, + }); + + final TextEditingController nameController; + final bool isNameValid; + final String initialName; + + String get _errorText => 'Name must be different from "$initialName"'; + + @override + Widget build(BuildContext context) { + return TextField( + controller: nameController, + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.blackColor, + ), + decoration: InputDecoration( + label: const Text('Space Name'), + border: _border(), + enabledBorder: _border(), + focusedBorder: _border(ColorsManager.primaryColor), + errorBorder: _border(context.theme.colorScheme.error), + focusedErrorBorder: _border(context.theme.colorScheme.error), + errorStyle: context.textTheme.bodyMedium!.copyWith( + color: context.theme.colorScheme.error, + fontSize: 8, + ), + errorText: isNameValid ? null : _errorText, + ), + ); + } + + OutlineInputBorder _border([Color? color]) { + return OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide( + color: color ?? ColorsManager.blackColor, + width: 0.5, + ), + ); + } +} From 3779176978c160012d38e85eae4a357a4018dd58 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 10:52:18 +0300 Subject: [PATCH 08/20] Add `DuplicateSpaceDialog` widget for user interaction in duplicate space management. --- .../views/duplicate_space_dialog.dart | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart 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 new file mode 100644 index 00000000..f7c491d7 --- /dev/null +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart'; + +class DuplicateSpaceDialog extends StatefulWidget { + const DuplicateSpaceDialog({ + required this.initialName, + required this.onSubmit, + super.key, + }); + + final String initialName; + final void Function(String) onSubmit; + + @override + State createState() => _DuplicateSpaceDialogState(); +} + +class _DuplicateSpaceDialogState extends State { + late final TextEditingController _nameController; + bool _isNameValid = true; + + @override + void initState() { + super.initState(); + _nameController = TextEditingController(text: '${widget.initialName}(1)'); + _nameController.addListener(_validateName); + } + + void _validateName() => setState( + () => _isNameValid = _nameController.text.trim() != widget.initialName, + ); + + @override + void dispose() { + _nameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const SelectableText('Duplicate Space'), + content: Column( + mainAxisSize: MainAxisSize.min, + spacing: 16, + children: [ + const SelectableText('Enter a new name for the duplicated space:'), + DuplicateSpaceTextField( + nameController: _nameController, + isNameValid: _isNameValid, + initialName: widget.initialName, + ), + ], + ), + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: const Text('Cancel'), + ), + TextButton( + onPressed: + _isNameValid ? () => widget.onSubmit(_nameController.text) : null, + child: const Text('Duplicate'), + ), + ], + ); + } +} From 19ddf443a999afde30591fb4cdc31acebccdacf9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 10:52:29 +0300 Subject: [PATCH 09/20] Refactor `RemoteDuplicateSpaceService` to improve code readability by aligning method chaining for URL replacements. --- .../data/services/remote_duplicate_space_service.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart b/lib/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart index bd0b21fa..7d5ffa30 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart @@ -51,8 +51,8 @@ final class RemoteDuplicateSpaceService implements DuplicateSpaceService { } return ApiEndpoints.duplicateSpace - ..replaceAll('{projectUuid}', projectUuid) - ..replaceAll('{communityUuid}', param.communityUuid) - ..replaceAll('{spaceUuid}', param.spaceUuid); + .replaceAll('{projectUuid}', projectUuid) + .replaceAll('{communityUuid}', param.communityUuid) + .replaceAll('{spaceUuid}', param.spaceUuid); } } From 04b7a506be2eb467ab43ae1a088cad296cbea11e Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 10:52:46 +0300 Subject: [PATCH 10/20] Remove `newSpaceIcon` parameter from `DuplicateSpaceParam` class since it isnt needed. --- .../duplicate_space/domain/params/duplicate_space_param.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart b/lib/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart index 0069faa9..4f955ab5 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart @@ -2,13 +2,11 @@ class DuplicateSpaceParam { final String communityUuid; final String spaceUuid; final String newSpaceName; - final String newSpaceIcon; DuplicateSpaceParam({ required this.communityUuid, required this.spaceUuid, required this.newSpaceName, - required this.newSpaceIcon, }); Map toJson() => { From 994efc302b5780e02e01c757aa411b1bed7dd538 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 12:36:45 +0300 Subject: [PATCH 11/20] Add `AppSnackBarsBuildContextExtension` for displaying success and failure snack bars in the app. --- lib/utils/extension/app_snack_bar.dart | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 lib/utils/extension/app_snack_bar.dart diff --git a/lib/utils/extension/app_snack_bar.dart b/lib/utils/extension/app_snack_bar.dart new file mode 100644 index 00000000..14cb9b43 --- /dev/null +++ b/lib/utils/extension/app_snack_bar.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +extension AppSnackBarsBuildContextExtension on BuildContext { + void showSuccessSnackbar(String message) { + ScaffoldMessenger.of(this).showSnackBar( + _makeSnackbar( + message: message, + icon: Icons.check_circle, + backgroundColor: Colors.green, + ), + ); + } + + void showFailureSnackbar(String message) { + ScaffoldMessenger.of(this).showSnackBar( + _makeSnackbar( + message: message, + icon: Icons.error, + backgroundColor: Colors.red, + ), + ); + } + + SnackBar _makeSnackbar({ + required String message, + required Color backgroundColor, + required IconData icon, + }) { + return SnackBar( + content: Row( + mainAxisSize: MainAxisSize.min, + spacing: 8, + children: [ + Icon(icon, color: Colors.white), + Text( + message, + style: textTheme.bodyMedium?.copyWith( + color: ColorsManager.whiteColors, + ), + ), + ], + ), + backgroundColor: backgroundColor, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + margin: const EdgeInsetsDirectional.symmetric( + horizontal: 92, + vertical: 32, + ), + ); + } +} From 5fde74fc7d46288ce7622d5cf07b5b6ebd6f9075 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 12:37:07 +0300 Subject: [PATCH 12/20] Add `DuplicateSpaceFailureDialog` widget to display error messages when duplicating spaces fails. --- .../duplicate_space_failure_dialog.dart | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_failure_dialog.dart diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_failure_dialog.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_failure_dialog.dart new file mode 100644 index 00000000..3aaa7b3d --- /dev/null +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_failure_dialog.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class DuplicateSpaceFailureDialog extends StatelessWidget { + const DuplicateSpaceFailureDialog(this.errorMessage, {super.key}); + + final String errorMessage; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Failed to duplicate space'), + content: Text(errorMessage), + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: const Text('Close'), + ), + ], + ); + } +} From 85f53ed1f281da86fff3b4a263602810a4b6b718 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 12:37:17 +0300 Subject: [PATCH 13/20] Remove `DuplicateSpaceBloc` and its associated service from `SpaceManagementPage` to streamline dependencies and improve code clarity. --- .../main_module/views/space_management_page.dart | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/views/space_management_page.dart b/lib/pages/space_management_v2/main_module/views/space_management_page.dart index e6949df5..47a67c36 100644 --- a/lib/pages/space_management_v2/main_module/views/space_management_page.dart +++ b/lib/pages/space_management_v2/main_module/views/space_management_page.dart @@ -7,8 +7,6 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/data/s import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.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'; import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/data/services/remote_reorder_spaces_service.dart'; @@ -69,11 +67,6 @@ class _SpaceManagementPageState extends State { RemoteReorderSpacesService(_httpService), ), ), - BlocProvider( - create: (context) => DuplicateSpaceBloc( - RemoteDuplicateSpaceService(_httpService), - ), - ), ], child: WebScaffold( appBarTitle: Text( From d21850edc853740206fdc18b72fd3ce016838cec Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 12:37:27 +0300 Subject: [PATCH 14/20] Enhance `DuplicateSpaceDialog` to use `Bloc` for state management and streamline the duplication process with success and error handling. Update `CommunityStructureHeaderActionButtonsComposer` to integrate the new dialog for duplicating spaces. --- ...ucture_header_action_buttons_composer.dart | 15 ++- .../views/duplicate_space_dialog.dart | 107 +++++++++--------- .../widgets/duplicate_space_dialog_form.dart | 84 ++++++++++++++ 3 files changed, 153 insertions(+), 53 deletions(-) create mode 100644 lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart 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 d7403588..d3e2d18a 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 @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart'; @@ -7,6 +9,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart'; class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget { @@ -44,7 +47,17 @@ class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget { }, ), ), - onDuplicate: (space) {}, + onDuplicate: (space) => showDialog( + context: context, + builder: (_) => DuplicateSpaceDialog( + initialName: space.spaceName, + selectedSpaceUuid: space.uuid, + selectedCommunityUuid: selectedCommunity.uuid, + onSuccess: (space) { + log('space: $space'); + }, + ), + ), onEdit: (space) => SpaceDetailsDialogHelper.showEdit( context, spaceModel: selectedSpace!, 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 f7c491d7..ec98dd51 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,68 +1,71 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.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/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'; +import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/extension/app_snack_bar.dart'; -class DuplicateSpaceDialog extends StatefulWidget { +class DuplicateSpaceDialog extends StatelessWidget { const DuplicateSpaceDialog({ required this.initialName, - required this.onSubmit, + required this.onSuccess, + required this.selectedSpaceUuid, + required this.selectedCommunityUuid, super.key, }); final String initialName; - final void Function(String) onSubmit; - - @override - State createState() => _DuplicateSpaceDialogState(); -} - -class _DuplicateSpaceDialogState extends State { - late final TextEditingController _nameController; - bool _isNameValid = true; - - @override - void initState() { - super.initState(); - _nameController = TextEditingController(text: '${widget.initialName}(1)'); - _nameController.addListener(_validateName); - } - - void _validateName() => setState( - () => _isNameValid = _nameController.text.trim() != widget.initialName, - ); - - @override - void dispose() { - _nameController.dispose(); - super.dispose(); - } + final void Function(SpaceModel space) onSuccess; + final String selectedSpaceUuid; + final String selectedCommunityUuid; @override Widget build(BuildContext context) { - return AlertDialog( - title: const SelectableText('Duplicate Space'), - content: Column( - mainAxisSize: MainAxisSize.min, - spacing: 16, - children: [ - const SelectableText('Enter a new name for the duplicated space:'), - DuplicateSpaceTextField( - nameController: _nameController, - isNameValid: _isNameValid, - initialName: widget.initialName, - ), - ], + return BlocProvider( + create: (context) => DuplicateSpaceBloc( + RemoteDuplicateSpaceService(HTTPService()), ), - actions: [ - TextButton( - onPressed: Navigator.of(context).pop, - child: const Text('Cancel'), + child: BlocListener( + listener: _listener, + child: DuplicateSpaceDialogForm( + initialName: initialName, + selectedSpaceUuid: selectedSpaceUuid, + selectedCommunityUuid: selectedCommunityUuid, ), - TextButton( - onPressed: - _isNameValid ? () => widget.onSubmit(_nameController.text) : null, - child: const Text('Duplicate'), - ), - ], + ), ); } + + void _listener(BuildContext context, DuplicateSpaceState state) { + switch (state) { + case DuplicateSpaceLoading(): + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const AppLoadingIndicator(), + ); + break; + + case DuplicateSpaceFailure(:final errorMessage): + Navigator.pop(context); + Navigator.pop(context); + context.showFailureSnackbar(errorMessage); + break; + + case DuplicateSpaceSuccess(:final space): + onSuccess.call(space); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + context.showSuccessSnackbar( + '${space.spaceName} duplicated successfully', + ); + break; + + default: + break; + } + } } 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 new file mode 100644 index 00000000..7f4ec966 --- /dev/null +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +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'; + +class DuplicateSpaceDialogForm extends StatefulWidget { + const DuplicateSpaceDialogForm({ + required this.initialName, + required this.selectedSpaceUuid, + required this.selectedCommunityUuid, + super.key, + }); + + final String initialName; + final String selectedSpaceUuid; + final String selectedCommunityUuid; + + @override + State createState() => _DuplicateSpaceDialogFormState(); +} + +class _DuplicateSpaceDialogFormState extends State { + late final TextEditingController _nameController; + bool _isNameValid = true; + + @override + void initState() { + super.initState(); + _nameController = TextEditingController(text: '${widget.initialName}(1)'); + _nameController.addListener(_validateName); + } + + void _validateName() => setState( + () => _isNameValid = _nameController.text.trim() != widget.initialName, + ); + + @override + void dispose() { + _nameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const SelectableText('Duplicate Space'), + content: Column( + mainAxisSize: MainAxisSize.min, + spacing: 16, + children: [ + const SelectableText('Enter a new name for the duplicated space:'), + DuplicateSpaceTextField( + nameController: _nameController, + isNameValid: _isNameValid, + initialName: widget.initialName, + ), + ], + ), + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: const Text('Cancel'), + ), + TextButton( + onPressed: _isNameValid ? () => _submit(context) : null, + child: const Text('Duplicate'), + ), + ], + ); + } + + void _submit(BuildContext context) { + context.read().add( + DuplicateSpaceEvent( + param: DuplicateSpaceParam( + newSpaceName: _nameController.text, + spaceUuid: widget.selectedSpaceUuid, + communityUuid: widget.selectedCommunityUuid, + ), + ), + ); + } +} From 2077ef053feeffc19268b8b39b83d22ae87eacbe Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 12:42:23 +0300 Subject: [PATCH 15/20] Refactor `DuplicateSpaceService` to return a list of `SpaceModel` objects instead of a single instance. Update related components including `DuplicateSpaceSuccess` state and `DuplicateSpaceDialog` to handle multiple spaces. Enhance `CommunityStructureHeaderActionButtonsComposer` to reflect these changes in the success callback. --- ...y_structure_header_action_buttons_composer.dart | 14 ++++++++++---- .../services/remote_duplicate_space_service.dart | 8 +++++--- .../domain/services/duplicate_space_service.dart | 2 +- .../presentation/bloc/duplicate_space_state.dart | 6 +++--- .../presentation/views/duplicate_space_dialog.dart | 10 ++++------ 5 files changed, 23 insertions(+), 17 deletions(-) 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 d3e2d18a..ef1ceac4 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 @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart'; @@ -53,8 +51,16 @@ class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget { initialName: space.spaceName, selectedSpaceUuid: space.uuid, selectedCommunityUuid: selectedCommunity.uuid, - onSuccess: (space) { - log('space: $space'); + onSuccess: (spaces) { + final updatedCommunity = selectedCommunity.copyWith( + spaces: spaces, + ); + context.read().add( + CommunitiesUpdateCommunity(updatedCommunity), + ); + context.read().add( + SelectCommunityEvent(community: updatedCommunity), + ); }, ), ), diff --git a/lib/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart b/lib/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart index 7d5ffa30..7a13d4eb 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart @@ -13,15 +13,17 @@ final class RemoteDuplicateSpaceService implements DuplicateSpaceService { final HTTPService _httpService; @override - Future duplicateSpace(DuplicateSpaceParam param) async { + Future> duplicateSpace(DuplicateSpaceParam param) async { try { final response = await _httpService.post( path: await _makeUrl(param), body: param.toJson(), expectedResponseModel: (json) { final response = json as Map; - final data = response['data'] as Map; - return SpaceModel.fromJson(data); + final data = response['data'] as List; + return data + .map((e) => SpaceModel.fromJson(e as Map)) + .toList(); }, ); diff --git a/lib/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart b/lib/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart index 955d28de..56f0d961 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart @@ -2,5 +2,5 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart'; abstract interface class DuplicateSpaceService { - Future duplicateSpace(DuplicateSpaceParam param); + Future> duplicateSpace(DuplicateSpaceParam param); } diff --git a/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_state.dart b/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_state.dart index d27a5b38..d104b184 100644 --- a/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_state.dart +++ b/lib/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_state.dart @@ -16,12 +16,12 @@ final class DuplicateSpaceLoading extends DuplicateSpaceState { } final class DuplicateSpaceSuccess extends DuplicateSpaceState { - const DuplicateSpaceSuccess(this.space); + const DuplicateSpaceSuccess(this.spaces); - final SpaceModel space; + final List spaces; @override - List get props => [space]; + List get props => [spaces]; } final class DuplicateSpaceFailure extends DuplicateSpaceState { 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 ec98dd51..dcda4a58 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 @@ -18,7 +18,7 @@ class DuplicateSpaceDialog extends StatelessWidget { }); final String initialName; - final void Function(SpaceModel space) onSuccess; + final void Function(List spaces) onSuccess; final String selectedSpaceUuid; final String selectedCommunityUuid; @@ -55,13 +55,11 @@ class DuplicateSpaceDialog extends StatelessWidget { context.showFailureSnackbar(errorMessage); break; - case DuplicateSpaceSuccess(:final space): - onSuccess.call(space); + case DuplicateSpaceSuccess(:final spaces): + onSuccess.call(spaces); Navigator.of(context).pop(); Navigator.of(context).pop(); - context.showSuccessSnackbar( - '${space.spaceName} duplicated successfully', - ); + context.showSuccessSnackbar('Space duplicated successfully'); break; default: From 845397e81981d41314d45a7e302e9e5d6e42f122 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 13:21:49 +0300 Subject: [PATCH 16/20] Update `CommunityStructureCanvas` to improve widget update logic by animating to the selected space based on community UUID changes. This enhances the responsiveness of the UI when the community context changes. --- .../main_module/widgets/community_structure_canvas.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart index 6614aa88..9e161f55 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart @@ -56,8 +56,9 @@ class _CommunityStructureCanvasState extends State @override void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.selectedSpace == null) return; - if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) { + if (oldWidget.community.uuid != widget.community.uuid) { + _animateToSpace(null); + } else if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { _animateToSpace(widget.selectedSpace); From a57b6e08539db419cb1e6dffab0781d0ff70ad5a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Jul 2025 14:19:25 +0300 Subject: [PATCH 17/20] Enhance `CommunityStructureCanvas` by adding a `_centerOnTree` method to improve the centering logic of the community structure. This method calculates the optimal view based on the positions of spaces and adjusts the transformation controller accordingly, ensuring a smoother user experience during updates and animations. --- .../widgets/community_structure_canvas.dart | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart index 9e161f55..ae9f846a 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart'; @@ -51,13 +53,22 @@ class _CommunityStructureCanvasState extends State duration: const Duration(milliseconds: 150), ); super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _centerOnTree(); + } + }); } @override void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.community.uuid != widget.community.uuid) { - _animateToSpace(null); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _centerOnTree(animate: true); + } + }); } else if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { @@ -152,6 +163,60 @@ class _CommunityStructureCanvasState extends State _runAnimation(matrix); } + void _centerOnTree({bool animate = false}) { + if (_positions.isEmpty) { + if (animate) { + _runAnimation(Matrix4.identity()); + } else { + _transformationController.value = Matrix4.identity(); + } + return; + } + + var minX = double.infinity; + var maxX = double.negativeInfinity; + var minY = double.infinity; + var maxY = double.negativeInfinity; + + _positions.forEach((uuid, offset) { + final cardWidth = _cardWidths[uuid] ?? _minCardWidth; + minX = min(minX, offset.dx); + maxX = max(maxX, offset.dx + cardWidth); + minY = min(minY, offset.dy); + maxY = max(maxY, offset.dy + _cardHeight); + }); + + if (!minX.isFinite || !maxX.isFinite || !minY.isFinite || !maxY.isFinite) { + return; + } + + final treeWidth = maxX - minX; + final treeHeight = maxY - minY; + + final viewSize = context.size; + if (viewSize == null) return; + + final scaleX = viewSize.width / treeWidth; + final scaleY = viewSize.height / treeHeight; + final scale = min(scaleX, scaleY).clamp(0.5, 1.0) * 0.9; + + final treeCenterX = minX + treeWidth / 2; + final treeCenterY = minY + treeHeight / 2; + + final x = -treeCenterX * scale + viewSize.width / 2; + final y = -treeCenterY * scale + viewSize.height / 2; + + final matrix = Matrix4.identity() + ..translate(x, y) + ..scale(scale); + + if (animate) { + _runAnimation(matrix); + } else { + _transformationController.value = matrix; + } + } + void _onReorder(SpaceReorderDataModel data, int newIndex) { final newCommunity = widget.community.copyWith(); final children = data.parent?.children ?? newCommunity.spaces; From 77d6d822cbf3d0fa6b1ca99de8ca2b4b0eb9a0fe Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 24 Jul 2025 09:39:33 +0300 Subject: [PATCH 18/20] Refactor `SpaceSubSpacesDialog` and `SubSpacesInput` to integrate a shared `TextEditingController` for improved state management of subspace names. This change enhances the input handling and ensures proper disposal of the controller, promoting better resource management. --- .../widgets/space_sub_spaces_dialog.dart | 13 +++++++++++++ .../presentation/widgets/sub_spaces_input.dart | 9 ++++----- 2 files changed, 17 insertions(+), 5 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 587c9ea7..6b05ab8a 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 @@ -19,6 +19,7 @@ class SpaceSubSpacesDialog extends StatefulWidget { } class _SpaceSubSpacesDialogState extends State { + late final TextEditingController _subspaceNameController; late List _subspaces; bool get _hasDuplicateNames => @@ -29,6 +30,13 @@ class _SpaceSubSpacesDialogState extends State { void initState() { super.initState(); _subspaces = List.from(widget.subspaces); + _subspaceNameController = TextEditingController(); + } + + @override + void dispose() { + _subspaceNameController.dispose(); + super.dispose(); } void _handleSubspaceAdded(String name) { @@ -49,6 +57,10 @@ class _SpaceSubSpacesDialogState extends State { ); void _handleSave() { + final name = _subspaceNameController.text.trim(); + if (name.isNotEmpty) { + _handleSubspaceAdded(name); + } widget.onSave(_subspaces); Navigator.of(context).pop(); } @@ -65,6 +77,7 @@ class _SpaceSubSpacesDialogState extends State { subSpaces: _subspaces, onSubspaceAdded: _handleSubspaceAdded, onSubspaceDeleted: _handleSubspaceDeleted, + controller: _subspaceNameController, ), AnimatedSwitcher( duration: const Duration(milliseconds: 100), diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart index 591f741c..dac52f93 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart @@ -10,29 +10,28 @@ class SubSpacesInput extends StatefulWidget { required this.subSpaces, required this.onSubspaceAdded, required this.onSubspaceDeleted, + required this.controller, }); final List subSpaces; final void Function(String name) onSubspaceAdded; final void Function(String uuid) onSubspaceDeleted; + final TextEditingController controller; @override State createState() => _SubSpacesInputState(); } class _SubSpacesInputState extends State { - late final TextEditingController _subspaceNameController; late final FocusNode _focusNode; @override void initState() { super.initState(); - _subspaceNameController = TextEditingController(); _focusNode = FocusNode(); } @override void dispose() { - _subspaceNameController.dispose(); _focusNode.dispose(); super.dispose(); } @@ -81,7 +80,7 @@ class _SubSpacesInputState extends State { width: 200, child: TextField( focusNode: _focusNode, - controller: _subspaceNameController, + controller: widget.controller, decoration: InputDecoration( border: InputBorder.none, hintText: widget.subSpaces.isEmpty ? 'Please enter the name' : null, @@ -93,7 +92,7 @@ class _SubSpacesInputState extends State { final trimmedValue = value.trim(); if (trimmedValue.isNotEmpty) { widget.onSubspaceAdded(trimmedValue); - _subspaceNameController.clear(); + widget.controller.clear(); _focusNode.requestFocus(); } }, From e98b091253a25acb278eae5267466f34514a23f4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 24 Jul 2025 09:39:38 +0300 Subject: [PATCH 19/20] Refactor `SpaceManagementBody` to use a `Stack` layout for improved UI structure, allowing better positioning of the `SpaceManagementCommunitiesTree` and the main content. Enhance `SpaceManagementCommunitiesTree` with a shadow effect for better visual separation. This change promotes a more organized and visually appealing interface. --- .../widgets/space_management_body.dart | 29 +++++++++++-------- .../space_management_communities_tree.dart | 11 ++++++- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/space_management_body.dart b/lib/pages/space_management_v2/main_module/widgets/space_management_body.dart index 5d81bffb..6e43b4e7 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_management_body.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_management_body.dart @@ -10,21 +10,26 @@ class SpaceManagementBody extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( + return Stack( children: [ - const SpaceManagementCommunitiesTree(), - Expanded( - child: BlocBuilder( - buildWhen: (previous, current) => - previous.selectedCommunity != current.selectedCommunity, - builder: (context, state) => Visibility( - visible: state.selectedCommunity == null, - replacement: const SpaceManagementCommunityStructure(), - child: const SpaceManagementTemplatesView(), + Row( + children: [ + const SizedBox(width: 320), + Expanded( + child: BlocBuilder( + buildWhen: (previous, current) => + previous.selectedCommunity != current.selectedCommunity, + builder: (context, state) => Visibility( + visible: state.selectedCommunity == null, + replacement: const SpaceManagementCommunityStructure(), + child: const SpaceManagementTemplatesView(), + ), + ), ), - ), + ], ), + const SpaceManagementCommunitiesTree(), ], ); } diff --git a/lib/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree.dart b/lib/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree.dart index 1adf9911..d986ef01 100644 --- a/lib/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree.dart +++ b/lib/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree.dart @@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree_community_tile.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_communities_list.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_header.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/style.dart'; class SpaceManagementCommunitiesTree extends StatefulWidget { @@ -44,7 +45,15 @@ class _SpaceManagementCommunitiesTreeState return BlocBuilder( builder: (context, state) => Container( width: 320, - decoration: subSectionContainerDecoration, + decoration: subSectionContainerDecoration.copyWith( + boxShadow: [ + BoxShadow( + color: ColorsManager.shadowBlackColor.withValues(alpha: 0.1), + blurRadius: 10, + offset: const Offset(10, 0), + ), + ], + ), child: Column( children: [ const SpaceManagementSidebarHeader(), From f341dcd48242dd64687f8e2412fe5161d7718e8f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 24 Jul 2025 09:47:39 +0300 Subject: [PATCH 20/20] Remove unnecessary event dispatch in `CommunityStructureHeaderActionButtonsComposer` to streamline community update logic. This change enhances code clarity by eliminating the selection event for the community, focusing solely on the update action. --- .../community_structure_header_action_buttons_composer.dart | 3 --- 1 file changed, 3 deletions(-) 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 ef1ceac4..b2a41274 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 @@ -58,9 +58,6 @@ class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget { context.read().add( CommunitiesUpdateCommunity(updatedCommunity), ); - context.read().add( - SelectCommunityEvent(community: updatedCommunity), - ); }, ), ),