Compare commits

..
8 changed files with 190 additions and 207 deletions
@@ -108,7 +108,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return DeviceManagementBody( return DeviceManagementBody(
devices: deviceState.filteredDevices); devices: deviceState.filteredDevices);
} else { } else {
return const DeviceManagementBody(devices: []); return const Center(child: Text('Error fetching Devices'));
} }
}, },
); );
@@ -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
@@ -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()),
@@ -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,
@@ -1,6 +1,4 @@
// Flutter imports // Flutter imports
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';
@@ -338,7 +336,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
spaces.add(newSpace); spaces.add(newSpace);
_updateNodePosition(newSpace, newSpace.position); _updateNodePosition(newSpace, newSpace.position);
realignTree();
}); });
}, },
); );
@@ -453,6 +450,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _saveSpaces() { void _saveSpaces() {
if (widget.selectedCommunity == null) { if (widget.selectedCommunity == null) {
debugPrint("No community selected for saving spaces.");
return; return;
} }
@@ -532,83 +530,35 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
Offset getBalancedChildPosition(SpaceModel parent) { Offset getBalancedChildPosition(SpaceModel parent) {
const double nodeWidth = 200; int totalSiblings = parent.children.length + 1;
const double verticalGap = 180; double totalWidth = (totalSiblings - 1) * 250; // Horizontal spacing
double startX = parent.position.dx - (totalWidth / 2);
if (parent.children.isEmpty) { Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180);
// 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);
double childX = startX + (parent.children.length * (nodeWidth + 60)); // Check for overlaps & adjust
return Offset(childX, parent.position.dy + verticalGap); while (spaces.any((s) => (s.position - position).distance < 250)) {
position = Offset(position.dx + 250, position.dy);
} }
return position;
} }
void realignTree() { void realignTree() {
const double nodeWidth = 200; void updatePositions(SpaceModel node, double x, double y) {
const double nodeHeight = 100; node.position = Offset(x, y);
const double horizontalGap = 60;
const double verticalGap = 180;
const double rootGap = 400; // extra space between different roots
double canvasRightEdge = 1000; int numChildren = node.children.length;
double canvasBottomEdge = 1000; double childStartX = x - ((numChildren - 1) * 250) / 2;
double calculateSubtreeWidth(SpaceModel node) { for (int i = 0; i < numChildren; i++) {
if (node.children.isEmpty) return nodeWidth; updatePositions(node.children[i], childStartX + (i * 250), y + 180);
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;
}
} }
} }
// ⚡ New: layout each root separately if (spaces.isNotEmpty) {
final List<SpaceModel> roots = spaces updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
.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) { void _onDuplicate(BuildContext parentContext) {
@@ -692,19 +642,63 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
void _duplicateSpace(SpaceModel space) { void _duplicateSpace(SpaceModel space) {
final double horizontalGap = 250.0; final Map<SpaceModel, SpaceModel> originalToDuplicate = {};
final double verticalGap = 180.0; double horizontalGap = 250.0; // Increased spacing
final double nodeWidth = 200; double verticalGap = 180.0; // Adjusted for better visualization
final double nodeHeight = 100;
final double breathingSpace = 300.0; // extra gap after original tree
/// Helper to recursively duplicate a node and its children 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**
SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) { 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); final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
print(
"🟡 Duplicating ${original.name} → ${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})");
final duplicated = SpaceModel( final duplicated = SpaceModel(
name: duplicatedName, name: duplicatedName,
icon: original.icon, icon: original.icon,
position: Offset.zero, position: newPosition,
isPrivate: original.isPrivate, isPrivate: original.isPrivate,
children: [], children: [],
status: SpaceStatus.newSpace, status: SpaceStatus.newSpace,
@@ -714,20 +708,28 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
tags: original.tags, tags: original.tags,
); );
spaces.add(duplicated); setState(() {
spaces.add(duplicated);
_updateNodePosition(duplicated, duplicated.position);
if (duplicatedParent != null) { if (duplicatedParent != null) {
final newConnection = Connection( final newConnection = Connection(
startSpace: duplicatedParent, startSpace: duplicatedParent,
endSpace: duplicated, endSpace: duplicated,
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);
} print("🔗 Created connection: ${duplicatedParent.name} → ${duplicated.name}");
}
// **Recalculate the whole tree to avoid overlaps**
realignTree();
});
// Recursively duplicate children
for (var child in original.children) { for (var child in original.children) {
duplicateRecursive(child, duplicated); duplicateRecursive(child, duplicated);
} }
@@ -735,49 +737,21 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return duplicated; return duplicated;
} }
/// Layout a subtree rooted at node /// **Handle root duplication**
void layoutSubtree(SpaceModel node, double startX, double startY) { if (space.parent == null) {
double calculateSubtreeWidth(SpaceModel n) { print("🟠 Duplicating root node: ${space.name}");
if (n.children.isEmpty) return nodeWidth; SpaceModel duplicatedRoot = duplicateRecursive(space, null);
double width = 0;
for (var child in n.children) {
width += calculateSubtreeWidth(child) + horizontalGap;
}
return width - horizontalGap;
}
void assignPositions(SpaceModel n, double x, double y) { setState(() {
double subtreeWidth = calculateSubtreeWidth(n); spaces.add(duplicatedRoot);
double centerX = x + subtreeWidth / 2 - nodeWidth / 2; realignTree();
n.position = Offset(centerX, y); });
if (n.children.length == 1) { print("✅ Root duplication successful: ${duplicatedRoot.name}");
assignPositions(n.children.first, centerX, y + verticalGap); } else {
} else { duplicateRecursive(space, space.parent);
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 print("🟢 Finished duplication process for: ${space.name}");
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,5 +1,3 @@
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';
@@ -38,7 +36,6 @@ 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;
@@ -46,10 +43,10 @@ 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();
} }
void _onScroll() { void _onScroll() {
@@ -70,22 +67,11 @@ 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) {
@@ -105,7 +91,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.searchQuery.isNotEmpty final filteredCommunities = spaceTreeState.isSearching
? spaceTreeState.filteredCommunity ? spaceTreeState.filteredCommunity
: spaceTreeState.communityList; : spaceTreeState.communityList;
@@ -118,7 +104,13 @@ class _SidebarWidgetState extends State<SidebarWidget> {
children: [ children: [
SidebarHeader(onAddCommunity: _onAddCommunity), SidebarHeader(onAddCommunity: _onAddCommunity),
CustomSearchBar( CustomSearchBar(
onSearchChanged: _onSearchChanged, onSearchChanged: (query) {
setState(() {
_searchQuery = query;
});
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
},
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
@@ -131,15 +123,10 @@ class _SidebarWidgetState extends State<SidebarWidget> {
communities: filteredCommunities, communities: filteredCommunities,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == filteredCommunities.length) { if (index == filteredCommunities.length) {
final spaceTreeState = context.read<SpaceTreeBloc>().state; return const Padding(
if (spaceTreeState.paginationIsLoading) { padding: EdgeInsets.all(8.0),
return const Padding( child: Center(child: CircularProgressIndicator()),
padding: EdgeInsets.all(8.0), );
child: Center(child: CircularProgressIndicator()),
);
} else {
return const SizedBox.shrink();
}
} }
return _buildCommunityTile(context, filteredCommunities[index]); return _buildCommunityTile(context, filteredCommunities[index]);
}), }),
+21 -11
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;
} }
+16 -7
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;
} }