diff --git a/lib/pages/spaces_management/view/spaces_management_page.dart b/lib/pages/spaces_management/view/spaces_management_page.dart index ef33604c..7d308dd1 100644 --- a/lib/pages/spaces_management/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/view/spaces_management_page.dart @@ -19,6 +19,7 @@ import 'package:syncrow_web/pages/spaces_management/widgets/space_container_widg import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; +import 'package:fl_chart/fl_chart.dart'; class SpaceManagementPage extends StatefulWidget { const SpaceManagementPage({super.key}); @@ -28,75 +29,44 @@ class SpaceManagementPage extends StatefulWidget { } class SpaceManagementPageState extends State { - // Store created spaces - List spaces = []; - List connections = []; - double canvasWidth = 1000; // Initial canvas width - double canvasHeight = 1000; // Initial canvas height - - // Track whether to show the community list view or community structure - - // Selected community CommunityModel? selectedCommunity; - - // API instance final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi(); - - // Data structure to store community and associated spaces Map> communitySpaces = {}; + double canvasWidth = 1000; + double canvasHeight = 1000; + + final List nodes = [ + NodeData(id: 'Node 1', position: Offset(100, 100)), + NodeData(id: 'Node 2', position: Offset(300, 300)), + NodeData(id: 'Node 3', position: Offset(500, 500)), + ]; @override void initState() { super.initState(); } - void updateCanvasSize() { - double maxX = 0; - double maxY = 0; - - // Calculate the maximum X and Y positions of all spaces - for (var space in spaces) { - maxX = max(maxX, space.position.dx + 150); // Add width of space - maxY = max(maxY, space.position.dy + 60); // Add height of space - } - - // Add padding (but avoid adding arbitrary amounts like 1000) - double newWidth = - max(maxX + 500, canvasWidth); // Use max to ensure the canvas only grows - double newHeight = max(maxY + 500, canvasHeight); - - // Set the new canvas size dynamically - setState(() { - canvasWidth = newWidth; - canvasHeight = newHeight; - }); - } - @override Widget build(BuildContext context) { Size screenSize = MediaQuery.of(context).size; - return BlocProvider( create: (context) => SpaceManagementBloc(CommunitySpaceManagementApi()) ..add(LoadCommunityAndSpacesEvent()), child: WebScaffold( - appBarTitle: Text( - 'Space Management', - style: Theme.of(context).textTheme.headlineLarge, - ), + appBarTitle: Text('Space Management', + style: Theme.of(context).textTheme.headlineLarge), enableMenuSidebar: false, scaffoldBody: BlocBuilder( - builder: (context, state) { - if (state is SpaceManagementLoading) { - return const Center(child: CircularProgressIndicator()); - } else if (state is SpaceManagementLoaded) { - return _buildLoadedState(context, screenSize, state.communities); - } else if (state is SpaceManagementError) { - return Center(child: Text('Error: ${state.errorMessage}')); - } - return Container(); - }, - ), + builder: (context, state) { + if (state is SpaceManagementLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is SpaceManagementLoaded) { + return _buildLoadedState(context, screenSize, state.communities); + } else if (state is SpaceManagementError) { + return Center(child: Text('Error: ${state.errorMessage}')); + } + return Container(); + }), ), ); } @@ -126,166 +96,271 @@ class SpaceManagementPageState extends State { Widget _buildCommunityStructureArea(BuildContext context, Size screenSize) { return Expanded( - child: Container( - decoration: const BoxDecoration( - border: Border( - left: BorderSide( - color: ColorsManager.whiteColors, - width: 1.0), // Light left border to match + child: Container( + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.whiteColors, + width: 1.0), // Light left border to match + ), + ), + // Background color for canvas + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Container( + padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 27.0), + width: double.infinity, + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + boxShadow: [ + BoxShadow( + color: ColorsManager.shadowBlackColor, // Subtle shadow + spreadRadius: 0, // No spread + blurRadius: 8, // Softer shadow edges + offset: const Offset(0, 4), // Shadow only on the bottom + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Community Structure', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + if (selectedCommunity != null) ...[ + Text( + selectedCommunity!.name, // Show community name + style: const TextStyle( + fontSize: 16, + color: ColorsManager.blackColor, + ), + ), + ], + ], ), ), - // Background color for canvas - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + Flexible( + child: InteractiveViewer( + boundaryMargin: EdgeInsets.all(500), // Adjusted for smoother panning + minScale: 0.5, + maxScale: 3.0, + constrained: false, + child: Container( + width: canvasWidth, + height: canvasHeight, + child: Stack( + children: [ + // Draw connections between nodes + for (int i = 0; i < nodes.length - 1; i++) + CustomPaint( + painter: + EdgePainter(nodes[i].position, nodes[i + 1].position), + ), + // Render each node and make it draggable + for (var node in nodes) + Positioned( + left: node.position.dx, + top: node.position.dy, + child: DraggableNode( + data: node, + onAddNode: (direction) => _addNode(node, direction), + onPositionChanged: (newPosition) { + _updateNodePosition(node, newPosition); + }, + ), + ), + ], + ), + ), + )) + ]), + )); + } + + void _updateNodePosition(NodeData node, Offset newPosition) { + setState(() { + node.position = newPosition; + + // Expand canvas to the right when node approaches the right edge + if (node.position.dx >= canvasWidth - 200) { + canvasWidth += 200; + print("Canvas width expanded to $canvasWidth"); + } + + // Expand canvas downward when node approaches the bottom edge + if (node.position.dy >= canvasHeight - 200) { + canvasHeight += 200; + print("Canvas height expanded to $canvasHeight"); + } + + // Expand canvas to the left when node approaches the left edge + if (node.position.dx <= 200) { + double shiftAmount = 200; + canvasWidth += shiftAmount; + + // Shift all nodes to the right by shiftAmount + for (var n in nodes) { + n.position = Offset(n.position.dx + shiftAmount, n.position.dy); + } + + print("Canvas expanded to the left. New width: $canvasWidth"); + } + + // Prevent nodes from going out of bounds on top edge + if (node.position.dy < 0) { + node.position = Offset(node.position.dx, 0); + } + + // Log the current canvas size for debugging + print( + "Current canvas size: width = $canvasWidth, height = $canvasHeight"); + }); + } + + void _addNode(NodeData parent, String direction) { + Offset newPosition; + switch (direction) { + case "right": + newPosition = parent.position + Offset(200, 0); + break; + case "left": + newPosition = parent.position - Offset(200, 0); + break; + case "bottom": + newPosition = parent.position + Offset(0, 200); + break; + default: + return; + } + + setState(() { + nodes + .add(NodeData(id: 'Node ${nodes.length + 1}', position: newPosition)); + + // Expand the canvas if necessary + if (newPosition.dx >= canvasWidth - 200) canvasWidth += 200; + if (newPosition.dy >= canvasHeight - 200) canvasHeight += 200; + if (newPosition.dx < 0) canvasWidth += 200; + if (newPosition.dy < 0) canvasHeight += 200; + + print("New node added in direction $direction at $newPosition"); + }); + } +} + +class NodeData { + String id; + Offset position; + + NodeData({required this.id, required this.position}); +} + +class DraggableNode extends StatefulWidget { + final NodeData data; + final ValueChanged onPositionChanged; + final ValueChanged + onAddNode; // Callback for adding a node in a specific direction + + DraggableNode({ + required this.data, + required this.onPositionChanged, + required this.onAddNode, + }); + + @override + _DraggableNodeState createState() => _DraggableNodeState(); +} + +class _DraggableNodeState extends State { + bool isHovered = false; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => setState(() => isHovered = true), + onExit: (_) => setState(() => isHovered = false), + child: GestureDetector( + onPanUpdate: (details) { + final newPosition = widget.data.position + details.delta; + widget.onPositionChanged(newPosition); + }, + child: Stack( + alignment: Alignment.center, children: [ + // Main node container Container( - padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 27.0), - width: double.infinity, + padding: EdgeInsets.all(8), + width: 150, + height: 60, decoration: BoxDecoration( color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( - color: ColorsManager.shadowBlackColor, // Subtle shadow - spreadRadius: 0, // No spread - blurRadius: 8, // Softer shadow edges - offset: const Offset(0, 4), // Shadow only on the bottom + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: const Offset(0, 3), // shadow position ), ], ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Community Structure', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - if (selectedCommunity != null) ...[ - Text( - selectedCommunity!.name, // Show community name - style: const TextStyle( - fontSize: 16, - color: ColorsManager.blackColor, - ), - ), - ] - ], + child: Text( + widget.data.id, + style: TextStyle(color: Colors.white), ), ), - // Use Expanded to ensure InteractiveViewer takes the available space - Flexible( - child: InteractiveViewer( - boundaryMargin: const EdgeInsets.all(20000), // Adjusted to 500 - minScale: 0.5, // Minimum zoom scale - maxScale: 5.5, // Maximum zoom scale - panEnabled: true, // Enable panning - scaleEnabled: true, // Enable zooming - child: Container( - width: canvasWidth, // Large width for free movement - height: canvasHeight, // Large height for free movement - child: spaces.isEmpty - ? Center( - child: AddSpaceButton( - onTap: () { - _showCreateSpaceDialog(screenSize); - }, - ), - ) - : Stack( - clipBehavior: Clip.none, - children: [ - CustomPaint( - size: const Size(4000, 4000), - painter: CurvedLinePainter(connections), - ), - ...spaces.asMap().entries.map((entry) { - final space = entry.value; - return Positioned( - left: space.position.dx, - top: space.position.dy, - child: SpaceCardWidget( - index: entry.key, - screenSize: screenSize, - position: space.position, - isHovered: space.isHovered, - onPanUpdate: (int index, Offset delta) { - setState(() { - spaces[index].position += delta; - }); - updateCanvasSize(); - }, - onHoverChanged: - (int index, bool isHovered) { - setState(() { - spaces[index].isHovered = isHovered; - }); - }, - onButtonTap: (int index, Offset newPosition, - String direction) { - _showCreateSpaceDialog( - screenSize, - position: spaces[index].position + - newPosition, - parentIndex: index, - direction: direction, - ); - }, - buildSpaceContainer: (int index) { - return SpaceContainerWidget( - index: index, - icon: spaces[index].icon, - name: spaces[index].name, - ); - }, - ), - ); - }), - ], - )), + if (isHovered) ...[ + // Add icon on the right + Positioned( + right: -20, + child: IconButton( + icon: Icon(Icons.add_circle, color: Colors.green, size: 20), + onPressed: () => widget.onAddNode("right"), + ), ), - ), + // Add icon on the left + Positioned( + left: -20, + child: IconButton( + icon: Icon(Icons.add_circle, color: Colors.green, size: 20), + onPressed: () => widget.onAddNode("left"), + ), + ), + // Add icon on the bottom + Positioned( + bottom: -20, + child: IconButton( + icon: Icon(Icons.add_circle, color: Colors.green, size: 20), + onPressed: () => widget.onAddNode("bottom"), + ), + ), + ], ], ), ), ); } - - void _showCreateSpaceDialog(Size screenSize, - {Offset? position, int? parentIndex, String? direction}) { - showDialog( - context: context, - builder: (BuildContext context) { - return CreateSpaceDialog( - onCreateSpace: (String name, String icon) { - setState(() { - // Set the first space in the center or use passed position - Offset centerPosition = position ?? - Offset( - screenSize.width / 2 - 75, // Center horizontally - screenSize.height / 2 - - 100, // Slightly above the center vertically - ); - - SpaceData newSpace = - SpaceData(name: name, icon: icon, position: centerPosition); - spaces.add(newSpace); - updateCanvasSize(); - - // Add connection for down-button - if (parentIndex != null && direction != null) { - connections.add(Connection( - startSpace: spaces[parentIndex], - endSpace: newSpace, - direction: direction, - )); - } - }); - }, - ); - }, - ); - } } +class EdgePainter extends CustomPainter { + final Offset start; + final Offset end; + + EdgePainter(this.start, this.end); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.black + ..strokeWidth = 2.0 + ..style = PaintingStyle.stroke; + + canvas.drawLine(start, end, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/lib/pages/spaces_management/widgets/space_container_widget.dart b/lib/pages/spaces_management/widgets/space_container_widget.dart index be6e43b4..0f98c236 100644 --- a/lib/pages/spaces_management/widgets/space_container_widget.dart +++ b/lib/pages/spaces_management/widgets/space_container_widget.dart @@ -63,6 +63,7 @@ class SpaceContainerWidget extends StatelessWidget { ), ], ), + ); } }