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, ); } }