Files
syncrow-web/lib/pages/spaces_management/view/spaces_management_page.dart
2024-11-14 21:33:35 +04:00

367 lines
12 KiB
Dart

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/add_space_button.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_state.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_data_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/view/curved_line_painter.dart';
import 'package:syncrow_web/pages/spaces_management/view/dialogs/create_space_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/gradient_canvas_border_widget.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/sidebar_widget.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/space_card_widget.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/space_container_widget.dart';
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});
@override
SpaceManagementPageState createState() => SpaceManagementPageState();
}
class SpaceManagementPageState extends State<SpaceManagementPage> {
CommunityModel? selectedCommunity;
final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi();
Map<String, List<SpaceModel>> communitySpaces = {};
double canvasWidth = 1000;
double canvasHeight = 1000;
final List<NodeData> 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();
}
@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),
enableMenuSidebar: false,
scaffoldBody: BlocBuilder<SpaceManagementBloc, SpaceManagementState>(
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();
}),
),
);
}
Widget _buildLoadedState(
BuildContext context, Size screenSize, List<CommunityModel> communities) {
return Stack(
clipBehavior: Clip.none,
children: [
Row(
children: [
SidebarWidget(
communities: communities,
onCommunitySelected: (community) {
setState(() {
selectedCommunity = community; // Set the selected community
});
},
),
_buildCommunityStructureArea(context, screenSize)
],
),
const GradientCanvasBorderWidget()
],
);
}
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
),
),
// 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,
),
),
],
],
),
),
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<Offset> onPositionChanged;
final ValueChanged<String>
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<DraggableNode> {
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: EdgeInsets.all(8),
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), // shadow position
),
],
),
child: Text(
widget.data.id,
style: TextStyle(color: Colors.white),
),
),
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"),
),
),
],
],
),
),
);
}
}
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;
}