Compare commits

...
Author SHA1 Message Date
hannathkadher 52608b1f35 fixed search query 2025-04-27 22:42:57 +04:00
Faris Armoush 0f56273d99 SP-1408 2025-04-27 12:10:38 +03:00
hannathkadher f30d7c0117 Merge pull request #158 from SyncrowIOT:bugfix/duplicate-space
Bugfix/duplicate-space
2025-04-27 11:13:07 +04:00
hannathkadher 976d6e385a duplicated space 2025-04-27 11:12:03 +04:00
hannathkadher ff07e7509d fixed the issue in aligning child space 2025-04-26 16:04:41 +04:00
3 changed files with 155 additions and 116 deletions
@@ -108,7 +108,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return DeviceManagementBody(
devices: deviceState.filteredDevices);
} else {
return const Center(child: Text('Error fetching Devices'));
return const DeviceManagementBody(devices: []);
}
},
);
@@ -1,4 +1,6 @@
// Flutter imports
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -336,6 +338,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
}
spaces.add(newSpace);
_updateNodePosition(newSpace, newSpace.position);
realignTree();
});
},
);
@@ -450,7 +453,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _saveSpaces() {
if (widget.selectedCommunity == null) {
debugPrint("No community selected for saving spaces.");
return;
}
@@ -530,35 +532,83 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
}
Offset getBalancedChildPosition(SpaceModel parent) {
int totalSiblings = parent.children.length + 1;
double totalWidth = (totalSiblings - 1) * 250; // Horizontal spacing
double startX = parent.position.dx - (totalWidth / 2);
const double nodeWidth = 200;
const double verticalGap = 180;
Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180);
if (parent.children.isEmpty) {
// First child → exactly center
return Offset(parent.position.dx, parent.position.dy + verticalGap);
} else {
// More children → arrange them spaced horizontally
double totalWidth = (parent.children.length) * (nodeWidth + 60);
double startX = parent.position.dx - (totalWidth / 2);
// Check for overlaps & adjust
while (spaces.any((s) => (s.position - position).distance < 250)) {
position = Offset(position.dx + 250, position.dy);
double childX = startX + (parent.children.length * (nodeWidth + 60));
return Offset(childX, parent.position.dy + verticalGap);
}
return position;
}
void realignTree() {
void updatePositions(SpaceModel node, double x, double y) {
node.position = Offset(x, y);
const double nodeWidth = 200;
const double nodeHeight = 100;
const double horizontalGap = 60;
const double verticalGap = 180;
const double rootGap = 400; // extra space between different roots
int numChildren = node.children.length;
double childStartX = x - ((numChildren - 1) * 250) / 2;
double canvasRightEdge = 1000;
double canvasBottomEdge = 1000;
for (int i = 0; i < numChildren; i++) {
updatePositions(node.children[i], childStartX + (i * 250), y + 180);
double calculateSubtreeWidth(SpaceModel node) {
if (node.children.isEmpty) return nodeWidth;
double totalWidth = 0;
for (var child in node.children) {
totalWidth += calculateSubtreeWidth(child) + horizontalGap;
}
return totalWidth - horizontalGap;
}
void layoutSubtree(SpaceModel node, double startX, double y) {
double subtreeWidth = calculateSubtreeWidth(node);
double centerX = startX + subtreeWidth / 2 - nodeWidth / 2;
node.position = Offset(centerX, y);
canvasRightEdge = max(canvasRightEdge, centerX + nodeWidth);
canvasBottomEdge = max(canvasBottomEdge, y + nodeHeight);
if (node.children.length == 1) {
final child = node.children.first;
layoutSubtree(child, centerX, y + verticalGap);
} else {
double childX = startX;
for (var child in node.children) {
double childWidth = calculateSubtreeWidth(child);
layoutSubtree(child, childX, y + verticalGap);
childX += childWidth + horizontalGap;
}
}
}
if (spaces.isNotEmpty) {
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
// ⚡ New: layout each root separately
final List<SpaceModel> roots = spaces
.where((s) =>
s.parent == null &&
s.status != SpaceStatus.deleted &&
s.status != SpaceStatus.parentDeleted)
.toList();
double currentX = 100; // start some margin from left
double currentY = 100; // top margin
for (var root in roots) {
layoutSubtree(root, currentX, currentY);
double rootWidth = calculateSubtreeWidth(root);
currentX += rootWidth + rootGap;
}
setState(() {
canvasWidth = canvasRightEdge + 400;
canvasHeight = canvasBottomEdge + 400;
});
}
void _onDuplicate(BuildContext parentContext) {
@@ -642,63 +692,19 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
}
void _duplicateSpace(SpaceModel space) {
final Map<SpaceModel, SpaceModel> originalToDuplicate = {};
double horizontalGap = 250.0; // Increased spacing
double verticalGap = 180.0; // Adjusted for better visualization
final double horizontalGap = 250.0;
final double verticalGap = 180.0;
final double nodeWidth = 200;
final double nodeHeight = 100;
final double breathingSpace = 300.0; // extra gap after original tree
print("🟢 Duplicating: ${space.name}");
/// **Find a new position ensuring no overlap**
Offset getBalancedChildPosition(SpaceModel parent) {
int totalSiblings = parent.children.length + 1;
double totalWidth = (totalSiblings - 1) * horizontalGap;
double startX = parent.position.dx - (totalWidth / 2);
Offset position = Offset(
startX + (parent.children.length * horizontalGap), parent.position.dy + verticalGap);
// **Check for overlaps & adjust**
while (spaces.any((s) => (s.position - position).distance < horizontalGap)) {
position = Offset(position.dx + horizontalGap, position.dy);
}
print("🔹 New position for ${parent.name}: (${position.dx}, ${position.dy})");
return position;
}
/// **Realign the entire tree after duplication**
void realignTree() {
void updatePositions(SpaceModel node, double x, double y) {
node.position = Offset(x, y);
print("✅ Adjusted ${node.name} to (${x}, ${y})");
int numChildren = node.children.length;
double childStartX = x - ((numChildren - 1) * horizontalGap) / 2;
for (int i = 0; i < numChildren; i++) {
updatePositions(node.children[i], childStartX + (i * horizontalGap), y + verticalGap);
}
}
if (spaces.isNotEmpty) {
print("🔄 Realigning tree...");
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
}
}
/// **Recursive duplication logic**
/// Helper to recursively duplicate a node and its children
SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) {
Offset newPosition = duplicatedParent == null
? Offset(original.position.dx + horizontalGap, original.position.dy)
: getBalancedChildPosition(duplicatedParent);
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
print(
"🟡 Duplicating ${original.name} → ${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})");
final duplicated = SpaceModel(
name: duplicatedName,
icon: original.icon,
position: newPosition,
position: Offset.zero,
isPrivate: original.isPrivate,
children: [],
status: SpaceStatus.newSpace,
@@ -708,28 +714,20 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
tags: original.tags,
);
setState(() {
spaces.add(duplicated);
_updateNodePosition(duplicated, duplicated.position);
spaces.add(duplicated);
if (duplicatedParent != null) {
final newConnection = Connection(
startSpace: duplicatedParent,
endSpace: duplicated,
direction: "down",
);
connections.add(newConnection);
duplicated.incomingConnection = newConnection;
duplicatedParent.addOutgoingConnection(newConnection);
duplicatedParent.children.add(duplicated);
print("🔗 Created connection: ${duplicatedParent.name} → ${duplicated.name}");
}
if (duplicatedParent != null) {
final newConnection = Connection(
startSpace: duplicatedParent,
endSpace: duplicated,
direction: "down",
);
connections.add(newConnection);
duplicated.incomingConnection = newConnection;
duplicatedParent.addOutgoingConnection(newConnection);
duplicatedParent.children.add(duplicated);
}
// **Recalculate the whole tree to avoid overlaps**
realignTree();
});
// Recursively duplicate children
for (var child in original.children) {
duplicateRecursive(child, duplicated);
}
@@ -737,21 +735,49 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return duplicated;
}
/// **Handle root duplication**
if (space.parent == null) {
print("🟠 Duplicating root node: ${space.name}");
SpaceModel duplicatedRoot = duplicateRecursive(space, null);
/// Layout a subtree rooted at node
void layoutSubtree(SpaceModel node, double startX, double startY) {
double calculateSubtreeWidth(SpaceModel n) {
if (n.children.isEmpty) return nodeWidth;
double width = 0;
for (var child in n.children) {
width += calculateSubtreeWidth(child) + horizontalGap;
}
return width - horizontalGap;
}
setState(() {
spaces.add(duplicatedRoot);
realignTree();
});
void assignPositions(SpaceModel n, double x, double y) {
double subtreeWidth = calculateSubtreeWidth(n);
double centerX = x + subtreeWidth / 2 - nodeWidth / 2;
n.position = Offset(centerX, y);
print("✅ Root duplication successful: ${duplicatedRoot.name}");
} else {
duplicateRecursive(space, space.parent);
if (n.children.length == 1) {
assignPositions(n.children.first, centerX, y + verticalGap);
} else {
double childX = x;
for (var child in n.children) {
double childWidth = calculateSubtreeWidth(child);
assignPositions(child, childX, y + verticalGap);
childX += childWidth + horizontalGap;
}
}
}
double totalSubtreeWidth = calculateSubtreeWidth(node);
assignPositions(node, startX, startY);
}
print("🟢 Finished duplication process for: ${space.name}");
/// Actual duplication process
setState(() {
if (space.parent == null) {
// Duplicating a ROOT node
SpaceModel duplicatedRoot = duplicateRecursive(space, null);
realignTree();
} else {
// Duplicating a CHILD node inside its parent
SpaceModel duplicated = duplicateRecursive(space, space.parent);
realignTree();
}
});
}
}
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/widgets/empty_search_result_widget.dart';
@@ -36,6 +38,7 @@ class SidebarWidget extends StatefulWidget {
class _SidebarWidgetState extends State<SidebarWidget> {
late final ScrollController _scrollController;
Timer? _debounce;
String _searchQuery = '';
String? _selectedSpaceUuid;
@@ -43,10 +46,10 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_onScroll);
_selectedId = widget.selectedSpaceUuid;
super.initState();
}
void _onScroll() {
@@ -67,11 +70,22 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override
void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
_debounce?.cancel();
super.dispose();
}
void _onSearchChanged(String query) {
if (_debounce?.isActive ?? false) _debounce?.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () {
setState(() {
_searchQuery = query;
});
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
});
}
@override
void didUpdateWidget(covariant SidebarWidget oldWidget) {
if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) {
@@ -91,7 +105,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override
Widget build(BuildContext context) {
final spaceTreeState = context.watch<SpaceTreeBloc>().state;
final filteredCommunities = spaceTreeState.isSearching
final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
@@ -104,13 +118,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
children: [
SidebarHeader(onAddCommunity: _onAddCommunity),
CustomSearchBar(
onSearchChanged: (query) {
setState(() {
_searchQuery = query;
});
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
},
onSearchChanged: _onSearchChanged,
),
const SizedBox(height: 16),
Expanded(
@@ -123,10 +131,15 @@ class _SidebarWidgetState extends State<SidebarWidget> {
communities: filteredCommunities,
itemBuilder: (context, index) {
if (index == filteredCommunities.length) {
return const Padding(
padding: EdgeInsets.all(8.0),
child: Center(child: CircularProgressIndicator()),
);
final spaceTreeState = context.read<SpaceTreeBloc>().state;
if (spaceTreeState.paginationIsLoading) {
return const Padding(
padding: EdgeInsets.all(8.0),
child: Center(child: CircularProgressIndicator()),
);
} else {
return const SizedBox.shrink();
}
}
return _buildCommunityTile(context, filteredCommunities[index]);
}),