Files
syncrow-web/lib/pages/spaces_management/view/spaces_management_page.dart
2024-09-10 21:22:43 +04:00

553 lines
20 KiB
Dart

import 'dart:ui_web';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/common/custom_expansion_tile.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';
import 'package:syncrow_web/utils/constants/assets.dart';
class SpaceManagementPage extends StatefulWidget {
@override
SpaceManagementPageState createState() => SpaceManagementPageState();
}
class SpaceManagementPageState extends State<SpaceManagementPage> {
// Store created spaces
List<SpaceData> spaces = [];
List<Connection> 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(
clipBehavior: Clip.none,
children: [
Row(
children: [
// Sidebar for navigation and community list
Container(
width: 300, // Fixed width for sidebar
decoration: BoxDecoration(
color: Colors.white, // No gradient inside
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// "Communities" title with the add button
Container(
decoration: BoxDecoration(
color: Colors.white, // Background color
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
spreadRadius: 1,
offset: Offset(0, 2), // Adjust the shadow position
),
],
),
padding: const EdgeInsets.all(
16.0), // Apply padding inside the container
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Communities',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
// Plus Button (+)
GestureDetector(
onTap: () {
// Handle the button tap action
},
child: Container(
width: 30,
height: 30,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: Center(
child: SvgPicture.asset(
Assets
.roundedAddIcon, // Path to your SVG asset
width: 24, // Set the width
height: 24, // Set the height
),
),
),
),
],
),
),
// Search bar
// Search bar
Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
boxShadow: [
BoxShadow(
color:
Colors.black.withOpacity(0.2), // Subtle shadow
spreadRadius: 0, // No spread
blurRadius: 8, // Softer shadow edges
offset: Offset(0, 4), // Shadow only on the bottom
),
],
),
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 20.0),
width: double
.infinity, // Make the container take the full width of its parent
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
12), // Smooth rounded corners
),
child: TextField(
style: const TextStyle(
color: Colors.black, // Set the text color to black
),
decoration: InputDecoration(
filled: true,
fillColor: ColorsManager
.textFieldGreyColor, // Background color for the text field itself
hintText: 'Search',
hintStyle: TextStyle(
color: Colors.grey[400], // Gray hint text color
),
suffixIcon: Padding(
padding: const EdgeInsets.only(
right:
16), // Add padding to avoid icon sticking to the edge
child: SvgPicture.asset(
Assets
.textFieldSearch, // Path to your SVG asset
width: 24, // Set the width
height: 24, // Set the height
),
), // Search icon
border: OutlineInputBorder(
borderSide: BorderSide.none, // Remove border
borderRadius: BorderRadius.circular(
12), // Rounded corners for text field
),
contentPadding: const EdgeInsets.symmetric(
vertical:
14, // Vertical padding for proper text alignment
horizontal:
16, // Add space between the text and the left edge
), // Proper padding for alignment
),
),
),
),
const SizedBox(height: 16),
// Example of community list
Expanded(
child: ListView(
children: [
CustomExpansionTile(
title: "Downtown Dubai",
),
CustomExpansionTile(
title: 'Dubai Creek Harbour',
),
CustomExpansionTile(
title: 'Dubai Hills Estate',
children: [
CustomExpansionTile(
title: 'South Side',
initiallyExpanded: false),
],
),
],
),
),
],
),
),
// Right Side: Community Structure Area
Expanded(
child: Container(
decoration: const BoxDecoration(
color: ColorsManager.whiteColors,
border: Border(
left: BorderSide(
color: Colors.white,
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: Colors.black
.withOpacity(0.2), // 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: Colors.transparent, // 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: [
Color(0x3F000000).withOpacity(0.1), // Light gray
Colors.transparent, // 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: 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;
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: 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;
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 = Colors.black
..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 = Colors.black;
canvas.drawCircle(start, 5, dotPaint); // Start dot
canvas.drawCircle(end, 5, dotPaint); // End dot
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}