mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-16 01:56:24 +00:00
Fixed space layout
This commit is contained in:
@ -1,8 +1,5 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.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/bloc/space_management_state.dart';
|
||||||
@ -10,16 +7,12 @@ 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/connection_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/model/space_data_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/model/space_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/view/curved_line_painter.dart';
|
import 'package:syncrow_web/pages/spaces_management/widgets/community_structure_widget.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/gradient_canvas_border_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/widgets/loaded_space_widget.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/widgets/sidebar_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/services/space_mana_api.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
|
||||||
|
|
||||||
class SpaceManagementPage extends StatefulWidget {
|
class SpaceManagementPage extends StatefulWidget {
|
||||||
const SpaceManagementPage({super.key});
|
const SpaceManagementPage({super.key});
|
||||||
@ -34,12 +27,8 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
|||||||
Map<String, List<SpaceModel>> communitySpaces = {};
|
Map<String, List<SpaceModel>> communitySpaces = {};
|
||||||
double canvasWidth = 1000;
|
double canvasWidth = 1000;
|
||||||
double canvasHeight = 1000;
|
double canvasHeight = 1000;
|
||||||
|
List<SpaceData> spaces = [];
|
||||||
final List<NodeData> nodes = [
|
List<Connection> connections = [];
|
||||||
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -48,7 +37,6 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Size screenSize = MediaQuery.of(context).size;
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => SpaceManagementBloc(CommunitySpaceManagementApi())
|
create: (context) => SpaceManagementBloc(CommunitySpaceManagementApi())
|
||||||
..add(LoadCommunityAndSpacesEvent()),
|
..add(LoadCommunityAndSpacesEvent()),
|
||||||
@ -61,7 +49,15 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
|||||||
if (state is SpaceManagementLoading) {
|
if (state is SpaceManagementLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
} else if (state is SpaceManagementLoaded) {
|
} else if (state is SpaceManagementLoaded) {
|
||||||
return _buildLoadedState(context, screenSize, state.communities);
|
return LoadedSpaceView(
|
||||||
|
communities: state.communities,
|
||||||
|
selectedCommunity: selectedCommunity,
|
||||||
|
onCommunitySelected: (community) {
|
||||||
|
setState(() {
|
||||||
|
selectedCommunity = community;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
} else if (state is SpaceManagementError) {
|
} else if (state is SpaceManagementError) {
|
||||||
return Center(child: Text('Error: ${state.errorMessage}'));
|
return Center(child: Text('Error: ${state.errorMessage}'));
|
||||||
}
|
}
|
||||||
@ -72,7 +68,7 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLoadedState(
|
Widget _buildLoadedState(
|
||||||
BuildContext context, Size screenSize, List<CommunityModel> communities) {
|
BuildContext context, List<CommunityModel> communities) {
|
||||||
return Stack(
|
return Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
@ -82,285 +78,15 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
|||||||
communities: communities,
|
communities: communities,
|
||||||
onCommunitySelected: (community) {
|
onCommunitySelected: (community) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedCommunity = community; // Set the selected community
|
selectedCommunity = community;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_buildCommunityStructureArea(context, screenSize)
|
CommunityStructureArea(selectedCommunity: selectedCommunity),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const GradientCanvasBorderWidget()
|
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;
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,215 @@
|
|||||||
|
import 'package:flutter/material.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/pages/spaces_management/widgets/curved_line_painter.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/widgets/space_card_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/model/space_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/widgets/space_container_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class CommunityStructureArea extends StatefulWidget {
|
||||||
|
final CommunityModel? selectedCommunity;
|
||||||
|
|
||||||
|
CommunityStructureArea({this.selectedCommunity});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CommunityStructureAreaState createState() => _CommunityStructureAreaState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||||
|
double canvasWidth = 1000;
|
||||||
|
double canvasHeight = 1000;
|
||||||
|
List<SpaceData> spaces = [];
|
||||||
|
List<Connection> connections = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Size screenSize = MediaQuery.of(context).size;
|
||||||
|
return Expanded(
|
||||||
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
left: BorderSide(color: ColorsManager.whiteColors, width: 1.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildHeader(),
|
||||||
|
Flexible(
|
||||||
|
child: InteractiveViewer(
|
||||||
|
boundaryMargin: EdgeInsets.all(500),
|
||||||
|
minScale: 0.5,
|
||||||
|
maxScale: 3.0,
|
||||||
|
constrained: false,
|
||||||
|
child: Container(
|
||||||
|
width: canvasWidth,
|
||||||
|
height: canvasHeight,
|
||||||
|
child: spaces.isEmpty
|
||||||
|
? Center(
|
||||||
|
child: AddSpaceButton(
|
||||||
|
onTap: () {
|
||||||
|
_showCreateSpaceDialog(screenSize);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Stack(
|
||||||
|
children: [
|
||||||
|
for (var connection in connections)
|
||||||
|
CustomPaint(
|
||||||
|
painter: CurvedLinePainter([connection])),
|
||||||
|
for (var entry in spaces.asMap().entries)
|
||||||
|
Positioned(
|
||||||
|
left: entry.value.position.dx,
|
||||||
|
top: entry.value.position.dy,
|
||||||
|
child: SpaceCardWidget(
|
||||||
|
index: entry.key,
|
||||||
|
onButtonTap: (int index, Offset newPosition,
|
||||||
|
String direction) {
|
||||||
|
_showCreateSpaceDialog(
|
||||||
|
screenSize,
|
||||||
|
position:
|
||||||
|
spaces[index].position + newPosition,
|
||||||
|
parentIndex: index,
|
||||||
|
direction: direction,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
position: entry.value.position,
|
||||||
|
isHovered: entry.value.isHovered,
|
||||||
|
screenSize: screenSize,
|
||||||
|
onHoverChanged: _handleHoverChanged,
|
||||||
|
onPositionChanged: (newPosition) {
|
||||||
|
_updateNodePosition(
|
||||||
|
entry.value, newPosition);
|
||||||
|
},
|
||||||
|
buildSpaceContainer: (int index) {
|
||||||
|
return SpaceContainerWidget(
|
||||||
|
index: index,
|
||||||
|
icon: spaces[index].icon,
|
||||||
|
name: spaces[index].name,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader() {
|
||||||
|
return 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,
|
||||||
|
spreadRadius: 0,
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Community Structure',
|
||||||
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
if (widget.selectedCommunity != null)
|
||||||
|
Text(
|
||||||
|
widget.selectedCommunity!.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16, color: ColorsManager.blackColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateNodePosition(SpaceData 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand canvas downward when node approaches the bottom edge
|
||||||
|
if (node.position.dy >= canvasHeight - 200) {
|
||||||
|
canvasHeight += 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 spaces) {
|
||||||
|
n.position = Offset(n.position.dx + shiftAmount, n.position.dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent nodes from going out of bounds on top edge
|
||||||
|
if (node.position.dy < 0) {
|
||||||
|
node.position = Offset(node.position.dx, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
_updateNodePosition(newSpace, newSpace.position);
|
||||||
|
|
||||||
|
// Add connection for down-button
|
||||||
|
if (parentIndex != null && direction != null) {
|
||||||
|
SpaceData parentSpace = spaces[parentIndex];
|
||||||
|
connections.add(Connection(
|
||||||
|
startSpace: parentSpace,
|
||||||
|
endSpace: newSpace,
|
||||||
|
direction: direction,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleHoverChanged(int index, bool isHovered) {
|
||||||
|
setState(() {
|
||||||
|
spaces[index].isHovered = isHovered;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,3 @@
|
|||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
@ -40,14 +38,20 @@ class CurvedLinePainter extends CustomPainter {
|
|||||||
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy);
|
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy);
|
||||||
canvas.drawPath(path, paint);
|
canvas.drawPath(path, paint);
|
||||||
} else if (connection.direction == 'right') {
|
} else if (connection.direction == 'right') {
|
||||||
// Straight line for right connections
|
start = connection.startSpace.position +
|
||||||
|
const Offset(150, 30); // Right center
|
||||||
|
end = connection.endSpace.position + const Offset(0, 30); // Left center
|
||||||
|
|
||||||
canvas.drawLine(start, end, paint);
|
canvas.drawLine(start, end, paint);
|
||||||
} else if (connection.direction == 'left') {
|
} else if (connection.direction == 'left') {
|
||||||
// Straight line for left connections
|
start =
|
||||||
|
connection.startSpace.position + const Offset(0, 30); // Left center
|
||||||
|
end = connection.endSpace.position +
|
||||||
|
const Offset(150, 30); // Right center
|
||||||
|
|
||||||
canvas.drawLine(start, end, paint);
|
canvas.drawLine(start, end, paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw small connection dots at the start and end points
|
|
||||||
final dotPaint = Paint()..color = ColorsManager.blackColor;
|
final dotPaint = Paint()..color = ColorsManager.blackColor;
|
||||||
canvas.drawCircle(start, 5, dotPaint); // Start dot
|
canvas.drawCircle(start, 5, dotPaint); // Start dot
|
||||||
canvas.drawCircle(end, 5, dotPaint); // End dot
|
canvas.drawCircle(end, 5, dotPaint); // End dot
|
42
lib/pages/spaces_management/widgets/loaded_space_widget.dart
Normal file
42
lib/pages/spaces_management/widgets/loaded_space_widget.dart
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/widgets/community_structure_widget.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';
|
||||||
|
|
||||||
|
class LoadedSpaceView extends StatefulWidget {
|
||||||
|
final List<CommunityModel> communities;
|
||||||
|
final CommunityModel? selectedCommunity;
|
||||||
|
final ValueChanged<CommunityModel> onCommunitySelected;
|
||||||
|
|
||||||
|
const LoadedSpaceView({
|
||||||
|
Key? key,
|
||||||
|
required this.communities,
|
||||||
|
this.selectedCommunity,
|
||||||
|
required this.onCommunitySelected,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LoadedStateViewState createState() => _LoadedStateViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoadedStateViewState extends State<LoadedSpaceView> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SidebarWidget(
|
||||||
|
communities: widget.communities,
|
||||||
|
onCommunitySelected: widget.onCommunitySelected,
|
||||||
|
),
|
||||||
|
CommunityStructureArea(selectedCommunity: widget.selectedCommunity),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const GradientCanvasBorderWidget(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,6 @@ class PlusButtonWidget extends StatelessWidget {
|
|||||||
final int index;
|
final int index;
|
||||||
final String direction;
|
final String direction;
|
||||||
final Offset offset;
|
final Offset offset;
|
||||||
final Size screenSize;
|
|
||||||
final Function(int index, Offset newPosition, String direction) onButtonTap;
|
final Function(int index, Offset newPosition, String direction) onButtonTap;
|
||||||
|
|
||||||
const PlusButtonWidget({
|
const PlusButtonWidget({
|
||||||
@ -13,7 +12,6 @@ class PlusButtonWidget extends StatelessWidget {
|
|||||||
required this.index,
|
required this.index,
|
||||||
required this.direction,
|
required this.direction,
|
||||||
required this.offset,
|
required this.offset,
|
||||||
required this.screenSize,
|
|
||||||
required this.onButtonTap,
|
required this.onButtonTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,18 +6,18 @@ class SpaceCardWidget extends StatelessWidget {
|
|||||||
final Size screenSize;
|
final Size screenSize;
|
||||||
final Offset position;
|
final Offset position;
|
||||||
final bool isHovered;
|
final bool isHovered;
|
||||||
final Function(int index, Offset delta) onPanUpdate;
|
|
||||||
final Function(int index, bool isHovered) onHoverChanged;
|
final Function(int index, bool isHovered) onHoverChanged;
|
||||||
final Function(int index, Offset newPosition, String direction) onButtonTap;
|
final Function(int index, Offset newPosition, String direction) onButtonTap;
|
||||||
final Widget Function(int index) buildSpaceContainer;
|
final Widget Function(int index) buildSpaceContainer;
|
||||||
|
final ValueChanged<Offset> onPositionChanged;
|
||||||
|
|
||||||
const SpaceCardWidget({
|
const SpaceCardWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.index,
|
required this.index,
|
||||||
|
required this.onPositionChanged,
|
||||||
required this.screenSize,
|
required this.screenSize,
|
||||||
required this.position,
|
required this.position,
|
||||||
required this.isHovered,
|
required this.isHovered,
|
||||||
required this.onPanUpdate,
|
|
||||||
required this.onHoverChanged,
|
required this.onHoverChanged,
|
||||||
required this.onButtonTap,
|
required this.onButtonTap,
|
||||||
required this.buildSpaceContainer,
|
required this.buildSpaceContainer,
|
||||||
@ -29,7 +29,8 @@ class SpaceCardWidget extends StatelessWidget {
|
|||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onPanUpdate: (details) {
|
onPanUpdate: (details) {
|
||||||
// Call the provided callback to update the position
|
// Call the provided callback to update the position
|
||||||
onPanUpdate(index, details.delta);
|
final newPosition = position + details.delta;
|
||||||
|
onPositionChanged(newPosition);
|
||||||
},
|
},
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
onEnter: (_) {
|
onEnter: (_) {
|
||||||
@ -41,7 +42,8 @@ class SpaceCardWidget extends StatelessWidget {
|
|||||||
onHoverChanged(index, false);
|
onHoverChanged(index, false);
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Stack(
|
||||||
clipBehavior: Clip.none, // Allow hovering elements to be displayed outside the boundary
|
clipBehavior: Clip
|
||||||
|
.none, // Allow hovering elements to be displayed outside the boundary
|
||||||
children: [
|
children: [
|
||||||
buildSpaceContainer(index), // Build the space container
|
buildSpaceContainer(index), // Build the space container
|
||||||
if (isHovered) ...[
|
if (isHovered) ...[
|
||||||
@ -49,21 +51,18 @@ class SpaceCardWidget extends StatelessWidget {
|
|||||||
index: index,
|
index: index,
|
||||||
direction: 'left',
|
direction: 'left',
|
||||||
offset: const Offset(-21, 20),
|
offset: const Offset(-21, 20),
|
||||||
screenSize: screenSize,
|
|
||||||
onButtonTap: onButtonTap,
|
onButtonTap: onButtonTap,
|
||||||
),
|
),
|
||||||
PlusButtonWidget(
|
PlusButtonWidget(
|
||||||
index: index,
|
index: index,
|
||||||
direction: 'right',
|
direction: 'right',
|
||||||
offset: const Offset(140, 20),
|
offset: const Offset(140, 20),
|
||||||
screenSize: screenSize,
|
|
||||||
onButtonTap: onButtonTap,
|
onButtonTap: onButtonTap,
|
||||||
),
|
),
|
||||||
PlusButtonWidget(
|
PlusButtonWidget(
|
||||||
index: index,
|
index: index,
|
||||||
direction: 'down',
|
direction: 'down',
|
||||||
offset: const Offset(63, 50),
|
offset: const Offset(63, 50),
|
||||||
screenSize: screenSize,
|
|
||||||
onButtonTap: onButtonTap,
|
onButtonTap: onButtonTap,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -19,7 +19,8 @@ class SpaceWidget extends StatelessWidget {
|
|||||||
top: position.dy,
|
top: position.dy,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Container(
|
child:
|
||||||
|
Container(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@ -41,6 +42,7 @@ class SpaceWidget extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user