import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/common/buttons/add_space_button.dart'; import 'package:syncrow_web/pages/spaces_management/view/dialogs/create_space_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class SpaceManagementPage extends StatefulWidget { @override SpaceManagementPageState createState() => SpaceManagementPageState(); } class SpaceManagementPageState extends State { // Store created spaces List spaces = []; List connections = []; @override Widget build(BuildContext context) { Size screenSize = MediaQuery.of(context).size; return Scaffold( backgroundColor: ColorsManager.whiteColors, appBar: AppBar( title: const Text('Space Management'), ), body: Stack( children: [ // Draw lines using a CustomPaint widget CustomPaint( size: Size.infinite, painter: CurvedLinePainter(connections), ), Center( child: spaces.isEmpty ? AddSpaceButton( onTap: () { _showCreateSpaceDialog(screenSize); }, ) : Stack( children: spaces .asMap() .entries .map((entry) => _buildSpaceCard(entry.key, screenSize)) .toList(), ), ), ], ), ); } // Function to open the Create Space dialog void _showCreateSpaceDialog(Size screenSize, {Offset? position, int? parentIndex, bool addConnection = false}) { 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); // Add connection for down-button if (addConnection && parentIndex != null) { connections.add(Connection( startSpace: spaces[parentIndex], endSpace: newSpace, )); } }); }, ); }, ); } // Function to build a draggable space card Widget _buildSpaceCard(int index, Size screenSize) { return Positioned( left: spaces[index].position.dx, top: spaces[index].position.dy, child: GestureDetector( onPanUpdate: (details) { // Update the position of the space card while dragging setState(() { spaces[index].position += details.delta; }); }, child: MouseRegion( onEnter: (_) { // Show plus buttons on hover setState(() { spaces[index].isHovered = true; }); }, onExit: (_) { // Hide plus buttons when not hovered setState(() { spaces[index].isHovered = false; }); }, child: Stack( clipBehavior: Clip.none, children: [ _buildSpaceContainer(index), if (spaces[index].isHovered) ...[ _buildPlusButton(index, 'left', const Offset(-21, 20), screenSize), _buildPlusButton(index, 'right', const Offset(140, 20), screenSize), _buildPlusButton(index, 'down', const Offset(63, 50), screenSize), ], ], ), ), ), ); } // Function to build the space container with the styled format Widget _buildSpaceContainer(int index) { return Container( width: 150, height: 60, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.5), spreadRadius: 2, blurRadius: 5, offset: const Offset(0, 3), ), ], ), child: Row( children: [ Container( width: 40, height: 60, decoration: const BoxDecoration( color: Color(0xFF023DFE), borderRadius: BorderRadius.only( topLeft: Radius.circular(15), bottomLeft: Radius.circular(15), ), ), child: Center( child: SvgPicture.asset( spaces[index].icon, width: 24, height: 24, color: Colors.white, ), ), ), const SizedBox(width: 10), Text( spaces[index].name, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), ], ), ); } // Function to build plus buttons for new space creation Widget _buildPlusButton(int index, String direction, Offset offset, Size screenSize) { return Positioned( left: offset.dx, top: offset.dy, child: GestureDetector( onTap: () { Offset newPosition; bool addConnection = false; switch (direction) { case 'left': newPosition = spaces[index].position + const Offset(-200, 0); break; case 'right': newPosition = spaces[index].position + const Offset(200, 0); break; case 'down': newPosition = spaces[index].position + const Offset(0, 150); addConnection = true; break; default: newPosition = spaces[index].position; } // Open the dialog to create a new space and pass down the new position _showCreateSpaceDialog(screenSize, position: newPosition, parentIndex: index, addConnection: addConnection); }, child: Container( width: 30, height: 30, decoration: const BoxDecoration( color: Color(0xFF023DFE), shape: BoxShape.circle, ), child: const Icon(Icons.add, color: Colors.white, size: 20), ), ), ); } } // Model for storing space information class SpaceData { final String name; final String icon; Offset position; bool isHovered; SpaceData({ required this.name, required this.icon, required this.position, this.isHovered = false, }); } // Class for connection lines between spaces class Connection { final SpaceData startSpace; final SpaceData endSpace; Connection({required this.startSpace, required this.endSpace}); } // Custom painter to draw lines between connected spaces class CurvedLinePainter extends CustomPainter { final List connections; CurvedLinePainter(this.connections); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.black ..strokeWidth = 2 ..style = PaintingStyle.stroke; for (var connection in connections) { final start = connection.startSpace.position + Offset(75, 60); // Bottom center of the starting space final end = connection.endSpace.position + Offset(75, 0); // Top center of the ending space // Calculate control point for Bézier curve (to create a curve) final controlPoint = Offset((start.dx + end.dx) / 2, start.dy + 50); // Draw the Bézier curve final path = Path() ..moveTo(start.dx, start.dy) ..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy); canvas.drawPath(path, paint); // Draw small connection dots at the start and end points final dotPaint = Paint()..color = ColorsManager.blackColor; canvas.drawCircle(start, 5, dotPaint); // Start dot canvas.drawCircle(end, 5, dotPaint); // End dot } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } }