diff --git a/assets/icons/energy_consumed_icon.svg b/assets/icons/energy_consumed_icon.svg new file mode 100644 index 00000000..d457619c --- /dev/null +++ b/assets/icons/energy_consumed_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index de1b7632..808a683f 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -12,6 +12,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/ import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gateway.dart'; +import 'package:syncrow_web/pages/routines/models/pc/energy_clamp_functions.dart'; import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_functions.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart'; @@ -248,6 +249,8 @@ SOS tempIcon = Assets.waterLeakNormal; } else if (type == DeviceType.NCPS) { tempIcon = Assets.sensors; + } else if (type == DeviceType.PC) { + tempIcon = Assets.powerClamp; } else { tempIcon = Assets.logoHorizontal; } @@ -393,6 +396,59 @@ SOS BacklightFunction( deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), ]; + case 'PC': + return [ + TotalEnergyConsumedStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + TotalActivePowerConsumedStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + VoltagePhaseSequenceDetectionFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + TotalCurrentStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + FrequencyStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + + // Phase A + EnergyConsumedAStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + ActivePowerAStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + VoltageAStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + PowerFactorAStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + CurrentAStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + + // Phase B + EnergyConsumedBStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + ActivePowerBStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + VoltageBStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + CurrentBStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + PowerFactorBStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + + // Phase C + EnergyConsumedCStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + ActivePowerCStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + VoltageCStatusFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + CurrentCStatusFunction( + deviceId: uuid ?? '', + deviceName: name ?? '', + type: 'IF'), + PowerFactorCStatusFunction( + deviceId: uuid ?? '', + deviceName: name ?? '', + type: 'IF'), + ]; default: return []; @@ -526,5 +582,6 @@ SOS "GD": DeviceType.GarageDoor, "WL": DeviceType.WaterLeak, "NCPS": DeviceType.NCPS, + "PC": DeviceType.PC, }; } diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index bdba5797..df4683d8 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_senso import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/power_clamp_enargy/energy_clamp_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart'; @@ -137,6 +138,16 @@ class DeviceDialogHelper { device: data['device'], ); + case 'PC': + return EnergyClampDialog.showEnergyClampFunctionsDialog( + context: context, + functions: functions, + uniqueCustomId: data['uniqueCustomId'], + deviceSelectedFunctions: deviceSelectedFunctions, + dialogType: dialogType, + device: data['device'], + ); + default: return null; } diff --git a/lib/pages/routines/models/device_functions.dart b/lib/pages/routines/models/device_functions.dart index b895dccc..40b26304 100644 --- a/lib/pages/routines/models/device_functions.dart +++ b/lib/pages/routines/models/device_functions.dart @@ -9,7 +9,6 @@ abstract class DeviceFunction { final double? max; final double? min; - DeviceFunction({ required this.deviceId, required this.deviceName, @@ -114,4 +113,28 @@ class DeviceFunctionData { max.hashCode ^ min.hashCode; } + + DeviceFunctionData copyWith({ + String? entityId, + String? functionCode, + String? operationName, + String? condition, + dynamic value, + double? step, + String? unit, + double? max, + double? min, + }) { + return DeviceFunctionData( + entityId: entityId ?? this.entityId, + functionCode: functionCode ?? this.functionCode, + operationName: operationName ?? this.operationName, + condition: condition ?? this.condition, + value: value ?? this.value, + step: step ?? this.step, + unit: unit ?? this.unit, + max: max ?? this.max, + min: min ?? this.min, + ); + } } diff --git a/lib/pages/routines/models/pc/energy_clamp_functions.dart b/lib/pages/routines/models/pc/energy_clamp_functions.dart new file mode 100644 index 00000000..4bf3ddd8 --- /dev/null +++ b/lib/pages/routines/models/pc/energy_clamp_functions.dart @@ -0,0 +1,416 @@ +import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/pc/enrgy_clamp_operational_value.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +abstract class EnergyClampFunctions extends DeviceFunction { + final String type; + + EnergyClampFunctions({ + required super.deviceId, + required super.deviceName, + required super.code, + required super.operationName, + required super.icon, + required this.type, + super.step, + super.unit, + super.max, + super.min, + }); + + List getOperationalValues(); +} + +// General & shared +class TotalEnergyConsumedStatusFunction extends EnergyClampFunctions { + TotalEnergyConsumedStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'EnergyConsumed', + operationName: 'Total Energy Consumed', + icon: Assets.energyConsumedIcon, + min: 0.00, + max: 20000000.00, + step: 1, + unit: "kWh", + ); + + @override + List getOperationalValues() => []; +} + +class TotalActivePowerConsumedStatusFunction extends EnergyClampFunctions { + TotalActivePowerConsumedStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'ActivePower', + operationName: 'Total Active Power', + icon: Assets.powerActiveIcon, + min: -19800000, + max: 19800000, + step: 0.1, + unit: "kW", + ); + + @override + List getOperationalValues() => []; +} + +class VoltagePhaseSequenceDetectionFunction extends EnergyClampFunctions { + VoltagePhaseSequenceDetectionFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'voltage_phase_seq', + operationName: 'Voltage phase sequence detection', + icon: Assets.voltageIcon, + ); + + @override + List getOperationalValues() => [ + EnergyClampOperationalValue( + icon: Assets.voltageIcon, description: '0', value: '0'), + EnergyClampOperationalValue( + icon: Assets.voltageIcon, description: '1', value: '1'), + EnergyClampOperationalValue( + icon: Assets.voltageIcon, description: '2', value: '2'), + EnergyClampOperationalValue( + icon: Assets.voltageIcon, description: '3', value: '3'), + EnergyClampOperationalValue( + icon: Assets.voltageIcon, description: '4', value: '4'), + EnergyClampOperationalValue( + icon: Assets.voltageIcon, description: '5', value: '5'), + ]; +} + +class TotalCurrentStatusFunction extends EnergyClampFunctions { + TotalCurrentStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'Current', + operationName: 'Total Current', + icon: Assets.voltMeterIcon, + min: 0.000, + max: 9000.000, + step: 1, + unit: "A", + ); + + @override + List getOperationalValues() => []; +} + +class FrequencyStatusFunction extends EnergyClampFunctions { + FrequencyStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'Frequency', + operationName: 'Frequency', + icon: Assets.frequencyIcon, + min: 0, + max: 80, + step: 1, + unit: "Hz", + ); + + @override + List getOperationalValues() => []; +} + +// Phase A +class EnergyConsumedAStatusFunction extends EnergyClampFunctions { + EnergyConsumedAStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'EnergyConsumedA', + operationName: 'Energy Consumed A', + icon: Assets.energyConsumedIcon, + min: 0.00, + max: 20000000.00, + step: 1, + unit: "kWh", + ); + + @override + List getOperationalValues() => []; +} + +class ActivePowerAStatusFunction extends EnergyClampFunctions { + ActivePowerAStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'ActivePowerA', + operationName: 'Active Power A', + icon: Assets.powerActiveIcon, + min: 200, + max: 300, + step: 1, + unit: "kW", + ); + + @override + List getOperationalValues() => []; +} + +class VoltageAStatusFunction extends EnergyClampFunctions { + VoltageAStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'VoltageA', + operationName: 'Voltage A', + icon: Assets.voltageIcon, + min: 0.0, + max: 500, + step: 1, + unit: "V", + ); + + @override + List getOperationalValues() => []; +} + +class PowerFactorAStatusFunction extends EnergyClampFunctions { + PowerFactorAStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'PowerFactorA', + operationName: 'Power Factor A', + icon: Assets.speedoMeter, + min: 0.00, + max: 1.00, + step: 0.1, + unit: "", + ); + + @override + List getOperationalValues() => []; +} + +class CurrentAStatusFunction extends EnergyClampFunctions { + CurrentAStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'CurrentA', + operationName: 'Current A', + icon: Assets.voltMeterIcon, + min: 0.000, + max: 3000.000, + step: 1, + unit: "A", + ); + + @override + List getOperationalValues() => []; +} + +// Phase B +class EnergyConsumedBStatusFunction extends EnergyClampFunctions { + EnergyConsumedBStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'EnergyConsumedB', + operationName: 'Energy Consumed B', + icon: Assets.energyConsumedIcon, + min: 0.00, + max: 20000000.00, + step: 1, + unit: "kWh", + ); + + @override + List getOperationalValues() => []; +} + +class ActivePowerBStatusFunction extends EnergyClampFunctions { + ActivePowerBStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'ActivePowerB', + operationName: 'Active Power B', + icon: Assets.powerActiveIcon, + min: -6600000, + max: 6600000, + step: 1, + unit: "kW", + ); + + @override + List getOperationalValues() => []; +} + +class VoltageBStatusFunction extends EnergyClampFunctions { + VoltageBStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'VoltageB', + operationName: 'Voltage B', + icon: Assets.voltageIcon, + min: 0.0, + max: 500, + step: 1, + unit: "V", + ); + + @override + List getOperationalValues() => []; +} + +class CurrentBStatusFunction extends EnergyClampFunctions { + CurrentBStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'CurrentB', + operationName: 'Current B', + icon: Assets.voltMeterIcon, + min: 0.000, + max: 3000.000, + step: 1, + unit: "A", + ); + + @override + List getOperationalValues() => []; +} + +class PowerFactorBStatusFunction extends EnergyClampFunctions { + PowerFactorBStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'PowerFactorB', + operationName: 'Power Factor B', + icon: Assets.speedoMeter, + min: 0.0, + max: 1.0, + step: 0.1, + unit: "", + ); + + @override + List getOperationalValues() => []; +} + +// Phase C +class EnergyConsumedCStatusFunction extends EnergyClampFunctions { + EnergyConsumedCStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'EnergyConsumedC', + operationName: 'Energy Consumed C', + icon: Assets.energyConsumedIcon, + min: 0.00, + max: 20000000.00, + step: 1, + unit: "kWh", + ); + + @override + List getOperationalValues() => []; +} + +class ActivePowerCStatusFunction extends EnergyClampFunctions { + ActivePowerCStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'ActivePowerC', + operationName: 'Active Power C', + icon: Assets.powerActiveIcon, + min: -6600000, + max: 6600000, + step: 1, + unit: "kW", + ); + + @override + List getOperationalValues() => []; +} + +class VoltageCStatusFunction extends EnergyClampFunctions { + VoltageCStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'VoltageC', + operationName: 'Voltage C', + icon: Assets.voltageIcon, + min: 0.00, + max: 500, + step: 0.1, + unit: "V", + ); + + @override + List getOperationalValues() => []; +} + +class CurrentCStatusFunction extends EnergyClampFunctions { + CurrentCStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'CurrentC', + operationName: 'Current C', + icon: Assets.voltMeterIcon, + min: 0.000, + max: 3000.000, + step: 0.1, + unit: "A", + ); + + @override + List getOperationalValues() => []; +} + +class PowerFactorCStatusFunction extends EnergyClampFunctions { + PowerFactorCStatusFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'PowerFactorC', + operationName: 'Power Factor C', + icon: Assets.speedoMeter, + min: 0.00, + max: 1.00, + step: 0.1, + unit: "", + ); + + @override + List getOperationalValues() => []; +} diff --git a/lib/pages/routines/models/pc/enrgy_clamp_operational_value.dart b/lib/pages/routines/models/pc/enrgy_clamp_operational_value.dart new file mode 100644 index 00000000..5d89acf6 --- /dev/null +++ b/lib/pages/routines/models/pc/enrgy_clamp_operational_value.dart @@ -0,0 +1,11 @@ +class EnergyClampOperationalValue { + final String icon; + final String description; + final dynamic value; + + EnergyClampOperationalValue({ + required this.icon, + required this.description, + required this.value, + }); +} diff --git a/lib/pages/routines/widgets/custom_routines_textbox.dart b/lib/pages/routines/widgets/custom_routines_textbox.dart index e9ada1c2..f0767df4 100644 --- a/lib/pages/routines/widgets/custom_routines_textbox.dart +++ b/lib/pages/routines/widgets/custom_routines_textbox.dart @@ -40,6 +40,7 @@ class CustomRoutinesTextbox extends StatefulWidget { class _CustomRoutinesTextboxState extends State { late final TextEditingController _controller; + bool hasError = false; String? errorMessage; @@ -55,29 +56,63 @@ class _CustomRoutinesTextboxState extends State { } } + bool _isInitialized = false; + @override void initState() { super.initState(); - int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount); - double initialValue; - if (widget.initialValue != null && - widget.initialValue is num && - (widget.initialValue as num) == 0) { - initialValue = 0.0; + _initializeController(); + } + + void _initializeController() { + final decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount); + final dynamic initialValue = widget.initialValue; + double parsedValue; + + if (initialValue is num) { + parsedValue = initialValue.toDouble(); + } else if (initialValue is String) { + parsedValue = double.tryParse(initialValue) ?? widget.sliderRange.$1; } else { - initialValue = double.tryParse(widget.displayedValue) ?? 0.0; + parsedValue = widget.sliderRange.$1; } + _controller = TextEditingController( - text: initialValue.toStringAsFixed(decimalPlaces), + text: parsedValue.toStringAsFixed(decimalPlaces), ); + _isInitialized = true; } @override - void dispose() { - _controller.dispose(); - super.dispose(); + void didUpdateWidget(CustomRoutinesTextbox oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.initialValue != oldWidget.initialValue && _isInitialized) { + final decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount); + final dynamic initialValue = widget.initialValue; + double newValue; + + if (initialValue is num) { + newValue = initialValue.toDouble(); + } else if (initialValue is String) { + newValue = double.tryParse(initialValue) ?? widget.sliderRange.$1; + } else { + newValue = widget.sliderRange.$1; + } + + final newValueText = newValue.toStringAsFixed(decimalPlaces); + if (_controller.text != newValueText) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _controller.text = newValueText; + _controller.selection = + TextSelection.collapsed(offset: _controller.text.length); + }); + } + } } + + void _validateInput(String value) { final doubleValue = double.tryParse(value); if (doubleValue == null) { @@ -121,18 +156,6 @@ class _CustomRoutinesTextboxState extends State { } } - @override - void didUpdateWidget(CustomRoutinesTextbox oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.initialValue != oldWidget.initialValue) { - if (widget.initialValue != null && - widget.initialValue is num && - (widget.initialValue as num) == 0) { - int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount); - _controller.text = 0.0.toStringAsFixed(decimalPlaces); - } - } - } void _correctAndUpdateValue(String value) { final doubleValue = double.tryParse(value) ?? 0.0; @@ -227,9 +250,15 @@ class _CustomRoutinesTextboxState extends State { color: ColorsManager.blackColor, ), keyboardType: TextInputType.number, - inputFormatters: widget.withSpecialChar == true - ? [FilteringTextInputFormatter.digitsOnly] - : null, + inputFormatters: [ + FilteringTextInputFormatter.allow( + widget.withSpecialChar + ? RegExp(r'^-?\d*\.?\d{0,' + + decimalPlaces.toString() + + r'}$') + : RegExp(r'\d+'), + ), + ], decoration: const InputDecoration( border: InputBorder.none, isDense: true, @@ -268,8 +297,9 @@ class _CustomRoutinesTextboxState extends State { const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Wrap( + alignment: WrapAlignment.spaceBetween, + direction: Axis.horizontal, children: [ Text( 'Min. ${widget.sliderRange.$1.toInt()}${widget.unit}', @@ -279,6 +309,9 @@ class _CustomRoutinesTextboxState extends State { fontWeight: FontWeight.w400, ), ), + const SizedBox( + width: 50, + ), Text( 'Max. ${widget.sliderRange.$2.toInt()}${widget.unit}', style: context.textTheme.bodySmall?.copyWith( diff --git a/lib/pages/routines/widgets/if_container.dart b/lib/pages/routines/widgets/if_container.dart index 0d7e9717..da77c7c2 100644 --- a/lib/pages/routines/widgets/if_container.dart +++ b/lib/pages/routines/widgets/if_container.dart @@ -78,9 +78,9 @@ class IfContainer extends StatelessWidget { 'CPS', 'NCPS', 'WH', + 'PC', ].contains(state.ifItems[index] ['productType'])) { - context.read().add( AddToIfContainer( state.ifItems[index], false)); @@ -137,8 +137,18 @@ class IfContainer extends StatelessWidget { context .read() .add(AddToIfContainer(mutableData, false)); - } else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS', 'NCPS','WH'] - .contains(mutableData['productType'])) { + } else if (![ + 'AC', + '1G', + '2G', + '3G', + 'WPS', + 'GW', + 'CPS', + 'NCPS', + 'WH', + 'PC', + ].contains(mutableData['productType'])) { context .read() .add(AddToIfContainer(mutableData, false)); diff --git a/lib/pages/routines/widgets/routine_devices.dart b/lib/pages/routines/widgets/routine_devices.dart index 11a52ba7..f0b77467 100644 --- a/lib/pages/routines/widgets/routine_devices.dart +++ b/lib/pages/routines/widgets/routine_devices.dart @@ -27,6 +27,7 @@ class _RoutineDevicesState extends State { 'CPS', 'NCPS', 'WH', + 'PC', }; @override diff --git a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart index fc58500e..cbf13178 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart @@ -74,25 +74,24 @@ class ACHelper { SizedBox( width: selectedFunction != null ? 320 : 360, child: _buildFunctionsList( - context: context, - acFunctions: acFunctions, - device: device, - onFunctionSelected: - (functionCode, operationName) { - RoutineTapFunctionHelper.onTapFunction( - context, - functionCode: functionCode, - functionOperationName: operationName, - functionValueDescription: - selectedFunctionData.valueDescription, - deviceUuid: device?.uuid, - codesToAddIntoFunctionsWithDefaultValue: [ - 'temp_set', - 'temp_current', - ], - defaultValue: 0); - }, - ), + context: context, + acFunctions: acFunctions, + onFunctionSelected: + (functionCode, operationName) { + RoutineTapFunctionHelper.onTapFunction( + context, + functionCode: functionCode, + functionOperationName: operationName, + functionValueDescription: + selectedFunctionData + .valueDescription, + deviceUuid: device?.uuid, + codesToAddIntoFunctionsWithDefaultValue: [ + 'temp_set', + 'temp_current', + ], + defaultValue: 0); + }), ), // Value selector if (selectedFunction != null) @@ -150,7 +149,6 @@ class ACHelper { required BuildContext context, required List acFunctions, required Function(String, String) onFunctionSelected, - required AllDevicesModel? device, }) { return ListView.separated( shrinkWrap: false, @@ -193,7 +191,6 @@ class ACHelper { ); } - /// Build value selector for AC functions dialog static Widget _buildValueSelector({ required BuildContext context, required String selectedFunction, @@ -207,19 +204,19 @@ class ACHelper { acFunctions.firstWhere((f) => f.code == selectedFunction); if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { - // Convert stored integer value to display value final displayValue = - (selectedFunctionData?.value ?? selectedFn.min ?? 0) / 10; + (selectedFunctionData?.value ?? selectedFn.min!) / 10; final minValue = selectedFn.min! / 10; final maxValue = selectedFn.max! / 10; + return CustomRoutinesTextbox( withSpecialChar: true, dividendOfRange: maxValue, currentCondition: selectedFunctionData?.condition, dialogType: selectedFn.type, sliderRange: (minValue, maxValue), - displayedValue: displayValue.toStringAsFixed(1), - initialValue: displayValue.toDouble(), + displayedValue: displayValue.toString(), + initialValue: displayValue, unit: selectedFn.unit!, onConditionChanged: (condition) => context.read().add( AddFunction( @@ -228,7 +225,7 @@ class ACHelper { functionCode: selectedFunction, operationName: selectedFn.operationName, condition: condition, - value: 0, + value: (displayValue * 10).round(), step: selectedFn.step, unit: selectedFn.unit, max: selectedFn.max, @@ -236,28 +233,33 @@ class ACHelper { ), ), ), - onTextChanged: (value) => context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectedFunction, - operationName: selectedFn.operationName, - value: (value * 10).round(), // Store as integer - condition: selectedFunctionData?.condition, - step: selectedFn.step, - unit: selectedFn.unit, - max: selectedFn.max, - min: selectedFn.min, + onTextChanged: (value) { + final numericValue = double.tryParse(value.toString()) ?? minValue; + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: selectedFn.operationName, + value: (numericValue * 10).round(), + condition: selectedFunctionData?.condition, + step: selectedFn.step, + unit: selectedFn.unit, + max: selectedFn.max, + min: selectedFn.min, + ), ), - ), - ), - stepIncreaseAmount: selectedFn.step! / 10, // Convert step for display + ); + }, + stepIncreaseAmount: selectedFn.step! / 10, ); } + // Rest of your existing code for other value selectors + final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( context: context, - values: selectedFn.getOperationalValues(), + values: values, selectedValue: selectedFunctionData?.value, device: device, operationName: operationName, @@ -311,7 +313,7 @@ class ACHelper { // ); // } - // /// Build condition toggle for AC functions dialog + /// Build condition toggle for AC functions dialog // static Widget _buildConditionToggle( // BuildContext context, // String? currentCondition, diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index 3d2473c9..f26bd52a 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -6,7 +6,6 @@ import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functi import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart'; -import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; class CpsDialogSliderSelector extends StatelessWidget { const CpsDialogSliderSelector({ @@ -33,7 +32,7 @@ class CpsDialogSliderSelector extends StatelessWidget { @override Widget build(BuildContext context) { return CustomRoutinesTextbox( - withSpecialChar: false, + withSpecialChar: true, currentCondition: selectedFunctionData.condition, dialogType: dialogType, sliderRange: diff --git a/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/enargy_operational_values_list.dart b/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/enargy_operational_values_list.dart new file mode 100644 index 00000000..2b8ba68f --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/enargy_operational_values_list.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.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/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/pc/enrgy_clamp_operational_value.dart'; + +class EnergyOperationalValuesList extends StatelessWidget { + final List values; + final dynamic selectedValue; + final AllDevicesModel? device; + final String operationName; + final String selectCode; + + const EnergyOperationalValuesList({ + required this.values, + required this.selectedValue, + required this.device, + required this.operationName, + required this.selectCode, + super.key, + }); + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.all(20), + itemCount: values.length, + itemBuilder: (context, index) => _buildValueItem(context, values[index]), + ); + } + + Widget _buildValueItem( + BuildContext context, EnergyClampOperationalValue value) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildValueIcon(context, value), + Expanded(child: _buildValueDescription(value)), + _buildValueRadio(context, value), + ], + ), + ); + } + + Widget _buildValueIcon(context, EnergyClampOperationalValue value) { + return Column( + children: [ + SvgPicture.asset(value.icon, width: 25, height: 25), + ], + ); + } + + Widget _buildValueDescription(EnergyClampOperationalValue value) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text(value.description), + ); + } + + Widget _buildValueRadio(context, EnergyClampOperationalValue value) { + return Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (_) => _selectValue(context, value.value), + ); + } + + void _selectValue(BuildContext context, dynamic value) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value, + ), + ), + ); + } + + +} diff --git a/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/energy_clamp_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/energy_clamp_dialog.dart new file mode 100644 index 00000000..c5bf8828 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/energy_clamp_dialog.dart @@ -0,0 +1,246 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.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/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/pc/energy_clamp_functions.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/routine_dialogs/helpers/routine_tap_function_helper.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/power_clamp_enargy/energy_value_selector_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class EnergyClampDialog extends StatefulWidget { + final List functions; + final AllDevicesModel? device; + final List? deviceSelectedFunctions; + final String? uniqueCustomId; + final String? dialogType; + final bool removeComparetors; + + const EnergyClampDialog({ + super.key, + required this.functions, + this.device, + this.deviceSelectedFunctions, + this.uniqueCustomId, + this.dialogType, + this.removeComparetors = false, + }); + + static Future?> showEnergyClampFunctionsDialog({ + required BuildContext context, + required List functions, + AllDevicesModel? device, + List? deviceSelectedFunctions, + String? uniqueCustomId, + String? dialogType, + bool removeComparetors = false, + }) async { + return showDialog?>( + context: context, + builder: (context) => EnergyClampDialog( + functions: functions, + device: device, + deviceSelectedFunctions: deviceSelectedFunctions, + uniqueCustomId: uniqueCustomId, + removeComparetors: removeComparetors, + dialogType: dialogType, + ), + ); + } + + @override + State createState() => _EnergyClampDialogState(); +} + +class _EnergyClampDialogState extends State { + late final List _functions; + + @override + void initState() { + super.initState(); + _functions = + widget.functions.whereType().where((function) { + if (widget.dialogType == 'THEN') { + return function.type == 'THEN' || function.type == 'BOTH'; + } + return function.type == 'IF' || function.type == 'BOTH'; + }).toList(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => FunctionBloc() + ..add(InitializeFunctions(widget.deviceSelectedFunctions ?? [])), + child: _buildDialogContent(), + ); + } + + Widget _buildDialogContent() { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: BlocBuilder( + builder: (context, state) { + final selectedFunction = state.selectedFunction; + return Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('Energy Clamp Conditions'), + Expanded(child: _buildMainContent(context, state)), + _buildDialogFooter(context, state), + ], + ), + ); + }, + ), + ); + } + + Widget _buildMainContent(BuildContext context, FunctionBlocState state) { + return Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildFunctionList(context, state), + if (state.selectedFunction != null) _buildValueSelector(context, state), + ], + ); + } + + 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( + width: 360, + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: _functions.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), + child: Divider(color: ColorsManager.dividerColor), + ), + itemBuilder: (context, index) { + final function = _functions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + width: 24, + height: 24, + placeholderBuilder: (context) => const SizedBox( + width: 24, + height: 24, + ), + ), + title: Text( + function.operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: () => RoutineTapFunctionHelper.onTapFunction( + context, + functionCode: function.code, + functionOperationName: function.operationName, + functionValueDescription: selectedFunctionData.valueDescription, + deviceUuid: widget.device?.uuid, + codesToAddIntoFunctionsWithDefaultValue: [ + 'VoltageA', + 'CurrentA', + 'ActivePowerA', + 'PowerFactorA', + 'ReactivePowerA', + 'EnergyConsumedA', + 'VoltageB', + 'CurrentB', + 'ActivePowerB', + 'PowerFactorB', + 'ReactivePowerB', + 'EnergyConsumedB', + 'VoltageC', + 'CurrentC', + 'ActivePowerC', + 'PowerFactorC', + 'ReactivePowerC', + 'EnergyConsumedC', + 'EnergyConsumed', + 'Current', + 'ActivePower', + 'ReactivePower', + 'Frequency', + ], + ), + ); + }, + ), + ); + } + + Widget _buildValueSelector(BuildContext context, FunctionBlocState state) { + final selectedFunction = state.selectedFunction!; + final functionData = state.addedFunctions.firstWhere( + (f) => f.functionCode == selectedFunction, + orElse: () => DeviceFunctionData( + entityId: '', + functionCode: selectedFunction, + operationName: state.selectedOperationName ?? '', + value: null, + ), + ); + + return Expanded( + child: EnergyValueSelectorWidget( + selectedFunction: selectedFunction, + functionData: functionData, + functions: _functions, + device: widget.device, + dialogType: widget.dialogType!, + removeComparators: widget.removeComparetors, + ), + ); + } + + Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) { + return DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: state.addedFunctions.isNotEmpty + ? () { + context.read().add( + AddFunctionToRoutine( + state.addedFunctions, + widget.uniqueCustomId!, + ), + ); + Navigator.pop( + context, + {'deviceId': widget.functions.first.deviceId}, + ); + } + : null, + isConfirmEnabled: state.selectedFunction != null, + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/energy_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/energy_value_selector_widget.dart new file mode 100644 index 00000000..696251a1 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/energy_value_selector_widget.dart @@ -0,0 +1,85 @@ +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/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/pc/energy_clamp_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/power_clamp_enargy/enargy_operational_values_list.dart'; + +class EnergyValueSelectorWidget extends StatelessWidget { + final String selectedFunction; + final DeviceFunctionData functionData; + final List functions; + final AllDevicesModel? device; + final String dialogType; + final bool removeComparators; + + const EnergyValueSelectorWidget({ + required this.selectedFunction, + required this.functionData, + required this.functions, + required this.device, + required this.dialogType, + required this.removeComparators, + super.key, + }); + + @override + Widget build(BuildContext context) { + final selectedFn = + functions.firstWhere((f) => f.code == selectedFunction); + final values = selectedFn.getOperationalValues(); + final step = selectedFn.step ?? 1.0; + final _unit = selectedFn.unit ?? ''; + final (double, double) sliderRange = + (selectedFn.min ?? 0.0, selectedFn.max ?? 100.0); + + if (_isSliderFunction(selectedFunction)) { + return CustomRoutinesTextbox( + withSpecialChar: false, + currentCondition: functionData.condition, + dialogType: dialogType, + sliderRange: sliderRange, + displayedValue: functionData.value, + initialValue: functionData.value ?? 0.0, + onConditionChanged: (condition) => context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: functionData.operationName, + condition: condition, + value: functionData.value ?? 0, + ), + ), + ), + onTextChanged: (value) => context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: functionData.operationName, + value: value.toInt(), + condition: functionData.condition, + ), + ), + ), + unit: _unit, + dividendOfRange: 1, + stepIncreaseAmount: step, + ); + } + + return EnergyOperationalValuesList( + values: values, + selectedValue: functionData.value, + device: device, + operationName: selectedFn.operationName, + selectCode: selectedFunction, + ); + } + + bool _isSliderFunction(String function) => + !['voltage_phase_seq'].contains(function); +} diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart index 677c26ee..61a7959b 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart @@ -33,7 +33,7 @@ class WpsValueSelectorWidget extends StatelessWidget { if (_isSliderFunction(selectedFunction)) { return CustomRoutinesTextbox( - withSpecialChar: false, + withSpecialChar: true, currentCondition: functionData.condition, dialogType: dialogType, sliderRange: sliderRange, diff --git a/lib/pages/routines/widgets/then_container.dart b/lib/pages/routines/widgets/then_container.dart index 0324d562..d9eee4c4 100644 --- a/lib/pages/routines/widgets/then_container.dart +++ b/lib/pages/routines/widgets/then_container.dart @@ -242,7 +242,8 @@ class ThenContainer extends StatelessWidget { 'GW', 'CPS', "NCPS", - "WH" + "WH", + 'PC', ].contains(mutableData['productType'])) { context.read().add(AddToThenContainer(mutableData)); } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index a1d782f2..8707e7fd 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -481,5 +481,8 @@ class Assets { static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg'; static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg'; static const String blankCalendar = 'assets/icons/blank_calendar.svg'; - static const String refreshStatusIcon = 'assets/icons/refresh_status_icon.svg'; + static const String refreshStatusIcon = + 'assets/icons/refresh_status_icon.svg'; + static const String energyConsumedIcon = + 'assets/icons/energy_consumed_icon.svg'; } diff --git a/lib/utils/enum/device_types.dart b/lib/utils/enum/device_types.dart index 7ad8e02c..9bfd322f 100644 --- a/lib/utils/enum/device_types.dart +++ b/lib/utils/enum/device_types.dart @@ -19,6 +19,7 @@ enum DeviceType { WaterLeak, NCPS, DoorSensor, + PC, Other, } /* @@ -59,4 +60,5 @@ Map devicesTypesMap = { 'GD': DeviceType.GarageDoor, 'WL': DeviceType.WaterLeak, 'NCPS': DeviceType.NCPS, + 'PC': DeviceType.PC, };