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/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/view/dialogs/create_space_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/view/sidebar_widget.dart'; import 'package:syncrow_web/services/space_mana_api.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 = []; // API instance final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi(); // Data structure to store community and associated spaces Map> communitySpaces = {}; @override void initState() { super.initState(); _loadCommunityAndSpacesData(); } // Function to load all communities and their respective spaces void _loadCommunityAndSpacesData() async { try { // Fetch all communities List communities = await _api.fetchCommunities(); for (CommunityModel community in communities) { // Fetch spaces hierarchy for each community List spaces = await _api.getSpaceHierarchy(community.uuid); // Store the result in the communitySpaces map communitySpaces[community.name] = spaces; } // Update the state to reflect loaded data setState(() { // Optionally, you can initialize something here based on the loaded data }); } catch (e) { debugPrint('Error loading communities and spaces: $e'); } } void _handleCommunitySelection(String community) { // Handle community selection here print("Selected Community: $community"); } @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( clipBehavior: Clip.none, children: [ Row( children: [ // Sidebar for navigation and community list SidebarWidget( communitySpaces: communitySpaces, // Pass communitySpaces here onCommunitySelected: _handleCommunitySelection, ), // Right Side: Community Structure Area Expanded( child: Container( decoration: const BoxDecoration( color: ColorsManager.whiteColors, 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: Offset(0, 4), // Shadow only on the bottom ), ], ), child: Text( 'Community Structure', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, ), ), ), // Use Expanded to ensure InteractiveViewer takes the available space Expanded( child: InteractiveViewer( boundaryMargin: const EdgeInsets.all(500), // Adjusted to 500 minScale: 0.5, // Minimum zoom scale maxScale: 2.5, // Maximum zoom scale panEnabled: true, // Enable panning scaleEnabled: true, // Enable zooming child: Container( width: 2000, // Large canvas height: 2000, // Large canvas color: ColorsManager.transparentColor, // Transparent background child: Stack( clipBehavior: Clip.none, children: [ // Draw lines using a CustomPaint widget CustomPaint( size: Size(2000, 2000), // Explicit canvas size painter: CurvedLinePainter(connections), ), Center( child: spaces.isEmpty ? AddSpaceButton( onTap: () { _showCreateSpaceDialog(screenSize); }, ) : Stack( children: spaces .asMap() .entries .map((entry) => _buildSpaceCard( entry.key, screenSize)) .toList(), ), ), ], ), ), ), ), ], ), ), ), ], ), Positioned( top: 0, bottom: 0, left: 300, // Align with the sidebar's width width: 8, // Width of the gradient border child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ ColorsManager.semiTransparentBlackColor .withOpacity(0.1), // Light gray ColorsManager.transparentColor, // Transparent fade ], ), ), ), ), ], ), ); } // Function to open the Create Space dialog 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); // Add connection for down-button if (parentIndex != null && direction != null) { connections.add(Connection( startSpace: spaces[parentIndex], endSpace: newSpace, direction: direction, )); } }); }, ); }, ); } // 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: ColorsManager.whiteColors, 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: ColorsManager.secondaryColor, borderRadius: BorderRadius.only( topLeft: Radius.circular(15), bottomLeft: Radius.circular(15), ), ), child: Center( child: SvgPicture.asset( spaces[index].icon, width: 24, height: 24, ), ), ), 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; 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); 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, direction: direction); }, child: Container( width: 30, height: 30, decoration: const BoxDecoration( color: ColorsManager.secondaryColor, 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; final String direction; Connection( {required this.startSpace, required this.endSpace, required this.direction}); } // 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 = ColorsManager.blackColor ..strokeWidth = 2 ..style = PaintingStyle.stroke; // Ensure connections exist before painting if (connections.isEmpty) { return; // Nothing to paint if there are no connections } for (var connection in connections) { // Ensure positions are valid before drawing lines if (connection.startSpace.position == null || connection.endSpace.position == null) { continue; } Offset start = connection.startSpace.position + Offset(75, 60); // Center bottom of start space Offset end = connection.endSpace.position + Offset(75, 0); // Center top of end space if (connection.direction == 'down') { // Curved line for down connections final controlPoint = Offset((start.dx + end.dx) / 2, start.dy + 50); final path = Path() ..moveTo(start.dx, start.dy) ..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy); canvas.drawPath(path, paint); } else if (connection.direction == 'right') { // Straight line for right connections canvas.drawLine(start, end, paint); } else if (connection.direction == 'left') { // Straight line for left connections canvas.drawLine(start, end, 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; } }