Compare commits

..

26 Commits

Author SHA1 Message Date
58755eafe1 Merge branch 'dev' of https://github.com/SyncrowIOT/web into bugfix/clear-search 2025-04-28 16:26:47 +04:00
ce225818fb fixed community search caching 2025-04-28 16:25:43 +04:00
8762a7aaa8 Merge pull request #165 from SyncrowIOT/bugfix/white-page-rendering 2025-04-28 15:22:37 +04:00
8dc833b2c3 fixed blank page rendering 2025-04-28 15:21:28 +04:00
13cef151aa Merge pull request #164 from SyncrowIOT/bugfix/space-model-with-tags 2025-04-28 14:37:26 +04:00
ab23be9828 fixed the issue in selecting space model and tag 2025-04-28 14:36:27 +04:00
687b68ab22 Merge pull request #163 from SyncrowIOT/fix/duplication-flatten
Fix/duplication-flatten
2025-04-28 13:00:17 +04:00
25614c3dd0 fix the flatten 2025-04-28 12:59:16 +04:00
7cbe20ae88 remove unused code 2025-04-28 12:56:29 +04:00
349fe6c555 realignment 2025-04-28 11:58:41 +04:00
9779f3783c Merge branch 'dev' of https://github.com/SyncrowIOT/web into dev 2025-04-28 10:03:31 +04:00
fe3db663b6 realign initially 2025-04-28 10:03:27 +04:00
888d444752 Merge pull request #160 from SyncrowIOT/SP-1478-FE-On-devices-management-page-when-we-open-power-clamp-device-loading-indicator-remains-loading-and-no-data-is-displayed
Sp 1478 fe on devices management page when we open power clamp device loading indicator remains loading and no data is displayed
2025-04-28 08:59:17 +03:00
bab5e06968 Merge pull request #159 from SyncrowIOT/SP-1463-rework
Sp 1463 rework
2025-04-28 08:58:39 +03:00
d7b6174dee Merge pull request #162 from SyncrowIOT:bugfix/save-spaces
fixed the save issue
2025-04-28 00:37:33 +04:00
6ef0b2f9d1 fixed the save issue 2025-04-28 00:36:58 +04:00
3ceb03826e Merge pull request #161 from SyncrowIOT/bugfix/searchquery 2025-04-27 22:44:57 +04:00
52608b1f35 fixed search query 2025-04-27 22:42:57 +04:00
34d4d892d9 refactor: streamline value calculations in FlushMountedPresenceSensorControlView 2025-04-27 11:11:38 +03:00
3193fd26fe refactor: update presence delay value and enhance request handling in DebouncedBatchControlDevicesService 2025-04-27 11:04:54 +03:00
43802a9fad refactor: update detection value calculations and adjust parameters for presence delay 2025-04-27 10:57:44 +03:00
6e0b1775f0 fix: reorder constructor parameters for consistency in FlushMountedPresenceSensorControlView 2025-04-27 10:55:18 +03:00
233fb2ee2c refactor: improve formatting of clamp method for near and far detection values 2025-04-27 10:55:03 +03:00
b26928b3d5 fixed ui bugs. 2025-04-27 10:14:35 +03:00
6fc35a7b9a enhanced device debouncing to accomodate for multiple calls from the different devices functions calls. 2025-04-27 10:14:19 +03:00
756457927c removed unnecessary isBatch flag from FlushMountedPresenceSensorChangeValueEvent. 2025-04-27 10:13:53 +03:00
11 changed files with 210 additions and 130 deletions

View File

