import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; class SpaceTreeBloc extends Bloc { SpaceTreeBloc() : super(const SpaceTreeState()) { on(_fetchSpaces); on(_onCommunityExpanded); on(_onSpaceExpanded); on(_onCommunitySelected); on(_onSpaceSelected); on(_onSearch); on(_clearAllData); on(_clearCachedData); } _fetchSpaces(InitialEvent event, Emitter emit) async { emit(SpaceTreeLoadingState()); try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; List communities = await CommunitySpaceManagementApi().fetchCommunities(projectUuid); List updatedCommunities = await Future.wait( communities.map((community) async { List spaces = await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid, projectUuid); return CommunityModel( uuid: community.uuid, createdAt: community.createdAt, updatedAt: community.updatedAt, name: community.name, description: community.description, spaces: spaces, region: community.region, ); }).toList(), ); emit(state.copyWith( communitiesList: updatedCommunities, expandedCommunity: [], expandedSpaces: [])); } catch (e) { emit(SpaceTreeErrorState('Error loading communities and spaces: $e')); } } _onCommunityExpanded(OnCommunityExpanded event, Emitter emit) async { try { List updatedExpandedCommunityList = List.from(state.expandedCommunities); if (updatedExpandedCommunityList.contains(event.communityId)) { updatedExpandedCommunityList.remove(event.communityId); } else { updatedExpandedCommunityList.add(event.communityId); } emit(state.copyWith( expandedCommunity: updatedExpandedCommunityList, )); } catch (e) { emit(const SpaceTreeErrorState('Something went wrong')); } } _onSpaceExpanded(OnSpaceExpanded event, Emitter emit) async { try { List updatedExpandedSpacesList = List.from(state.expandedSpaces); if (updatedExpandedSpacesList.contains(event.spaceId)) { updatedExpandedSpacesList.remove(event.spaceId); } else { updatedExpandedSpacesList.add(event.spaceId); } emit(state.copyWith(expandedSpaces: updatedExpandedSpacesList)); } catch (e) { emit(const SpaceTreeErrorState('Something went wrong')); } } _onCommunitySelected(OnCommunitySelected event, Emitter emit) async { try { List updatedSelectedCommunities = List.from(state.selectedCommunities.toSet().toList()); List updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList()); List updatedSoldChecks = List.from(state.soldCheck.toSet().toList()); Map> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces); List childrenIds = _getAllChildIds(event.children); if (!updatedSelectedCommunities.contains(event.communityId)) { // Select the community and all its children updatedSelectedCommunities.add(event.communityId); updatedSelectedSpaces.addAll(childrenIds); } else { // Unselect the community and all its children updatedSelectedCommunities.remove(event.communityId); updatedSelectedSpaces.removeWhere(childrenIds.contains); updatedSoldChecks.removeWhere(childrenIds.contains); } communityAndSpaces[event.communityId] = updatedSelectedSpaces; emit(state.copyWith( selectedCommunities: updatedSelectedCommunities, selectedSpaces: updatedSelectedSpaces, soldCheck: updatedSoldChecks, selectedCommunityAndSpaces: communityAndSpaces)); } catch (e) { emit(const SpaceTreeErrorState('Something went wrong')); } } _onSpaceSelected(OnSpaceSelected event, Emitter emit) async { try { List updatedSelectedCommunities = List.from(state.selectedCommunities.toSet().toList()); List updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList()); List updatedSoldChecks = List.from(state.soldCheck.toSet().toList()); Map> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces); List childrenIds = _getAllChildIds(event.children); bool isChildSelected = false; for (String id in childrenIds) { if (updatedSelectedSpaces.contains(id)) { isChildSelected = true; } } if (!updatedSelectedSpaces.contains(event.spaceId) && !updatedSoldChecks.contains(event.spaceId)) { // First click: Select the space and all its children updatedSelectedSpaces.add(event.spaceId); updatedSelectedCommunities.add(event.communityModel.uuid); if (childrenIds.isNotEmpty) { updatedSelectedSpaces.addAll(childrenIds); } List spaces = _getThePathToChild(event.communityModel.uuid, event.spaceId); for (String space in spaces) { if (!updatedSelectedSpaces.contains(space) && !updatedSoldChecks.contains(space)) { updatedSoldChecks.add(space); } } } else if (!updatedSoldChecks.contains(event.spaceId) && childrenIds.isNotEmpty && isChildSelected) { // Second click: Unselect space but keep children updatedSelectedSpaces.remove(event.spaceId); updatedSoldChecks.add(event.spaceId); } else { // Third click: Unselect space and all its children updatedSelectedSpaces.remove(event.spaceId); if (childrenIds.isNotEmpty) { updatedSelectedSpaces.removeWhere(childrenIds.contains); updatedSoldChecks.removeWhere(childrenIds.contains); } updatedSoldChecks.remove(event.spaceId); List parents = _getThePathToChild(event.communityModel.uuid, event.spaceId).toSet().toList(); if (updatedSelectedSpaces.isEmpty) { updatedSoldChecks.removeWhere(parents.contains); updatedSelectedCommunities.remove(event.communityModel.uuid); } else { // Check if any parent has selected children for (String space in parents) { if (!_noChildrenSelected(event.communityModel, space, updatedSelectedSpaces, parents)) { updatedSoldChecks.remove(space); } } if (!_anySpacesSelectedInCommunity( event.communityModel, updatedSelectedSpaces, updatedSoldChecks)) { updatedSelectedCommunities.remove(event.communityModel.uuid); } } } communityAndSpaces[event.communityModel.uuid] = updatedSelectedSpaces; emit(state.copyWith( selectedCommunities: updatedSelectedCommunities.toSet().toList(), selectedSpaces: updatedSelectedSpaces, soldCheck: updatedSoldChecks, selectedCommunityAndSpaces: communityAndSpaces)); emit(state.copyWith(selectedSpaces: updatedSelectedSpaces)); } catch (e) { emit(const SpaceTreeErrorState('Something went wrong')); } } _noChildrenSelected( CommunityModel community, String spaceId, List selectedSpaces, List parents) { if (selectedSpaces.contains(spaceId)) { return true; } List children = _getAllChildSpaces(community.spaces); for (var child in children) { if (spaceId == child.uuid) { List ids = _getAllChildIds(child.children); for (var id in ids) { if (selectedSpaces.contains(id)) { return true; } } } } return false; } _onSearch(SearchQueryEvent event, Emitter emit) async { try { List communities = List.from(state.communityList); List filteredCommunity = []; // Filter communities and expand only those that match the query filteredCommunity = communities.where((community) { final containsQueryInCommunity = community.name.toLowerCase().contains(event.searchQuery.toLowerCase()); final containsQueryInSpaces = community.spaces.any((space) => _containsQuery(space, event.searchQuery.toLowerCase())); return containsQueryInCommunity || containsQueryInSpaces; }).toList(); emit(state.copyWith( filteredCommunity: filteredCommunity, isSearching: event.searchQuery.isNotEmpty, searchQuery: event.searchQuery)); } catch (e) { emit(const SpaceTreeErrorState('Something went wrong')); } } _clearAllData(ClearAllData event, Emitter emit) async { try { emit(state.copyWith( communitiesList: [], filteredCommunity: [], isSearching: false, soldCheck: [], selectedSpaces: [], selectedCommunities: [], selectedCommunityAndSpaces: {}, searchQuery: '', expandedSpaces: [], expandedCommunity: [])); } catch (e) { emit(const SpaceTreeErrorState('Something went wrong')); } } _clearCachedData(ClearCachedData event, Emitter emit) async { try { emit(state.copyWith( communitiesList: state.communityList, filteredCommunity: [], isSearching: false, soldCheck: [], selectedSpaces: [], selectedCommunities: [], selectedCommunityAndSpaces: {}, searchQuery: '', expandedSpaces: [], expandedCommunity: [])); } catch (e) { emit(const SpaceTreeErrorState('Something went wrong')); } } // Helper function to determine if any space or its children match the search query bool _containsQuery(SpaceModel space, String query) { final matchesSpace = space.name.toLowerCase().contains(query); final matchesChildren = space.children.any((child) => _containsQuery(child, query)); // Recursive check for children return matchesSpace || matchesChildren; } List _getAllChildIds(List spaces) { List ids = []; for (var child in spaces) { ids.add(child.uuid!); ids.addAll(_getAllChildIds(child.children)); } return ids.toSet().toList(); } List _getAllChildSpaces(List spaces) { List children = []; for (var child in spaces) { children.add(child); children.addAll(_getAllChildSpaces(child.children)); } return children; } bool _anySpacesSelectedInCommunity( CommunityModel community, List selectedSpaces, List partialCheckedList) { bool result = false; List ids = _getAllChildIds(community.spaces); for (var id in ids) { result = selectedSpaces.contains(id) || partialCheckedList.contains(id); if (result) { return result; } } return result; } List _getThePathToChild(String communityId, String selectedSpaceId) { List ids = []; for (var community in state.communityList) { if (community.uuid == communityId) { for (var space in community.spaces) { List list = []; list.add(space.uuid!); ids = _getAllParentsIds(space, selectedSpaceId, List.from(list)); if (ids.isNotEmpty) { return ids; } } } } return ids; } List _getAllParentsIds(SpaceModel child, String spaceId, List listIds) { List ids = listIds; ids.add(child.uuid ?? ''); if (child.uuid == spaceId) { return ids; } if (child.children.isNotEmpty) { for (var space in child.children) { var result = _getAllParentsIds(space, spaceId, List.from(ids)); if (result.isNotEmpty) { return result; } } } ids.removeLast(); return []; } @override Future close() async { super.close(); } }