fixed space layout

This commit is contained in:
hannathkadher
2024-11-14 21:33:35 +04:00
parent ddea69f332
commit eacee98de2
2 changed files with 269 additions and 193 deletions

View File

@ -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/services/space_mana_api.dart';
import 'package:syncrow_web/utils/color_manager.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});
@ -28,75 +29,44 @@ class SpaceManagementPage extends StatefulWidget {
} }
class SpaceManagementPageState extends State<SpaceManagementPage> { 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; CommunityModel? selectedCommunity;
// API instance
final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi(); final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi();
// Data structure to store community and associated spaces
Map<String, List<SpaceModel>> communitySpaces = {}; 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 @override
void initState() { void initState() {
super.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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Size screenSize = MediaQuery.of(context).size; Size screenSize = MediaQuery.of(context).size;
return BlocProvider( return BlocProvider(
create: (context) => SpaceManagementBloc(CommunitySpaceManagementApi()) create: (context) => SpaceManagementBloc(CommunitySpaceManagementApi())
..add(LoadCommunityAndSpacesEvent()), ..add(LoadCommunityAndSpacesEvent()),
child: WebScaffold( child: WebScaffold(
appBarTitle: Text( appBarTitle: Text('Space Management',
'Space Management', style: Theme.of(context).textTheme.headlineLarge),
style: Theme.of(context).textTheme.headlineLarge,
),
enableMenuSidebar: false, enableMenuSidebar: false,
scaffoldBody: BlocBuilder<SpaceManagementBloc, SpaceManagementState>( scaffoldBody: BlocBuilder<SpaceManagementBloc, SpaceManagementState>(
builder: (context, state) { builder: (context, state) {
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 _buildLoadedState(context, screenSize, state.communities);
} else if (state is SpaceManagementError) { } else if (state is SpaceManagementError) {
return Center(child: Text('Error: ${state.errorMessage}')); return Center(child: Text('Error: ${state.errorMessage}'));
} }
return Container(); return Container();
}, }),
),
), ),
); );
} }
@ -126,166 +96,271 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
Widget _buildCommunityStructureArea(BuildContext context, Size screenSize) { Widget _buildCommunityStructureArea(BuildContext context, Size screenSize) {
return Expanded( return Expanded(
child: Container( child: Container(
decoration: const BoxDecoration( decoration: const BoxDecoration(
border: Border( border: Border(
left: BorderSide( left: BorderSide(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
width: 1.0), // Light left border to match 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,
),
),
],
],
), ),
), ),
// Background color for canvas Flexible(
child: Column( child: InteractiveViewer(
crossAxisAlignment: CrossAxisAlignment.start, 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: [ children: [
// Main node container
Container( Container(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 27.0), padding: EdgeInsets.all(8),
width: double.infinity, width: 150,
height: 60,
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(15),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: ColorsManager.shadowBlackColor, // Subtle shadow color: Colors.grey.withOpacity(0.5),
spreadRadius: 0, // No spread spreadRadius: 2,
blurRadius: 8, // Softer shadow edges blurRadius: 5,
offset: const Offset(0, 4), // Shadow only on the bottom offset: const Offset(0, 3), // shadow position
), ),
], ],
), ),
child: Column( child: Text(
crossAxisAlignment: CrossAxisAlignment.start, widget.data.id,
children: [ style: TextStyle(color: Colors.white),
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,
),
),
]
],
), ),
), ),
// Use Expanded to ensure InteractiveViewer takes the available space if (isHovered) ...[
Flexible( // Add icon on the right
child: InteractiveViewer( Positioned(
boundaryMargin: const EdgeInsets.all(20000), // Adjusted to 500 right: -20,
minScale: 0.5, // Minimum zoom scale child: IconButton(
maxScale: 5.5, // Maximum zoom scale icon: Icon(Icons.add_circle, color: Colors.green, size: 20),
panEnabled: true, // Enable panning onPressed: () => widget.onAddNode("right"),
scaleEnabled: true, // Enable zooming ),
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,
children: [
CustomPaint(
size: const Size(4000, 4000),
painter: CurvedLinePainter(connections),
),
...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();
},
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,
);
},
),
);
}),
],
)),
), ),
), // 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"),
),
),
],
], ],
), ),
), ),
); );
} }
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,
));
}
});
},
);
},
);
}
} }
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;
}

View File

@ -63,6 +63,7 @@ class SpaceContainerWidget extends StatelessWidget {
), ),
], ],
), ),
); );
} }
} }