// Flutter imports import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; // Syncrow project imports import 'package:syncrow_web/pages/common/buttons/add_space_button.dart'; import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart'; import 'package:syncrow_web/pages/spaces_management/widgets/blank_community_widget.dart'; import 'package:syncrow_web/pages/spaces_management/widgets/community_structure_header_widget.dart'; import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/create_space_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/widgets/curved_line_painter.dart'; import 'package:syncrow_web/pages/spaces_management/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/spaces_management/widgets/space_container_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CommunityStructureArea extends StatefulWidget { final CommunityModel? selectedCommunity; SpaceModel? selectedSpace; final List? products; final ValueChanged? onSpaceSelected; final List communities; final List spaces; CommunityStructureArea({ this.selectedCommunity, this.selectedSpace, required this.communities, this.products, required this.spaces, this.onSpaceSelected, }); @override _CommunityStructureAreaState createState() => _CommunityStructureAreaState(); } class _CommunityStructureAreaState extends State { double canvasWidth = 1000; double canvasHeight = 1000; List spaces = []; List connections = []; late TextEditingController _nameController; bool isEditingName = false; late TransformationController _transformationController; @override void initState() { super.initState(); spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; _adjustCanvasSizeForSpaces(); _nameController = TextEditingController( text: widget.selectedCommunity?.name ?? '', ); _transformationController = TransformationController(); if (widget.selectedSpace != null) { WidgetsBinding.instance.addPostFrameCallback((_) { _moveToSpace(widget.selectedSpace!); }); } } @override void dispose() { _nameController.dispose(); _transformationController.dispose(); super.dispose(); } @override void didUpdateWidget(covariant CommunityStructureArea oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.spaces != widget.spaces) { setState(() { spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; _adjustCanvasSizeForSpaces(); }); } if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) { WidgetsBinding.instance.addPostFrameCallback((_) { _moveToSpace(widget.selectedSpace!); }); } } @override Widget build(BuildContext context) { if (widget.selectedCommunity == null) { return BlankCommunityWidget( communities: widget.communities, ); } Size screenSize = MediaQuery.of(context).size; return Expanded( child: GestureDetector( onTap: () { _deselectSpace(context); }, child: Container( decoration: const BoxDecoration( border: Border( left: BorderSide(color: ColorsManager.whiteColors, width: 1.0), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ CommunityStructureHeader( communities: widget.communities, communityName: widget.selectedCommunity?.name, community: widget.selectedCommunity, isSave: isSave(spaces), isEditingName: isEditingName, nameController: _nameController, onSave: _saveSpaces, onDelete: _onDelete, onEditName: () { setState(() { isEditingName = !isEditingName; if (isEditingName) { _nameController.text = widget.selectedCommunity?.name ?? ''; } }); }, onNameSubmitted: (value) { context.read().add( UpdateCommunityEvent( communityUuid: widget.selectedCommunity!.uuid, name: value, ), ); setState(() { widget.selectedCommunity?.name = value; isEditingName = false; }); }, ), Flexible( child: Stack( children: [ InteractiveViewer( transformationController: _transformationController, boundaryMargin: EdgeInsets.all(500), minScale: 0.5, maxScale: 3.0, constrained: false, child: Container( width: canvasWidth, height: canvasHeight, child: Stack( children: [ for (var connection in connections) Opacity( opacity: _isHighlightedConnection(connection) ? 1.0 : 0.3, // Adjust opacity child: CustomPaint( painter: CurvedLinePainter([connection])), ), for (var entry in spaces.asMap().entries) if (entry.value.status != SpaceStatus.deleted) Positioned( left: entry.value.position.dx, top: entry.value.position.dy, child: SpaceCardWidget( index: entry.key, onButtonTap: (int index, Offset newPosition, String direction) { _showCreateSpaceDialog( screenSize, position: spaces[index].position + newPosition, parentIndex: index, direction: direction, ); }, position: entry.value.position, isHovered: entry.value.isHovered, screenSize: screenSize, onHoverChanged: _handleHoverChanged, onPositionChanged: (newPosition) { _updateNodePosition(entry.value, newPosition); }, buildSpaceContainer: (int index) { final bool isHighlighted = _isHighlightedSpace(spaces[index]); return Opacity( opacity: isHighlighted ? 1.0 : 0.3, child: SpaceContainerWidget( index: index, onDoubleTap: () { _showEditSpaceDialog(spaces[index]); }, onTap: () { _selectSpace(context, spaces[index]); }, icon: spaces[index].icon ?? '', name: spaces[index].name, )); }, ), ), ], ), ), ), if (spaces.isEmpty) Center( child: AddSpaceButton( onTap: () { _showCreateSpaceDialog(screenSize, canvasHeight: canvasHeight, canvasWidth: canvasWidth); }, ), ), ], )), ], ), ), )); } void _updateNodePosition(SpaceModel node, Offset newPosition) { setState(() { node.position = newPosition; if (node.status != SpaceStatus.newSpace) { node.status = SpaceStatus.modified; // Mark as modified } if (node.position.dx >= canvasWidth - 200) { canvasWidth += 200; } if (node.position.dy >= canvasHeight - 200) { canvasHeight += 200; } if (node.position.dx <= 200) { double shiftAmount = 200; canvasWidth += shiftAmount; for (var n in spaces) { n.position = Offset(n.position.dx + shiftAmount, n.position.dy); } } if (node.position.dy < 0) { node.position = Offset(node.position.dx, 0); } }); } void _adjustCanvasSizeForSpaces() { for (var space in spaces) { if (space.position.dx >= canvasWidth - 200) { canvasWidth = space.position.dx + 200; } if (space.position.dy >= canvasHeight - 200) { canvasHeight = space.position.dy + 200; } } } void _showCreateSpaceDialog(Size screenSize, {Offset? position, int? parentIndex, String? direction, double? canvasWidth, double? canvasHeight}) { showDialog( context: context, builder: (BuildContext context) { return CreateSpaceDialog( products: widget.products, parentSpace: parentIndex != null ? spaces[parentIndex] : null, onCreateSpace: (String name, String icon, List selectedProducts) { setState(() { // Set the first space in the center or use passed position Offset centerPosition = position ?? _getCenterPosition(screenSize); SpaceModel newSpace = SpaceModel( name: name, icon: icon, position: centerPosition, isPrivate: false, children: [], status: SpaceStatus.newSpace, selectedProducts: selectedProducts); if (parentIndex != null && direction != null) { SpaceModel parentSpace = spaces[parentIndex]; parentSpace.internalId = spaces[parentIndex].internalId; newSpace.parent = parentSpace; final newConnection = Connection( startSpace: parentSpace, endSpace: newSpace, direction: direction, ); connections.add(newConnection); newSpace.incomingConnection = newConnection; parentSpace.addOutgoingConnection(newConnection); parentSpace.children.add(newSpace); } spaces.add(newSpace); _updateNodePosition(newSpace, newSpace.position); }); }, ); }, ); } void _showEditSpaceDialog(SpaceModel space) { showDialog( context: context, builder: (BuildContext context) { return CreateSpaceDialog( products: widget.products, name: space.name, icon: space.icon, editSpace: space, isEdit: true, selectedProducts: space.selectedProducts, onCreateSpace: (String name, String icon, List selectedProducts) { setState(() { // Update the space's properties space.name = name; space.icon = icon; space.selectedProducts = selectedProducts; if (space.status != SpaceStatus.newSpace) { space.status = SpaceStatus.modified; // Mark as modified } }); }, key: Key(space.name), ); }, ); } void _handleHoverChanged(int index, bool isHovered) { setState(() { spaces[index].isHovered = isHovered; }); } List flattenSpaces(List spaces) { List result = []; void flatten(SpaceModel space) { if (space.status == SpaceStatus.deleted) return; result.add(space); for (var child in space.children) { flatten(child); } } for (var space in spaces) { flatten(space); } return result; } List createConnections(List spaces) { List connections = []; void addConnections(SpaceModel parent, String direction) { if (parent.status == SpaceStatus.deleted) return; for (var child in parent.children) { if (child.status == SpaceStatus.deleted) continue; connections.add( Connection( startSpace: parent, endSpace: child, direction: child.incomingConnection?.direction ?? "down", ), ); // Recursively process the child's children addConnections(child, direction); } } for (var space in spaces) { addConnections(space, "down"); } return connections; } void _saveSpaces() { if (widget.selectedCommunity == null) { debugPrint("No community selected for saving spaces."); return; } List spacesToSave = spaces.where((space) { return space.status == SpaceStatus.newSpace || space.status == SpaceStatus.modified || space.status == SpaceStatus.deleted; }).toList(); if (spacesToSave.isEmpty) { debugPrint("No new or modified spaces to save."); return; } String communityUuid = widget.selectedCommunity!.uuid; context.read().add(SaveSpacesEvent( spaces: spacesToSave, communityUuid: communityUuid, )); } void _onDelete() { if (widget.selectedCommunity != null && widget.selectedCommunity?.uuid != null && widget.selectedSpace == null) { context.read().add(DeleteCommunityEvent( communityUuid: widget.selectedCommunity!.uuid, )); } if (widget.selectedSpace != null) { setState(() { for (var space in spaces) { if (space.uuid == widget.selectedSpace?.uuid) { space.status = SpaceStatus.deleted; _markChildrenAsDeleted(space); } } _removeConnectionsForDeletedSpaces(); }); } } void _markChildrenAsDeleted(SpaceModel parent) { for (var child in parent.children) { child.status = SpaceStatus.deleted; _markChildrenAsDeleted(child); } } void _removeConnectionsForDeletedSpaces() { connections.removeWhere((connection) { return connection.startSpace.status == SpaceStatus.deleted || connection.endSpace.status == SpaceStatus.deleted; }); } void _moveToSpace(SpaceModel space) { final double viewportWidth = MediaQuery.of(context).size.width; final double viewportHeight = MediaQuery.of(context).size.height; final double dx = -space.position.dx + (viewportWidth / 2) - 400; final double dy = -space.position.dy + (viewportHeight / 2) - 300; _transformationController.value = Matrix4.identity() ..translate(dx, dy) ..scale(1.2); } void _selectSpace(BuildContext context, SpaceModel space) { context.read().add( SelectSpaceEvent( selectedCommunity: widget.selectedCommunity, selectedSpace: space), ); } bool _isHighlightedSpace(SpaceModel space) { final selectedSpace = widget.selectedSpace; if (selectedSpace == null) return true; return space == selectedSpace || selectedSpace.parent?.internalId == space.internalId || selectedSpace.children ?.any((child) => child.internalId == space.internalId) == true; } void _deselectSpace(BuildContext context) { context.read().add( SelectSpaceEvent( selectedCommunity: widget.selectedCommunity, selectedSpace: null), ); } bool _isHighlightedConnection(Connection connection) { if (widget.selectedSpace == null) return true; return connection.startSpace == widget.selectedSpace || connection.endSpace == widget.selectedSpace; } Offset _getCenterPosition(Size screenSize) { return Offset( screenSize.width / 2 - 260, screenSize.height / 2 - 200, ); } bool isSave(List spaces) { return spaces.isNotEmpty && spaces.any((space) => space.status == SpaceStatus.newSpace || space.status == SpaceStatus.modified || space.status == SpaceStatus.deleted); } }