Compare commits

...
Author SHA1 Message Date
Faris Armoush 34d4d892d9 refactor: streamline value calculations in FlushMountedPresenceSensorControlView 2025-04-27 11:11:38 +03:00
Faris Armoush 3193fd26fe refactor: update presence delay value and enhance request handling in DebouncedBatchControlDevicesService 2025-04-27 11:04:54 +03:00
Faris Armoush 43802a9fad refactor: update detection value calculations and adjust parameters for presence delay 2025-04-27 10:57:44 +03:00
Faris Armoush 6e0b1775f0 fix: reorder constructor parameters for consistency in FlushMountedPresenceSensorControlView 2025-04-27 10:55:18 +03:00
Faris Armoush 233fb2ee2c refactor: improve formatting of clamp method for near and far detection values 2025-04-27 10:55:03 +03:00
Faris Armoush b26928b3d5 fixed ui bugs. 2025-04-27 10:14:35 +03:00
Faris Armoush 6fc35a7b9a enhanced device debouncing to accomodate for multiple calls from the different devices functions calls. 2025-04-27 10:14:19 +03:00
Faris Armoush 756457927c removed unnecessary isBatch flag from FlushMountedPresenceSensorChangeValueEvent. 2025-04-27 10:13:53 +03:00
hannathkadher 17a582ab99 Merge pull request #157 from SyncrowIOT/SP-1415 2025-04-25 10:50:06 +04:00
hannathkadher 09fb604acc added filtering 2025-04-25 10:49:25 +04:00
Faris Armoush 2068df173d Merge pull request #155 from SyncrowIOT/SP-1441-rework-FE-On-routine-creation-page-When-the-user-drags-a-card-that-has-signs-and-selects-a-sign-without-a-number-then-confirms-the-value-appears-to-be-Null
Sp 1441 rework fe on routine creation page when the user drags a card that has signs and selects a sign without a number then confirms the value appears to be null
2025-04-24 16:25:39 +03:00
Faris Armoush bfc2a381d2 Merge pull request #156 from SyncrowIOT/SP-1464-FE-implement-Batch-Control-Dialog
Sp 1464 fe implement batch control dialog
2025-04-24 16:25:17 +03:00
Faris Armoush c03b8f290c refactor function tap handlers to use RoutineTapFunctionHelper for improved code reuse and readability, and to remove code duplication. 2025-04-24 10:25:41 +03:00
Faris Armoush 2c684a9495 SP-1441 rework. 2025-04-23 16:58:50 +03:00
14 changed files with 333 additions and 185 deletions
@@ -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,17 +137,19 @@ 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,
action: (double value) =>
context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorBatchControlEvent( FlushMountedPresenceSensorBatchControlEvent(
deviceIds: devicesIds, deviceIds: devicesIds,
code: FlushMountedPresenceSensorModel.codeSensiReduce, code: FlushMountedPresenceSensorModel.codePresenceDelay,
value: value, value: (value * 10).toInt(),
), ),
), ),
), ),
@@ -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) =>
context.read<FlushMountedPresenceSensorBloc>().add(
FlushMountedPresenceSensorChangeValueEvent( FlushMountedPresenceSensorChangeValueEvent(
code: FlushMountedPresenceSensorModel.codeSensiReduce, code: FlushMountedPresenceSensorModel.codePresenceDelay,
value: value, 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,
@@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@@ -74,11 +75,26 @@ class ACHelper {
child: _buildFunctionsList( child: _buildFunctionsList(
context: context, context: context,
acFunctions: acFunctions, acFunctions: acFunctions,
onFunctionSelected: (functionCode, operationName) => device: device,
context.read<FunctionBloc>().add(SelectFunction( onFunctionSelected: (functionCode, operationName) {
RoutineTapFunctionHelper.onTapFunction(
context,
functionCode: functionCode, functionCode: functionCode,
operationName: operationName, functionOperationName: operationName,
)), functionValueDescription:
selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'temp_set',
'temp_current',
],
defaultValue: functionCode == 'temp_set'
? 200
: functionCode == 'temp_current'
? -100
: 0,
);
},
), ),
), ),
// Value selector // Value selector
@@ -137,6 +153,7 @@ class ACHelper {
required BuildContext context, required BuildContext context,
required List<ACFunction> acFunctions, required List<ACFunction> acFunctions,
required Function(String, String) onFunctionSelected, required Function(String, String) onFunctionSelected,
required AllDevicesModel? device,
}) { }) {
return ListView.separated( return ListView.separated(
shrinkWrap: false, shrinkWrap: false,
@@ -131,7 +131,12 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
CpsFunctionsList(cpsFunctions: _cpsFunctions), CpsFunctionsList(
cpsFunctions: _cpsFunctions,
device: widget.device,
selectedFunctionData: selectedFunctionData,
dialogType: widget.dialogType,
),
if (state.selectedFunction != null) if (state.selectedFunction != null)
Expanded( Expanded(
child: isToggleFunction child: isToggleFunction
@@ -1,14 +1,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class CpsFunctionsList extends StatelessWidget { class CpsFunctionsList extends StatelessWidget {
const CpsFunctionsList({required this.cpsFunctions, super.key}); const CpsFunctionsList({
required this.cpsFunctions,
required this.device,
required this.selectedFunctionData,
required this.dialogType,
super.key,
});
final List<CpsFunctions> cpsFunctions; final List<CpsFunctions> cpsFunctions;
final AllDevicesModel? device;
final DeviceFunctionData? selectedFunctionData;
final String dialogType;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -26,11 +36,26 @@ class CpsFunctionsList extends StatelessWidget {
return RoutineDialogFunctionListTile( return RoutineDialogFunctionListTile(
iconPath: function.icon, iconPath: function.icon,
operationName: function.operationName, operationName: function.operationName,
onTap: () => context.read<FunctionBloc>().add( onTap: () => RoutineTapFunctionHelper.onTapFunction(
SelectFunction( context,
functionCode: function.code, functionCode: function.code,
operationName: function.operationName, functionOperationName: function.operationName,
), functionValueDescription: selectedFunctionData?.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'static_max_dis',
'presence_reference',
'moving_reference',
'perceptual_boundary',
'moving_boundary',
'moving_rigger_time',
'moving_static_time',
'none_body_time',
'moving_max_dis',
'moving_range',
'presence_range',
if (dialogType == "IF") 'sensitivity',
],
), ),
); );
}, },
@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
abstract final class RoutineTapFunctionHelper {
const RoutineTapFunctionHelper._();
static void onTapFunction(
BuildContext context, {
required String functionCode,
required String functionOperationName,
required String? functionValueDescription,
required String? deviceUuid,
required List<String> codesToAddIntoFunctionsWithDefaultValue,
int defaultValue = 0,
}) {
final functionsBloc = context.read<FunctionBloc>();
functionsBloc.add(
SelectFunction(
functionCode: functionCode,
operationName: functionOperationName,
),
);
final addedFunctions = functionsBloc.state.addedFunctions;
final isFunctionAlreadyAdded = addedFunctions.any(
(e) => e.functionCode == functionCode,
);
final shouldAddFunction =
codesToAddIntoFunctionsWithDefaultValue.contains(functionCode);
if (!isFunctionAlreadyAdded && shouldAddFunction) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: deviceUuid ?? '',
functionCode: functionCode,
operationName: functionOperationName,
value: defaultValue,
condition: '==',
valueDescription: functionValueDescription,
),
),
);
}
}
}
@@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@@ -24,21 +25,23 @@ class OneGangSwitchHelper {
required String uniqueCustomId, required String uniqueCustomId,
required bool removeComparetors, required bool removeComparetors,
}) async { }) async {
List<BaseSwitchFunction> oneGangFunctions = functions.whereType<BaseSwitchFunction>().toList(); List<BaseSwitchFunction> oneGangFunctions =
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>( return showDialog<Map<String, dynamic>?>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return BlocProvider( return BlocProvider(
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])), create: (_) => FunctionBloc()
..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
child: AlertDialog( child: AlertDialog(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: BlocBuilder<FunctionBloc, FunctionBlocState>( content: BlocBuilder<FunctionBloc, FunctionBlocState>(
builder: (context, state) { builder: (context, state) {
final selectedFunction = state.selectedFunction; final selectedFunction = state.selectedFunction;
final selectedOperationName = state.selectedOperationName; final selectedOperationName = state.selectedOperationName;
final selectedFunctionData = final selectedFunctionData = state.addedFunctions
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction, .firstWhere((f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData( orElse: () => DeviceFunctionData(
entityId: '', entityId: '',
functionCode: selectedFunction ?? '', functionCode: selectedFunction ?? '',
@@ -84,12 +87,19 @@ class OneGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () { onTap: () =>
context.read<FunctionBloc>().add(SelectFunction( RoutineTapFunctionHelper.onTapFunction(
context,
functionCode: function.code, functionCode: function.code,
operationName: function.operationName, functionOperationName:
)); function.operationName,
}, functionValueDescription:
selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1',
],
),
); );
}, },
), ),
@@ -220,11 +230,11 @@ class OneGangSwitchHelper {
selectedFunctionData, selectedFunctionData,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownDisplay( _buildCountDownDisplay(context, initialValue, device, operationName,
context, initialValue, device, operationName, selectedFunctionData, selectCode), selectedFunctionData, selectCode),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownSlider( _buildCountDownSlider(context, initialValue, device, operationName,
context, initialValue, device, operationName, selectedFunctionData, selectCode), selectedFunctionData, selectCode),
], ],
); );
} }
@@ -314,7 +324,8 @@ class OneGangSwitchHelper {
value: (initialValue ?? 0).toDouble(), value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0, min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1)) (operationalValues.stepValue ?? 1))
.round(), .round(),
onChanged: (value) { onChanged: (value) {
@@ -368,7 +379,9 @@ class OneGangSwitchHelper {
trailing: Icon( trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray, color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
if (!isSelected) { if (!isSelected) {
@@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@@ -85,15 +86,21 @@ class ThreeGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () { onTap: () =>
context RoutineTapFunctionHelper.onTapFunction(
.read<FunctionBloc>() context,
.add(SelectFunction(
functionCode: function.code, functionCode: function.code,
operationName: functionOperationName:
function.operationName, function.operationName,
)); functionValueDescription:
}, selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1',
'countdown_2',
'countdown_3',
],
),
); );
}, },
), ),
@@ -183,8 +190,7 @@ class ThreeGangSwitchHelper {
); );
} }
final selectedFn = final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@@ -266,8 +272,7 @@ class ThreeGangSwitchHelper {
minHeight: 40.0, minHeight: 40.0,
minWidth: 40.0, minWidth: 40.0,
), ),
isSelected: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(), children: conditions.map((c) => Text(c)).toList(),
); );
} }
@@ -316,8 +321,8 @@ class ThreeGangSwitchHelper {
value: (initialValue ?? 0).toDouble(), value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0, min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - divisions:
(operationalValues.minValue ?? 0)) / (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1)) (operationalValues.stepValue ?? 1))
.round(), .round(),
onChanged: (value) { onChanged: (value) {
@@ -369,9 +374,7 @@ class ThreeGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@@ -387,8 +390,7 @@ class ThreeGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );
@@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@@ -85,15 +86,20 @@ class TwoGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () { onTap: () =>
context RoutineTapFunctionHelper.onTapFunction(
.read<FunctionBloc>() context,
.add(SelectFunction(
functionCode: function.code, functionCode: function.code,
operationName: functionOperationName:
function.operationName, function.operationName,
)); functionValueDescription:
}, selectedFunctionData.valueDescription,
deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1',
'countdown_2',
],
),
); );
}, },
), ),
@@ -167,8 +173,7 @@ class TwoGangSwitchHelper {
required String operationName, required String operationName,
required bool removeComparetors, required bool removeComparetors,
}) { }) {
if (selectedFunction == 'countdown_1' || if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') {
selectedFunction == 'countdown_2') {
final initialValue = selectedFunctionData?.value ?? 0; final initialValue = selectedFunctionData?.value ?? 0;
return _buildTemperatureSelector( return _buildTemperatureSelector(
context: context, context: context,
@@ -182,8 +187,7 @@ class TwoGangSwitchHelper {
); );
} }
final selectedFn = final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@@ -265,8 +269,7 @@ class TwoGangSwitchHelper {
minHeight: 40.0, minHeight: 40.0,
minWidth: 40.0, minWidth: 40.0,
), ),
isSelected: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(), children: conditions.map((c) => Text(c)).toList(),
); );
} }
@@ -315,8 +318,8 @@ class TwoGangSwitchHelper {
value: (initialValue ?? 0).toDouble(), value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0, min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) - divisions:
(operationalValues.minValue ?? 0)) / (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1)) (operationalValues.stepValue ?? 1))
.round(), .round(),
onChanged: (value) { onChanged: (value) {
@@ -368,9 +371,7 @@ class TwoGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@@ -386,8 +387,7 @@ class TwoGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );
@@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@@ -111,13 +112,23 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
_buildFunctionList(context), _buildFunctionList(context, state),
if (state.selectedFunction != null) _buildValueSelector(context, state), if (state.selectedFunction != null) _buildValueSelector(context, state),
], ],
); );
} }
Widget _buildFunctionList(BuildContext context) { Widget _buildFunctionList(BuildContext context, FunctionBlocState state) {
final selectedFunction = state.selectedFunction;
final selectedFunctionData = state.addedFunctions.firstWhere(
(f) => f.functionCode == selectedFunction,
orElse: () => DeviceFunctionData(
entityId: '',
functionCode: selectedFunction ?? '',
operationName: '',
value: null,
),
);
return SizedBox( return SizedBox(
width: 360, width: 360,
child: ListView.separated( child: ListView.separated(
@@ -149,11 +160,17 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () => context.read<FunctionBloc>().add( onTap: () => RoutineTapFunctionHelper.onTapFunction(
SelectFunction( context,
functionCode: function.code, functionCode: function.code,
operationName: function.operationName, functionOperationName: function.operationName,
), functionValueDescription: selectedFunctionData.valueDescription,
deviceUuid: widget.device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [
'dis_current',
'presence_time',
'illuminance_value',
],
), ),
); );
}, },
@@ -3,6 +3,7 @@ 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';
import 'package:syncrow_web/common/widgets/search_bar.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart';
import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart'; import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_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_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';
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';
@@ -15,6 +16,8 @@ import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/cent
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
import '../../../space_tree/bloc/space_tree_event.dart';
class SidebarWidget extends StatefulWidget { class SidebarWidget extends StatefulWidget {
final List<CommunityModel> communities; final List<CommunityModel> communities;
final String? selectedSpaceUuid; final String? selectedSpaceUuid;
@@ -40,13 +43,31 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override @override
void initState() { void initState() {
_selectedId = widget.selectedSpaceUuid;
_scrollController = ScrollController(); _scrollController = ScrollController();
_scrollController.addListener(_onScroll);
_selectedId = widget.selectedSpaceUuid;
super.initState(); super.initState();
} }
void _onScroll() {
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) {
// Trigger pagination event
final bloc = context.read<SpaceTreeBloc>();
if (!bloc.state.paginationIsLoading && bloc.state.paginationModel?.hasNext == true) {
bloc.add(
PaginationEvent(
bloc.state.paginationModel!,
bloc.state.communityList,
),
);
}
}
}
@override @override
void dispose() { void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose(); _scrollController.dispose();
super.dispose(); super.dispose();
} }
@@ -59,38 +80,6 @@ class _SidebarWidgetState extends State<SidebarWidget> {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
} }
List<CommunityModel> _filteredCommunities() {
if (_searchQuery.isEmpty) {
_selectedSpaceUuid = null;
return widget.communities;
}
return widget.communities.where((community) {
final containsQueryInCommunity =
community.name.toLowerCase().contains(_searchQuery.toLowerCase());
final containsQueryInSpaces = community.spaces.any((space) =>
_containsQuery(space: space, query: _searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces;
}).toList();
}
bool _containsQuery({
required SpaceModel space,
required String query,
}) {
final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren = space.children.any(
(child) => _containsQuery(space: child, query: query),
);
if (matchesSpace || matchesChildren) {
_selectedSpaceUuid = space.uuid;
}
return matchesSpace || matchesChildren;
}
bool _isSpaceOrChildSelected(SpaceModel space) { bool _isSpaceOrChildSelected(SpaceModel space) {
final isSpaceSelected = _selectedSpaceUuid == space.uuid; final isSpaceSelected = _selectedSpaceUuid == space.uuid;
final anySubSpaceIsSelected = space.children.any(_isSpaceOrChildSelected); final anySubSpaceIsSelected = space.children.any(_isSpaceOrChildSelected);
@@ -101,7 +90,10 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final filteredCommunities = _filteredCommunities(); final spaceTreeState = context.watch<SpaceTreeBloc>().state;
final filteredCommunities = spaceTreeState.isSearching
? spaceTreeState.filteredCommunity
: spaceTreeState.communityList;
return Container( return Container(
width: _width, width: _width,
@@ -112,7 +104,13 @@ class _SidebarWidgetState extends State<SidebarWidget> {
children: [ children: [
SidebarHeader(onAddCommunity: _onAddCommunity), SidebarHeader(onAddCommunity: _onAddCommunity),
CustomSearchBar( CustomSearchBar(
onSearchChanged: (query) => setState(() => _searchQuery = query), onSearchChanged: (query) {
setState(() {
_searchQuery = query;
});
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
},
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
@@ -123,11 +121,15 @@ class _SidebarWidgetState extends State<SidebarWidget> {
scrollController: _scrollController, scrollController: _scrollController,
onScrollToEnd: () {}, onScrollToEnd: () {},
communities: filteredCommunities, communities: filteredCommunities,
itemBuilder: (context, index) => _buildCommunityTile( itemBuilder: (context, index) {
context, if (index == filteredCommunities.length) {
filteredCommunities[index], return const Padding(
), padding: EdgeInsets.all(8.0),
), child: Center(child: CircularProgressIndicator()),
);
}
return _buildCommunityTile(context, filteredCommunities[index]);
}),
), ),
), ),
], ],
@@ -205,9 +207,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
); );
} }
void _onAddCommunity() => _selectedId?.isNotEmpty ?? true void _onAddCommunity() =>
? _clearSelection() _selectedId?.isNotEmpty ?? true ? _clearSelection() : _showCreateCommunityDialog();
: _showCreateCommunityDialog();
void _clearSelection() { void _clearSelection() {
setState(() => _selectedId = ''); setState(() => _selectedId = '');
@@ -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) {
final (lastRequestUuids, lastRequestCode, lastRequestValue) = request;
final success = await decoratee.batchControlDevices(
uuids: lastRequestUuids, uuids: lastRequestUuids,
code: lastRequestCode, code: lastRequestCode,
value: lastRequestValue, value: lastRequestValue,
); );
if (!success) allSuccessful = false;
}
return allSuccessful;
} finally { } finally {
_isProcessing = false; _isProcessing = false;
} }
+13 -4
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) {
final (lastRequestDeviceUuid, lastRequestStatus) = request;
final success = await decoratee.controlDevice(
deviceUuid: lastRequestDeviceUuid, deviceUuid: lastRequestDeviceUuid,
status: lastRequestStatus, status: lastRequestStatus,
); );
if (!success) allSuccessful = false;
}
return allSuccessful;
} finally { } finally {
_isProcessing = false; _isProcessing = false;
} }