@ -24,11 +24,10 @@ class FlushMountedPresenceSensorChangeValueEvent
extends FlushMountedPresenceSensorEvent { extends FlushMountedPresenceSensorEvent {
final int value; final int value;
final String code; final String code;
final bool isBatchControl;
const FlushMountedPresenceSensorChangeValueEvent({ const FlushMountedPresenceSensorChangeValueEvent({
required this.value, required this.value,
required this.code, required this.code,
this.isBatchControl = false,
}); });
@override @override

View File

@ -75,7 +75,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.nearDetection / 100).toDouble(), value: (model.nearDetection / 100).clamp(0.0, double.infinity),
title: 'Nearest Detect Dist:', title: 'Nearest Detect Dist:',
description: 'm', description: 'm',
minValue: 0.0, minValue: 0.0,
@ -92,7 +92,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.farDetection / 100).toDouble(), value: (model.farDetection / 100).clamp(0.0, double.infinity),
title: 'Max Detect Dist:', title: 'Max Detect Dist:',
description: 'm', description: 'm',
minValue: 0.0, minValue: 0.0,
@ -109,7 +109,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: model.presenceDelay.toDouble(), value: model.sensiReduce.toDouble(),
title: 'Trigger Level:', title: 'Trigger Level:',
minValue: 0, minValue: 0,
maxValue: 3, maxValue: 3,
@ -117,7 +117,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add( action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorBatchControlEvent( FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds, deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codePresenceDelay, code: FlushMountedPresenceSensorModel.codeSensiReduce,
value: value, value: value,
), ),
), ),
@ -137,19 +137,21 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.sensiReduce.toDouble()), value: (model.presenceDelay / 10).toDouble(),
title: 'Target Confirm Time:', title: 'Target Confirm Time:',
description: 's', description: 's',
minValue: 0, minValue: 0.0,
maxValue: 3, maxValue: 0.5,
steps: 1, steps: 0.1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add( valuesPercision: 1,
FlushMountedPresenceSensorBatchControlEvent( action: (double value) =>
deviceIds: devicesIds, context.read<FlushMountedPresenceSensorBloc>().add(
code: FlushMountedPresenceSensorModel.codeSensiReduce, FlushMountedPresenceSensorBatchControlEvent(
value: value, deviceIds: devicesIds,
), code: FlushMountedPresenceSensorModel.codePresenceDelay,
), value: (value * 10).toInt(),
),
),
), ),
PresenceUpdateData( PresenceUpdateData(
value: ((model.noneDelay / 10).toDouble()), value: ((model.noneDelay / 10).toDouble()),

View File

@ -15,7 +15,7 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la
class FlushMountedPresenceSensorControlView extends StatelessWidget class FlushMountedPresenceSensorControlView extends StatelessWidget
with HelperResponsiveLayout { with HelperResponsiveLayout {
const FlushMountedPresenceSensorControlView({super.key, required this.device}); const FlushMountedPresenceSensorControlView({required this.device, super.key});
final AllDevicesModel device; final AllDevicesModel device;
@ -113,7 +113,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.nearDetection / 100).toDouble(), value: (model.nearDetection / 100).clamp(0.0, double.infinity),
title: 'Nearest Detect Dist:', title: 'Nearest Detect Dist:',
description: 'm', description: 'm',
minValue: 0.0, minValue: 0.0,
@ -129,7 +129,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.farDetection / 100).toDouble(), value: (model.farDetection / 100).clamp(0.0, double.infinity),
title: 'Max Detect Dist:', title: 'Max Detect Dist:',
description: 'm', description: 'm',
minValue: 0.0, minValue: 0.0,
@ -145,20 +145,20 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.presenceDelay.toDouble()), value: model.sensiReduce.toDouble(),
title: 'Trigger Level:', title: 'Trigger Level:',
minValue: 0, minValue: 0,
maxValue: 3, maxValue: 3,
steps: 1, steps: 1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add( action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent( FlushMountedPresenceSensorChangeValueEvent(
code: FlushMountedPresenceSensorModel.codePresenceDelay, code: FlushMountedPresenceSensorModel.codeSensiReduce,
value: value, value: value,
), ),
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.occurDistReduce.toDouble()), value: model.occurDistReduce.toDouble(),
title: 'Indent Level:', title: 'Indent Level:',
minValue: 0, minValue: 0,
maxValue: 3, maxValue: 3,
@ -171,21 +171,23 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
), ),
), ),
PresenceUpdateData( PresenceUpdateData(
value: (model.sensiReduce.toDouble()), value: (model.presenceDelay / 10).toDouble(),
valuesPercision: 1,
title: 'Target Confirm Time:', title: 'Target Confirm Time:',
description: 's', description: 's',
minValue: 0, minValue: 0.0,
maxValue: 3, maxValue: 0.5,
steps: 1, steps: 0.1,
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add( action: (double value) =>
FlushMountedPresenceSensorChangeValueEvent( context.read<FlushMountedPresenceSensorBloc>().add(
code: FlushMountedPresenceSensorModel.codeSensiReduce, FlushMountedPresenceSensorChangeValueEvent(
value: value, code: FlushMountedPresenceSensorModel.codePresenceDelay,
), value: (value * 10).toInt(),
), ),
),
), ),
PresenceUpdateData( PresenceUpdateData(
value: ((model.noneDelay / 10).toDouble()), value: (model.noneDelay / 10).toDouble(),
description: 's', description: 's',
title: 'Disappe Delay:', title: 'Disappe Delay:',
minValue: 20, minValue: 20,

View File

@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
@ -246,7 +247,9 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
final previousState = state; final previousState = state;
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = event.context.read<SpaceTreeBloc>(); var spaceBloc = event.context.read<SpaceTreeBloc>();
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc); var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
await fetchSpaceModels(); await fetchSpaceModels();
await fetchTags(); await fetchTags();
@ -277,11 +280,13 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
LoadCommunityAndSpacesEvent event, LoadCommunityAndSpacesEvent event,
Emitter<SpaceManagementState> emit, Emitter<SpaceManagementState> emit,
) async { ) async {
var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
var spaceBloc = event.context.read<SpaceTreeBloc>(); var spaceBloc = event.context.read<SpaceTreeBloc>();
_onloadProducts(); _onloadProducts();
await fetchTags(); await fetchTags();
// Wait until `communityList` is loaded // Wait until `communityList` is loaded
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc); List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
// Fetch space models after communities are available // Fetch space models after communities are available
final prevSpaceModels = await fetchSpaceModels(); final prevSpaceModels = await fetchSpaceModels();
@ -292,23 +297,38 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
allTags: _cachedTags ?? [])); allTags: _cachedTags ?? []));
} }
Future<List<CommunityModel>> _waitForCommunityList(SpaceTreeBloc spaceBloc) async { Future<List<CommunityModel>> _waitForCommunityList(
SpaceTreeBloc spaceBloc, SpaceTreeState spaceTreeState) async {
// Check if communityList is already populated // Check if communityList is already populated
if (spaceBloc.state.communityList.isNotEmpty) { final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
return spaceBloc.state.communityList; ? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
if (filteredCommunities.isNotEmpty) {
return filteredCommunities;
} }
final completer = Completer<List<CommunityModel>>(); final completer = Completer<List<CommunityModel>>();
final subscription = spaceBloc.stream.listen((state) { final subscription = spaceBloc.stream.listen((state) {
if (state.communityList.isNotEmpty) { if (!completer.isCompleted && state.communityList.isNotEmpty) {
completer.complete(state.communityList); completer
.complete(state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList);
} }
}); });
try {
final communities = await completer.future.timeout(
const Duration(seconds: 10),
onTimeout: () {
if (!completer.isCompleted) {
completer.complete([]);
}
return [];
},
);
// Return the list once available, then cancel the listener return communities;
final communities = await completer.future; } finally {
await subscription.cancel(); await subscription.cancel();
return communities; }
} }
void _onCommunityDelete( void _onCommunityDelete(
@ -446,6 +466,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
if (previousState is SpaceManagementLoaded) { if (previousState is SpaceManagementLoaded) {
await _updateLoadedState( await _updateLoadedState(
event.context,
previousState, previousState,
allSpaces, allSpaces,
event.communityUuid, event.communityUuid,
@ -462,6 +483,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
} }
Future<void> _updateLoadedState( Future<void> _updateLoadedState(
BuildContext context,
SpaceManagementLoaded previousState, SpaceManagementLoaded previousState,
List<SpaceModel> allSpaces, List<SpaceModel> allSpaces,
String communityUuid, String communityUuid,
@ -469,7 +491,10 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
) async { ) async {
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();
await fetchTags(); await fetchTags();
final communities = List<CommunityModel>.from(previousState.communities); final spaceTreeState = context.read<SpaceTreeBloc>().state;
final communities = spaceTreeState.searchQuery.isNotEmpty
? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
for (var community in communities) { for (var community in communities) {
if (community.uuid == communityUuid) { if (community.uuid == communityUuid) {
@ -484,6 +509,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
spaceModels: prevSpaceModels, spaceModels: prevSpaceModels,
allTags: _cachedTags ?? [])); allTags: _cachedTags ?? []));
return; return;
} else {
print("Community not found");
} }
} }
} }
@ -491,12 +518,21 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
Future<List<SpaceModel>> saveSpacesHierarchically( Future<List<SpaceModel>> saveSpacesHierarchically(
BuildContext context, List<SpaceModel> spaces, String communityUuid) async { BuildContext context, List<SpaceModel> spaces, String communityUuid) async {
final orderedSpaces = flattenHierarchy(spaces); final orderedSpaces = flattenHierarchy(spaces);
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = context.read<SpaceTreeBloc>(); CommunityModel? selectedCommunity;
List<CommunityModel> communities = spaceBloc.state.communityList; try {
CommunityModel? selectedCommunity = communities.firstWhere( final spaceTreeState = context.read<SpaceTreeBloc>().state;
(community) => community.uuid == communityUuid, final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
); ? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
selectedCommunity = filteredCommunities.firstWhere(
(community) => community.uuid == communityUuid,
);
} catch (e) {
return [];
}
final parentsToDelete = orderedSpaces.where((space) => final parentsToDelete = orderedSpaces.where((space) =>
space.status == SpaceStatus.deleted && space.status == SpaceStatus.deleted &&
@ -605,11 +641,11 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
projectId: projectUuid); projectId: projectUuid);
} else { } else {
// Call create if the space does not have a UUID // Call create if the space does not have a UUID
final List<CreateTagBodyModel> tagBodyModels = space.tags != null List<CreateTagBodyModel> tagBodyModels = space.tags != null
? space.tags!.map((tag) => tag.toCreateTagBodyModel()).toList() ? space.tags!.map((tag) => tag.toCreateTagBodyModel()).toList()
: []; : [];
final createSubspaceBodyModels = space.subspaces?.map((subspace) { var createSubspaceBodyModels = space.subspaces?.map((subspace) {
final tagBodyModels = final tagBodyModels =
subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? []; subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? [];
return CreateSubspaceModel() return CreateSubspaceModel()
@ -618,6 +654,11 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
}).toList() ?? }).toList() ??
[]; [];
if (space.spaceModel?.uuid != null) {
tagBodyModels = [];
createSubspaceBodyModels = [];
}
final response = await _api.createSpace( final response = await _api.createSpace(
communityId: communityUuid, communityId: communityUuid,
name: space.name, name: space.name,
@ -669,9 +710,12 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
try { try {
await fetchTags(); await fetchTags();
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
var spaceBloc = event.context.read<SpaceTreeBloc>(); final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
List<CommunityModel> communities = spaceBloc.state.communityList; ? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
List<CommunityModel> communities = filteredCommunities;
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();

View File

@ -72,6 +72,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_nameController = TextEditingController( _nameController = TextEditingController(
text: widget.selectedCommunity?.name ?? '', text: widget.selectedCommunity?.name ?? '',
); );
realignTree();
_transformationController = TransformationController(); _transformationController = TransformationController();
if (widget.selectedSpace != null) { if (widget.selectedSpace != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@ -96,6 +98,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
realignTree();
}); });
} }
@ -404,21 +407,31 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
List<SpaceModel> flattenSpaces(List<SpaceModel> spaces) { List<SpaceModel> flattenSpaces(List<SpaceModel> spaces) {
List<SpaceModel> result = []; List<SpaceModel> result = [];
Map<String, SpaceModel> idToSpace = {};
void flatten(SpaceModel space) { void flatten(SpaceModel space) {
if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) { if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) {
return; return;
} }
result.add(space); result.add(space);
idToSpace[space.internalId] = space;
for (var child in space.children) { for (var child in space.children) {
flatten(child); flatten(child);
} }
} }
for (var space in spaces) { for (var space in spaces) {
flatten(space); flatten(space);
} }
for (var space in result) {
if (space.parent != null) {
space.parent = idToSpace[space.parent!.internalId];
}
}
return result; return result;
} }
@ -467,7 +480,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
String communityUuid = widget.selectedCommunity!.uuid; String communityUuid = widget.selectedCommunity!.uuid;
context.read<SpaceManagementBloc>().add(SaveSpacesEvent( context.read<SpaceManagementBloc>().add(SaveSpacesEvent(
context, context,
spaces: spacesToSave, spaces: spacesToSave,
@ -696,7 +708,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final double verticalGap = 180.0; final double verticalGap = 180.0;
final double nodeWidth = 200; final double nodeWidth = 200;
final double nodeHeight = 100; final double nodeHeight = 100;
final double breathingSpace = 300.0; // extra gap after original tree final double breathingSpace = 300.0;
/// Helper to recursively duplicate a node and its children /// Helper to recursively duplicate a node and its children
SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) { SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) {
@ -704,7 +716,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final duplicated = SpaceModel( final duplicated = SpaceModel(
name: duplicatedName, name: duplicatedName,
icon: original.icon, icon: original.icon,
position: Offset.zero, position: original.position,
isPrivate: original.isPrivate, isPrivate: original.isPrivate,
children: [], children: [],
status: SpaceStatus.newSpace, status: SpaceStatus.newSpace,
@ -723,11 +735,13 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
direction: "down", direction: "down",
); );
connections.add(newConnection); connections.add(newConnection);
duplicated.incomingConnection = newConnection; duplicated.incomingConnection = newConnection;
duplicatedParent.addOutgoingConnection(newConnection); duplicatedParent.addOutgoingConnection(newConnection);
duplicatedParent.children.add(duplicated); duplicatedParent.children.add(duplicated);
} }
// Duplicate its children recursively
for (var child in original.children) { for (var child in original.children) {
duplicateRecursive(child, duplicated); duplicateRecursive(child, duplicated);
} }
@ -735,48 +749,29 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return duplicated; return duplicated;
} }
/// 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;
}
void assignPositions(SpaceModel n, double x, double y) {
double subtreeWidth = calculateSubtreeWidth(n);
double centerX = x + subtreeWidth / 2 - nodeWidth / 2;
n.position = Offset(centerX, y);
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);
}
/// Actual duplication process /// Actual duplication process
setState(() { setState(() {
if (space.parent == null) { if (space.parent == null) {
// Duplicating a ROOT node // Duplicating a ROOT node
SpaceModel duplicatedRoot = duplicateRecursive(space, null); duplicateRecursive(space, null);
connections = createConnections(spaces);
realignTree(); realignTree();
} else { } else {
// Duplicating a CHILD node inside its parent final parent = space.parent!;
SpaceModel duplicated = duplicateRecursive(space, space.parent);
final duplicated = duplicateRecursive(space, parent);
final newConnection = Connection(
startSpace: parent,
endSpace: duplicated,
direction: "down",
);
connections.add(newConnection);
duplicated.incomingConnection = newConnection;
parent.addOutgoingConnection(newConnection);
parent.children.add(duplicated);
connections = createConnections(spaces);
realignTree(); realignTree();
//_realignSubtree(space.parent!);
} }
}); });
} }

