From 0d45a155e320322318b071084d34ad4adc375b37 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 19 May 2025 11:22:15 +0300 Subject: [PATCH] add step parameter in onTapFunction. Add dialogType parameter in WaterHeaterPresenceSensor and CeilingSensorDialog. Update step parameter in FlushValueSelectorWidget. Update step parameter in FunctionBloc and WaterHeaterFunctions. Update step, unit, min, and max parameters in ACFunction subclasses. --- analysis_options.yaml | 1 + .../functions_bloc/functions_bloc_bloc.dart | 10 +- lib/pages/routines/models/ac/ac_function.dart | 36 +- .../ceiling_presence_sensor_functions.dart | 42 +- .../routines/models/device_functions.dart | 37 +- .../models/flush/flush_functions.dart | 79 ++-- .../water_heater/water_heater_functions.dart | 12 +- .../routines/models/wps/wps_functions.dart | 57 +-- .../widgets/custom_routines_textbox.dart | 297 ++++++++++++++ .../widgets/routine_dialogs/ac_dialog.dart | 379 ++++++++++-------- .../ceiling_sensor/ceiling_sensor_dialog.dart | 4 +- .../cps_dialog_slider_selector.dart | 12 +- .../ceiling_sensor/cps_functions_list.dart | 51 +-- .../flush_operational_values_list.dart | 20 +- .../flush_value_selector_widget.dart | 7 +- .../helpers/routine_tap_function_helper.dart | 1 + .../one_gang_switch_dialog.dart | 162 +++----- .../three_gang_switch_dialog.dart | 200 ++++----- .../two_gang_switch_dialog.dart | 108 ++--- .../wps_value_selector_widget.dart | 17 +- .../water_heater_presence_sensor.dart | 1 + .../water_heater_value_selector_widget.dart | 133 ++---- 22 files changed, 938 insertions(+), 728 deletions(-) create mode 100644 lib/pages/routines/widgets/custom_routines_textbox.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 81bdd00d..80a63bcb 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -10,6 +10,7 @@ analyzer: errors: constant_identifier_names: ignore + overridden_fields: ignore include: package:flutter_lints/flutter.yaml linter: diff --git a/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart b/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart index bb4a7a1e..659d3261 100644 --- a/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart +++ b/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart @@ -26,8 +26,10 @@ class FunctionBloc extends Bloc { functionCode: event.functionData.functionCode, operationName: event.functionData.operationName, value: event.functionData.value ?? existingData.value, - valueDescription: event.functionData.valueDescription ?? existingData.valueDescription, + valueDescription: event.functionData.valueDescription ?? + existingData.valueDescription, condition: event.functionData.condition ?? existingData.condition, + step: event.functionData.step ?? existingData.step, ); } else { functions.clear(); @@ -59,8 +61,10 @@ class FunctionBloc extends Bloc { ); } - FutureOr _onSelectFunction(SelectFunction event, Emitter emit) { + FutureOr _onSelectFunction( + SelectFunction event, Emitter emit) { emit(state.copyWith( - selectedFunction: event.functionCode, selectedOperationName: event.operationName)); + selectedFunction: event.functionCode, + selectedOperationName: event.operationName)); } } diff --git a/lib/pages/routines/models/ac/ac_function.dart b/lib/pages/routines/models/ac/ac_function.dart index 0b534e88..edc377dd 100644 --- a/lib/pages/routines/models/ac/ac_function.dart +++ b/lib/pages/routines/models/ac/ac_function.dart @@ -14,6 +14,10 @@ abstract class ACFunction extends DeviceFunction { required super.operationName, required super.icon, required this.type, + super.step, + super.unit, + super.max, + super.min, }); List getOperationalValues(); @@ -75,26 +79,24 @@ class ModeFunction extends ACFunction { } class TempSetFunction extends ACFunction { - final int min; - final int max; - final int step; - - TempSetFunction( - {required super.deviceId, required super.deviceName, required type}) - : min = 160, - max = 300, - step = 1, - super( + TempSetFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( code: 'temp_set', operationName: 'Set Temperature', icon: Assets.assetsTempreture, - type: type, + min: 200, + max: 300, + step: 1, + unit: "°C", ); @override List getOperationalValues() { List values = []; - for (int temp = min; temp <= max; temp += step) { + for (int temp = min!.toInt(); temp <= max!; temp += step!.toInt()) { values.add(ACOperationalValue( icon: Assets.assetsTempreture, description: "${temp / 10}°C", @@ -104,7 +106,6 @@ class TempSetFunction extends ACFunction { return values; } } - class LevelFunction extends ACFunction { LevelFunction( {required super.deviceId, required super.deviceName, required type}) @@ -166,9 +167,10 @@ class ChildLockFunction extends ACFunction { } class CurrentTempFunction extends ACFunction { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; + final String unit = "°C"; CurrentTempFunction( {required super.deviceId, required super.deviceName, required type}) @@ -185,7 +187,7 @@ class CurrentTempFunction extends ACFunction { @override List getOperationalValues() { List values = []; - for (int temp = min; temp <= max; temp += step) { + for (int temp = min.toInt(); temp <= max; temp += step.toInt()) { values.add(ACOperationalValue( icon: Assets.currentTemp, description: "${temp / 10}°C", diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart index 6dbe5cf6..122d8ea1 100644 --- a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -6,10 +6,12 @@ class CpsOperationalValue { final String description; final dynamic value; + CpsOperationalValue({ required this.icon, required this.description, required this.value, + }); } @@ -94,9 +96,9 @@ final class CpsSensitivityFunction extends CpsFunctions { icon: Assets.sensitivity, ); - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; static const _images = [ Assets.sensitivityFeature1, @@ -115,10 +117,10 @@ final class CpsSensitivityFunction extends CpsFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min; value <= max; value += step.toInt()) { values.add( CpsOperationalValue( - icon: _images[value], + icon: _images[value.toInt()], description: '$value', value: value, ), @@ -142,9 +144,9 @@ final class CpsMovingSpeedFunction extends CpsFunctions { icon: Assets.speedoMeter, ); - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; @override List getOperationalValues() { @@ -173,9 +175,9 @@ final class CpsSpatialStaticValueFunction extends CpsFunctions { icon: Assets.spatialStaticValue, ); - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; @override List getOperationalValues() { @@ -204,9 +206,9 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions { icon: Assets.spatialMotionValue, ); - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; @override List getOperationalValues() { @@ -375,9 +377,9 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions { icon: Assets.presenceJudgementThrshold, ); - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; @override List getOperationalValues() { @@ -406,9 +408,9 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions { icon: Assets.presenceJudgementThrshold, ); - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; @override List getOperationalValues() { diff --git a/lib/pages/routines/models/device_functions.dart b/lib/pages/routines/models/device_functions.dart index 59b63a4f..b895dccc 100644 --- a/lib/pages/routines/models/device_functions.dart +++ b/lib/pages/routines/models/device_functions.dart @@ -4,6 +4,11 @@ abstract class DeviceFunction { final String code; final String operationName; final String icon; + final double? step; + final String? unit; + final double? max; + final double? min; + DeviceFunction({ required this.deviceId, @@ -11,6 +16,10 @@ abstract class DeviceFunction { required this.code, required this.operationName, required this.icon, + this.step, + this.unit, + this.max, + this.min, }); } @@ -22,6 +31,10 @@ class DeviceFunctionData { final dynamic value; final String? condition; final String? valueDescription; + final double? step; + final String? unit; + final double? max; + final double? min; DeviceFunctionData({ required this.entityId, @@ -31,6 +44,10 @@ class DeviceFunctionData { required this.value, this.condition, this.valueDescription, + this.step, + this.unit, + this.max, + this.min, }); Map toJson() { @@ -42,6 +59,10 @@ class DeviceFunctionData { 'value': value, if (condition != null) 'condition': condition, if (valueDescription != null) 'valueDescription': valueDescription, + if (step != null) 'step': step, + if (unit != null) 'unit': unit, + if (max != null) 'max': max, + if (min != null) 'min': min, }; } @@ -54,6 +75,10 @@ class DeviceFunctionData { value: json['value'], condition: json['condition'], valueDescription: json['valueDescription'], + step: json['step']?.toDouble(), + unit: json['unit'], + max: json['max']?.toDouble(), + min: json['min']?.toDouble(), ); } @@ -68,7 +93,11 @@ class DeviceFunctionData { other.operationName == operationName && other.value == value && other.condition == condition && - other.valueDescription == valueDescription; + other.valueDescription == valueDescription && + other.step == step && + other.unit == unit && + other.max == max && + other.min == min; } @override @@ -79,6 +108,10 @@ class DeviceFunctionData { operationName.hashCode ^ value.hashCode ^ condition.hashCode ^ - valueDescription.hashCode; + valueDescription.hashCode ^ + step.hashCode ^ + unit.hashCode ^ + max.hashCode ^ + min.hashCode; } } diff --git a/lib/pages/routines/models/flush/flush_functions.dart b/lib/pages/routines/models/flush/flush_functions.dart index 5013c0b8..a8f6ccd4 100644 --- a/lib/pages/routines/models/flush/flush_functions.dart +++ b/lib/pages/routines/models/flush/flush_functions.dart @@ -20,12 +20,11 @@ abstract class FlushFunctions } class FlushPresenceDelayFunction extends FlushFunctions { - final int min; FlushPresenceDelayFunction({ required super.deviceId, required super.deviceName, required super.type, - }) : min = 0, + }) : super( code: FlushMountedPresenceSensorModel.codePresenceState, operationName: 'Presence State', @@ -50,9 +49,9 @@ class FlushPresenceDelayFunction extends FlushFunctions { } class FlushSensiReduceFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; FlushSensiReduceFunction({ required super.deviceId, @@ -80,8 +79,8 @@ class FlushSensiReduceFunction extends FlushFunctions { } class FlushNoneDelayFunction extends FlushFunctions { - final int min; - final int max; + final double min; + final double max; final String unit; FlushNoneDelayFunction({ @@ -110,9 +109,9 @@ class FlushNoneDelayFunction extends FlushFunctions { } class FlushIlluminanceFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; FlushIlluminanceFunction({ required super.deviceId, @@ -130,7 +129,7 @@ class FlushIlluminanceFunction extends FlushFunctions { @override List getOperationalValues() { List values = []; - for (int lux = min; lux <= max; lux += step) { + for (int lux = min.toInt(); lux <= max; lux += step.toInt()) { values.add(FlushOperationalValue( icon: Assets.IlluminanceIcon, description: "$lux Lux", @@ -142,9 +141,9 @@ class FlushIlluminanceFunction extends FlushFunctions { } class FlushOccurDistReduceFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; FlushOccurDistReduceFunction({ required super.deviceId, @@ -173,9 +172,9 @@ class FlushOccurDistReduceFunction extends FlushFunctions { // ==== then functions ==== class FlushSensitivityFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; FlushSensitivityFunction({ required super.deviceId, @@ -203,9 +202,9 @@ class FlushSensitivityFunction extends FlushFunctions { } class FlushNearDetectionFunction extends FlushFunctions { - final int min; + final double min; final double max; - final int step; + final double step; final String unit; FlushNearDetectionFunction({ @@ -225,7 +224,7 @@ class FlushNearDetectionFunction extends FlushFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min.toDouble(); value <= max; value += step) { values.add(FlushOperationalValue( icon: Assets.nobodyTime, description: '$value $unit', @@ -237,9 +236,9 @@ class FlushNearDetectionFunction extends FlushFunctions { } class FlushMaxDetectDistFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; final String unit; FlushMaxDetectDistFunction({ @@ -259,7 +258,7 @@ class FlushMaxDetectDistFunction extends FlushFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min; value <= max; value += step.toInt()) { values.add(FlushOperationalValue( icon: Assets.nobodyTime, description: '$value $unit', @@ -271,9 +270,9 @@ class FlushMaxDetectDistFunction extends FlushFunctions { } class FlushTargetConfirmTimeFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; final String unit; FlushTargetConfirmTimeFunction({ @@ -293,7 +292,7 @@ class FlushTargetConfirmTimeFunction extends FlushFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min.toDouble(); value <= max; value += step) { values.add(FlushOperationalValue( icon: Assets.nobodyTime, description: '$value $unit', @@ -305,9 +304,9 @@ class FlushTargetConfirmTimeFunction extends FlushFunctions { } class FlushDisappeDelayFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; final String unit; FlushDisappeDelayFunction({ @@ -327,7 +326,7 @@ class FlushDisappeDelayFunction extends FlushFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min.toDouble(); value <= max; value += step) { values.add(FlushOperationalValue( icon: Assets.nobodyTime, description: '$value $unit', @@ -339,9 +338,9 @@ class FlushDisappeDelayFunction extends FlushFunctions { } class FlushIndentLevelFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; final String unit; FlushIndentLevelFunction({ @@ -361,7 +360,7 @@ class FlushIndentLevelFunction extends FlushFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min.toDouble(); value <= max; value += step) { values.add(FlushOperationalValue( icon: Assets.nobodyTime, description: '$value $unit', @@ -373,9 +372,9 @@ class FlushIndentLevelFunction extends FlushFunctions { } class FlushTriggerLevelFunction extends FlushFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; final String unit; FlushTriggerLevelFunction({ @@ -395,7 +394,7 @@ class FlushTriggerLevelFunction extends FlushFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min.toDouble(); value <= max; value += step) { values.add(FlushOperationalValue( icon: Assets.nobodyTime, description: '$value $unit', diff --git a/lib/pages/routines/models/water_heater/water_heater_functions.dart b/lib/pages/routines/models/water_heater/water_heater_functions.dart index 571e15f7..7ebea019 100644 --- a/lib/pages/routines/models/water_heater/water_heater_functions.dart +++ b/lib/pages/routines/models/water_heater/water_heater_functions.dart @@ -20,17 +20,16 @@ abstract class WaterHeaterFunctions } class WHRestartStatusFunction extends WaterHeaterFunctions { - final int min; WHRestartStatusFunction({ required super.deviceId, required super.deviceName, required super.type, - }) : min = 0, - super( + }) : super( code: 'relay_status', operationName: 'Restart Status', icon: Assets.refreshStatusIcon, ); + @override List getOperationalValues() { @@ -55,13 +54,11 @@ class WHRestartStatusFunction extends WaterHeaterFunctions { } class WHSwitchFunction extends WaterHeaterFunctions { - final int min; WHSwitchFunction({ required super.deviceId, required super.deviceName, required super.type, - }) : min = 0, - super( + }) : super( code: 'switch_1', operationName: 'Switch', icon: Assets.assetsAcPower, @@ -104,12 +101,11 @@ class TimerConfirmTimeFunction extends WaterHeaterFunctions { } class BacklightFunction extends WaterHeaterFunctions { - final int min; BacklightFunction({ required super.deviceId, required super.deviceName, required super.type, - }) : min = 0, + }) : super( code: 'switch_backlight', operationName: 'Backlight', diff --git a/lib/pages/routines/models/wps/wps_functions.dart b/lib/pages/routines/models/wps/wps_functions.dart index 8907927c..101c5cf0 100644 --- a/lib/pages/routines/models/wps/wps_functions.dart +++ b/lib/pages/routines/models/wps/wps_functions.dart @@ -4,7 +4,7 @@ import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart import 'package:syncrow_web/utils/constants/assets.dart'; abstract class WpsFunctions extends DeviceFunction { - final String type; + final String type; WpsFunctions({ required super.deviceId, @@ -13,6 +13,10 @@ abstract class WpsFunctions extends DeviceFunction { required super.operationName, required super.icon, required this.type, + super.step, + super.unit, + super.max, + super.min, }); List getOperationalValues(); @@ -20,9 +24,13 @@ abstract class WpsFunctions extends DeviceFunction { // For far_detection (75-600cm in 75cm steps) class FarDetectionFunction extends WpsFunctions { - final int min; - final int max; - final int step; + + final double min; + @override + final double max; + @override + final double step; + @override final String unit; FarDetectionFunction( @@ -41,7 +49,7 @@ class FarDetectionFunction extends WpsFunctions { @override List getOperationalValues() { final values = []; - for (var value = min; value <= max; value += step) { + for (var value = min; value <= max; value += step.toInt()) { values.add(WpsOperationalValue( icon: Assets.currentDistanceIcon, description: '$value $unit', @@ -54,9 +62,9 @@ class FarDetectionFunction extends WpsFunctions { // For presence_time (0-65535 minutes) class PresenceTimeFunction extends WpsFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; final String unit; PresenceTimeFunction( @@ -86,9 +94,9 @@ class PresenceTimeFunction extends WpsFunctions { // For motion_sensitivity_value (1-5 levels) class MotionSensitivityFunction extends WpsFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; MotionSensitivityFunction( {required super.deviceId, required super.deviceName, required type}) @@ -116,9 +124,9 @@ class MotionSensitivityFunction extends WpsFunctions { } class MotionLessSensitivityFunction extends WpsFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; MotionLessSensitivityFunction( {required super.deviceId, required super.deviceName, required type}) @@ -171,8 +179,8 @@ class IndicatorFunction extends WpsFunctions { } class NoOneTimeFunction extends WpsFunctions { - final int min; - final int max; + final double min; + final double max; final String unit; NoOneTimeFunction( @@ -225,9 +233,9 @@ class PresenceStateFunction extends WpsFunctions { } class CurrentDistanceFunction extends WpsFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; CurrentDistanceFunction( {required super.deviceId, required super.deviceName, required type}) @@ -244,11 +252,10 @@ class CurrentDistanceFunction extends WpsFunctions { @override List getOperationalValues() { List values = []; - for (int cm = min; cm <= max; cm += step) { + for (int cm = min.toInt(); cm <= max; cm += step.toInt()) { values.add(WpsOperationalValue( icon: Assets.assetsTempreture, description: "${cm}CM", - value: cm, )); } @@ -257,9 +264,9 @@ class CurrentDistanceFunction extends WpsFunctions { } class IlluminanceValueFunction extends WpsFunctions { - final int min; - final int max; - final int step; + final double min; + final double max; + final double step; IlluminanceValueFunction({ required super.deviceId, @@ -277,7 +284,7 @@ class IlluminanceValueFunction extends WpsFunctions { @override List getOperationalValues() { List values = []; - for (int lux = min; lux <= max; lux += step) { + for (int lux = min.toInt(); lux <= max; lux += step.toInt()) { values.add(WpsOperationalValue( icon: Assets.IlluminanceIcon, description: "$lux Lux", diff --git a/lib/pages/routines/widgets/custom_routines_textbox.dart b/lib/pages/routines/widgets/custom_routines_textbox.dart new file mode 100644 index 00000000..e9ada1c2 --- /dev/null +++ b/lib/pages/routines/widgets/custom_routines_textbox.dart @@ -0,0 +1,297 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncrow_web/pages/routines/widgets/condition_toggle.dart'; +import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class CustomRoutinesTextbox extends StatefulWidget { + final String? currentCondition; + final String dialogType; + final (double, double) sliderRange; + final dynamic displayedValue; + final dynamic initialValue; + final void Function(String condition) onConditionChanged; + final void Function(double value) onTextChanged; + final String unit; + final double dividendOfRange; + final double stepIncreaseAmount; + final bool withSpecialChar; + + const CustomRoutinesTextbox({ + required this.dialogType, + required this.sliderRange, + required this.displayedValue, + required this.initialValue, + required this.onConditionChanged, + required this.onTextChanged, + required this.currentCondition, + required this.unit, + required this.dividendOfRange, + required this.stepIncreaseAmount, + required this.withSpecialChar, + super.key, + }); + + @override + State createState() => _CustomRoutinesTextboxState(); +} + +class _CustomRoutinesTextboxState extends State { + late final TextEditingController _controller; + bool hasError = false; + String? errorMessage; + + int getDecimalPlaces(double step) { + String stepStr = step.toString(); + if (stepStr.contains('.')) { + List parts = stepStr.split('.'); + String decimalPart = parts[1]; + decimalPart = decimalPart.replaceAll(RegExp(r'0+$'), ''); + return decimalPart.isEmpty ? 0 : decimalPart.length; + } else { + return 0; + } + } + + @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; + } else { + initialValue = double.tryParse(widget.displayedValue) ?? 0.0; + } + _controller = TextEditingController( + text: initialValue.toStringAsFixed(decimalPlaces), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _validateInput(String value) { + final doubleValue = double.tryParse(value); + if (doubleValue == null) { + setState(() { + errorMessage = "Invalid number"; + hasError = true; + }); + return; + } + + final min = widget.sliderRange.$1; + final max = widget.sliderRange.$2; + + if (doubleValue < min) { + setState(() { + errorMessage = "Value must be at least $min"; + hasError = true; + }); + } else if (doubleValue > max) { + setState(() { + errorMessage = "Value must be at most $max"; + hasError = true; + }); + } else { + int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount); + int factor = pow(10, decimalPlaces).toInt(); + int scaledStep = (widget.stepIncreaseAmount * factor).round(); + int scaledValue = (doubleValue * factor).round(); + + if (scaledValue % scaledStep != 0) { + setState(() { + errorMessage = "must be a multiple of ${widget.stepIncreaseAmount}"; + hasError = true; + }); + } else { + setState(() { + errorMessage = null; + hasError = false; + }); + } + } + } + + @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; + int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount); + double rounded = (doubleValue / widget.stepIncreaseAmount).round() * + widget.stepIncreaseAmount; + rounded = rounded.clamp(widget.sliderRange.$1, widget.sliderRange.$2); + rounded = double.parse(rounded.toStringAsFixed(decimalPlaces)); + + setState(() { + hasError = false; + errorMessage = null; + }); + + _controller.text = rounded.toStringAsFixed(decimalPlaces); + _controller.selection = TextSelection.fromPosition( + TextPosition(offset: _controller.text.length), + ); + widget.onTextChanged(rounded); + } + + @override + Widget build(BuildContext context) { + int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount); + + List formatters = []; + if (decimalPlaces == 0) { + formatters.add(FilteringTextInputFormatter.digitsOnly); + } else { + formatters.add(FilteringTextInputFormatter.allow( + RegExp(r'^\d*\.?\d{0,' + decimalPlaces.toString() + r'}$'), + )); + } + formatters.add(RangeInputFormatter( + min: widget.sliderRange.$1, + max: widget.sliderRange.$2, + )); + + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (widget.dialogType == 'IF') + ConditionToggle( + currentCondition: widget.currentCondition, + onChanged: widget.onConditionChanged, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 2), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + 'Step: ${widget.stepIncreaseAmount}', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.grayColor, + fontSize: 10, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + Center( + child: Container( + width: 170, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + decoration: BoxDecoration( + color: const Color(0xFFF8F8F8), + borderRadius: BorderRadius.circular(20), + border: hasError + ? Border.all(color: Colors.red, width: 1) + : Border.all( + color: ColorsManager.lightGrayBorderColor, width: 1), + boxShadow: [ + BoxShadow( + color: ColorsManager.blackColor.withOpacity(0.05), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Row( + children: [ + Expanded( + child: TextFormField( + controller: _controller, + style: context.textTheme.bodyLarge?.copyWith( + fontSize: 20, + fontWeight: FontWeight.bold, + color: ColorsManager.blackColor, + ), + keyboardType: TextInputType.number, + inputFormatters: widget.withSpecialChar == true + ? [FilteringTextInputFormatter.digitsOnly] + : null, + decoration: const InputDecoration( + border: InputBorder.none, + isDense: true, + contentPadding: EdgeInsets.zero, + ), + onChanged: _validateInput, + onFieldSubmitted: _correctAndUpdateValue, + onTapOutside: (_) => + _correctAndUpdateValue(_controller.text), + ), + ), + const SizedBox(width: 12), + Text( + widget.unit, + style: context.textTheme.bodyMedium?.copyWith( + fontSize: 20, + fontWeight: FontWeight.bold, + color: ColorsManager.vividBlue, + ), + ), + ], + ), + ), + ), + if (errorMessage != null) + Padding( + padding: const EdgeInsets.only(top: 2.0), + child: Text( + errorMessage!, + style: context.textTheme.bodySmall?.copyWith( + color: Colors.red, + fontSize: 10, + ), + ), + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Min. ${widget.sliderRange.$1.toInt()}${widget.unit}', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.grayColor, + fontSize: 10, + fontWeight: FontWeight.w400, + ), + ), + Text( + 'Max. ${widget.sliderRange.$2.toInt()}${widget.unit}', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.grayColor, + fontSize: 10, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + const SizedBox(height: 16), + ], + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart index 1de9b0d4..fc58500e 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart @@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; 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/widgets/custom_routines_textbox.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'; @@ -76,24 +77,20 @@ class ACHelper { context: context, acFunctions: acFunctions, device: device, - onFunctionSelected: (functionCode, operationName) { + onFunctionSelected: + (functionCode, operationName) { RoutineTapFunctionHelper.onTapFunction( - context, - functionCode: functionCode, - functionOperationName: operationName, - functionValueDescription: - selectedFunctionData.valueDescription, - deviceUuid: device?.uuid, - codesToAddIntoFunctionsWithDefaultValue: [ - 'temp_set', - 'temp_current', - ], - defaultValue: functionCode == 'temp_set' - ? 200 - : functionCode == 'temp_current' - ? -100 - : 0, - ); + context, + functionCode: functionCode, + functionOperationName: operationName, + functionValueDescription: + selectedFunctionData.valueDescription, + deviceUuid: device?.uuid, + codesToAddIntoFunctionsWithDefaultValue: [ + 'temp_set', + 'temp_current', + ], + defaultValue: 0); }, ), ), @@ -206,27 +203,61 @@ class ACHelper { required String operationName, bool? removeComparators, }) { - final initialVal = selectedFunction == 'temp_set' ? 200 : -100; + final selectedFn = + acFunctions.firstWhere((f) => f.code == selectedFunction); + if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { - final initialValue = selectedFunctionData?.value ?? initialVal; - return _buildTemperatureSelector( - context: context, - initialValue: initialValue, - selectCode: selectedFunction, + // Convert stored integer value to display value + final displayValue = + (selectedFunctionData?.value ?? selectedFn.min ?? 0) / 10; + final minValue = selectedFn.min! / 10; + final maxValue = selectedFn.max! / 10; + return CustomRoutinesTextbox( + withSpecialChar: true, + dividendOfRange: maxValue, currentCondition: selectedFunctionData?.condition, - device: device, - operationName: operationName, - selectedFunctionData: selectedFunctionData, - removeComparators: removeComparators, + dialogType: selectedFn.type, + sliderRange: (minValue, maxValue), + displayedValue: displayValue.toStringAsFixed(1), + initialValue: displayValue.toDouble(), + unit: selectedFn.unit!, + onConditionChanged: (condition) => context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: selectedFn.operationName, + condition: condition, + value: 0, + step: selectedFn.step, + unit: selectedFn.unit, + max: selectedFn.max, + min: selectedFn.min, + ), + ), + ), + 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, + ), + ), + ), + stepIncreaseAmount: selectedFn.step! / 10, // Convert step for display ); } - final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction); - final values = selectedFn.getOperationalValues(); - return _buildOperationalValuesList( context: context, - values: values, + values: selectedFn.getOperationalValues(), selectedValue: selectedFunctionData?.value, device: device, operationName: operationName, @@ -235,150 +266,151 @@ class ACHelper { ); } - /// Build temperature selector for AC functions dialog - static Widget _buildTemperatureSelector({ - required BuildContext context, - required dynamic initialValue, - required String? currentCondition, - required String selectCode, - AllDevicesModel? device, - required String operationName, - DeviceFunctionData? selectedFunctionData, - bool? removeComparators, - }) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (removeComparators != true) - _buildConditionToggle( - context, - currentCondition, - selectCode, - device, - operationName, - selectedFunctionData, - ), - const SizedBox(height: 20), - _buildTemperatureDisplay( - context, - initialValue, - device, - operationName, - selectedFunctionData, - selectCode, - ), - const SizedBox(height: 20), - _buildTemperatureSlider( - context, - initialValue, - device, - operationName, - selectedFunctionData, - selectCode, - ), - ], - ); - } + // /// Build temperature selector for AC functions dialog + // static Widget _buildTemperatureSelector({ + // required BuildContext context, + // required dynamic initialValue, + // required String? currentCondition, + // required String selectCode, + // AllDevicesModel? device, + // required String operationName, + // DeviceFunctionData? selectedFunctionData, + // bool? removeComparators, + // }) { + // return Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // if (removeComparators != true) + // _buildConditionToggle( + // context, + // currentCondition, + // selectCode, + // device, + // operationName, + // selectedFunctionData, + // ), + // const SizedBox(height: 20), + // _buildTemperatureDisplay( + // context, + // initialValue, + // device, + // operationName, + // selectedFunctionData, + // selectCode, + // ), + // const SizedBox(height: 20), + // _buildTemperatureSlider( + // context, + // initialValue, + // device, + // operationName, + // selectedFunctionData, + // selectCode, + // ), + // ], + // ); + // } - /// Build condition toggle for AC functions dialog - static Widget _buildConditionToggle( - BuildContext context, - String? currentCondition, - String selectCode, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, + // /// Build condition toggle for AC functions dialog + // static Widget _buildConditionToggle( + // BuildContext context, + // String? currentCondition, + // String selectCode, + // AllDevicesModel? device, + // String operationName, + // DeviceFunctionData? selectedFunctionData, - // Function(String) onConditionChanged, - ) { - final conditions = ["<", "==", ">"]; + // // Function(String) onConditionChanged, + // ) { + // final conditions = ["<", "==", ">"]; - return ToggleButtons( - onPressed: (int index) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - condition: conditions[index], - value: selectedFunctionData?.value ?? selectCode == 'temp_set' - ? 200 - : -100, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), - children: conditions.map((c) => Text(c)).toList(), - ); - } + // return ToggleButtons( + // onPressed: (int index) { + // context.read().add( + // AddFunction( + // functionData: DeviceFunctionData( + // entityId: device?.uuid ?? '', + // functionCode: selectCode, + // operationName: operationName, + // condition: conditions[index], + // value: selectedFunctionData?.value ?? selectCode == 'temp_set' + // ? 200 + // : -100, + // valueDescription: selectedFunctionData?.valueDescription, + // ), + // ), + // ); + // }, + // borderRadius: const BorderRadius.all(Radius.circular(8)), + // selectedBorderColor: ColorsManager.primaryColorWithOpacity, + // selectedColor: Colors.white, + // fillColor: ColorsManager.primaryColorWithOpacity, + // color: ColorsManager.primaryColorWithOpacity, + // constraints: const BoxConstraints( + // minHeight: 40.0, + // minWidth: 40.0, + // ), + // isSelected: + // conditions.map((c) => c == (currentCondition ?? "==")).toList(), + // children: conditions.map((c) => Text(c)).toList(), + // ); + // } - /// Build temperature display for AC functions dialog - static Widget _buildTemperatureDisplay( - BuildContext context, - dynamic initialValue, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - String selectCode, - ) { - final initialVal = selectCode == 'temp_set' ? 200 : -100; - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - '${(initialValue ?? initialVal) / 10}°C', - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ); - } + // /// Build temperature display for AC functions dialog + // static Widget _buildTemperatureDisplay( + // BuildContext context, + // dynamic initialValue, + // AllDevicesModel? device, + // String operationName, + // DeviceFunctionData? selectedFunctionData, + // String selectCode, + // ) { + // final initialVal = selectCode == 'temp_set' ? 200 : -100; + // return Container( + // padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + // decoration: BoxDecoration( + // color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), + // borderRadius: BorderRadius.circular(10), + // ), + // child: Text( + // '${(initialValue ?? initialVal) / 10}°C', + // style: context.textTheme.headlineMedium!.copyWith( + // color: ColorsManager.primaryColorWithOpacity, + // ), + // ), + // ); + // } - static Widget _buildTemperatureSlider( - BuildContext context, - dynamic initialValue, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - String selectCode, - ) { - return Slider( - value: initialValue is int ? initialValue.toDouble() : 200.0, - min: selectCode == 'temp_current' ? -100 : 200, - max: selectCode == 'temp_current' ? 900 : 300, - divisions: 10, - label: '${((initialValue ?? 160) / 10).toInt()}°C', - onChanged: (value) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - value: value, - condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - }, - ); - } + // static Widget _buildTemperatureSlider( + // BuildContext context, + // dynamic initialValue, + // AllDevicesModel? device, + // String operationName, + // DeviceFunctionData? selectedFunctionData, + // String selectCode, + // ) { + // return Slider( + // value: initialValue is int ? initialValue.toDouble() : 200.0, + // min: selectCode == 'temp_current' ? -100 : 200, + // max: selectCode == 'temp_current' ? 900 : 300, + // divisions: 10, + // label: '${((initialValue ?? 160) / 10).toInt()}°C', + // onChanged: (value) { + // context.read().add( + // AddFunction( + // functionData: DeviceFunctionData( + // entityId: device?.uuid ?? '', + // functionCode: selectCode, + // operationName: operationName, + // value: value, + // condition: selectedFunctionData?.condition, + // valueDescription: selectedFunctionData?.valueDescription, + // ), + // ), + // ); + // }, + // ); + // } static Widget _buildOperationalValuesList({ required BuildContext context, @@ -414,7 +446,9 @@ class ACHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, size: 24, color: isSelected ? ColorsManager.primaryColorWithOpacity @@ -430,7 +464,8 @@ class ACHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, + valueDescription: + selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index f3d07a66..8fab09e8 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -41,7 +41,8 @@ class _CeilingSensorDialogState extends State { void initState() { super.initState(); - _cpsFunctions = widget.functions.whereType().where((function) { + _cpsFunctions = + widget.functions.whereType().where((function) { if (widget.dialogType == 'THEN') { return function.type == 'THEN' || function.type == 'BOTH'; } @@ -149,6 +150,7 @@ class _CeilingSensorDialogState extends State { device: widget.device, ) : CpsDialogSliderSelector( + step: selectedCpsFunctions.step!, operations: operations, selectedFunction: selectedFunction ?? '', selectedFunctionData: selectedFunctionData, 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 cd8e4c46..3d2473c9 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 @@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo 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/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'; @@ -16,6 +17,7 @@ class CpsDialogSliderSelector extends StatelessWidget { required this.device, required this.operationName, required this.dialogType, + required this.step, super.key, }); @@ -26,13 +28,16 @@ class CpsDialogSliderSelector extends StatelessWidget { final AllDevicesModel? device; final String operationName; final String dialogType; + final double step; @override Widget build(BuildContext context) { - return SliderValueSelector( + return CustomRoutinesTextbox( + withSpecialChar: false, currentCondition: selectedFunctionData.condition, dialogType: dialogType, - sliderRange: CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode), + sliderRange: + CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode), displayedValue: CpsSliderHelpers.displayText( value: selectedFunctionData.value, functionCode: selectedFunctionData.functionCode, @@ -50,7 +55,7 @@ class CpsDialogSliderSelector extends StatelessWidget { ), ), ), - onSliderChanged: (value) => context.read().add( + onTextChanged: (value) => context.read().add( AddFunction( functionData: DeviceFunctionData( entityId: device?.uuid ?? '', @@ -64,6 +69,7 @@ class CpsDialogSliderSelector extends StatelessWidget { dividendOfRange: CpsSliderHelpers.dividendOfRange( selectedFunctionData.functionCode, ), + stepIncreaseAmount: step, ); } } diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart index efc57653..d11871a7 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart @@ -34,30 +34,33 @@ class CpsFunctionsList extends StatelessWidget { itemBuilder: (context, index) { final function = cpsFunctions[index]; return RoutineDialogFunctionListTile( - iconPath: function.icon, - operationName: function.operationName, - onTap: () => RoutineTapFunctionHelper.onTapFunction( - context, - functionCode: function.code, - 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', - ], - ), - ); + iconPath: function.icon, + operationName: function.operationName, + onTap: () { + RoutineTapFunctionHelper.onTapFunction( + context, + step: function.step, + functionCode: function.code, + 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', + ], + ); + }); }, ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart index 3e618c35..4c780058 100644 --- a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart +++ b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart'; @@ -21,22 +22,20 @@ class FlushOperationalValuesList extends StatelessWidget { @override Widget build(BuildContext context) { - return ListView.builder( - padding: const EdgeInsets.all(20), - itemCount: values.length, - itemBuilder: (context, index) => - _buildValueItem(context, values[index]), - ); + return ListView.builder( + padding: const EdgeInsets.all(20), + itemCount: values.length, + itemBuilder: (context, index) => _buildValueItem(context, values[index]), + ); } - - Widget _buildValueItem(BuildContext context, FlushOperationalValue value) { return Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + SvgPicture.asset(value.icon, width: 25, height: 25), Expanded(child: _buildValueDescription(value)), _buildValueRadio(context, value), ], @@ -44,9 +43,6 @@ class FlushOperationalValuesList extends StatelessWidget { ); } - - - Widget _buildValueDescription(FlushOperationalValue value) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), @@ -60,6 +56,4 @@ class FlushOperationalValuesList extends StatelessWidget { groupValue: selectedValue, onChanged: (_) => onSelect(value)); } - - } diff --git a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart index 64f060e5..7ca89edb 100644 --- a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart +++ b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor 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/flush/flush_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart'; import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; @@ -66,7 +67,8 @@ class FlushValueSelectorWidget extends StatelessWidget { if (isDistanceDetection) { initialValue = initialValue / 100; } - return SliderValueSelector( + return CustomRoutinesTextbox( + withSpecialChar: true, currentCondition: functionData.condition, dialogType: dialogType, sliderRange: sliderRange, @@ -83,7 +85,7 @@ class FlushValueSelectorWidget extends StatelessWidget { ), ), ), - onSliderChanged: (value) { + onTextChanged: (value) { final roundedValue = _roundToStep(value, stepSize); final finalValue = isDistanceDetection ? (roundedValue * 100).toInt() : roundedValue; @@ -102,6 +104,7 @@ class FlushValueSelectorWidget extends StatelessWidget { }, unit: _unit, dividendOfRange: stepSize, + stepIncreaseAmount: stepSize, ); } diff --git a/lib/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart b/lib/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart index 2b09f579..a3223abd 100644 --- a/lib/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart +++ b/lib/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart @@ -8,6 +8,7 @@ abstract final class RoutineTapFunctionHelper { static void onTapFunction( BuildContext context, { + double? step, required String functionCode, required String functionOperationName, required String? functionValueDescription, diff --git a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart index 3c786045..641fd234 100644 --- a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -4,11 +4,11 @@ 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/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.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'; @@ -87,14 +87,15 @@ class OneGangSwitchHelper { size: 16, color: ColorsManager.textGray, ), - onTap: () => - RoutineTapFunctionHelper.onTapFunction( + onTap: () => RoutineTapFunctionHelper + .onTapFunction( context, functionCode: function.code, functionOperationName: function.operationName, functionValueDescription: - selectedFunctionData.valueDescription, + selectedFunctionData + .valueDescription, deviceUuid: device?.uuid, codesToAddIntoFunctionsWithDefaultValue: [ 'countdown_1', @@ -108,14 +109,16 @@ class OneGangSwitchHelper { if (selectedFunction != null) Expanded( child: _buildValueSelector( - context: context, - selectedFunction: selectedFunction, - selectedFunctionData: selectedFunctionData, - acFunctions: oneGangFunctions, - device: device, - operationName: selectedOperationName ?? '', - removeComparetors: removeComparetors, - ), + context: context, + selectedFunction: selectedFunction, + selectedFunctionData: + selectedFunctionData, + acFunctions: oneGangFunctions, + device: device, + operationName: + selectedOperationName ?? '', + removeComparetors: removeComparetors, + dialogType: dialogType), ), ], ), @@ -172,6 +175,7 @@ class OneGangSwitchHelper { AllDevicesModel? device, required String operationName, required bool removeComparetors, + required String dialogType, }) { if (selectedFunction == 'countdown_1') { final initialValue = selectedFunctionData?.value ?? 0; @@ -184,6 +188,7 @@ class OneGangSwitchHelper { operationName: operationName, selectedFunctionData: selectedFunctionData, removeComparetors: removeComparetors, + dialogType: dialogType, ); } final selectedFn = acFunctions.firstWhere( @@ -216,93 +221,18 @@ class OneGangSwitchHelper { required String operationName, DeviceFunctionData? selectedFunctionData, required bool removeComparetors, + String? dialogType, }) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (removeComparetors != true) - _buildConditionToggle( - context, - currentCondition, - selectCode, - device, - operationName, - selectedFunctionData, - ), - const SizedBox(height: 20), - _buildCountDownDisplay(context, initialValue, device, operationName, - selectedFunctionData, selectCode), const SizedBox(height: 20), _buildCountDownSlider(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + selectedFunctionData, selectCode, dialogType!), ], ); } - /// Build condition toggle for AC functions dialog - static Widget _buildConditionToggle( - BuildContext context, - String? currentCondition, - String selectCode, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - // Function(String) onConditionChanged, - ) { - final conditions = ["<", "==", ">"]; - - return ToggleButtons( - onPressed: (int index) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - condition: conditions[index], - value: selectedFunctionData?.value ?? 0, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), - children: conditions.map((c) => Text(c)).toList(), - ); - } - - /// Build temperature display for AC functions dialog - static Widget _buildCountDownDisplay( - BuildContext context, - dynamic initialValue, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - String selectCode) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0), - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ); - } - static Widget _buildCountDownSlider( BuildContext context, dynamic initialValue, @@ -310,38 +240,47 @@ class OneGangSwitchHelper { String operationName, DeviceFunctionData? selectedFunctionData, String selectCode, + String dialogType, ) { - const twelveHoursInSeconds = 43200.0; - final operationalValues = SwitchOperationalValue( - icon: '', - description: "sec", - value: 0.0, - minValue: 0, - maxValue: twelveHoursInSeconds, - stepValue: 1, - ); - return Slider( - value: (initialValue ?? 0).toDouble(), - min: operationalValues.minValue?.toDouble() ?? 0.0, - max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: - (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / - (operationalValues.stepValue ?? 1)) - .round(), - onChanged: (value) { + return CustomRoutinesTextbox( + withSpecialChar: false, + currentCondition: selectedFunctionData?.condition, + dialogType: dialogType, + sliderRange: (0, 43200), + displayedValue: (initialValue ?? 0).toString(), + initialValue: (initialValue ?? 0).toString(), + onConditionChanged: (condition) { context.read().add( AddFunction( functionData: DeviceFunctionData( entityId: device?.uuid ?? '', functionCode: selectCode, operationName: operationName, - value: value, + condition: condition, + value: selectedFunctionData?.value ?? 0, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + }, + onTextChanged: (value) { + final roundedValue = value.round(); + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: roundedValue, condition: selectedFunctionData?.condition, valueDescription: selectedFunctionData?.valueDescription, ), ), ); }, + unit: 'sec', + dividendOfRange: 1, + stepIncreaseAmount: 1, ); } @@ -377,7 +316,9 @@ class OneGangSwitchHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, size: 24, color: isSelected ? ColorsManager.primaryColorWithOpacity @@ -393,7 +334,8 @@ class OneGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, + valueDescription: + selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart index 44a367ef..5e50c11d 100644 --- a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -4,10 +4,10 @@ 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/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.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'; @@ -86,20 +86,21 @@ class ThreeGangSwitchHelper { size: 16, color: ColorsManager.textGray, ), - onTap: () => - RoutineTapFunctionHelper.onTapFunction( + onTap: () => RoutineTapFunctionHelper + .onTapFunction( context, functionCode: function.code, functionOperationName: function.operationName, functionValueDescription: - selectedFunctionData.valueDescription, + selectedFunctionData + .valueDescription, deviceUuid: device?.uuid, - codesToAddIntoFunctionsWithDefaultValue: [ - 'countdown_1', - 'countdown_2', - 'countdown_3', - ], + codesToAddIntoFunctionsWithDefaultValue: + function.code + .startsWith('countdown') + ? [function.code] + : [], ), ); }, @@ -109,14 +110,16 @@ class ThreeGangSwitchHelper { if (selectedFunction != null) Expanded( child: _buildValueSelector( - context: context, - selectedFunction: selectedFunction, - selectedFunctionData: selectedFunctionData, - switchFunctions: switchFunctions, - device: device, - operationName: selectedOperationName ?? '', - removeComparetors: removeComparetors, - ), + context: context, + selectedFunction: selectedFunction, + selectedFunctionData: + selectedFunctionData, + switchFunctions: switchFunctions, + device: device, + operationName: + selectedOperationName ?? '', + removeComparetors: removeComparetors, + dialogType: dialogType), ), ], ), @@ -133,14 +136,6 @@ class ThreeGangSwitchHelper { onConfirm: state.addedFunctions.isNotEmpty ? () { /// add the functions to the routine bloc - // for (var function in state.addedFunctions) { - // context.read().add( - // AddFunctionToRoutine( - // function, - // uniqueCustomId, - // ), - // ); - // } context.read().add( AddFunctionToRoutine( state.addedFunctions, @@ -173,24 +168,26 @@ class ThreeGangSwitchHelper { AllDevicesModel? device, required String operationName, required bool removeComparetors, + required String dialogType, }) { if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2' || selectedFunction == 'countdown_3') { final initialValue = selectedFunctionData?.value ?? 0; return _buildTemperatureSelector( - context: context, - initialValue: initialValue, - selectCode: selectedFunction, - currentCondition: selectedFunctionData?.condition, - device: device, - operationName: operationName, - selectedFunctionData: selectedFunctionData, - removeComparetors: removeComparetors, - ); + context: context, + initialValue: initialValue, + selectCode: selectedFunction, + currentCondition: selectedFunctionData?.condition, + device: device, + operationName: operationName, + selectedFunctionData: selectedFunctionData, + removeComparetors: removeComparetors, + dialogType: dialogType); } - final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = + switchFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -213,93 +210,18 @@ class ThreeGangSwitchHelper { required String operationName, DeviceFunctionData? selectedFunctionData, bool? removeComparetors, + required String dialogType, }) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (removeComparetors != true) - _buildConditionToggle( - context, - currentCondition, - selectCode, - device, - operationName, - selectedFunctionData, - ), - const SizedBox(height: 20), - _buildCountDownDisplay(context, initialValue, device, operationName, - selectedFunctionData, selectCode), const SizedBox(height: 20), _buildCountDownSlider(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + selectedFunctionData, selectCode, dialogType), ], ); } - /// Build condition toggle for AC functions dialog - static Widget _buildConditionToggle( - BuildContext context, - String? currentCondition, - String selectCode, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - // Function(String) onConditionChanged, - ) { - final conditions = ["<", "==", ">"]; - - return ToggleButtons( - onPressed: (int index) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - condition: conditions[index], - value: selectedFunctionData?.value ?? 0, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), - children: conditions.map((c) => Text(c)).toList(), - ); - } - - /// Build temperature display for AC functions dialog - static Widget _buildCountDownDisplay( - BuildContext context, - dynamic initialValue, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - String selectCode) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0), - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ); - } - static Widget _buildCountDownSlider( BuildContext context, dynamic initialValue, @@ -307,38 +229,47 @@ class ThreeGangSwitchHelper { String operationName, DeviceFunctionData? selectedFunctionData, String selectCode, + String dialogType, ) { - const twelveHoursInSeconds = 43200.0; - final operationalValues = SwitchOperationalValue( - icon: '', - description: "sec", - value: 0.0, - minValue: 0, - maxValue: twelveHoursInSeconds, - stepValue: 1, - ); - return Slider( - value: (initialValue ?? 0).toDouble(), - min: operationalValues.minValue?.toDouble() ?? 0.0, - max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: - (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / - (operationalValues.stepValue ?? 1)) - .round(), - onChanged: (value) { + return CustomRoutinesTextbox( + withSpecialChar: true, + currentCondition: selectedFunctionData?.condition, + dialogType: dialogType, + sliderRange: (0, 43200), + displayedValue: (initialValue ?? 0).toString(), + initialValue: (initialValue ?? 0).toString(), + onConditionChanged: (condition) { context.read().add( AddFunction( functionData: DeviceFunctionData( entityId: device?.uuid ?? '', functionCode: selectCode, operationName: operationName, - value: value, + condition: condition, + value: selectedFunctionData?.value ?? 0, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + }, + onTextChanged: (value) { + final roundedValue = value.round(); + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: roundedValue, condition: selectedFunctionData?.condition, valueDescription: selectedFunctionData?.valueDescription, ), ), ); }, + unit: 'sec', + dividendOfRange: 1, + stepIncreaseAmount: 1, ); } @@ -374,7 +305,9 @@ class ThreeGangSwitchHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, size: 24, color: isSelected ? ColorsManager.primaryColorWithOpacity @@ -390,7 +323,8 @@ class ThreeGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, + valueDescription: + selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart index f551d21b..6b3dc813 100644 --- a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.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'; @@ -86,14 +87,15 @@ class TwoGangSwitchHelper { size: 16, color: ColorsManager.textGray, ), - onTap: () => - RoutineTapFunctionHelper.onTapFunction( + onTap: () => RoutineTapFunctionHelper + .onTapFunction( context, functionCode: function.code, functionOperationName: function.operationName, functionValueDescription: - selectedFunctionData.valueDescription, + selectedFunctionData + .valueDescription, deviceUuid: device?.uuid, codesToAddIntoFunctionsWithDefaultValue: [ 'countdown_1', @@ -115,6 +117,7 @@ class TwoGangSwitchHelper { device: device, operationName: selectedOperationName ?? '', removeComparetors: removeComparetors, + dialogType: dialogType, ), ), ], @@ -172,22 +175,25 @@ class TwoGangSwitchHelper { AllDevicesModel? device, required String operationName, required bool removeComparetors, + required String dialogType, }) { - if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') { + if (selectedFunction == 'countdown_1' || + selectedFunction == 'countdown_2') { final initialValue = selectedFunctionData?.value ?? 0; return _buildTemperatureSelector( - context: context, - initialValue: initialValue, - selectCode: selectedFunction, - currentCondition: selectedFunctionData?.condition, - device: device, - operationName: operationName, - selectedFunctionData: selectedFunctionData, - removeComparetors: removeComparetors, - ); + context: context, + initialValue: initialValue, + selectCode: selectedFunction, + currentCondition: selectedFunctionData?.condition, + device: device, + operationName: operationName, + selectedFunctionData: selectedFunctionData, + removeComparetors: removeComparetors, + dialogType: dialogType); } - final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = + switchFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -210,25 +216,13 @@ class TwoGangSwitchHelper { required String operationName, DeviceFunctionData? selectedFunctionData, bool? removeComparetors, + String? dialogType, }) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (removeComparetors != true) - _buildConditionToggle( - context, - currentCondition, - selectCode, - device, - operationName, - selectedFunctionData, - ), - const SizedBox(height: 20), - _buildCountDownDisplay(context, initialValue, device, operationName, - selectedFunctionData, selectCode), - const SizedBox(height: 20), _buildCountDownSlider(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + selectedFunctionData, selectCode, dialogType!), ], ); } @@ -269,7 +263,8 @@ class TwoGangSwitchHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: + conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -304,38 +299,48 @@ class TwoGangSwitchHelper { String operationName, DeviceFunctionData? selectedFunctionData, String selectCode, + String dialogType, ) { - const twelveHoursInSeconds = 43200.0; - final operationalValues = SwitchOperationalValue( - icon: '', - description: "sec", - value: 0.0, - minValue: 0, - maxValue: twelveHoursInSeconds, - stepValue: 1, - ); - return Slider( - value: (initialValue ?? 0).toDouble(), - min: operationalValues.minValue?.toDouble() ?? 0.0, - max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: - (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / - (operationalValues.stepValue ?? 1)) - .round(), - onChanged: (value) { + return CustomRoutinesTextbox( + withSpecialChar: true, + currentCondition: selectedFunctionData?.condition, + dialogType: dialogType, + sliderRange: (0, 43200), + displayedValue: (initialValue ?? 0).toString(), + initialValue: (initialValue ?? 0).toString(), + onConditionChanged: (condition) { context.read().add( AddFunction( functionData: DeviceFunctionData( entityId: device?.uuid ?? '', functionCode: selectCode, operationName: operationName, - value: value, + condition: condition, + value: selectedFunctionData?.value ?? 0, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + }, + onTextChanged: (value) { + final roundedValue = + value.round(); // Round to nearest integer (stepSize 1) + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: roundedValue, condition: selectedFunctionData?.condition, valueDescription: selectedFunctionData?.valueDescription, ), ), ); }, + unit: 'sec', + dividendOfRange: 1, + stepIncreaseAmount: 1, ); } @@ -371,7 +376,9 @@ class TwoGangSwitchHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, size: 24, color: isSelected ? ColorsManager.primaryColorWithOpacity @@ -387,7 +394,8 @@ class TwoGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, + valueDescription: + selectedFunctionData?.valueDescription, ), ), ); 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 30232846..677c26ee 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 @@ -4,8 +4,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo 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/wps/wps_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_operational_values_list.dart'; -import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; class WpsValueSelectorWidget extends StatelessWidget { final String selectedFunction; @@ -27,11 +27,13 @@ class WpsValueSelectorWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final selectedFn = wpsFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = + wpsFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); if (_isSliderFunction(selectedFunction)) { - return SliderValueSelector( + return CustomRoutinesTextbox( + withSpecialChar: false, currentCondition: functionData.condition, dialogType: dialogType, sliderRange: sliderRange, @@ -48,7 +50,7 @@ class WpsValueSelectorWidget extends StatelessWidget { ), ), ), - onSliderChanged: (value) => context.read().add( + onTextChanged: (value) => context.read().add( AddFunction( functionData: DeviceFunctionData( entityId: device?.uuid ?? '', @@ -61,6 +63,7 @@ class WpsValueSelectorWidget extends StatelessWidget { ), unit: _unit, dividendOfRange: 1, + stepIncreaseAmount: _steps, ); } @@ -99,4 +102,10 @@ class WpsValueSelectorWidget extends StatelessWidget { 'illuminance_value' => 'Lux', _ => '', }; + double get _steps => switch (functionData.functionCode) { + 'presence_time' => 1, + 'dis_current' => 1, + 'illuminance_value' => 1, + _ => 1, + }; } diff --git a/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_presence_sensor.dart b/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_presence_sensor.dart index 8721613e..d87e8484 100644 --- a/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_presence_sensor.dart +++ b/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_presence_sensor.dart @@ -176,6 +176,7 @@ class _WaterHeaterDialogRoutinesState extends State { functionData: functionData, whFunctions: _waterHeaterFunctions, device: widget.device, + dialogType: widget.dialogType, ), ); } diff --git a/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_value_selector_widget.dart index d305e5f6..a09bbba7 100644 --- a/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_value_selector_widget.dart +++ b/lib/pages/routines/widgets/routine_dialogs/water_heater/water_heater_value_selector_widget.dart @@ -2,25 +2,24 @@ 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/helper/duration_format_helper.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; -import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/water_heater/water_heater_operational_values_list.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; class WaterHeaterValueSelectorWidget extends StatelessWidget { final String selectedFunction; final DeviceFunctionData functionData; final List whFunctions; final AllDevicesModel? device; + final String dialogType; const WaterHeaterValueSelectorWidget({ required this.selectedFunction, required this.functionData, required this.whFunctions, required this.device, + required this.dialogType, super.key, }); @@ -39,22 +38,6 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildConditionToggle( - context, - functionData.condition, - selectedFunction, - device, - selectedFn.operationName, - functionData, - ), - _buildCountDownDisplay( - context, - functionData.value, - device, - selectedFn.operationName, - functionData, - selectedFunction, - ), _buildCountDownSlider( context, functionData.value, @@ -62,6 +45,7 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget { selectedFn.operationName, functionData, selectedFunction, + dialogType ), const SizedBox(height: 10), ], @@ -90,28 +74,6 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget { ); } - static Widget _buildCountDownDisplay( - BuildContext context, - dynamic initialValue, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - String selectCode) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0), - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ); - } - static Widget _buildCountDownSlider( BuildContext context, dynamic initialValue, @@ -119,78 +81,47 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget { String operationName, DeviceFunctionData? selectedFunctionData, String selectCode, + String dialogType, ) { - const twelveHoursInSeconds = 43200.0; - final operationalValues = SwitchOperationalValue( - icon: '', - description: "sec", - value: 0.0, - minValue: 0, - maxValue: twelveHoursInSeconds, - stepValue: 1, - ); - return Slider( - value: (initialValue ?? 0).toDouble(), - min: operationalValues.minValue?.toDouble() ?? 0.0, - max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: (((operationalValues.maxValue ?? 0) - - (operationalValues.minValue ?? 0)) / - (operationalValues.stepValue ?? 1)) - .round(), - onChanged: (value) { + return CustomRoutinesTextbox( + withSpecialChar: false, + currentCondition: selectedFunctionData?.condition, + dialogType: dialogType, + sliderRange: (0, 43200), + displayedValue: (initialValue ?? 0).toString(), + initialValue: (initialValue ?? 0).toString(), + onConditionChanged: (condition) { context.read().add( AddFunction( functionData: DeviceFunctionData( entityId: device?.uuid ?? '', functionCode: selectCode, operationName: operationName, - value: value, + value: condition, + condition: condition, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + }, + onTextChanged: (value) { + final roundedValue = value.round(); + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: roundedValue, condition: selectedFunctionData?.condition, valueDescription: selectedFunctionData?.valueDescription, ), ), ); }, - ); - } - - static Widget _buildConditionToggle( - BuildContext context, - String? currentCondition, - String selectCode, - AllDevicesModel? device, - String operationName, - DeviceFunctionData? selectedFunctionData, - ) { - final conditions = ["<", "==", ">"]; - - return ToggleButtons( - onPressed: (int index) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - condition: conditions[index], - value: selectedFunctionData?.value ?? 0, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - }, - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: - conditions.map((c) => c == (currentCondition ?? "==")).toList(), - children: conditions.map((c) => Text(c)).toList(), + unit: 'sec', + dividendOfRange: 1, + stepIncreaseAmount: 1, ); } }