From e0980b324c778815825fd7a9790b173085bef733 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 10:54:07 +0300 Subject: [PATCH 01/35] Add DeleteSpaceParam and DeleteSpaceService for space deletion functionality --- .../delete_space/domain/params/delete_space_param.dart | 9 +++++++++ .../domain/services/delete_space_service.dart | 5 +++++ 2 files changed, 14 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart create mode 100644 lib/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart diff --git a/lib/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart b/lib/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart new file mode 100644 index 00000000..d6781876 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart @@ -0,0 +1,9 @@ +class DeleteSpaceParam { + const DeleteSpaceParam({ + required this.spaceUuid, + required this.communityUuid, + }); + + final String spaceUuid; + final String communityUuid; +} diff --git a/lib/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart b/lib/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart new file mode 100644 index 00000000..a537645c --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart @@ -0,0 +1,5 @@ +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart'; + +abstract interface class DeleteSpaceService { + Future delete(DeleteSpaceParam param); +} From 3cd01253109e25219155b261223006210dfae82a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 10:54:13 +0300 Subject: [PATCH 02/35] Refactor space deletion: Introduce DeleteSpaceParam and DeleteSpaceService for enhanced space management functionality --- .../data/remote_delete_space_service.dart | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart diff --git a/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart b/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart new file mode 100644 index 00000000..48d65934 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart @@ -0,0 +1,63 @@ +import 'package:dio/dio.dart'; +import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart'; +import 'package:syncrow_web/services/api/api_exception.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; + +final class RemoteDeleteSpaceService implements DeleteSpaceService { + RemoteDeleteSpaceService({ + required this.httpService, + }); + + final HTTPService httpService; + + @override + Future delete(DeleteSpaceParam param) async { + try { + await httpService.delete( + path: await _makeUrl(param), + expectedResponseModel: (json) { + final response = json as Map; + final data = response['data'] as Map?; + final hasSuccessfullyDeletedSpace = data?['success'] as bool? ?? false; + + if (!hasSuccessfullyDeletedSpace) { + throw APIException('Failed to delete space'); + } + + return hasSuccessfullyDeletedSpace; + }, + ); + } on DioException catch (e) { + final message = e.response?.data as Map?; + throw APIException(_getErrorMessageFromBody(message)); + } catch (e) { + final formattedErrorMessage = ['Failed to delete space', '$e'].join(': '); + throw APIException(formattedErrorMessage); + } + } + + String _getErrorMessageFromBody(Map? body) { + if (body == null) return 'Failed to delete space'; + final error = body['error'] as Map?; + final errorMessage = error?['message'] as String? ?? ''; + return errorMessage; + } + + Future _makeUrl(DeleteSpaceParam 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 '/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}'; + } +} From 5663e2084eb752b17229fa1701d56c0f26518d7d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 10:57:34 +0300 Subject: [PATCH 03/35] Created `DeleteSpaceBloc`. --- .../presentation/bloc/delete_space_bloc.dart | 31 +++++++++++++++++++ .../presentation/bloc/delete_space_event.dart | 17 ++++++++++ .../presentation/bloc/delete_space_state.dart | 30 ++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_event.dart create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_state.dart diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart new file mode 100644 index 00000000..f515fc09 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart @@ -0,0 +1,31 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart'; +import 'package:syncrow_web/services/api/api_exception.dart'; + +part 'delete_space_event.dart'; +part 'delete_space_state.dart'; + +class DeleteSpaceBloc extends Bloc { + DeleteSpaceBloc(this._deleteSpaceService) : super(DeleteSpaceInitial()) { + on(_onDeleteSpace); + } + + final DeleteSpaceService _deleteSpaceService; + + Future _onDeleteSpace( + DeleteSpace event, + Emitter emit + ) async { + emit(DeleteSpaceLoading()); + try { + await _deleteSpaceService.delete(event.param); + emit(const DeleteSpaceSuccess('Space deleted successfully')); + } on APIException catch (e) { + emit(DeleteSpaceFailure(e.message)); + } catch (e) { + emit(const DeleteSpaceFailure('Failed to delete space')); + } + } +} diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_event.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_event.dart new file mode 100644 index 00000000..c80346e8 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_event.dart @@ -0,0 +1,17 @@ +part of 'delete_space_bloc.dart'; + +sealed class DeleteSpaceEvent extends Equatable { + const DeleteSpaceEvent(); + + @override + List get props => []; +} + +final class DeleteSpace extends DeleteSpaceEvent { + const DeleteSpace(this.param); + + final DeleteSpaceParam param; + + @override + List get props => [param]; +} diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_state.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_state.dart new file mode 100644 index 00000000..96b6d5b7 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_state.dart @@ -0,0 +1,30 @@ +part of 'delete_space_bloc.dart'; + +sealed class DeleteSpaceState extends Equatable { + const DeleteSpaceState(); + + @override + List get props => []; +} + +final class DeleteSpaceInitial extends DeleteSpaceState {} + +final class DeleteSpaceLoading extends DeleteSpaceState {} + +final class DeleteSpaceSuccess extends DeleteSpaceState { + const DeleteSpaceSuccess(this.successMessage); + + final String successMessage; + + @override + List get props => [successMessage]; +} + +final class DeleteSpaceFailure extends DeleteSpaceState { + const DeleteSpaceFailure(this.errorMessage); + + final String errorMessage; + + @override + List get props => [errorMessage]; +} From cf1b34ee0ac4e5d1d552f9c135bbd282d16a4d34 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 12:17:07 +0300 Subject: [PATCH 04/35] Add x_delete icon asset. --- assets/icons/x_delete.svg | 5 +++++ lib/utils/constants/assets.dart | 1 + 2 files changed, 6 insertions(+) create mode 100644 assets/icons/x_delete.svg diff --git a/assets/icons/x_delete.svg b/assets/icons/x_delete.svg new file mode 100644 index 00000000..637f2e72 --- /dev/null +++ b/assets/icons/x_delete.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index f92975f3..b7ad15b5 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -517,4 +517,5 @@ class Assets { static const String emptyRangeOfAqi = 'assets/icons/empty_range_of_aqi.svg'; static const String homeIcon = 'assets/icons/home_icon.svg'; static const String groupIcon = 'assets/icons/group_icon.svg'; + static const String xDelete = 'assets/icons/x_delete.svg'; } From 035c03c6b2b0239844178b55e6b7420fa6ab5ae4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 14:17:01 +0300 Subject: [PATCH 05/35] Fix error handling in DeleteSpaceBloc: update failure message to include exception details --- .../delete_space/presentation/bloc/delete_space_bloc.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart index f515fc09..6334bb33 100644 --- a/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart @@ -16,7 +16,7 @@ class DeleteSpaceBloc extends Bloc { Future _onDeleteSpace( DeleteSpace event, - Emitter emit + Emitter emit, ) async { emit(DeleteSpaceLoading()); try { @@ -25,7 +25,7 @@ class DeleteSpaceBloc extends Bloc { } on APIException catch (e) { emit(DeleteSpaceFailure(e.message)); } catch (e) { - emit(const DeleteSpaceFailure('Failed to delete space')); + emit(DeleteSpaceFailure(e.toString())); } } } From 086f3cedf824f77af40eef6f828984cdcb6fd622 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 14:30:17 +0300 Subject: [PATCH 06/35] Refactor RemoteDeleteSpaceService: Simplify success response handling and improve error message formatting --- .../delete_space/data/remote_delete_space_service.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart b/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart index 48d65934..74724644 100644 --- a/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart +++ b/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart @@ -19,8 +19,7 @@ final class RemoteDeleteSpaceService implements DeleteSpaceService { path: await _makeUrl(param), expectedResponseModel: (json) { final response = json as Map; - final data = response['data'] as Map?; - final hasSuccessfullyDeletedSpace = data?['success'] as bool? ?? false; + final hasSuccessfullyDeletedSpace = response['success'] as bool? ?? false; if (!hasSuccessfullyDeletedSpace) { throw APIException('Failed to delete space'); @@ -33,8 +32,7 @@ final class RemoteDeleteSpaceService implements DeleteSpaceService { final message = e.response?.data as Map?; throw APIException(_getErrorMessageFromBody(message)); } catch (e) { - final formattedErrorMessage = ['Failed to delete space', '$e'].join(': '); - throw APIException(formattedErrorMessage); + throw APIException(e.toString()); } } From f4b5c6fb52917d4fabf1ee7d3483c2c72ea09e42 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 14:44:08 +0300 Subject: [PATCH 07/35] Implement delete space functionality in CommunityStructureHeader: Integrate DeleteSpaceDialog for space deletion confirmation and update routing for space management page. --- .../widgets/community_structure_header.dart | 15 ++- .../widgets/delete_space_dialog.dart | 118 ++++++++++++++++++ .../widgets/delete_space_dialog_form.dart | 106 ++++++++++++++++ 3 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart index 4f71075b..b8380bb7 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart @@ -4,6 +4,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.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/space_details/presentation/helpers/space_details_dialog_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -91,7 +92,19 @@ class CommunityStructureHeader extends StatelessWidget { ), const SizedBox(width: 8), CommunityStructureHeaderActionButtons( - onDelete: (space) {}, + onDelete: (space) => showDialog( + context: context, + barrierDismissible: false, + builder: (_) => DeleteSpaceDialog( + space: space, + community: selectedCommunity, + onSuccess: () { + context.read().add( + SelectCommunityEvent(community: selectedCommunity), + ); + }, + ), + ), onDuplicate: (space) {}, onEdit: (space) { SpaceDetailsDialogHelper.showEdit( diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart new file mode 100644 index 00000000..c8356e32 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/delete_space/data/remote_delete_space_service.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class DeleteSpaceDialog extends StatelessWidget { + const DeleteSpaceDialog({ + required this.space, + required this.community, + required this.onSuccess, + super.key, + }); + + final SpaceModel space; + final CommunityModel community; + final void Function() onSuccess; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => DeleteSpaceBloc( + RemoteDeleteSpaceService(httpService: HTTPService()), + ), + child: Builder( + builder: (context) => Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Container( + padding: const EdgeInsetsDirectional.all(32), + constraints: BoxConstraints( + maxWidth: context.screenWidth * 0.2, + ), + child: BlocConsumer( + listener: (context, state) { + if (state case DeleteSpaceSuccess()) onSuccess(); + }, + builder: (context, state) => switch (state) { + DeleteSpaceInitial() => DeleteSpaceDialogForm( + space: space, + communityUuid: community.uuid, + ), + DeleteSpaceLoading() => const DeleteSpaceLoadingWidget(), + DeleteSpaceSuccess(:final successMessage) => DeleteSpaceStatusWidget( + message: successMessage, + icon: const Icon( + Icons.check_circle, + size: 92, + color: ColorsManager.goodGreen, + ), + ), + DeleteSpaceFailure() => DeleteSpaceStatusWidget( + message: state.errorMessage, + icon: const Icon( + Icons.error, + size: 92, + color: ColorsManager.red, + ), + ), + }, + ), + ), + ), + ), + ); + } +} + +class DeleteSpaceLoadingWidget extends StatelessWidget { + const DeleteSpaceLoadingWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const SizedBox.square( + dimension: 32, + child: Center(child: CircularProgressIndicator()), + ); + } +} + +class DeleteSpaceStatusWidget extends StatelessWidget { + const DeleteSpaceStatusWidget({ + required this.message, + required this.icon, + super.key, + }); + + final String message; + final Widget icon; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + spacing: 16, + children: [ + icon, + SelectableText( + message, + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.blackColor, + fontSize: 22, + ), + textAlign: TextAlign.center, + ), + FilledButton( + onPressed: Navigator.of(context).pop, + child: const Text('Close'), + ), + ], + ); + } +} diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart new file mode 100644 index 00000000..055b67b8 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.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/delete_space/domain/params/delete_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class DeleteSpaceDialogForm extends StatelessWidget { + const DeleteSpaceDialogForm({ + required this.space, + required this.communityUuid, + super.key, + }); + + final SpaceModel space; + final String communityUuid; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset(Assets.xDelete, width: 36, height: 36), + const SizedBox(height: 16), + SelectableText( + 'Delete Space', + textAlign: TextAlign.center, + style: context.textTheme.titleLarge?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 24, + ), + ), + const SizedBox(height: 8), + SelectableText( + 'Are you sure you want to delete this space? This action is irreversible', + textAlign: TextAlign.center, + style: context.textTheme.bodyLarge?.copyWith( + color: ColorsManager.lightGreyColor, + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + const SizedBox(height: 24), + Row( + children: [ + Expanded( + child: FilledButton( + style: _buildButtonStyle( + context, + color: ColorsManager.grey25, + textColor: ColorsManager.blackColor, + ), + onPressed: Navigator.of(context).pop, + child: const Text('Cancel'), + ), + ), + const SizedBox(width: 16), + Expanded( + child: FilledButton( + style: _buildButtonStyle( + context, + color: ColorsManager.semiTransparentRed, + textColor: ColorsManager.whiteColors, + ), + onPressed: () { + context.read().add( + DeleteSpace( + DeleteSpaceParam( + spaceUuid: space.uuid, + communityUuid: communityUuid, + ), + ), + ); + }, + child: const Text('Delete'), + ), + ), + ], + ), + ], + ); + } + + ButtonStyle _buildButtonStyle( + BuildContext context, { + required Color color, + required Color textColor, + }) { + return FilledButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), + backgroundColor: color, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + foregroundColor: textColor, + textStyle: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ); + } +} From beb33e37fa10b72cc546a3343db9f8cd3190dafe Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 14:59:34 +0300 Subject: [PATCH 08/35] Add SpacesRecursiveHelper for recursive space updates and deletions; refactor CommunityStructureHeader to use CommunityStructureHeaderActionButtonsComposer for improved action handling. --- .../helpers/spaces_recursive_helper.dart | 41 +++++++++++ .../widgets/community_structure_header.dart | 73 ++----------------- ...ucture_header_action_buttons_composer.dart | 69 ++++++++++++++++++ 3 files changed, 118 insertions(+), 65 deletions(-) create mode 100644 lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart create mode 100644 lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.dart 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 new file mode 100644 index 00000000..e751e8d8 --- /dev/null +++ b/lib/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart @@ -0,0 +1,41 @@ +import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; + +abstract final class SpacesRecursiveHelper { + static List recusrivelyUpdate( + List spaces, + SpaceDetailsModel updatedSpace, + ) { + return spaces.map((space) { + if (space.uuid == updatedSpace.uuid) { + return space.copyWith( + spaceName: updatedSpace.spaceName, + icon: updatedSpace.icon, + ); + } + if (space.children.isNotEmpty) { + return space.copyWith( + children: recusrivelyUpdate(space.children, updatedSpace), + ); + } + return space; + }).toList(); + } + + static List recusrivelyDelete( + List spaces, + String spaceUuid, + ) { + final s = spaces.map((space) { + if (space.uuid == spaceUuid) return null; + if (space.children.isNotEmpty) { + return space.copyWith( + children: recusrivelyDelete(space.children, spaceUuid), + ); + } + return space; + }).toList(); + + return s.whereType().toList(); + } +} diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart index de148ecd..432b3ce4 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart @@ -2,42 +2,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart'; -import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.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/communities/presentation/bloc/communities_bloc.dart'; +import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.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/space_details/domain/models/space_details_model.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class CommunityStructureHeader extends StatelessWidget { const CommunityStructureHeader({super.key}); - List _updateRecursive( - List spaces, - SpaceDetailsModel updatedSpace, - ) { - return spaces.map((space) { - if (space.uuid == updatedSpace.uuid) { - return space.copyWith( - spaceName: updatedSpace.spaceName, - icon: updatedSpace.icon, - ); - } - if (space.children.isNotEmpty) { - return space.copyWith( - children: _updateRecursive(space.children, updatedSpace), - ); - } - return space; - }).toList(); - } - @override Widget build(BuildContext context) { - final theme = Theme.of(context); final screenWidth = MediaQuery.of(context).size.width; return Container( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), @@ -58,7 +33,7 @@ class CommunityStructureHeader extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: _buildCommunityInfo(context, theme, screenWidth), + child: _buildCommunityInfo(context, screenWidth), ), const SizedBox(width: 16), ], @@ -68,8 +43,7 @@ class CommunityStructureHeader extends StatelessWidget { ); } - Widget _buildCommunityInfo( - BuildContext context, ThemeData theme, double screenWidth) { + Widget _buildCommunityInfo(BuildContext context, double screenWidth) { final selectedCommunity = context.watch().state.selectedCommunity; final selectedSpace = @@ -79,7 +53,7 @@ class CommunityStructureHeader extends StatelessWidget { children: [ Text( 'Community Structure', - style: theme.textTheme.headlineLarge?.copyWith( + style: context.textTheme.headlineLarge?.copyWith( color: ColorsManager.blackColor, ), ), @@ -92,7 +66,7 @@ class CommunityStructureHeader extends StatelessWidget { Flexible( child: SelectableText( selectedCommunity.name, - style: theme.textTheme.bodyLarge?.copyWith( + style: context.textTheme.bodyLarge?.copyWith( color: ColorsManager.blackColor, ), maxLines: 1, @@ -116,39 +90,8 @@ class CommunityStructureHeader extends StatelessWidget { ), ), const SizedBox(width: 8), - CommunityStructureHeaderActionButtons( - onDelete: (space) => showDialog( - context: context, - barrierDismissible: false, - builder: (_) => DeleteSpaceDialog( - space: space, - community: selectedCommunity, - onSuccess: () { - context.read().add( - SelectCommunityEvent(community: selectedCommunity), - ); - }, - ), - ), - onDuplicate: (space) {}, - onEdit: (space) => SpaceDetailsDialogHelper.showEdit( - context, - spaceModel: selectedSpace!, - communityUuid: selectedCommunity.uuid, - onSuccess: (updatedSpaceDetails) { - final communitiesBloc = context.read(); - final updatedSpaces = _updateRecursive( - selectedCommunity.spaces, - updatedSpaceDetails, - ); - - final community = selectedCommunity.copyWith( - spaces: updatedSpaces, - ); - - communitiesBloc.add(CommunitiesUpdateCommunity(community)); - }, - ), + CommunityStructureHeaderActionButtonsComposer( + selectedCommunity: selectedCommunity, selectedSpace: selectedSpace, ), ], 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 new file mode 100644 index 00000000..d7403588 --- /dev/null +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.dart @@ -0,0 +1,69 @@ +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'; +import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.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/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/space_details/presentation/helpers/space_details_dialog_helper.dart'; + +class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget { + const CommunityStructureHeaderActionButtonsComposer({ + required this.selectedCommunity, + required this.selectedSpace, + super.key, + }); + final CommunityModel selectedCommunity; + final SpaceModel? selectedSpace; + + @override + Widget build(BuildContext context) { + return CommunityStructureHeaderActionButtons( + onDelete: (space) => showDialog( + context: context, + barrierDismissible: false, + builder: (_) => DeleteSpaceDialog( + space: space, + community: selectedCommunity, + onSuccess: () { + final updatedSpaces = SpacesRecursiveHelper.recusrivelyDelete( + selectedCommunity.spaces, + space.uuid, + ); + final community = selectedCommunity.copyWith( + spaces: updatedSpaces, + ); + context.read().add( + CommunitiesUpdateCommunity(community), + ); + context.read().add( + SelectCommunityEvent(community: selectedCommunity), + ); + }, + ), + ), + onDuplicate: (space) {}, + onEdit: (space) => SpaceDetailsDialogHelper.showEdit( + context, + spaceModel: selectedSpace!, + communityUuid: selectedCommunity.uuid, + onSuccess: (updatedSpaceDetails) { + final communitiesBloc = context.read(); + final updatedSpaces = SpacesRecursiveHelper.recusrivelyUpdate( + selectedCommunity.spaces, + updatedSpaceDetails, + ); + + final community = selectedCommunity.copyWith( + spaces: updatedSpaces, + ); + + communitiesBloc.add(CommunitiesUpdateCommunity(community)); + }, + ), + selectedSpace: selectedSpace, + ); + } +} From ab6a6851f2b732e38b71e6441f50ea308b15b62b Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 15:23:01 +0300 Subject: [PATCH 09/35] Update control points in SpacesConnectionsArrowPainter for smoother arrow rendering --- .../painters/spaces_connections_arrow_painter.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart b/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart index e9fa0a15..5200cfff 100644 --- a/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart +++ b/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart @@ -37,8 +37,8 @@ class SpacesConnectionsArrowPainter extends CustomPainter { final path = Path()..moveTo(startPoint.dx, startPoint.dy); - final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 20); - final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 60); + final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 100); + final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 100); path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx, controlPoint2.dy, endPoint.dx, endPoint.dy); From 52186417058a846e265242e823c5d1c1d63d8942 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 16:05:44 +0300 Subject: [PATCH 10/35] Refactor didUpdateWidget in CommunityStructureCanvas to ensure proper widget lifecycle management --- .../main_module/widgets/community_structure_canvas.dart | 2 +- 1 file changed, 1 insertion(+), 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 3cf761ad..67ccf380 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 @@ -51,7 +51,6 @@ class _CommunityStructureCanvasState extends State @override void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) { - super.didUpdateWidget(oldWidget); if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { @@ -59,6 +58,7 @@ class _CommunityStructureCanvasState extends State } }); } + super.didUpdateWidget(oldWidget); } @override From de5d8df01c184b36866d18e27e0df651687eb330 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 16:19:57 +0300 Subject: [PATCH 11/35] Update SpaceCell widget shadow properties for improved visual appearance --- .../space_management_v2/main_module/widgets/space_cell.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/space_cell.dart b/lib/pages/space_management_v2/main_module/widgets/space_cell.dart index 80b18526..1f7bd808 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_cell.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_cell.dart @@ -74,9 +74,8 @@ class SpaceCell extends StatelessWidget { borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( - color: ColorsManager.lightGrayColor.withValues(alpha: 0.5), - spreadRadius: 2, - blurRadius: 5, + color: ColorsManager.lightGrayColor.withValues(alpha: 0.25), + blurRadius: 7, offset: const Offset(0, 3), ), ], From 466f5b89c7ac621f216d1d9ded017b9a97550af9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 16:56:51 +0300 Subject: [PATCH 12/35] Enhanced SpacesConnectionsArrowPainter and CommunityStructureCanvas to support dynamic card widths; enhance SpaceCell widget layout and shadow properties for improved UI consistency. --- .../spaces_connections_arrow_painter.dart | 30 +++++--- .../widgets/community_structure_canvas.dart | 68 ++++++++++++++----- .../main_module/widgets/space_cell.dart | 23 +++---- 3 files changed, 83 insertions(+), 38 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart b/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart index 5200cfff..ed797c74 100644 --- a/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart +++ b/lib/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart @@ -5,13 +5,14 @@ import 'package:syncrow_web/utils/color_manager.dart'; class SpacesConnectionsArrowPainter extends CustomPainter { final List connections; final Map positions; - final double cardWidth = 150.0; + final Map cardWidths; final double cardHeight = 90.0; final Set highlightedUuids; SpacesConnectionsArrowPainter({ required this.connections, required this.positions, + required this.cardWidths, required this.highlightedUuids, }); @@ -29,19 +30,30 @@ class SpacesConnectionsArrowPainter extends CustomPainter { final from = positions[connection.from]; final to = positions[connection.to]; + final fromWidth = cardWidths[connection.from] ?? 150.0; + final toWidth = cardWidths[connection.to] ?? 150.0; if (from != null && to != null) { final startPoint = - Offset(from.dx + cardWidth / 2, from.dy + cardHeight - 10); - final endPoint = Offset(to.dx + cardWidth / 2, to.dy); + Offset(from.dx + fromWidth / 2, from.dy + cardHeight - 10); + final endPoint = Offset(to.dx + toWidth / 2, to.dy); final path = Path()..moveTo(startPoint.dx, startPoint.dy); - final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 100); - final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 100); - - path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx, - controlPoint2.dy, endPoint.dx, endPoint.dy); + if ((startPoint.dx - endPoint.dx).abs() < 1.0) { + path.lineTo(endPoint.dx, endPoint.dy); + } else { + final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 100); + final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 100); + path.cubicTo( + controlPoint1.dx, + controlPoint1.dy, + controlPoint2.dx, + controlPoint2.dy, + endPoint.dx, + endPoint.dy, + ); + } canvas.drawPath(path, paint); @@ -51,7 +63,7 @@ class SpacesConnectionsArrowPainter extends CustomPainter { : ColorsManager.blackColor.withValues(alpha: 0.5) ..style = PaintingStyle.fill ..blendMode = BlendMode.srcIn; - canvas.drawCircle(endPoint, 4, circlePaint); + canvas.drawCircle(endPoint, 6, circlePaint); } } } 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 67ccf380..65c0b95f 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 @@ -30,10 +30,11 @@ class CommunityStructureCanvas extends StatefulWidget { class _CommunityStructureCanvasState extends State with SingleTickerProviderStateMixin { final Map _positions = {}; - final double _cardWidth = 150.0; + final Map _cardWidths = {}; final double _cardHeight = 90.0; final double _horizontalSpacing = 150.0; final double _verticalSpacing = 120.0; + static const double _minCardWidth = 150.0; late final TransformationController _transformationController; late final AnimationController _animationController; @@ -51,6 +52,7 @@ class _CommunityStructureCanvasState extends State @override void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) { + super.didUpdateWidget(oldWidget); if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { @@ -58,7 +60,6 @@ class _CommunityStructureCanvasState extends State } }); } - super.didUpdateWidget(oldWidget); } @override @@ -68,6 +69,34 @@ class _CommunityStructureCanvasState extends State super.dispose(); } + double _calculateCardWidth(String text) { + final style = context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.bold, + ); + final textPainter = TextPainter( + text: TextSpan(text: text, style: style), + maxLines: 1, + textDirection: TextDirection.ltr, + )..layout(); + + const iconWidth = 40.0; + const horizontalPadding = 10.0; + const contentPadding = 10.0; + final calculatedWidth = + iconWidth + horizontalPadding + textPainter.width + contentPadding; + + return calculatedWidth.clamp(_minCardWidth, double.infinity); + } + + void _calculateAllCardWidths(List spaces) { + for (final space in spaces) { + _cardWidths[space.uuid] = _calculateCardWidth(space.spaceName); + if (space.children.isNotEmpty) { + _calculateAllCardWidths(space.children); + } + } + } + Set _getAllDescendantUuids(SpaceModel space) { final uuids = {}; for (final child in space.children) { @@ -102,11 +131,12 @@ class _CommunityStructureCanvasState extends State final position = _positions[space.uuid]; if (position == null) return; + final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth; const scale = 1; final viewSize = context.size; if (viewSize == null) return; - final x = -position.dx * scale + (viewSize.width / 2) - (_cardWidth * scale / 2); + final x = -position.dx * scale + (viewSize.width / 2) - (cardWidth * scale / 2); final y = -position.dy * scale + (viewSize.height / 2) - (_cardHeight * scale / 2); @@ -155,13 +185,16 @@ class _CommunityStructureCanvasState extends State Map levelXOffset, ) { for (final space in spaces) { + final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth; double childSubtreeWidth = 0; if (space.children.isNotEmpty) { _calculateLayout(space.children, depth + 1, levelXOffset); final firstChildPos = _positions[space.children.first.uuid]; final lastChildPos = _positions[space.children.last.uuid]; if (firstChildPos != null && lastChildPos != null) { - childSubtreeWidth = (lastChildPos.dx + _cardWidth) - firstChildPos.dx; + final lastChildWidth = + _cardWidths[space.children.last.uuid] ?? _minCardWidth; + childSubtreeWidth = (lastChildPos.dx + lastChildWidth) - firstChildPos.dx; } } @@ -170,7 +203,7 @@ class _CommunityStructureCanvasState extends State if (space.children.isNotEmpty) { final firstChildPos = _positions[space.children.first.uuid]!; - x = firstChildPos.dx + (childSubtreeWidth - _cardWidth) / 2; + x = firstChildPos.dx + (childSubtreeWidth - cardWidth) / 2; } else { x = currentX; } @@ -187,7 +220,7 @@ class _CommunityStructureCanvasState extends State final y = depth * (_verticalSpacing + _cardHeight); _positions[space.uuid] = Offset(x, y); - levelXOffset[depth] = x + _cardWidth + _horizontalSpacing; + levelXOffset[depth] = x + cardWidth + _horizontalSpacing; } } @@ -202,8 +235,11 @@ class _CommunityStructureCanvasState extends State List _buildTreeWidgets() { _positions.clear(); + _cardWidths.clear(); final community = widget.community; + _calculateAllCardWidths(community.spaces); + final levelXOffset = {}; _calculateLayout(community.spaces, 0, levelXOffset); @@ -240,6 +276,7 @@ class _CommunityStructureCanvasState extends State painter: SpacesConnectionsArrowPainter( connections: connections, positions: _positions, + cardWidths: _cardWidths, highlightedUuids: highlightedUuids, ), child: Stack(alignment: AlignmentDirectional.center, children: widgets), @@ -271,6 +308,7 @@ class _CommunityStructureCanvasState extends State continue; } + final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth; final isHighlighted = highlightedUuids.contains(space.uuid); final hasNoSelectedSpace = widget.selectedSpace == null; @@ -278,14 +316,10 @@ class _CommunityStructureCanvasState extends State buildSpaceContainer: () { return Opacity( opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5, - child: Tooltip( - message: space.spaceName, - preferBelow: false, - child: SpaceCell( - onTap: () => _onSpaceTapped(space), - icon: space.icon, - name: space.spaceName, - ), + child: SpaceCell( + onTap: () => _onSpaceTapped(space), + icon: space.icon, + name: space.spaceName, ), ); }, @@ -305,7 +339,7 @@ class _CommunityStructureCanvasState extends State Positioned( left: position.dx, top: position.dy, - width: _cardWidth, + width: cardWidth, height: _cardHeight, child: Draggable( data: reorderData, @@ -314,7 +348,7 @@ class _CommunityStructureCanvasState extends State child: Opacity( opacity: 0.2, child: SizedBox( - width: _cardWidth, + width: cardWidth, height: _cardHeight, child: spaceCard, ), @@ -330,7 +364,7 @@ class _CommunityStructureCanvasState extends State ); final targetPos = Offset( - position.dx + _cardWidth + (_horizontalSpacing / 4) - 20, + position.dx + cardWidth + (_horizontalSpacing / 4) - 20, position.dy, ); widgets.add(_buildDropTarget(parent, community, i + 1, targetPos)); diff --git a/lib/pages/space_management_v2/main_module/widgets/space_cell.dart b/lib/pages/space_management_v2/main_module/widgets/space_cell.dart index 1f7bd808..3eb6d5df 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_cell.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_cell.dart @@ -20,21 +20,19 @@ class SpaceCell extends StatelessWidget { return InkWell( onTap: onTap, child: Container( - width: 150, + padding: const EdgeInsetsDirectional.only(end: 10), height: 70, decoration: _containerDecoration(), child: Row( + spacing: 10, + mainAxisSize: MainAxisSize.min, children: [ _buildIconContainer(), - const SizedBox(width: 10), - Expanded( - child: Text( - name, - style: context.textTheme.bodyLarge?.copyWith( - fontWeight: FontWeight.bold, - color: ColorsManager.blackColor, - ), - overflow: TextOverflow.ellipsis, + Text( + name, + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.blackColor, ), ), ], @@ -74,8 +72,9 @@ class SpaceCell extends StatelessWidget { borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( - color: ColorsManager.lightGrayColor.withValues(alpha: 0.25), - blurRadius: 7, + color: ColorsManager.lightGrayColor.withValues(alpha: 0.5), + spreadRadius: 2, + blurRadius: 5, offset: const Offset(0, 3), ), ], From b2231949506223f69333ff702c57422d067bc6a3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 10:07:12 +0300 Subject: [PATCH 13/35] Add loading and status widgets for delete space dialog; refactor dialog to utilize new components for improved user feedback during space deletion process. --- .../widgets/delete_space_dialog.dart | 53 ++----------------- .../widgets/delete_space_loading_widget.dart | 13 +++++ .../widgets/delete_space_status_widget.dart | 38 +++++++++++++ 3 files changed, 55 insertions(+), 49 deletions(-) create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart create mode 100644 lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart index c8356e32..b8734eee 100644 --- a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart @@ -5,6 +5,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -45,8 +47,8 @@ class DeleteSpaceDialog extends StatelessWidget { communityUuid: community.uuid, ), DeleteSpaceLoading() => const DeleteSpaceLoadingWidget(), - DeleteSpaceSuccess(:final successMessage) => DeleteSpaceStatusWidget( - message: successMessage, + DeleteSpaceSuccess() => DeleteSpaceStatusWidget( + message: state.successMessage, icon: const Icon( Icons.check_circle, size: 92, @@ -69,50 +71,3 @@ class DeleteSpaceDialog extends StatelessWidget { ); } } - -class DeleteSpaceLoadingWidget extends StatelessWidget { - const DeleteSpaceLoadingWidget({super.key}); - - @override - Widget build(BuildContext context) { - return const SizedBox.square( - dimension: 32, - child: Center(child: CircularProgressIndicator()), - ); - } -} - -class DeleteSpaceStatusWidget extends StatelessWidget { - const DeleteSpaceStatusWidget({ - required this.message, - required this.icon, - super.key, - }); - - final String message; - final Widget icon; - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - spacing: 16, - children: [ - icon, - SelectableText( - message, - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.blackColor, - fontSize: 22, - ), - textAlign: TextAlign.center, - ), - FilledButton( - onPressed: Navigator.of(context).pop, - child: const Text('Close'), - ), - ], - ); - } -} diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart new file mode 100644 index 00000000..b658b3b3 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class DeleteSpaceLoadingWidget extends StatelessWidget { + const DeleteSpaceLoadingWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const SizedBox.square( + dimension: 32, + child: Center(child: CircularProgressIndicator()), + ); + } +} diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart new file mode 100644 index 00000000..d597a451 --- /dev/null +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class DeleteSpaceStatusWidget extends StatelessWidget { + const DeleteSpaceStatusWidget({ + required this.message, + required this.icon, + super.key, + }); + + final String message; + final Widget icon; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + spacing: 16, + children: [ + icon, + SelectableText( + message, + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.blackColor, + fontSize: 22, + ), + textAlign: TextAlign.center, + ), + FilledButton( + onPressed: Navigator.of(context).pop, + child: const Text('Close'), + ), + ], + ); + } +} From acefe7b35510d9db30fc5c6c9f1aa5899beef61e Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 11:09:04 +0300 Subject: [PATCH 14/35] Refactor RemoteDeleteSpaceService to use a private HTTPService instance and update URL construction with ApiEndpoints for improved maintainability. Update DeleteSpaceDialog to reflect changes in service initialization. --- .../data/remote_delete_space_service.dart | 15 +++++++++------ .../presentation/widgets/delete_space_dialog.dart | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart b/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart index 74724644..5320f625 100644 --- a/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart +++ b/lib/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart @@ -4,18 +4,17 @@ import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domai import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/services/delete_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 RemoteDeleteSpaceService implements DeleteSpaceService { - RemoteDeleteSpaceService({ - required this.httpService, - }); + const RemoteDeleteSpaceService(this._httpService); - final HTTPService httpService; + final HTTPService _httpService; @override Future delete(DeleteSpaceParam param) async { try { - await httpService.delete( + await _httpService.delete( path: await _makeUrl(param), expectedResponseModel: (json) { final response = json as Map; @@ -56,6 +55,10 @@ final class RemoteDeleteSpaceService implements DeleteSpaceService { if (param.spaceUuid.isEmpty) { throw APIException('Space UUID is not set'); } - return '/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}'; + + return ApiEndpoints.deleteSpace + .replaceAll('{projectId}', projectUuid) + .replaceAll('{communityId}', param.communityUuid) + .replaceAll('{spaceId}', param.spaceUuid); } } diff --git a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart index b8734eee..f2ddf24a 100644 --- a/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart +++ b/lib/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart @@ -27,7 +27,7 @@ class DeleteSpaceDialog extends StatelessWidget { Widget build(BuildContext context) { return BlocProvider( create: (context) => DeleteSpaceBloc( - RemoteDeleteSpaceService(httpService: HTTPService()), + RemoteDeleteSpaceService(HTTPService()), ), child: Builder( builder: (context) => Dialog( From fa930571dc45cfa97ad3493fd911732d186f3581 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 12:27:45 +0300 Subject: [PATCH 15/35] Ensure proper handling of null selectedSpace in CommunityStructureCanvas during widget updates to prevent unnecessary processing. --- .../main_module/widgets/community_structure_canvas.dart | 1 + 1 file changed, 1 insertion(+) 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 65c0b95f..02a49d8c 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 @@ -53,6 +53,7 @@ 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) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { From f832c5d884ea950225a57a8910aa00f80cc85ce1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 12:30:24 +0300 Subject: [PATCH 16/35] Refactor SpaceManagementCommunityStructure to improve widget structure and visibility handling. Introduce separate methods for building the canvas and empty state, enhancing readability and maintainability. --- .../space_management_community_structure.dart | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart b/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart index 050eac87..b325c1ec 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart @@ -3,6 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.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/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart'; class SpaceManagementCommunityStructure extends StatelessWidget { @@ -13,28 +15,44 @@ class SpaceManagementCommunityStructure extends StatelessWidget { final selectionBloc = context.watch().state; final selectedCommunity = selectionBloc.selectedCommunity; final selectedSpace = selectionBloc.selectedSpace; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CommunityStructureHeader(), + Visibility( + visible: selectedCommunity!.spaces.isNotEmpty, + replacement: _buildEmptyWidget(selectedCommunity), + child: _buildCanvas(selectedCommunity, selectedSpace), + ), + ], + ); + } + + Widget _buildCanvas( + CommunityModel selectedCommunity, + SpaceModel? selectedSpace, + ) { + return Expanded( + child: CommunityStructureCanvas( + community: selectedCommunity, + selectedSpace: selectedSpace, + ), + ); + } + + Widget _buildEmptyWidget(CommunityModel selectedCommunity) { const spacer = Spacer(flex: 6); - return Visibility( - visible: selectedCommunity!.spaces.isNotEmpty, - replacement: Row( + + return Expanded( + child: Row( children: [ spacer, Expanded( - child: CreateSpaceButton(communityUuid: selectedCommunity.uuid), - ), - spacer - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const CommunityStructureHeader(), - Expanded( - child: CommunityStructureCanvas( - community: selectedCommunity, - selectedSpace: selectedSpace, + child: CreateSpaceButton( + communityUuid: selectedCommunity.uuid, ), ), + spacer, ], ), ); From df39fca050099f000d6d977c431cab242ceacc84 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 12:49:37 +0300 Subject: [PATCH 17/35] Refactor CommunityStructureHeaderActionButtons to simplify null handling for selectedSpace and improve widget structure. Ensure buttons are always displayed when selectedSpace is not null, enhancing readability and maintainability. --- ...unity_structure_header_action_buttons.dart | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart index a965c866..edeb4d8e 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart @@ -19,27 +19,27 @@ class CommunityStructureHeaderActionButtons extends StatelessWidget { @override Widget build(BuildContext context) { + if (selectedSpace == null) return const SizedBox.shrink(); + return Wrap( alignment: WrapAlignment.end, spacing: 10, children: [ - if (selectedSpace != null) ...[ - CommunityStructureHeaderButton( - label: 'Edit', - svgAsset: Assets.editSpace, - onPressed: () => onEdit(selectedSpace!), - ), - CommunityStructureHeaderButton( - label: 'Duplicate', - svgAsset: Assets.duplicate, - onPressed: () => onDuplicate(selectedSpace!), - ), - CommunityStructureHeaderButton( - label: 'Delete', - svgAsset: Assets.spaceDelete, - onPressed: () => onDelete(selectedSpace!), - ), - ], + CommunityStructureHeaderButton( + label: 'Edit', + svgAsset: Assets.editSpace, + onPressed: () => onEdit(selectedSpace!), + ), + CommunityStructureHeaderButton( + label: 'Duplicate', + svgAsset: Assets.duplicate, + onPressed: () => onDuplicate(selectedSpace!), + ), + CommunityStructureHeaderButton( + label: 'Delete', + svgAsset: Assets.spaceDelete, + onPressed: () => onDelete(selectedSpace!), + ), ], ); } From 903c5dd29b28b9143901f280fdc6c1610672e860 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 12:55:26 +0300 Subject: [PATCH 18/35] Refactor SpacesRecursiveHelper to improve variable naming and enhance readability. Update mapping logic to clarify the distinction between updated and non-null spaces, ensuring better maintainability of the recursive space handling. --- .../main_module/helpers/spaces_recursive_helper.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 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 e751e8d8..b725bf31 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 @@ -7,13 +7,15 @@ abstract final class SpacesRecursiveHelper { SpaceDetailsModel updatedSpace, ) { return spaces.map((space) { - if (space.uuid == updatedSpace.uuid) { + final isUpdatedSpace = space.uuid == updatedSpace.uuid; + if (isUpdatedSpace) { return space.copyWith( spaceName: updatedSpace.spaceName, icon: updatedSpace.icon, ); } - if (space.children.isNotEmpty) { + final hasChildren = space.children.isNotEmpty; + if (hasChildren) { return space.copyWith( children: recusrivelyUpdate(space.children, updatedSpace), ); @@ -26,7 +28,7 @@ abstract final class SpacesRecursiveHelper { List spaces, String spaceUuid, ) { - final s = spaces.map((space) { + final updatedSpaces = spaces.map((space) { if (space.uuid == spaceUuid) return null; if (space.children.isNotEmpty) { return space.copyWith( @@ -35,7 +37,7 @@ abstract final class SpacesRecursiveHelper { } return space; }).toList(); - - return s.whereType().toList(); + final nonNullSpaces = updatedSpaces.whereType().toList(); + return nonNullSpaces; } } From c60078c96ab7aac6d4fedc8c576b96aff0c1a515 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 13:04:37 +0300 Subject: [PATCH 19/35] Refactor CommunityStructureCanvas to improve widget structure by rearranging the InteractiveViewer and GestureDetector hierarchy. This change enhances readability and maintainability while ensuring proper interaction handling. --- .../widgets/community_structure_canvas.dart | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 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 02a49d8c..0fa5630b 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 @@ -453,17 +453,17 @@ class _CommunityStructureCanvasState extends State @override Widget build(BuildContext context) { final treeWidgets = _buildTreeWidgets(); - return InteractiveViewer( - transformationController: _transformationController, - boundaryMargin: EdgeInsets.symmetric( - horizontal: context.screenWidth * 0.3, - vertical: context.screenHeight * 0.3, - ), - minScale: 0.5, - maxScale: 3.0, - constrained: false, - child: GestureDetector( - onTap: _resetSelectionAndZoom, + return GestureDetector( + onTap: _resetSelectionAndZoom, + child: InteractiveViewer( + transformationController: _transformationController, + boundaryMargin: EdgeInsets.symmetric( + horizontal: context.screenWidth * 0.3, + vertical: context.screenHeight * 0.3, + ), + minScale: 0.5, + maxScale: 3.0, + constrained: false, child: SizedBox( width: context.screenWidth * 5, height: context.screenHeight * 5, From e74065250796fbdb69b8f562da355b771b091dc9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 13:11:22 +0300 Subject: [PATCH 20/35] Refactor PlusButtonWidget and SpaceCardWidget to improve widget structure and interaction handling. Replace GestureDetector with IconButton for better usability and update positioning logic for the PlusButtonWidget, enhancing maintainability and readability. --- .../widgets/plus_button_widget.dart | 27 +++++++------------ .../widgets/space_card_widget.dart | 5 ++-- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/plus_button_widget.dart b/lib/pages/space_management_v2/main_module/widgets/plus_button_widget.dart index 68169861..236b73c9 100644 --- a/lib/pages/space_management_v2/main_module/widgets/plus_button_widget.dart +++ b/lib/pages/space_management_v2/main_module/widgets/plus_button_widget.dart @@ -2,31 +2,22 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class PlusButtonWidget extends StatelessWidget { - final Offset offset; - final void Function() onButtonTap; + final void Function() onTap; const PlusButtonWidget({ + required this.onTap, super.key, - required this.offset, - required this.onButtonTap, }); @override Widget build(BuildContext context) { - return GestureDetector( - onTap: onButtonTap, - child: Container( - width: 30, - height: 30, - decoration: const BoxDecoration( - color: ColorsManager.spaceColor, - shape: BoxShape.circle, - ), - child: const Icon( - Icons.add, - color: ColorsManager.whiteColors, - size: 20, - ), + return IconButton.filled( + onPressed: onTap, + style: IconButton.styleFrom(backgroundColor: ColorsManager.spaceColor), + icon: const Icon( + Icons.add, + color: ColorsManager.whiteColors, + size: 20, ), ); } diff --git a/lib/pages/space_management_v2/main_module/widgets/space_card_widget.dart b/lib/pages/space_management_v2/main_module/widgets/space_card_widget.dart index 54902280..da79b817 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_card_widget.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_card_widget.dart @@ -29,10 +29,9 @@ class _SpaceCardWidgetState extends State { widget.buildSpaceContainer(), if (isHovered) Positioned( - bottom: 0, + bottom: -5, child: PlusButtonWidget( - offset: Offset.zero, - onButtonTap: widget.onTap, + onTap: widget.onTap, ), ), ], From 5a3cf93748d7143414c711649b906bdba6fb3094 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 14:37:16 +0300 Subject: [PATCH 21/35] Improved UniqueSubspacesDecorator implementation to improve handling of duplicate subspace names. --- .../data/services/unique_subspaces_decorator.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart b/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart index 8309c545..df0ab727 100644 --- a/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart +++ b/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart @@ -12,13 +12,17 @@ class UniqueSubspacesDecorator implements SpaceDetailsService { final response = await _decoratee.getSpaceDetails(param); final uniqueSubspaces = {}; + final duplicateNames = {}; for (final subspace in response.subspaces) { final normalizedName = subspace.name.trim().toLowerCase(); - if (!uniqueSubspaces.containsKey(normalizedName)) { + if (uniqueSubspaces.containsKey(normalizedName)) { + duplicateNames.add(normalizedName); + } else { uniqueSubspaces[normalizedName] = subspace; } } + duplicateNames.forEach(uniqueSubspaces.remove); return response.copyWith( subspaces: uniqueSubspaces.values.toList(), From f539b0ac8d559ab2e18758a50e58828e188fbd26 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 14:38:44 +0300 Subject: [PATCH 22/35] Rename `UniqueSubspacesDecorator` to `UniqueSpaceDetailsSpacesDecoratorService` --- .../main_module/views/space_management_page.dart | 4 ++-- ...art => unique_space_details_spaces_decorator_service.dart} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename lib/pages/space_management_v2/modules/space_details/data/services/{unique_subspaces_decorator.dart => unique_space_details_spaces_decorator_service.dart} (88%) 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 40a37891..55e47de1 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 @@ -10,7 +10,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen 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/space_details/data/services/remote_space_details_service.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; @@ -49,7 +49,7 @@ class _SpaceManagementPageState extends State { ), BlocProvider( create: (context) => SpaceDetailsBloc( - UniqueSubspacesDecorator( + UniqueSpaceDetailsSpacesDecoratorService( RemoteSpaceDetailsService(httpService: HTTPService()), ), ), diff --git a/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart b/lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart similarity index 88% rename from lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart rename to lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart index df0ab727..72cf8b89 100644 --- a/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart +++ b/lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart @@ -2,10 +2,10 @@ import 'package:syncrow_web/pages/space_management_v2/modules/space_details/doma import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart'; -class UniqueSubspacesDecorator implements SpaceDetailsService { +class UniqueSpaceDetailsSpacesDecoratorService implements SpaceDetailsService { final SpaceDetailsService _decoratee; - const UniqueSubspacesDecorator(this._decoratee); + const UniqueSpaceDetailsSpacesDecoratorService(this._decoratee); @override Future getSpaceDetails(LoadSpaceDetailsParam param) async { From d65f9ceea9a230c2abe0ea374e7e5ef56c87122f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Jul 2025 14:53:04 +0300 Subject: [PATCH 23/35] Enhance AddDeviceTypeWidget to support initial product counts and update selection logic. Modify AssignTagsDialog to pass initial products from space allocations, improving user experience and maintainability. --- .../widgets/add_device_type_widget.dart | 42 +++++++++++++++++-- .../widgets/assign_tags_dialog.dart | 9 +++- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/lib/pages/space_management_v2/modules/tags/presentation/widgets/add_device_type_widget.dart b/lib/pages/space_management_v2/modules/tags/presentation/widgets/add_device_type_widget.dart index 4c9990ae..2c100b15 100644 --- a/lib/pages/space_management_v2/modules/tags/presentation/widgets/add_device_type_widget.dart +++ b/lib/pages/space_management_v2/modules/tags/presentation/widgets/add_device_type_widget.dart @@ -10,7 +10,12 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class AddDeviceTypeWidget extends StatefulWidget { - const AddDeviceTypeWidget({super.key}); + const AddDeviceTypeWidget({ + super.key, + this.initialProducts = const [], + }); + + final List initialProducts; @override State createState() => _AddDeviceTypeWidgetState(); @@ -18,6 +23,16 @@ class AddDeviceTypeWidget extends StatefulWidget { class _AddDeviceTypeWidgetState extends State { final Map _selectedProducts = {}; + final Map _initialProductCounts = {}; + + @override + void initState() { + super.initState(); + for (final product in widget.initialProducts) { + _initialProductCounts[product] = (_initialProductCounts[product] ?? 0) + 1; + } + _selectedProducts.addAll(_initialProductCounts); + } void _onIncrement(Product product) { setState(() { @@ -27,8 +42,12 @@ class _AddDeviceTypeWidgetState extends State { void _onDecrement(Product product) { setState(() { - if ((_selectedProducts[product] ?? 0) > 0) { - _selectedProducts[product] = _selectedProducts[product]! - 1; + final initialCount = _initialProductCounts[product] ?? 0; + final currentCount = _selectedProducts[product] ?? 0; + if (currentCount > initialCount) { + _selectedProducts[product] = currentCount - 1; + } else if (currentCount > 0 && initialCount == 0) { + _selectedProducts[product] = currentCount - 1; if (_selectedProducts[product] == 0) { _selectedProducts.remove(product); } @@ -63,7 +82,22 @@ class _AddDeviceTypeWidgetState extends State { actions: [ SpaceDetailsActionButtons( onSave: () { - final result = _selectedProducts.entries + final resultMap = {}; + resultMap.addAll(_selectedProducts); + + for (final entry in _initialProductCounts.entries) { + final product = entry.key; + final initialCount = entry.value; + final currentCount = resultMap[product] ?? 0; + + if (currentCount > initialCount) { + resultMap[product] = currentCount - initialCount; + } else { + resultMap.remove(product); + } + } + + final result = resultMap.entries .expand((entry) => List.generate(entry.value, (_) => entry.key)) .toList(); Navigator.of(context).pop(result); 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 3f6d42ab..7a1002dc 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 @@ -205,7 +205,14 @@ class _AssignTagsDialogState extends State { onCancel: () async { final newProducts = await showDialog>( context: context, - builder: (context) => const AddDeviceTypeWidget(), + builder: (context) => AddDeviceTypeWidget( + initialProducts: [ + ..._space.productAllocations.map((e) => e.product), + ..._space.subspaces + .expand((s) => s.productAllocations) + .map((e) => e.product), + ], + ), ); if (newProducts == null || newProducts.isEmpty) return; From 308eb65d469141eda5f302bc5a36e798b014a09a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 09:56:55 +0300 Subject: [PATCH 24/35] Update error handling in RemoteUpdateSpaceService to retrieve error messages from the 'message' field instead of 'error', enhancing clarity in error reporting. --- .../update_space/data/services/remote_update_space_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart b/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart index a70d3b85..2585b1e7 100644 --- a/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart +++ b/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart @@ -34,7 +34,7 @@ class RemoteUpdateSpaceService implements UpdateSpaceService { } on DioException catch (e) { final message = e.response?.data as Map?; final error = message?['error'] as Map?; - final errorMessage = error?['error'] as String? ?? ''; + final errorMessage = error?['message'] as String? ?? ''; final formattedErrorMessage = [ _defaultErrorMessage, errorMessage, From 39c5fd1bcaa32f1dd870414bf0e32a59f4cbc124 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 09:57:29 +0300 Subject: [PATCH 25/35] Refactor SpaceDetailsModel to integrate Subspace and ProductAllocation models, enhancing data structure and serialization. Update related widgets to utilize Subspace instead of SpaceDetailsModel for improved clarity and maintainability. --- .../services/remote_create_space_service.dart | 63 +++++++++++++ .../domain/params/create_space_param.dart | 17 ++++ .../domain/services/create_space_service.dart | 6 ++ ...pace_details_spaces_decorator_service.dart | 1 + .../domain/models/product_allocation.dart | 47 ++++++++++ .../domain/models/space_details_model.dart | 91 ++++--------------- .../space_details/domain/models/subspace.dart | 48 ++++++++++ .../widgets/space_sub_spaces_box.dart | 2 +- .../widgets/space_sub_spaces_dialog.dart | 2 +- .../widgets/sub_spaces_input.dart | 2 +- .../presentation/widgets/subspace_chip.dart | 2 +- .../widgets/subspace_name_display_widget.dart | 2 +- .../widgets/assign_tags_dialog.dart | 1 + .../widgets/assign_tags_table.dart | 3 +- .../domain/params/update_space_param.dart | 31 +------ .../space_details_model_bloc.dart | 2 + 16 files changed, 210 insertions(+), 110 deletions(-) create mode 100644 lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart create mode 100644 lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart create mode 100644 lib/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart create mode 100644 lib/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart create mode 100644 lib/pages/space_management_v2/modules/space_details/domain/models/subspace.dart diff --git a/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart b/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart new file mode 100644 index 00000000..f8dba7dc --- /dev/null +++ b/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart @@ -0,0 +1,63 @@ +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/create_space/domain/params/create_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart'; +import 'package:syncrow_web/services/api/api_exception.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; + +final class RemoteCreateSpaceService implements CreateSpaceService { + const RemoteCreateSpaceService(this._httpService); + + final HTTPService _httpService; + + static const _defaultErrorMessage = 'Failed to create space'; + + @override + Future createSpace(CreateSpaceParam param) async { + try { + final path = await _makeUrl(param); + final response = await _httpService.post( + path: path, + body: param.toJson(), + expectedResponseModel: (data) { + final response = data as Map; + final isSuccess = response['success'] as bool; + if (!isSuccess) { + throw APIException(response['error'] as String); + } + + return SpaceModel.fromJson(response['data'] as Map); + }, + ); + + 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? ?? ''; + final formattedErrorMessage = [ + _defaultErrorMessage, + errorMessage, + ].join(': '); + throw APIException(formattedErrorMessage); + } catch (e) { + final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': '); + throw APIException(formattedErrorMessage); + } + } + + Future _makeUrl(CreateSpaceParam param) async { + final projectUuid = await ProjectManager.getProjectUUID(); + if (projectUuid == null || projectUuid.isEmpty) { + throw APIException('Project UUID is not set'); + } + + final communityUuid = param.communityUuid; + if (communityUuid.isEmpty) { + throw APIException('Community UUID is not set'); + } + + return '/projects/$projectUuid/communities/$communityUuid'; + } +} diff --git a/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart b/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart new file mode 100644 index 00000000..75c6d1d5 --- /dev/null +++ b/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart @@ -0,0 +1,17 @@ +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; + +class CreateSpaceParam { + final String communityUuid; + final SpaceDetailsModel space; + final String? parentUuid; + + const CreateSpaceParam({ + required this.communityUuid, + required this.space, + required this.parentUuid, + }); + + Map toJson() { + return {'spaceModelUuid': parentUuid, ...space.toJson()}; + } +} diff --git a/lib/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart b/lib/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart new file mode 100644 index 00000000..553b87e7 --- /dev/null +++ b/lib/pages/space_management_v2/modules/create_space/domain/services/create_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/create_space/domain/params/create_space_param.dart'; + +abstract interface class CreateSpaceService { + Future createSpace(CreateSpaceParam param); +} diff --git a/lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart b/lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart index 72cf8b89..da7fd4eb 100644 --- a/lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart +++ b/lib/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart @@ -1,4 +1,5 @@ import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart'; diff --git a/lib/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart b/lib/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart new file mode 100644 index 00000000..250dad5c --- /dev/null +++ b/lib/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart @@ -0,0 +1,47 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart'; +import 'package:uuid/uuid.dart'; + +class ProductAllocation extends Equatable { + final String uuid; + final Product product; + final Tag tag; + + const ProductAllocation({ + required this.uuid, + required this.product, + required this.tag, + }); + + factory ProductAllocation.fromJson(Map json) { + return ProductAllocation( + uuid: json['uuid'] as String? ?? const Uuid().v4(), + product: Product.fromJson(json['product'] as Map), + tag: Tag.fromJson(json['tag'] as Map), + ); + } + + ProductAllocation copyWith({ + String? uuid, + Product? product, + Tag? tag, + }) { + return ProductAllocation( + uuid: uuid ?? this.uuid, + product: product ?? this.product, + tag: tag ?? this.tag, + ); + } + + Map toJson() { + final isNewTag = tag.uuid.isEmpty; + return { + if (isNewTag) 'tagName': tag.name else 'tagUuid': tag.uuid, + 'productUuid': product.uuid, + }; + } + + @override + List get props => [uuid, product, tag]; +} diff --git a/lib/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart b/lib/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart index ec3c9f81..bd8ff714 100644 --- a/lib/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart +++ b/lib/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart @@ -1,8 +1,7 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -import 'package:uuid/uuid.dart'; class SpaceDetailsModel extends Equatable { final String uuid; @@ -26,6 +25,7 @@ class SpaceDetailsModel extends Equatable { productAllocations: [], subspaces: [], ); + factory SpaceDetailsModel.fromJson(Map json) { return SpaceDetailsModel( uuid: json['uuid'] as String, @@ -56,78 +56,21 @@ class SpaceDetailsModel extends Equatable { ); } - @override - List get props => [uuid, spaceName, icon, productAllocations, subspaces]; -} - -class ProductAllocation extends Equatable { - final String uuid; - final Product product; - final Tag tag; - - const ProductAllocation({ - required this.uuid, - required this.product, - required this.tag, - }); - - factory ProductAllocation.fromJson(Map json) { - return ProductAllocation( - uuid: json['uuid'] as String? ?? const Uuid().v4(), - product: Product.fromJson(json['product'] as Map), - tag: Tag.fromJson(json['tag'] as Map), - ); - } - - ProductAllocation copyWith({ - String? uuid, - Product? product, - Tag? tag, - }) { - return ProductAllocation( - uuid: uuid ?? this.uuid, - product: product ?? this.product, - tag: tag ?? this.tag, - ); + Map toJson() { + return { + 'spaceName': spaceName, + 'icon': icon, + 'subspaces': subspaces.map((e) => e.toJson()).toList(), + 'productAllocations': productAllocations.map((e) => e.toJson()).toList(), + }; } @override - List get props => [uuid, product, tag]; -} - -class Subspace extends Equatable { - final String uuid; - final String name; - final List productAllocations; - - const Subspace({ - required this.uuid, - required this.name, - required this.productAllocations, - }); - - factory Subspace.fromJson(Map json) { - return Subspace( - uuid: json['uuid'] as String, - name: json['subspaceName'] as String, - productAllocations: (json['productAllocations'] as List) - .map((e) => ProductAllocation.fromJson(e as Map)) - .toList(), - ); - } - - Subspace copyWith({ - String? uuid, - String? name, - List? productAllocations, - }) { - return Subspace( - uuid: uuid ?? this.uuid, - name: name ?? this.name, - productAllocations: productAllocations ?? this.productAllocations, - ); - } - - @override - List get props => [uuid, name, productAllocations]; + List get props => [ + uuid, + spaceName, + icon, + productAllocations, + subspaces, + ]; } diff --git a/lib/pages/space_management_v2/modules/space_details/domain/models/subspace.dart b/lib/pages/space_management_v2/modules/space_details/domain/models/subspace.dart new file mode 100644 index 00000000..4a962eb3 --- /dev/null +++ b/lib/pages/space_management_v2/modules/space_details/domain/models/subspace.dart @@ -0,0 +1,48 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart'; + +class Subspace extends Equatable { + final String uuid; + final String name; + final List productAllocations; + + const Subspace({ + required this.uuid, + required this.name, + required this.productAllocations, + }); + + factory Subspace.fromJson(Map json) { + return Subspace( + uuid: json['uuid'] as String, + name: json['subspaceName'] as String, + productAllocations: (json['productAllocations'] as List) + .map((e) => ProductAllocation.fromJson(e as Map)) + .toList(), + ); + } + + Map toJson() { + final isNewSubspace = uuid.endsWith('-NewTag'); + return { + if (!isNewSubspace) 'uuid': uuid, + 'subspaceName': name, + 'productAllocations': productAllocations.map((e) => e.toJson()).toList(), + }; + } + + Subspace copyWith({ + String? uuid, + String? name, + List? productAllocations, + }) { + return Subspace( + uuid: uuid ?? this.uuid, + name: name ?? this.name, + productAllocations: productAllocations ?? this.productAllocations, + ); + } + + @override + List get props => [uuid, name, productAllocations]; +} diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_box.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_box.dart index 68bf68bd..719988c6 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_box.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_box.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/common/edit_chip.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart'; 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 8faac548..587c9ea7 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 @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_action_buttons.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart'; import 'package:uuid/uuid.dart'; 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 854b79bc..591f741c 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 @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart index a80ddd15..6bc9f6d1 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart index bf13ffd3..e72bffde 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; 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 7a1002dc..d14a3923 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 @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_action_buttons.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart'; diff --git a/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_table.dart b/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_table.dart index 6e7e2097..1711e019 100644 --- a/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_table.dart +++ b/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_table.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/common/dialog_dropdown.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/data/services/remote_tags_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/bloc/tags_bloc.dart'; diff --git a/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart b/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart index 5dd9106d..25d54caa 100644 --- a/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart +++ b/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart @@ -9,34 +9,5 @@ class UpdateSpaceParam { final SpaceDetailsModel space; final String communityUuid; - Map toJson() { - return { - 'spaceName': space.spaceName, - 'icon': space.icon, - 'subspaces': space.subspaces.map((e) => e._toJson()).toList(), - 'productAllocations': - space.productAllocations.map((e) => e._toJson()).toList(), - }; - } -} - -extension _ProductAllocationToJson on ProductAllocation { - Map _toJson() { - final isNewTag = tag.uuid.isEmpty; - return { - if (isNewTag) 'tagName': tag.name else 'tagUuid': tag.uuid, - 'productUuid': product.uuid, - }; - } -} - -extension _SubspaceToJson on Subspace { - Map _toJson() { - final isNewSubspace = uuid.endsWith('-NewTag'); - return { - if (!isNewSubspace) 'uuid': uuid, - 'subspaceName': name, - 'productAllocations': productAllocations.map((e) => e._toJson()).toList(), - }; - } + Map toJson() => space.toJson(); } diff --git a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart index 15a22fda..4c11e694 100644 --- a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart +++ b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart @@ -1,6 +1,8 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart'; part 'space_details_model_event.dart'; From 9a203b2fd9de08c531c045611df941f935d6ae4e Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 10:01:51 +0300 Subject: [PATCH 26/35] Add CreateSpaceBloc, CreateSpaceEvent, and CreateSpaceState for managing space creation logic. Implement event handling and state management to enhance user experience during space creation. --- .../presentation/bloc/create_space_bloc.dart | 34 +++++++++++++++++++ .../presentation/bloc/create_space_event.dart | 17 ++++++++++ .../presentation/bloc/create_space_state.dart | 31 +++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_bloc.dart create mode 100644 lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_event.dart create mode 100644 lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_state.dart diff --git a/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_bloc.dart b/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_bloc.dart new file mode 100644 index 00000000..46a8abb8 --- /dev/null +++ b/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_bloc.dart @@ -0,0 +1,34 @@ +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/create_space/domain/params/create_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart'; +import 'package:syncrow_web/services/api/api_exception.dart'; + +part 'create_space_event.dart'; +part 'create_space_state.dart'; + +class CreateSpaceBloc extends Bloc { + CreateSpaceBloc( + this._createSpaceService, + ) : super(const CreateSpaceInitial()) { + on(_onCreateSpace); + } + + final CreateSpaceService _createSpaceService; + + Future _onCreateSpace( + CreateSpace event, + Emitter emit, + ) async { + emit(const CreateSpaceLoading()); + try { + final result = await _createSpaceService.createSpace(event.param); + emit(CreateSpaceSuccess(result)); + } on APIException catch (e) { + emit(CreateSpaceFailure(e.message)); + } catch (e) { + emit(CreateSpaceFailure(e.toString())); + } + } +} diff --git a/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_event.dart b/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_event.dart new file mode 100644 index 00000000..09ef8698 --- /dev/null +++ b/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_event.dart @@ -0,0 +1,17 @@ +part of 'create_space_bloc.dart'; + +sealed class CreateSpaceEvent extends Equatable { + const CreateSpaceEvent(); + + @override + List get props => []; +} + +final class CreateSpace extends CreateSpaceEvent { + const CreateSpace(this.param); + + final CreateSpaceParam param; + + @override + List get props => [param]; +} diff --git a/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_state.dart b/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_state.dart new file mode 100644 index 00000000..c5b035bb --- /dev/null +++ b/lib/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_state.dart @@ -0,0 +1,31 @@ +part of 'create_space_bloc.dart'; + +sealed class CreateSpaceState extends Equatable { + const CreateSpaceState(); + + @override + List get props => []; +} + +final class CreateSpaceInitial extends CreateSpaceState { + const CreateSpaceInitial(); +} + +final class CreateSpaceLoading extends CreateSpaceState { + const CreateSpaceLoading(); +} + +final class CreateSpaceSuccess extends CreateSpaceState { + const CreateSpaceSuccess(this.space); + + final SpaceModel space; + + @override + List get props => [space]; +} + +final class CreateSpaceFailure extends CreateSpaceState { + const CreateSpaceFailure(this.errorMessage); + + final String errorMessage; +} From 4ef4858ceeca7b11e54437daeed0e83bbfac7036 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 10:29:43 +0300 Subject: [PATCH 27/35] Added x, and y to the `toJson` method of `CreateSpaceParam`, since the API still needs them as required properties. Revert the once the BE removes those properties. --- .../create_space/domain/params/create_space_param.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart b/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart index 75c6d1d5..931588d2 100644 --- a/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart +++ b/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart @@ -12,6 +12,11 @@ class CreateSpaceParam { }); Map toJson() { - return {'spaceModelUuid': parentUuid, ...space.toJson()}; + return { + 'spaceModelUuid': parentUuid, + ...space.toJson(), + 'x': 0, + 'y': 0, + }; } } From 8e303af0d73fd908ea6bb2d20a685b9696de1b1d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 11:01:26 +0300 Subject: [PATCH 28/35] Update RemoteCreateSpaceService to correct API endpoint for space creation, changing the return path to include '/spaces' for accurate resource targeting. --- .../create_space/data/services/remote_create_space_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart b/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart index f8dba7dc..768f6438 100644 --- a/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart +++ b/lib/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart @@ -58,6 +58,6 @@ final class RemoteCreateSpaceService implements CreateSpaceService { throw APIException('Community UUID is not set'); } - return '/projects/$projectUuid/communities/$communityUuid'; + return '/projects/$projectUuid/communities/$communityUuid/spaces'; } } From 62f67f5a5f8250ff94ad4697fd9ce0c11eb0e08d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 11:16:41 +0300 Subject: [PATCH 29/35] Refactor CreateSpaceButton and CommunityStructureCanvas to utilize CommunityModel directly, enhancing data handling during space creation. Implement success callback for space creation to update community state in CommunitiesBloc, improving user experience and maintainability. --- .../widgets/community_structure_canvas.dart | 15 +++++- .../widgets/create_space_button.dart | 17 ++++-- .../space_management_community_structure.dart | 49 ++++++++++------- .../helpers/space_details_dialog_helper.dart | 54 ++++++++++++++++--- 4 files changed, 107 insertions(+), 28 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 0fa5630b..63f8e8ec 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 @@ -268,7 +268,7 @@ class _CommunityStructureCanvasState extends State Positioned( left: createButtonX, top: createButtonY, - child: CreateSpaceButton(communityUuid: widget.community.uuid), + child: CreateSpaceButton(community: widget.community), ), ); @@ -327,6 +327,19 @@ class _CommunityStructureCanvasState extends State onTap: () => SpaceDetailsDialogHelper.showCreate( context, communityUuid: widget.community.uuid, + parentUuid: space.uuid, + onSuccess: (updatedSpaceModel) { + // TODO(FarisArmoush): insert the space under the parent, which is the space that was tapped + final newCommunity = widget.community.copyWith(); + final parentIndex = + newCommunity.spaces.indexWhere((s) => s.uuid == space.uuid); + if (parentIndex != -1) { + newCommunity.spaces.insert(parentIndex + 1, updatedSpaceModel); + } + context.read().add( + CommunitiesUpdateCommunity(newCommunity), + ); + }, ), ); diff --git a/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart b/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart index e6dfbb15..610962e5 100644 --- a/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart +++ b/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart @@ -1,14 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/presentation/bloc/communities_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSpaceButton extends StatefulWidget { const CreateSpaceButton({ - required this.communityUuid, + required this.community, super.key, }); - final String communityUuid; + final CommunityModel community; @override State createState() => _CreateSpaceButtonState(); @@ -25,7 +28,15 @@ class _CreateSpaceButtonState extends State { child: InkWell( onTap: () => SpaceDetailsDialogHelper.showCreate( context, - communityUuid: widget.communityUuid, + communityUuid: widget.community.uuid, + onSuccess: (updatedSpaceModel) { + final newCommunity = widget.community.copyWith( + spaces: [updatedSpaceModel, ...widget.community.spaces], + ); + context.read().add( + CommunitiesUpdateCommunity(newCommunity), + ); + }, ), child: MouseRegion( onEnter: (_) => setState(() => _isHovered = true), diff --git a/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart b/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart index b325c1ec..11478fbe 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/commun import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.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/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'; class SpaceManagementCommunityStructure extends StatelessWidget { @@ -12,19 +13,35 @@ class SpaceManagementCommunityStructure extends StatelessWidget { @override Widget build(BuildContext context) { - final selectionBloc = context.watch().state; - final selectedCommunity = selectionBloc.selectedCommunity; - final selectedSpace = selectionBloc.selectedSpace; - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - const CommunityStructureHeader(), - Visibility( - visible: selectedCommunity!.spaces.isNotEmpty, - replacement: _buildEmptyWidget(selectedCommunity), - child: _buildCanvas(selectedCommunity, selectedSpace), - ), - ], + return BlocBuilder( + builder: (context, state) { + final selectedCommunity = state.selectedCommunity; + final selectedSpace = state.selectedSpace; + + if (selectedCommunity == null) { + return const SizedBox.shrink(); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CommunityStructureHeader(), + BlocBuilder( + builder: (context, state) { + final community = state.communities.firstWhere( + (element) => element.uuid == selectedCommunity.uuid, + orElse: () => selectedCommunity, + ); + return Visibility( + visible: community.spaces.isNotEmpty, + replacement: _buildEmptyWidget(community), + child: _buildCanvas(community, selectedSpace), + ); + }, + ), + ], + ); + }, ); } @@ -47,11 +64,7 @@ class SpaceManagementCommunityStructure extends StatelessWidget { child: Row( children: [ spacer, - Expanded( - child: CreateSpaceButton( - communityUuid: selectedCommunity.uuid, - ), - ), + Expanded(child: CreateSpaceButton(community: selectedCommunity)), spacer, ], ), diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart b/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart index c5de7dad..45cb0e89 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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/create_space/data/services/remote_create_space_service.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart'; @@ -14,6 +17,8 @@ abstract final class SpaceDetailsDialogHelper { static void showCreate( BuildContext context, { required String communityUuid, + required void Function(SpaceModel updatedSpaceModel)? onSuccess, + String? parentUuid, }) { showDialog( context: context, @@ -24,14 +29,41 @@ abstract final class SpaceDetailsDialogHelper { RemoteSpaceDetailsService(httpService: HTTPService()), ), ), + BlocProvider( + create: (context) => CreateSpaceBloc( + RemoteCreateSpaceService(HTTPService()), + ), + ), ], child: Builder( - builder: (context) => SpaceDetailsDialog( - context: context, - title: const SelectableText('Create Space'), - spaceModel: SpaceModel.empty(), - onSave: (space) {}, - communityUuid: communityUuid, + builder: (context) => BlocListener( + listener: (context, state) => switch (state) { + CreateSpaceInitial() => null, + CreateSpaceLoading() => _onLoading(context), + CreateSpaceSuccess() => _onCreateSuccess( + context, + state.space, + onSuccess, + ), + CreateSpaceFailure() => _onError(context, state.errorMessage), + }, + child: SpaceDetailsDialog( + context: context, + title: const SelectableText('Create Space'), + spaceModel: SpaceModel.empty(), + onSave: (space) { + context.read().add( + CreateSpace( + CreateSpaceParam( + communityUuid: communityUuid, + space: space, + parentUuid: parentUuid, + ), + ), + ); + }, + communityUuid: communityUuid, + ), ), ), ), @@ -135,4 +167,14 @@ abstract final class SpaceDetailsDialogHelper { ), ); } + + static void _onCreateSuccess( + BuildContext context, + SpaceModel space, + void Function(SpaceModel updatedSpaceModel)? onSuccess, + ) { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + onSuccess?.call(space); + } } From 6ec972a5207f993c919ef7884854a3560e548364 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 11:18:24 +0300 Subject: [PATCH 30/35] Integrate CommunitiesTreeSelectionBloc into CreateSpaceButton to handle space selection upon successful space creation. Update community state in CommunitiesBloc to reflect new space addition, enhancing user experience and maintainability. --- .../main_module/widgets/create_space_button.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart b/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart index 610962e5..4032c2ab 100644 --- a/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart +++ b/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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/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/space_details/presentation/helpers/space_details_dialog_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -31,11 +32,17 @@ class _CreateSpaceButtonState extends State { communityUuid: widget.community.uuid, onSuccess: (updatedSpaceModel) { final newCommunity = widget.community.copyWith( - spaces: [updatedSpaceModel, ...widget.community.spaces], + spaces: [...widget.community.spaces, updatedSpaceModel], ); context.read().add( CommunitiesUpdateCommunity(newCommunity), ); + context.read().add( + SelectSpaceEvent( + space: updatedSpaceModel, + community: newCommunity, + ), + ); }, ), child: MouseRegion( From f03c28f7fdc1bcc11eba3aabea3b05bc8a06da3c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 11:47:27 +0300 Subject: [PATCH 31/35] Add recursive insertion method in SpacesRecursiveHelper for managing space hierarchy. Update CommunityStructureCanvas to utilize this method for inserting new spaces under the correct parent, enhancing community state management during space creation. --- .../helpers/spaces_recursive_helper.dart | 20 +++++++++++++++++++ .../widgets/community_structure_canvas.dart | 17 ++++++++-------- 2 files changed, 29 insertions(+), 8 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 b725bf31..4322c4e8 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 @@ -40,4 +40,24 @@ abstract final class SpacesRecursiveHelper { final nonNullSpaces = updatedSpaces.whereType().toList(); return nonNullSpaces; } + + static List recusrivelyInsert( + List spaces, + SpaceModel newSpace, + String parentUuid, + ) { + return spaces.map((space) { + if (space.uuid == parentUuid) { + return space.copyWith( + children: [...space.children, newSpace], + ); + } + if (space.children.isNotEmpty) { + return space.copyWith( + children: recusrivelyInsert(space.children, newSpace, parentUuid), + ); + } + return space; + }).toList(); + } } 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 63f8e8ec..03efab7d 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,5 +1,6 @@ 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'; import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_reorder_data_model.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart'; @@ -329,15 +330,15 @@ class _CommunityStructureCanvasState extends State communityUuid: widget.community.uuid, parentUuid: space.uuid, onSuccess: (updatedSpaceModel) { - // TODO(FarisArmoush): insert the space under the parent, which is the space that was tapped - final newCommunity = widget.community.copyWith(); - final parentIndex = - newCommunity.spaces.indexWhere((s) => s.uuid == space.uuid); - if (parentIndex != -1) { - newCommunity.spaces.insert(parentIndex + 1, updatedSpaceModel); - } + final updatedSpaces = SpacesRecursiveHelper.recusrivelyInsert( + spaces, + updatedSpaceModel, + space.uuid, + ); context.read().add( - CommunitiesUpdateCommunity(newCommunity), + CommunitiesUpdateCommunity( + widget.community.copyWith(spaces: updatedSpaces), + ), ); }, ), From fc70669f1d2032c5923f69d9d00007e9ca78e131 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 15:27:31 +0300 Subject: [PATCH 32/35] sends correct parentUuid key in create space. --- .../modules/create_space/domain/params/create_space_param.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart b/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart index 931588d2..90a82a6b 100644 --- a/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart +++ b/lib/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart @@ -13,7 +13,7 @@ class CreateSpaceParam { Map toJson() { return { - 'spaceModelUuid': parentUuid, + 'parentUuid': parentUuid, ...space.toJson(), 'x': 0, 'y': 0, From 8522c0bbc35bf123941faea4f6a5450927f6207f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 16:26:15 +0300 Subject: [PATCH 33/35] Made `SpacesRecursiveHelper` private, to avoid creating unnecessary instances, since all its methods are static. --- .../main_module/helpers/spaces_recursive_helper.dart | 2 ++ 1 file changed, 2 insertions(+) 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 4322c4e8..c470ffc1 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 @@ -2,6 +2,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; abstract final class SpacesRecursiveHelper { + const SpacesRecursiveHelper._(); + static List recusrivelyUpdate( List spaces, SpaceDetailsModel updatedSpace, From 7b5b40a03cda611236defe5a4ad67ebae5ba2bab Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Jul 2025 16:30:38 +0300 Subject: [PATCH 34/35] Refactor `recursivelyInsert` method in `SpacesRecursiveHelper` to use named parameters. Update `CommunityStructureCanvas` to reflect these changes, ensuring correct space insertion under the specified parent. --- .../helpers/spaces_recursive_helper.dart | 22 ++++++++++++------- .../widgets/community_structure_canvas.dart | 8 +++---- 2 files changed, 18 insertions(+), 12 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 c470ffc1..de5ce34f 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 @@ -43,20 +43,26 @@ abstract final class SpacesRecursiveHelper { return nonNullSpaces; } - static List recusrivelyInsert( - List spaces, - SpaceModel newSpace, - String parentUuid, - ) { + static List recursivelyInsert({ + required List spaces, + required String parentUuid, + required SpaceModel newSpace, + }) { return spaces.map((space) { - if (space.uuid == parentUuid) { + final isParentSpace = space.uuid == parentUuid; + if (isParentSpace) { return space.copyWith( children: [...space.children, newSpace], ); } - if (space.children.isNotEmpty) { + final hasChildren = space.children.isNotEmpty; + if (hasChildren) { return space.copyWith( - children: recusrivelyInsert(space.children, newSpace, parentUuid), + children: recursivelyInsert( + spaces: space.children, + parentUuid: parentUuid, + newSpace: newSpace, + ), ); } return space; 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 03efab7d..692ffc0a 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 @@ -330,10 +330,10 @@ class _CommunityStructureCanvasState extends State communityUuid: widget.community.uuid, parentUuid: space.uuid, onSuccess: (updatedSpaceModel) { - final updatedSpaces = SpacesRecursiveHelper.recusrivelyInsert( - spaces, - updatedSpaceModel, - space.uuid, + final updatedSpaces = SpacesRecursiveHelper.recursivelyInsert( + spaces: widget.community.spaces, + parentUuid: space.uuid, + newSpace: updatedSpaceModel, ); context.read().add( CommunitiesUpdateCommunity( From 06f00da02c67e366c126af502dc54d99be5fb414 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Jul 2025 13:00:38 +0300 Subject: [PATCH 35/35] Update shadow properties in CommunityStructureHeader for improved visual aesthetics. Adjusted blur radius and offset to enhance the header's appearance in the space management interface. --- .../main_module/widgets/community_structure_header.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart index 432b3ce4..2e1a350e 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart @@ -20,9 +20,9 @@ class CommunityStructureHeader extends StatelessWidget { color: ColorsManager.whiteColors, boxShadow: [ BoxShadow( - color: ColorsManager.shadowBlackColor, - blurRadius: 8, - offset: const Offset(0, 4), + color: ColorsManager.shadowBlackColor.withValues(alpha: 0.1), + blurRadius: 20, + offset: const Offset(0, 1), ), ], ),