View File

@ -1,6 +1,7 @@
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/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
@ -45,7 +46,6 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_spaceModels = List.from(widget.spaceModels ?? []); _spaceModels = List.from(widget.spaceModels ?? []);
} }
@ -106,9 +106,8 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
children: [ children: [
SidebarWidget( SidebarWidget(
communities: widget.communities, communities: widget.communities,
selectedSpaceUuid: widget.selectedSpace?.uuid ?? selectedSpaceUuid:
widget.selectedCommunity?.uuid ?? widget.selectedSpace?.uuid ?? widget.selectedCommunity?.uuid ?? '',
'',
onCreateCommunity: (name, description) { onCreateCommunity: (name, description) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
CreateCommunityEvent(name, description, context), CreateCommunityEvent(name, description, context),

View File

@ -1,3 +1,5 @@
import 'dart:async';
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/common/widgets/empty_search_result_widget.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> { class _SidebarWidgetState extends State<SidebarWidget> {
late final ScrollController _scrollController; late final ScrollController _scrollController;
Timer? _debounce;
String _searchQuery = ''; String _searchQuery = '';
String? _selectedSpaceUuid; String? _selectedSpaceUuid;
@ -43,10 +46,12 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override @override
void initState() { void initState() {
super.initState();
_scrollController = ScrollController(); _scrollController = ScrollController();
_scrollController.addListener(_onScroll); _scrollController.addListener(_onScroll);
_selectedId = widget.selectedSpaceUuid; _selectedId = widget.selectedSpaceUuid;
super.initState(); _searchQuery = '';
context.read<SpaceTreeBloc>().add(ClearCachedData());
} }
void _onScroll() { void _onScroll() {
@ -67,11 +72,22 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override @override
void dispose() { void dispose() {
_scrollController.removeListener(_onScroll); _scrollController.removeListener(_onScroll);
_scrollController.dispose(); _scrollController.dispose();
_debounce?.cancel();
super.dispose(); 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 @override
void didUpdateWidget(covariant SidebarWidget oldWidget) { void didUpdateWidget(covariant SidebarWidget oldWidget) {
if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) { if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) {
@ -91,7 +107,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final spaceTreeState = context.watch<SpaceTreeBloc>().state; final spaceTreeState = context.watch<SpaceTreeBloc>().state;
final filteredCommunities = spaceTreeState.isSearching final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty
? spaceTreeState.filteredCommunity ? spaceTreeState.filteredCommunity
: spaceTreeState.communityList; : spaceTreeState.communityList;
@ -104,13 +120,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
children: [ children: [
SidebarHeader(onAddCommunity: _onAddCommunity), SidebarHeader(onAddCommunity: _onAddCommunity),
CustomSearchBar( CustomSearchBar(
onSearchChanged: (query) { onSearchChanged: _onSearchChanged,
setState(() {
_searchQuery = query;
});
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
},
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
@ -123,10 +133,15 @@ class _SidebarWidgetState extends State<SidebarWidget> {
communities: filteredCommunities, communities: filteredCommunities,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == filteredCommunities.length) { if (index == filteredCommunities.length) {
return const Padding( final spaceTreeState = context.read<SpaceTreeBloc>().state;
padding: EdgeInsets.all(8.0), if (spaceTreeState.paginationIsLoading) {
child: Center(child: CircularProgressIndicator()), return const Padding(
); padding: EdgeInsets.all(8.0),
child: Center(child: CircularProgressIndicator()),
);
} else {
return const SizedBox.shrink();
}
} }
return _buildCommunityTile(context, filteredCommunities[index]); return _buildCommunityTile(context, filteredCommunities[index]);
}), }),

View File

@ -1,5 +1,7 @@
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/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
@ -27,6 +29,8 @@ class SpaceModelPage extends StatelessWidget {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (state is SpaceModelLoaded) { } else if (state is SpaceModelLoaded) {
final spaceModels = state.spaceModels; final spaceModels = state.spaceModels;
context.read<SpaceTreeBloc>().add(ClearCachedData());
final allTagValues = _getAllTagValues(spaceModels); final allTagValues = _getAllTagValues(spaceModels);
final allSpaceModelNames = _getAllSpaceModelName(spaceModels); final allSpaceModelNames = _getAllSpaceModelName(spaceModels);

View File

@ -46,14 +46,14 @@ final class DebouncedBatchControlDevicesService
final BatchControlDevicesService decoratee; final BatchControlDevicesService decoratee;
final Duration debounceDuration; final Duration debounceDuration;
final _pendingRequests = <(List<String> uuids, String code, Object value)>[];
var _isProcessing = false;
DebouncedBatchControlDevicesService({ DebouncedBatchControlDevicesService({
required this.decoratee, required this.decoratee,
this.debounceDuration = const Duration(milliseconds: 800), this.debounceDuration = const Duration(milliseconds: 1500),
}); });
final _pendingRequests = <(List<String> uuids, String code, Object value)>[];
var _isProcessing = false;
@override @override
Future<bool> batchControlDevices({ Future<bool> batchControlDevices({
required List<String> uuids, required List<String> uuids,
@ -68,16 +68,26 @@ final class DebouncedBatchControlDevicesService
await Future.delayed(debounceDuration); await Future.delayed(debounceDuration);
final lastRequest = _pendingRequests.last; final groupedRequests =
<String, (List<String> uuids, String code, Object value)>{};
for (final request in _pendingRequests) {
final (_, requestCode, requestValue) = request;
groupedRequests[requestCode] = request;
}
_pendingRequests.clear(); _pendingRequests.clear();
try { try {
final (lastRequestUuids, lastRequestCode, lastRequestValue) = lastRequest; var allSuccessful = true;
return decoratee.batchControlDevices( for (final request in groupedRequests.values) {
uuids: lastRequestUuids, final (lastRequestUuids, lastRequestCode, lastRequestValue) = request;
code: lastRequestCode, final success = await decoratee.batchControlDevices(
value: lastRequestValue, uuids: lastRequestUuids,
); code: lastRequestCode,
value: lastRequestValue,
);
if (!success) allSuccessful = false;
}
return allSuccessful;
} finally { } finally {
_isProcessing = false; _isProcessing = false;
} }

View File

@ -40,7 +40,7 @@ final class DebouncedControlDeviceService implements ControlDeviceService {
DebouncedControlDeviceService({ DebouncedControlDeviceService({
required this.decoratee, required this.decoratee,
this.debounceDuration = const Duration(milliseconds: 800), this.debounceDuration = const Duration(milliseconds: 1500),
}); });
final _pendingRequests = <(String deviceUuid, Status status)>[]; final _pendingRequests = <(String deviceUuid, Status status)>[];
@ -59,15 +59,24 @@ final class DebouncedControlDeviceService implements ControlDeviceService {
await Future.delayed(debounceDuration); await Future.delayed(debounceDuration);
final lastRequest = _pendingRequests.last; final groupedRequests = <String, (String deviceUuid, Status status)>{};
for (final request in _pendingRequests) {
final (_, requestStatus) = request;
groupedRequests[requestStatus.code] = request;
}
_pendingRequests.clear(); _pendingRequests.clear();
try { try {
final (lastRequestDeviceUuid, lastRequestStatus) = lastRequest; var allSuccessful = true;
return decoratee.controlDevice( for (final request in groupedRequests.values) {
deviceUuid: lastRequestDeviceUuid, final (lastRequestDeviceUuid, lastRequestStatus) = request;
status: lastRequestStatus, final success = await decoratee.controlDevice(
); deviceUuid: lastRequestDeviceUuid,
status: lastRequestStatus,
);
if (!success) allSuccessful = false;
}
return allSuccessful;
} finally { } finally {
_isProcessing = false; _isProcessing = false;
} }

View File

@ -281,6 +281,7 @@ class CommunitySpaceManagementApi {
return json['success'] ?? false; return json['success'] ?? false;
}, },
); );
return response; return response;
} catch (e) { } catch (e) {
debugPrint('Error updating space: $e'); debugPrint('Error updating space: $e');