From 7331c8440b567e8413c525ad8a5bbba51a193c5a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Jul 2025 10:27:22 +0300 Subject: [PATCH] Refactor SpaceManagementPage to use StatefulWidget and initialize CommunitiesBloc in initState. Update CommunityStructureHeader to handle community updates and improve state management in CommunitiesTreeSelectionBloc with new event for community state updates. --- .../views/space_management_page.dart | 30 +++++-- .../widgets/community_structure_header.dart | 36 +++++++++ .../domain/models/space_model.dart | 19 +++++ .../communities_tree_selection_bloc.dart | 79 ++++++++++++++++++- .../communities_tree_selection_event.dart | 9 +++ .../communities_tree_selection_state.dart | 12 +-- .../helpers/space_details_dialog_helper.dart | 24 +++++- 7 files changed, 189 insertions(+), 20 deletions(-) diff --git a/lib/pages/space_management_v2/main_module/views/space_management_page.dart b/lib/pages/space_management_v2/main_module/views/space_management_page.dart index 106b9a3a..40a37891 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 @@ -16,21 +16,37 @@ import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; -class SpaceManagementPage extends StatelessWidget { +class SpaceManagementPage extends StatefulWidget { const SpaceManagementPage({super.key}); + @override + State createState() => _SpaceManagementPageState(); +} + +class _SpaceManagementPageState extends State { + late final CommunitiesBloc communitiesBloc; + + @override + void initState() { + communitiesBloc = CommunitiesBloc( + communitiesService: DebouncedCommunitiesService( + RemoteCommunitiesService(HTTPService()), + ), + )..add(const LoadCommunities(LoadCommunitiesParam())); + + super.initState(); + } + @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ + BlocProvider.value(value: communitiesBloc), BlocProvider( - create: (context) => CommunitiesBloc( - communitiesService: DebouncedCommunitiesService( - RemoteCommunitiesService(HTTPService()), - ), - )..add(const LoadCommunities(LoadCommunitiesParam())), + create: (context) => CommunitiesTreeSelectionBloc( + communitiesBloc: communitiesBloc, + ), ), - BlocProvider(create: (context) => CommunitiesTreeSelectionBloc()), BlocProvider( create: (context) => SpaceDetailsBloc( UniqueSubspacesDecorator( 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 f27dc8b9..cb6271d1 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 @@ -3,7 +3,10 @@ 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/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_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/presentation/helpers/space_details_dialog_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -11,6 +14,26 @@ import 'package:syncrow_web/utils/constants/assets.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); @@ -99,6 +122,19 @@ class CommunityStructureHeader extends StatelessWidget { 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)); + }, ), selectedSpace: selectedSpace, ), diff --git a/lib/pages/space_management_v2/modules/communities/domain/models/space_model.dart b/lib/pages/space_management_v2/modules/communities/domain/models/space_model.dart index ddcc6a86..bd5a2e50 100644 --- a/lib/pages/space_management_v2/modules/communities/domain/models/space_model.dart +++ b/lib/pages/space_management_v2/modules/communities/domain/models/space_model.dart @@ -46,6 +46,25 @@ class SpaceModel extends Equatable { ); } + SpaceModel copyWith({ + String? uuid, + DateTime? createdAt, + DateTime? updatedAt, + String? spaceName, + String? icon, + List? children, + }) { + return SpaceModel( + uuid: uuid ?? this.uuid, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + spaceName: spaceName ?? this.spaceName, + icon: icon ?? this.icon, + children: children ?? this.children, + parent: parent, + ); + } + @override List get props => [uuid, spaceName, icon, children]; } diff --git a/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart b/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart index bdda04ee..25263d35 100644 --- a/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart +++ b/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart @@ -1,17 +1,39 @@ +import 'dart:async'; + 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/communities/domain/models/space_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart'; part 'communities_tree_selection_event.dart'; part 'communities_tree_selection_state.dart'; class CommunitiesTreeSelectionBloc extends Bloc { - CommunitiesTreeSelectionBloc() : super(const CommunitiesTreeSelectionState()) { + CommunitiesTreeSelectionBloc({ + required CommunitiesBloc communitiesBloc, + }) : _communitiesBloc = communitiesBloc, + super(const CommunitiesTreeSelectionState()) { on(_onSelectCommunity); on(_onSelectSpace); on(_onClearSelection); + on<_CommunitiesStateUpdated>(_onCommunitiesStateUpdated); + + _communitiesSubscription = _communitiesBloc.stream.listen((communitiesState) { + if (state.selectedCommunity != null) { + add(_CommunitiesStateUpdated(communitiesState)); + } + }); + } + + final CommunitiesBloc _communitiesBloc; + late final StreamSubscription _communitiesSubscription; + + @override + Future close() { + _communitiesSubscription.cancel(); + return super.close(); } void _onSelectCommunity( @@ -44,4 +66,59 @@ class CommunitiesTreeSelectionBloc ) { emit(const CommunitiesTreeSelectionState()); } + + void _onCommunitiesStateUpdated( + _CommunitiesStateUpdated event, + Emitter emit, + ) { + if (state.selectedCommunity == null) return; + + final communities = event.communitiesState.communities; + try { + final updatedCommunity = communities.firstWhere( + (c) => c.uuid == state.selectedCommunity!.uuid, + ); + + var updatedSelectedSpace = state.selectedSpace; + if (state.selectedSpace != null) { + updatedSelectedSpace = _findSpaceInCommunity( + updatedCommunity, + state.selectedSpace!.uuid, + ); + } + emit( + state.copyWith( + selectedCommunity: updatedCommunity, + selectedSpace: updatedSelectedSpace, + clearSelectedSpace: updatedSelectedSpace == null, + ), + ); + } catch (_) { + add(const ClearCommunitiesTreeSelectionEvent()); + } + } + + SpaceModel? _findSpaceInCommunity(CommunityModel community, String spaceUuid) { + try { + return _findSpaceRecursive(community.spaces, spaceUuid); + } catch (_) { + return null; + } + } + + SpaceModel _findSpaceRecursive(List spaces, String spaceUuid) { + for (final space in spaces) { + if (space.uuid == spaceUuid) { + return space; + } + if (space.children.isNotEmpty) { + try { + return _findSpaceRecursive(space.children, spaceUuid); + } catch (_) { + // not found in this branch + } + } + } + throw Exception('Space not found'); + } } diff --git a/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_event.dart b/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_event.dart index 21088632..43a69e05 100644 --- a/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_event.dart +++ b/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_event.dart @@ -29,3 +29,12 @@ final class ClearCommunitiesTreeSelectionEvent extends CommunitiesTreeSelectionEvent { const ClearCommunitiesTreeSelectionEvent(); } + +final class _CommunitiesStateUpdated extends CommunitiesTreeSelectionEvent { + const _CommunitiesStateUpdated(this.communitiesState); + + final CommunitiesState communitiesState; + + @override + List get props => [communitiesState]; +} diff --git a/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_state.dart b/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_state.dart index b14d330b..4c36f778 100644 --- a/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_state.dart +++ b/lib/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_state.dart @@ -12,18 +12,14 @@ final class CommunitiesTreeSelectionState extends Equatable { CommunitiesTreeSelectionState copyWith({ CommunityModel? selectedCommunity, SpaceModel? selectedSpace, - List? expandedCommunities, - List? expandedSpaces, + bool clearSelectedSpace = false, }) { return CommunitiesTreeSelectionState( selectedCommunity: selectedCommunity ?? this.selectedCommunity, - selectedSpace: selectedSpace ?? this.selectedSpace, + selectedSpace: clearSelectedSpace ? null : selectedSpace ?? this.selectedSpace, ); } @override - List get props => [ - selectedCommunity, - selectedSpace, - ]; - } + List get props => [selectedCommunity, selectedSpace]; +} 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 031e0399..c5de7dad 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 @@ -42,6 +42,7 @@ abstract final class SpaceDetailsDialogHelper { BuildContext context, { required SpaceModel spaceModel, required String communityUuid, + required void Function(SpaceDetailsModel updatedSpaceDetails)? onSuccess, }) { showDialog( context: context, @@ -60,7 +61,11 @@ abstract final class SpaceDetailsDialogHelper { ], child: Builder( builder: (context) => BlocListener( - listener: _updateListener, + listener: (context, state) => _updateListener( + context, + state, + onSuccess, + ), child: SpaceDetailsDialog( context: context, title: const SelectableText('Edit Space'), @@ -81,17 +86,28 @@ abstract final class SpaceDetailsDialogHelper { ); } - static void _updateListener(BuildContext context, UpdateSpaceState state) { + static void _updateListener( + BuildContext context, + UpdateSpaceState state, + void Function(SpaceDetailsModel updatedSpaceDetails)? onSuccess, + ) { return switch (state) { UpdateSpaceInitial() => null, UpdateSpaceLoading() => _onLoading(context), - UpdateSpaceSuccess(:final space) => _onUpdateSuccess(context, space), + UpdateSpaceSuccess(:final space) => + _onUpdateSuccess(context, space, onSuccess), UpdateSpaceFailure(:final errorMessage) => _onError(context, errorMessage), }; } - static void _onUpdateSuccess(BuildContext context, SpaceDetailsModel space) { + static void _onUpdateSuccess( + BuildContext context, + SpaceDetailsModel space, + void Function(SpaceDetailsModel updatedSpaceDetails)? onSuccess, + ) { Navigator.of(context).pop(); + Navigator.of(context).pop(); + onSuccess?.call(space); } static void _onLoading(BuildContext context) {