mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 07:07:19 +00:00
447 lines
15 KiB
Dart
447 lines
15 KiB
Dart
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<SpaceManagementPage> {
|
|
// Store created spaces
|
|
List<SpaceData> spaces = [];
|
|
List<Connection> connections = [];
|
|
|
|
// API instance
|
|
final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi();
|
|
|
|
// Data structure to store community and associated spaces
|
|
Map<String, List<SpaceModel>> communitySpaces = {};
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadCommunityAndSpacesData();
|
|
}
|
|
|
|
// Function to load all communities and their respective spaces
|
|
void _loadCommunityAndSpacesData() async {
|
|
try {
|
|
// Fetch all communities
|
|
List<CommunityModel> communities = await _api.fetchCommunities();
|
|
|
|
for (CommunityModel community in communities) {
|
|
// Fetch spaces hierarchy for each community
|
|
List<SpaceModel> 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<Connection> 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;
|
|
}
|
|
}
|