mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 15:17:31 +00:00
fixed space layout
This commit is contained in:
@ -19,6 +19,7 @@ import 'package:syncrow_web/pages/spaces_management/widgets/space_container_widg
|
||||
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});
|
||||
@ -28,62 +29,32 @@ class SpaceManagementPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class SpaceManagementPageState extends State<SpaceManagementPage> {
|
||||
// Store created spaces
|
||||
List<SpaceData> spaces = [];
|
||||
List<Connection> connections = [];
|
||||
double canvasWidth = 1000; // Initial canvas width
|
||||
double canvasHeight = 1000; // Initial canvas height
|
||||
|
||||
// Track whether to show the community list view or community structure
|
||||
|
||||
// Selected community
|
||||
CommunityModel? selectedCommunity;
|
||||
|
||||
// API instance
|
||||
final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi();
|
||||
|
||||
// Data structure to store community and associated spaces
|
||||
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();
|
||||
}
|
||||
|
||||
void updateCanvasSize() {
|
||||
double maxX = 0;
|
||||
double maxY = 0;
|
||||
|
||||
// Calculate the maximum X and Y positions of all spaces
|
||||
for (var space in spaces) {
|
||||
maxX = max(maxX, space.position.dx + 150); // Add width of space
|
||||
maxY = max(maxY, space.position.dy + 60); // Add height of space
|
||||
}
|
||||
|
||||
// Add padding (but avoid adding arbitrary amounts like 1000)
|
||||
double newWidth =
|
||||
max(maxX + 500, canvasWidth); // Use max to ensure the canvas only grows
|
||||
double newHeight = max(maxY + 500, canvasHeight);
|
||||
|
||||
// Set the new canvas size dynamically
|
||||
setState(() {
|
||||
canvasWidth = newWidth;
|
||||
canvasHeight = newHeight;
|
||||
});
|
||||
}
|
||||
|
||||
@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,
|
||||
),
|
||||
appBarTitle: Text('Space Management',
|
||||
style: Theme.of(context).textTheme.headlineLarge),
|
||||
enableMenuSidebar: false,
|
||||
scaffoldBody: BlocBuilder<SpaceManagementBloc, SpaceManagementState>(
|
||||
builder: (context, state) {
|
||||
@ -95,8 +66,7 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
||||
return Center(child: Text('Error: ${state.errorMessage}'));
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -135,9 +105,7 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
||||
),
|
||||
),
|
||||
// Background color for canvas
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 27.0),
|
||||
width: double.infinity,
|
||||
@ -170,122 +138,229 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
// Use Expanded to ensure InteractiveViewer takes the available space
|
||||
Flexible(
|
||||
child: InteractiveViewer(
|
||||
boundaryMargin: const EdgeInsets.all(20000), // Adjusted to 500
|
||||
minScale: 0.5, // Minimum zoom scale
|
||||
maxScale: 5.5, // Maximum zoom scale
|
||||
panEnabled: true, // Enable panning
|
||||
scaleEnabled: true, // Enable zooming
|
||||
boundaryMargin: EdgeInsets.all(500), // Adjusted for smoother panning
|
||||
minScale: 0.5,
|
||||
maxScale: 3.0,
|
||||
constrained: false,
|
||||
child: Container(
|
||||
width: canvasWidth, // Large width for free movement
|
||||
height: canvasHeight, // Large height for free movement
|
||||
child: spaces.isEmpty
|
||||
? Center(
|
||||
child: AddSpaceButton(
|
||||
onTap: () {
|
||||
_showCreateSpaceDialog(screenSize);
|
||||
},
|
||||
),
|
||||
)
|
||||
: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
child: Stack(
|
||||
children: [
|
||||
// Draw connections between nodes
|
||||
for (int i = 0; i < nodes.length - 1; i++)
|
||||
CustomPaint(
|
||||
size: const Size(4000, 4000),
|
||||
painter: CurvedLinePainter(connections),
|
||||
painter:
|
||||
EdgePainter(nodes[i].position, nodes[i + 1].position),
|
||||
),
|
||||
...spaces.asMap().entries.map((entry) {
|
||||
final space = entry.value;
|
||||
return Positioned(
|
||||
left: space.position.dx,
|
||||
top: space.position.dy,
|
||||
child: SpaceCardWidget(
|
||||
index: entry.key,
|
||||
screenSize: screenSize,
|
||||
position: space.position,
|
||||
isHovered: space.isHovered,
|
||||
onPanUpdate: (int index, Offset delta) {
|
||||
setState(() {
|
||||
spaces[index].position += delta;
|
||||
});
|
||||
updateCanvasSize();
|
||||
// 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);
|
||||
},
|
||||
onHoverChanged:
|
||||
(int index, bool isHovered) {
|
||||
setState(() {
|
||||
spaces[index].isHovered = isHovered;
|
||||
});
|
||||
},
|
||||
onButtonTap: (int index, Offset newPosition,
|
||||
String direction) {
|
||||
_showCreateSpaceDialog(
|
||||
screenSize,
|
||||
position: spaces[index].position +
|
||||
newPosition,
|
||||
parentIndex: index,
|
||||
direction: direction,
|
||||
);
|
||||
},
|
||||
buildSpaceContainer: (int index) {
|
||||
return SpaceContainerWidget(
|
||||
index: index,
|
||||
icon: spaces[index].icon,
|
||||
name: spaces[index].name,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
updateCanvasSize();
|
||||
|
||||
// Add connection for down-button
|
||||
if (parentIndex != null && direction != null) {
|
||||
connections.add(Connection(
|
||||
startSpace: spaces[parentIndex],
|
||||
endSpace: newSpace,
|
||||
direction: direction,
|
||||
))
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ class SpaceContainerWidget extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user