From 15b36fd0527ebd0f0afdeb0123135e8a0f0286db Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 6 Jul 2025 09:33:27 +0300 Subject: [PATCH 1/8] Enhance SpaceDetailsDialog: Adjust loading indicator layout for improved responsiveness. The CircularProgressIndicator is now wrapped in a SizedBox to ensure proper sizing based on screen dimensions, enhancing user experience and maintainability. --- .../presentation/widgets/space_details_dialog.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart index 2461b3c6..ae772036 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart @@ -78,7 +78,11 @@ class _SpaceDetailsDialogState extends State { return AlertDialog( title: widget.title, backgroundColor: ColorsManager.whiteColors, - content: const Center(child: CircularProgressIndicator()), + content: SizedBox( + height: context.screenHeight * 0.3, + width: context.screenWidth * 0.5, + child: const Center(child: CircularProgressIndicator()), + ), ); } From 6dcc851d971742fcc5fd82efe95ea0aa3cf33740 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 6 Jul 2025 10:46:20 +0300 Subject: [PATCH 2/8] Made `CommunityDialog` reusable for edit and create features. --- ...ce_management_community_dialog_helper.dart | 27 +++---- .../shared/widgets/community_dialog.dart} | 78 ++++++++----------- .../widgets/community_structure_header.dart | 21 ++--- .../domain/models/community_model.dart | 20 +++++ .../presentation/create_community_dialog.dart | 44 ++++++----- .../remote_update_community_service.dart | 3 +- .../domain/params/update_community_param.dart | 10 --- .../services/update_community_service.dart | 3 +- .../bloc/update_community_bloc.dart | 3 +- .../bloc/update_community_event.dart | 6 +- .../presentation/edit_community_dialog.dart | 57 ++++++++++++++ 11 files changed, 161 insertions(+), 111 deletions(-) rename lib/pages/space_management_v2/{modules/create_community/presentation/create_community_dialog_widget.dart => main_module/shared/widgets/community_dialog.dart} (55%) delete mode 100644 lib/pages/space_management_v2/modules/update_community/domain/params/update_community_param.dart create mode 100644 lib/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart diff --git a/lib/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart b/lib/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart index 5322c3ea..791ed10e 100644 --- a/lib/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart +++ b/lib/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart @@ -1,24 +1,21 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.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/communities/domain/models/community_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart'; abstract final class SpaceManagementCommunityDialogHelper { - static void showCreateDialog(BuildContext context) { + static void showCreateDialog(BuildContext context) => showDialog( + context: context, + builder: (_) => const CreateCommunityDialog(), + ); + + static void showEditDialog( + BuildContext context, + CommunityModel community, + ) { showDialog( context: context, - builder: (_) => CreateCommunityDialog( - title: const SelectableText('Community Name'), - onCreateCommunity: (community) { - context.read().add( - InsertCommunity(community), - ); - context.read().add( - SelectCommunityEvent(community: community), - ); - }, - ), + builder: (_) => EditCommunityDialog(community: community), ); } } diff --git a/lib/pages/space_management_v2/modules/create_community/presentation/create_community_dialog_widget.dart b/lib/pages/space_management_v2/main_module/shared/widgets/community_dialog.dart similarity index 55% rename from lib/pages/space_management_v2/modules/create_community/presentation/create_community_dialog_widget.dart rename to lib/pages/space_management_v2/main_module/shared/widgets/community_dialog.dart index ab9f7b9a..f420abbd 100644 --- a/lib/pages/space_management_v2/modules/create_community/presentation/create_community_dialog_widget.dart +++ b/lib/pages/space_management_v2/main_module/shared/widgets/community_dialog.dart @@ -1,28 +1,28 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/create_community/domain/param/create_community_param.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/bloc/create_community_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_name_text_field.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class CreateCommunityDialogWidget extends StatefulWidget { +class CommunityDialog extends StatefulWidget { final String? initialName; final Widget title; + final void Function(String name) onSubmit; + final String? errorMessage; - const CreateCommunityDialogWidget({ - super.key, + const CommunityDialog({ required this.title, + required this.onSubmit, this.initialName, + this.errorMessage, + super.key, }); @override - State createState() => - _CreateCommunityDialogWidgetState(); + State createState() => _CommunityDialogState(); } -class _CreateCommunityDialogWidgetState extends State { +class _CommunityDialogState extends State { late final TextEditingController _nameController; @override @@ -63,35 +63,31 @@ class _CreateCommunityDialogWidgetState extends State( - builder: (context, state) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - DefaultTextStyle( - style: Theme.of(context).textTheme.headlineMedium!, - child: widget.title, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DefaultTextStyle( + style: Theme.of(context).textTheme.headlineMedium!, + child: widget.title, + ), + const SizedBox(height: 18), + CreateCommunityNameTextField( + nameController: _nameController, + ), + if (widget.errorMessage != null) + Padding( + padding: const EdgeInsets.only(top: 18), + child: SelectableText( + '* ${widget.errorMessage}', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.error, + ), ), - const SizedBox(height: 18), - CreateCommunityNameTextField( - nameController: _nameController, - ), - if (state case CreateCommunityFailure(:final message)) - Padding( - padding: const EdgeInsets.only(top: 18), - child: SelectableText( - '* $message', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.error, - ), - ), - ), - const SizedBox(height: 24), - _buildActionButtons(context), - ], - ); - }, + ), + const SizedBox(height: 24), + _buildActionButtons(context), + ], ), ), ), @@ -132,13 +128,7 @@ class _CreateCommunityDialogWidgetState extends State().add( - CreateCommunity( - CreateCommunityParam( - name: _nameController.text.trim(), - ), - ), - ); + widget.onSubmit.call(_nameController.text.trim()); } } } 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 b457c413..4f71075b 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 @@ -1,9 +1,9 @@ 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/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_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'; @@ -44,18 +44,6 @@ class CommunityStructureHeader extends StatelessWidget { ); } - void _showCreateCommunityDialog(BuildContext context) { - showDialog( - context: context, - builder: (context) => CreateCommunityDialog( - title: const Text('Edit Community'), - onCreateCommunity: (community) { - // TODO(FarisArmoush): Implement - }, - ), - ); - } - Widget _buildCommunityInfo( BuildContext context, ThemeData theme, double screenWidth) { final selectedCommunity = @@ -86,7 +74,12 @@ class CommunityStructureHeader extends StatelessWidget { ), const SizedBox(width: 2), GestureDetector( - onTap: () => _showCreateCommunityDialog(context), + onTap: () { + SpaceManagementCommunityDialogHelper.showEditDialog( + context, + selectedCommunity, + ); + }, child: SvgPicture.asset( Assets.iconEdit, width: 16, diff --git a/lib/pages/space_management_v2/modules/communities/domain/models/community_model.dart b/lib/pages/space_management_v2/modules/communities/domain/models/community_model.dart index 37f131b3..c2489bf6 100644 --- a/lib/pages/space_management_v2/modules/communities/domain/models/community_model.dart +++ b/lib/pages/space_management_v2/modules/communities/domain/models/community_model.dart @@ -39,6 +39,26 @@ class CommunityModel extends Equatable { .toList(); } + CommunityModel copyWith({ + String? uuid, + String? name, + DateTime? createdAt, + DateTime? updatedAt, + String? description, + String? externalId, + List? spaces, + }) { + return CommunityModel( + uuid: uuid ?? this.uuid, + name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + description: description ?? this.description, + externalId: externalId ?? this.externalId, + spaces: spaces ?? this.spaces, + ); + } + @override List get props => [uuid, name, spaces]; } diff --git a/lib/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart b/lib/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart index a9af44d6..2949145a 100644 --- a/lib/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart +++ b/lib/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart @@ -1,28 +1,23 @@ 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/main_module/shared/widgets/community_dialog.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/create_community/data/services/remote_create_community_service.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/create_community/domain/param/create_community_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/bloc/create_community_bloc.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_dialog_widget.dart'; import 'package:syncrow_web/services/api/http_service.dart'; class CreateCommunityDialog extends StatelessWidget { - final void Function(CommunityModel community) onCreateCommunity; - final String? initialName; - final Widget title; - - const CreateCommunityDialog({ - super.key, - required this.onCreateCommunity, - required this.title, - this.initialName, - }); + const CreateCommunityDialog({super.key}); @override Widget build(BuildContext context) { return BlocProvider( - create: (_) => CreateCommunityBloc(RemoteCreateCommunityService(HTTPService())), - child: BlocListener( + create: (_) => CreateCommunityBloc( + RemoteCreateCommunityService(HTTPService()), + ), + child: BlocConsumer( listener: (context, state) { switch (state) { case CreateCommunityLoading(): @@ -39,7 +34,12 @@ class CreateCommunityDialog extends StatelessWidget { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Community created successfully')), ); - onCreateCommunity.call(community); + context.read().add( + InsertCommunity(community), + ); + context.read().add( + SelectCommunityEvent(community: community), + ); break; case CreateCommunityFailure(): Navigator.of(context).pop(); @@ -48,10 +48,16 @@ class CreateCommunityDialog extends StatelessWidget { break; } }, - child: CreateCommunityDialogWidget( - title: title, - initialName: initialName, - ), + builder: (BuildContext context, CreateCommunityState state) { + return CommunityDialog( + title: const Text('Create Community'), + initialName: null, + onSubmit: (name) => context.read().add( + CreateCommunity(CreateCommunityParam(name: name)), + ), + errorMessage: state is CreateCommunityFailure ? state.message : null, + ); + }, ), ); } diff --git a/lib/pages/space_management_v2/modules/update_community/data/services/remote_update_community_service.dart b/lib/pages/space_management_v2/modules/update_community/data/services/remote_update_community_service.dart index 6c550673..f8f71afd 100644 --- a/lib/pages/space_management_v2/modules/update_community/data/services/remote_update_community_service.dart +++ b/lib/pages/space_management_v2/modules/update_community/data/services/remote_update_community_service.dart @@ -1,6 +1,5 @@ import 'package:dio/dio.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/update_community/domain/params/update_community_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_community/domain/services/update_community_service.dart'; import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -13,7 +12,7 @@ class RemoteUpdateCommunityService implements UpdateCommunityService { static const _defaultErrorMessage = 'Failed to update community'; @override - Future updateCommunity(UpdateCommunityParam param) async { + Future updateCommunity(CommunityModel param) async { try { final response = await _httpService.put( path: 'endpoint', diff --git a/lib/pages/space_management_v2/modules/update_community/domain/params/update_community_param.dart b/lib/pages/space_management_v2/modules/update_community/domain/params/update_community_param.dart deleted file mode 100644 index 69dfc4e2..00000000 --- a/lib/pages/space_management_v2/modules/update_community/domain/params/update_community_param.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:equatable/equatable.dart'; - -class UpdateCommunityParam extends Equatable { - const UpdateCommunityParam({required this.name}); - - final String name; - - @override - List get props => [name]; -} diff --git a/lib/pages/space_management_v2/modules/update_community/domain/services/update_community_service.dart b/lib/pages/space_management_v2/modules/update_community/domain/services/update_community_service.dart index 9703fdc6..d32e79b6 100644 --- a/lib/pages/space_management_v2/modules/update_community/domain/services/update_community_service.dart +++ b/lib/pages/space_management_v2/modules/update_community/domain/services/update_community_service.dart @@ -1,6 +1,5 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/update_community/domain/params/update_community_param.dart'; abstract class UpdateCommunityService { - Future updateCommunity(UpdateCommunityParam param); + Future updateCommunity(CommunityModel community); } diff --git a/lib/pages/space_management_v2/modules/update_community/presentation/bloc/update_community_bloc.dart b/lib/pages/space_management_v2/modules/update_community/presentation/bloc/update_community_bloc.dart index 4e913c22..6a4c2051 100644 --- a/lib/pages/space_management_v2/modules/update_community/presentation/bloc/update_community_bloc.dart +++ b/lib/pages/space_management_v2/modules/update_community/presentation/bloc/update_community_bloc.dart @@ -1,7 +1,6 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.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/update_community/domain/params/update_community_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_community/domain/services/update_community_service.dart'; import 'package:syncrow_web/services/api/api_exception.dart'; @@ -24,7 +23,7 @@ class UpdateCommunityBloc extends Bloc get props => [param]; + List get props => [communityModel]; } diff --git a/lib/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart b/lib/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart new file mode 100644 index 00000000..f42cea78 --- /dev/null +++ b/lib/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/space_management_v2/main_module/shared/widgets/community_dialog.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/update_community/data/services/remote_update_community_service.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_community/presentation/bloc/update_community_bloc.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; + +class EditCommunityDialog extends StatelessWidget { + const EditCommunityDialog({required this.community, super.key}); + + final CommunityModel community; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => + UpdateCommunityBloc(RemoteUpdateCommunityService(HTTPService())), + child: BlocConsumer( + listener: (context, state) { + switch (state) { + case UpdateCommunityLoading(): + showDialog( + context: context, + builder: (context) => const Center( + child: CircularProgressIndicator(), + ), + ); + break; + case UpdateCommunitySuccess(:final community): + Navigator.of(context).pop(); + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('${community.name} updated successfully'), + ), + ); + break; + case UpdateCommunityFailure(): + Navigator.of(context).pop(); + break; + default: + break; + } + }, + builder: (context, state) => CommunityDialog( + title: const Text('Edit Community'), + initialName: community.name, + errorMessage: state is UpdateCommunityFailure ? state.message : null, + onSubmit: (name) => context.read().add( + UpdateCommunity(community.copyWith(name: name)), + ), + ), + ), + ); + } +} From dd735032eab3b90333dc4f03f79a75547eb0bdbb Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 6 Jul 2025 11:00:01 +0300 Subject: [PATCH 3/8] Update SpaceSubSpacesDialog: Replace Text with SelectableText for title and error message, and add spacing in the content column to enhance user experience and maintainability. --- .../helpers/space_details_dialog_helper.dart | 4 ++-- .../presentation/widgets/space_details_form.dart | 14 +++++++------- .../widgets/space_sub_spaces_dialog.dart | 5 +++-- 3 files changed, 12 insertions(+), 11 deletions(-) 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 c8835716..6b95556a 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 @@ -16,7 +16,7 @@ abstract final class SpaceDetailsDialogHelper { ), child: SpaceDetailsDialog( context: context, - title: const Text('Create Space'), + title: const SelectableText('Create Space'), spaceModel: SpaceModel.empty(), onSave: print, ), @@ -36,7 +36,7 @@ abstract final class SpaceDetailsDialogHelper { ), child: SpaceDetailsDialog( context: context, - title: const Text('Edit Space'), + title: const SelectableText('Edit Space'), spaceModel: spaceModel, onSave: (space) {}, ), diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_form.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_form.dart index f9a5ad64..d0495dd3 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_form.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_form.dart @@ -28,7 +28,7 @@ class SpaceDetailsForm extends StatelessWidget { create: (context) => SpaceDetailsModelBloc(initialState: space), child: BlocBuilder( buildWhen: (previous, current) => previous != current, - builder: (context, state) { + builder: (context, space) { return AlertDialog( title: title, backgroundColor: ColorsManager.whiteColors, @@ -39,7 +39,7 @@ class SpaceDetailsForm extends StatelessWidget { spacing: 20, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded(child: SpaceIconPicker(iconPath: state.icon)), + Expanded(child: SpaceIconPicker(iconPath: space.icon)), Expanded( flex: 2, child: Column( @@ -47,17 +47,17 @@ class SpaceDetailsForm extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ SpaceNameTextField( - initialValue: state.spaceName, - isNameFieldExist: (value) => state.subspaces.any( + initialValue: space.spaceName, + isNameFieldExist: (value) => space.subspaces.any( (subspace) => subspace.name == value, ), ), const Spacer(), SpaceSubSpacesBox( - subspaces: state.subspaces, + subspaces: space.subspaces, ), const SizedBox(height: 16), - SpaceDetailsDevicesBox(space: state), + SpaceDetailsDevicesBox(space: space), ], ), ), @@ -66,7 +66,7 @@ class SpaceDetailsForm extends StatelessWidget { ), actions: [ SpaceDetailsActionButtons( - onSave: () => onSave(state), + onSave: () => onSave(space), onCancel: Navigator.of(context).pop, ), ], 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 4c537a8a..9e81c323 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 @@ -56,8 +56,9 @@ class _SpaceSubSpacesDialogState extends State { @override Widget build(BuildContext context) { return AlertDialog( - title: const Text('Create Sub Spaces'), + title: const SelectableText('Create Sub Spaces'), content: Column( + spacing: 12, mainAxisSize: MainAxisSize.min, children: [ SubSpacesInput( @@ -70,7 +71,7 @@ class _SpaceSubSpacesDialogState extends State { child: Visibility( key: ValueKey(_hasDuplicateNames), visible: _hasDuplicateNames, - child: const Text( + child: const SelectableText( 'Error: Duplicate subspace names are not allowed.', style: TextStyle(color: Colors.red), ), From 823d86fd80589829411347139a4739b21d0383fb Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 6 Jul 2025 11:17:28 +0300 Subject: [PATCH 4/8] Add loading and success snackbar helpers: Introduced `showLoadingDialog` and `showSuccessSnackBar` methods in `SpaceManagementCommunityDialogHelper` for consistent loading indicators and success messages across community dialogs. Updated `CreateCommunityDialog` and `EditCommunityDialog` to utilize these new helpers, enhancing user experience and maintainability. --- ...ce_management_community_dialog_helper.dart | 15 ++++++++++ .../presentation/bloc/communities_bloc.dart | 14 +++++++++ .../presentation/bloc/communities_event.dart | 9 ++++++ .../presentation/create_community_dialog.dart | 17 ++++------- .../bloc/update_community_state.dart | 6 ++-- .../presentation/edit_community_dialog.dart | 30 +++++++++---------- 6 files changed, 61 insertions(+), 30 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart b/lib/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart index 791ed10e..2446ae81 100644 --- a/lib/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart +++ b/lib/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart @@ -18,4 +18,19 @@ abstract final class SpaceManagementCommunityDialogHelper { builder: (_) => EditCommunityDialog(community: community), ); } + + static void showLoadingDialog(BuildContext context) => showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const Center( + child: CircularProgressIndicator(), + ), + ); + + static void showSuccessSnackBar(BuildContext context, String message) => + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + ), + ); } diff --git a/lib/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart b/lib/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart index 9094a632..1b4f10a1 100644 --- a/lib/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart +++ b/lib/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart @@ -16,6 +16,7 @@ class CommunitiesBloc extends Bloc { on(_onLoadCommunities); on(_onLoadMoreCommunities); on(_onInsertCommunity); + on(_onCommunitiesUpdateCommunity); } final CommunitiesService _communitiesService; @@ -114,4 +115,17 @@ class CommunitiesBloc extends Bloc { ) { emit(state.copyWith(communities: [event.community, ...state.communities])); } + + void _onCommunitiesUpdateCommunity( + CommunitiesUpdateCommunity event, + Emitter emit, + ) { + emit( + state.copyWith( + communities: state.communities + .map((e) => e.uuid == event.community.uuid ? event.community : e) + .toList(), + ), + ); + } } diff --git a/lib/pages/space_management_v2/modules/communities/presentation/bloc/communities_event.dart b/lib/pages/space_management_v2/modules/communities/presentation/bloc/communities_event.dart index cd14fa3d..9f8d1126 100644 --- a/lib/pages/space_management_v2/modules/communities/presentation/bloc/communities_event.dart +++ b/lib/pages/space_management_v2/modules/communities/presentation/bloc/communities_event.dart @@ -31,3 +31,12 @@ final class InsertCommunity extends CommunitiesEvent { @override List get props => [community]; } + +final class CommunitiesUpdateCommunity extends CommunitiesEvent { + const CommunitiesUpdateCommunity(this.community); + + final CommunityModel community; + + @override + List get props => [community]; +} diff --git a/lib/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart b/lib/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart index 2949145a..299c0078 100644 --- a/lib/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart +++ b/lib/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.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/shared/helpers/space_management_community_dialog_helper.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/shared/widgets/community_dialog.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'; @@ -20,19 +21,15 @@ class CreateCommunityDialog extends StatelessWidget { child: BlocConsumer( listener: (context, state) { switch (state) { - case CreateCommunityLoading(): - showDialog( - context: context, - builder: (context) => const Center( - child: CircularProgressIndicator(), - ), - ); + case CreateCommunityLoading() || CreateCommunityInitial(): + SpaceManagementCommunityDialogHelper.showLoadingDialog(context); break; case CreateCommunitySuccess(:final community): Navigator.of(context).pop(); Navigator.of(context).pop(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Community created successfully')), + SpaceManagementCommunityDialogHelper.showSuccessSnackBar( + context, + '${community.name} community created successfully', ); context.read().add( InsertCommunity(community), @@ -44,8 +41,6 @@ class CreateCommunityDialog extends StatelessWidget { case CreateCommunityFailure(): Navigator.of(context).pop(); break; - default: - break; } }, builder: (BuildContext context, CreateCommunityState state) { diff --git a/lib/pages/space_management_v2/modules/update_community/presentation/bloc/update_community_state.dart b/lib/pages/space_management_v2/modules/update_community/presentation/bloc/update_community_state.dart index 9126be0a..14ca7f6e 100644 --- a/lib/pages/space_management_v2/modules/update_community/presentation/bloc/update_community_state.dart +++ b/lib/pages/space_management_v2/modules/update_community/presentation/bloc/update_community_state.dart @@ -21,10 +21,10 @@ final class UpdateCommunitySuccess extends UpdateCommunityState { } final class UpdateCommunityFailure extends UpdateCommunityState { - final String message; + final String errorMessage; - const UpdateCommunityFailure(this.message); + const UpdateCommunityFailure(this.errorMessage); @override - List get props => [message]; + List get props => [errorMessage]; } diff --git a/lib/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart b/lib/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart index f42cea78..e4c13f3c 100644 --- a/lib/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart +++ b/lib/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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/shared/widgets/community_dialog.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/update_community/data/services/remote_update_community_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_community/presentation/bloc/update_community_bloc.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -14,39 +16,35 @@ class EditCommunityDialog extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (_) => - UpdateCommunityBloc(RemoteUpdateCommunityService(HTTPService())), + create: (_) => UpdateCommunityBloc( + RemoteUpdateCommunityService(HTTPService()), + ), child: BlocConsumer( listener: (context, state) { switch (state) { - case UpdateCommunityLoading(): - showDialog( - context: context, - builder: (context) => const Center( - child: CircularProgressIndicator(), - ), - ); + case UpdateCommunityInitial() || UpdateCommunityLoading(): + SpaceManagementCommunityDialogHelper.showLoadingDialog(context); break; case UpdateCommunitySuccess(:final community): Navigator.of(context).pop(); Navigator.of(context).pop(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('${community.name} updated successfully'), - ), + SpaceManagementCommunityDialogHelper.showSuccessSnackBar( + context, + '${community.name} community updated successfully', ); + context.read().add( + CommunitiesUpdateCommunity(community), + ); break; case UpdateCommunityFailure(): Navigator.of(context).pop(); break; - default: - break; } }, builder: (context, state) => CommunityDialog( title: const Text('Edit Community'), initialName: community.name, - errorMessage: state is UpdateCommunityFailure ? state.message : null, + errorMessage: state is UpdateCommunityFailure ? state.errorMessage : null, onSubmit: (name) => context.read().add( UpdateCommunity(community.copyWith(name: name)), ), From fdea4b1cd054f2040e6c1843fa29d9baaee5ccfe Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 6 Jul 2025 12:32:18 +0300 Subject: [PATCH 5/8] Refactor CommunityDialog: Extract error message display into a separate method `_buildErrorMessage` for improved readability and maintainability. This change enhances the structure of the dialog while ensuring consistent error handling. --- .../shared/widgets/community_dialog.dart | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/shared/widgets/community_dialog.dart b/lib/pages/space_management_v2/main_module/shared/widgets/community_dialog.dart index f420abbd..32f6f39c 100644 --- a/lib/pages/space_management_v2/main_module/shared/widgets/community_dialog.dart +++ b/lib/pages/space_management_v2/main_module/shared/widgets/community_dialog.dart @@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_name_text_field.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class CommunityDialog extends StatefulWidget { final String? initialName; @@ -72,19 +73,8 @@ class _CommunityDialogState extends State { child: widget.title, ), const SizedBox(height: 18), - CreateCommunityNameTextField( - nameController: _nameController, - ), - if (widget.errorMessage != null) - Padding( - padding: const EdgeInsets.only(top: 18), - child: SelectableText( - '* ${widget.errorMessage}', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.error, - ), - ), - ), + CreateCommunityNameTextField(nameController: _nameController), + _buildErrorMessage(), const SizedBox(height: 24), _buildActionButtons(context), ], @@ -131,4 +121,19 @@ class _CommunityDialogState extends State { widget.onSubmit.call(_nameController.text.trim()); } } + + Widget _buildErrorMessage() { + return Visibility( + visible: widget.errorMessage != null, + child: Padding( + padding: const EdgeInsetsDirectional.symmetric(vertical: 18), + child: SelectableText( + '* ${widget.errorMessage}', + style: context.textTheme.bodyMedium?.copyWith( + color: context.theme.colorScheme.error, + ), + ), + ), + ); + } } From 826dea80546279e1300d74e2310b784d08b3f943 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 6 Jul 2025 12:39:54 +0300 Subject: [PATCH 6/8] Implement community update endpoint: Refactor `RemoteUpdateCommunityService` to dynamically construct the update URL using the project UUID. This change enhances the service's flexibility and error handling by ensuring the correct endpoint is used for community updates. --- .../remote_update_community_service.dart | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/pages/space_management_v2/modules/update_community/data/services/remote_update_community_service.dart b/lib/pages/space_management_v2/modules/update_community/data/services/remote_update_community_service.dart index f8f71afd..d6451a00 100644 --- a/lib/pages/space_management_v2/modules/update_community/data/services/remote_update_community_service.dart +++ b/lib/pages/space_management_v2/modules/update_community/data/services/remote_update_community_service.dart @@ -1,4 +1,5 @@ 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/community_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_community/domain/services/update_community_service.dart'; import 'package:syncrow_web/services/api/api_exception.dart'; @@ -13,14 +14,14 @@ class RemoteUpdateCommunityService implements UpdateCommunityService { @override Future updateCommunity(CommunityModel param) async { + final endpoint = await _makeUrl(param.uuid); try { - final response = await _httpService.put( - path: 'endpoint', - expectedResponseModel: (data) => CommunityModel.fromJson( - data as Map, - ), + await _httpService.put( + path: endpoint, + body: {'name': param.name}, + expectedResponseModel: (data) => null, ); - return response; + return param; } on DioException catch (e) { final message = e.response?.data as Map?; final error = message?['error'] as Map?; @@ -35,4 +36,12 @@ class RemoteUpdateCommunityService implements UpdateCommunityService { throw APIException(formattedErrorMessage); } } + + Future _makeUrl(String communityUuid) async { + final projectUuid = await ProjectManager.getProjectUUID(); + if (projectUuid == null) { + throw APIException('Project UUID is not set'); + } + return '/projects/$projectUuid/communities/$communityUuid'; + } } From 73de1e6ff9dc9e4cdad56647ba3feff257049205 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 6 Jul 2025 12:52:35 +0300 Subject: [PATCH 7/8] Enhance EditCommunityDialog: Refactor to accept parent context and streamline community update handling. Introduced a new method `_onUpdateCommunitySuccess` for improved readability and maintainability. Updated `SpaceManagementCommunityDialogHelper` to pass the parent context for better state management in the community update flow. --- ...ce_management_community_dialog_helper.dart | 5 ++- .../presentation/bloc/communities_bloc.dart | 7 ++-- .../presentation/edit_community_dialog.dart | 36 +++++++++++++------ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart b/lib/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart index 2446ae81..e1981208 100644 --- a/lib/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart +++ b/lib/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart @@ -15,7 +15,10 @@ abstract final class SpaceManagementCommunityDialogHelper { ) { showDialog( context: context, - builder: (_) => EditCommunityDialog(community: community), + builder: (_) => EditCommunityDialog( + community: community, + parentContext: context, + ), ); } diff --git a/lib/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart b/lib/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart index 1b4f10a1..245448ea 100644 --- a/lib/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart +++ b/lib/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart @@ -120,11 +120,12 @@ class CommunitiesBloc extends Bloc { CommunitiesUpdateCommunity event, Emitter emit, ) { + final updatedCommunities = state.communities + .map((e) => e.uuid == event.community.uuid ? event.community : e) + .toList(); emit( state.copyWith( - communities: state.communities - .map((e) => e.uuid == event.community.uuid ? event.community : e) - .toList(), + communities: updatedCommunities, ), ); } diff --git a/lib/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart b/lib/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart index e4c13f3c..b566c02a 100644 --- a/lib/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart +++ b/lib/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart @@ -4,14 +4,20 @@ import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers import 'package:syncrow_web/pages/space_management_v2/main_module/shared/widgets/community_dialog.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/update_community/data/services/remote_update_community_service.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_community/presentation/bloc/update_community_bloc.dart'; import 'package:syncrow_web/services/api/http_service.dart'; class EditCommunityDialog extends StatelessWidget { - const EditCommunityDialog({required this.community, super.key}); + const EditCommunityDialog({ + required this.community, + required this.parentContext, + super.key, + }); final CommunityModel community; + final BuildContext parentContext; @override Widget build(BuildContext context) { @@ -26,15 +32,7 @@ class EditCommunityDialog extends StatelessWidget { SpaceManagementCommunityDialogHelper.showLoadingDialog(context); break; case UpdateCommunitySuccess(:final community): - Navigator.of(context).pop(); - Navigator.of(context).pop(); - SpaceManagementCommunityDialogHelper.showSuccessSnackBar( - context, - '${community.name} community updated successfully', - ); - context.read().add( - CommunitiesUpdateCommunity(community), - ); + _onUpdateCommunitySuccess(context, community); break; case UpdateCommunityFailure(): Navigator.of(context).pop(); @@ -52,4 +50,22 @@ class EditCommunityDialog extends StatelessWidget { ), ); } + + void _onUpdateCommunitySuccess( + BuildContext context, + CommunityModel community, + ) { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + SpaceManagementCommunityDialogHelper.showSuccessSnackBar( + context, + '${community.name} community updated successfully', + ); + parentContext.read().add( + CommunitiesUpdateCommunity(community), + ); + parentContext.read().add( + SelectCommunityEvent(community: community), + ); + } } From 46a7add90de418a7f9b4c9e33b8bf763d336b8bb Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 6 Jul 2025 13:23:50 +0300 Subject: [PATCH 8/8] made subspaces unique and removed duplication from BE side. --- .../views/space_management_page.dart | 5 +++- .../services/unique_subspaces_decorator.dart | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart 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 05768035..af6b1308 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 @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/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/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'; @@ -30,7 +31,9 @@ class SpaceManagementPage extends StatelessWidget { BlocProvider(create: (context) => CommunitiesTreeSelectionBloc()), BlocProvider( create: (context) => SpaceDetailsBloc( - RemoteSpaceDetailsService(httpService: HTTPService()), + UniqueSubspacesDecorator( + 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_subspaces_decorator.dart new file mode 100644 index 00000000..8309c545 --- /dev/null +++ b/lib/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart @@ -0,0 +1,27 @@ +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/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 { + final SpaceDetailsService _decoratee; + + const UniqueSubspacesDecorator(this._decoratee); + + @override + Future getSpaceDetails(LoadSpaceDetailsParam param) async { + final response = await _decoratee.getSpaceDetails(param); + + final uniqueSubspaces = {}; + + for (final subspace in response.subspaces) { + final normalizedName = subspace.name.trim().toLowerCase(); + if (!uniqueSubspaces.containsKey(normalizedName)) { + uniqueSubspaces[normalizedName] = subspace; + } + } + + return response.copyWith( + subspaces: uniqueSubspaces.values.toList(), + ); + } +}