From 74046c5aede8d97ed0497daf2f69ce143087fd2b Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 16:22:02 +0300 Subject: [PATCH] Refactor SliderValueSelector and ValueDisplay to include unit handling and improve UI consistency across sensor dialogs. --- .../cps_dialog_slider_selector.dart | 30 ++-- .../wps_value_selector_widget.dart | 18 ++- .../widgets/slider_value_selector.dart | 132 +++++++++++++++--- lib/pages/routines/widgets/value_display.dart | 4 +- 4 files changed, 145 insertions(+), 39 deletions(-) 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 d826afb5..3c792eca 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 @@ -29,13 +29,12 @@ class CpsDialogSliderSelector extends StatelessWidget { @override Widget build(BuildContext context) { return SliderValueSelector( - selectedFunction: selectedFunction, - functionData: selectedFunctionData, - device: device, + currentCondition: selectedFunctionData.condition, dialogType: dialogType, sliderRange: _sliderRange, displayedValue: _displayText, initialValue: selectedFunctionData.value ?? 0, + unit: _unit, onConditionChanged: (condition) => context.read().add( AddFunction( functionData: DeviceFunctionData( @@ -58,13 +57,10 @@ class CpsDialogSliderSelector extends StatelessWidget { ), ), ), + ); } - double get sliderStepper { - return 1; - } - (double, double) get _sliderRange => switch (selectedFunctionData.functionCode) { 'moving_speed' => (0, 32), 'space_static_val' => (0, 255), @@ -94,12 +90,26 @@ class CpsDialogSliderSelector extends StatelessWidget { 'presence_range' || 'perceptual_boundary' || 'moving_boundary' => - '${parsedValue?.toStringAsFixed(1) ?? '0'} M', - 'moving_rigger_time' => '${parsedValue?.toStringAsFixed(3) ?? '0'} s', + '${parsedValue?.toStringAsFixed(1) ?? '0'}', + 'moving_rigger_time' => '${parsedValue?.toStringAsFixed(3) ?? '0'}', 'moving_static_time' || 'none_body_time' => - '${parsedValue?.toStringAsFixed(0) ?? '0'} s', + '${parsedValue?.toStringAsFixed(0) ?? '0'}', _ => '${parsedValue ?? 0}', }; } + + String get _unit { + return switch (selectedFunctionData.functionCode) { + 'moving_max_dis' || + 'static_max_dis' || + 'moving_range' || + 'presence_range' || + 'perceptual_boundary' || + 'moving_boundary' => + 'M', + 'moving_rigger_time' || 'moving_static_time' || 'none_body_time' => 'sec', + _ => '', + }; + } } 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 5f38b240..ccd82e23 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 @@ -32,9 +32,7 @@ class WpsValueSelectorWidget extends StatelessWidget { if (_isSliderFunction(selectedFunction)) { return SliderValueSelector( - selectedFunction: selectedFunction, - functionData: functionData, - device: device, + currentCondition: functionData.condition, dialogType: dialogType, sliderRange: sliderRange, displayedValue: getDisplayText, @@ -61,6 +59,7 @@ class WpsValueSelectorWidget extends StatelessWidget { ), ), ), + unit: _unit, ); } @@ -86,10 +85,17 @@ class WpsValueSelectorWidget extends StatelessWidget { String get getDisplayText { final intValue = int.tryParse('${functionData.value ?? ''}'); return switch (functionData.functionCode) { - 'presence_time' => '${intValue ?? '0'} Min', - 'dis_current' => '${intValue ?? '250'} CM', - 'illuminance_value' => '${intValue ?? '0'} Lux', + 'presence_time' => '${intValue ?? '0'}', + 'dis_current' => '${intValue ?? '250'}', + 'illuminance_value' => '${intValue ?? '0'}', _ => '$intValue', }; } + + String get _unit => switch (functionData.functionCode) { + 'presence_time' => 'Min', + 'dis_current' => 'CM', + 'illuminance_value' => 'Lux', + _ => '', + }; } diff --git a/lib/pages/routines/widgets/slider_value_selector.dart b/lib/pages/routines/widgets/slider_value_selector.dart index dd26caf1..dfcfeac4 100644 --- a/lib/pages/routines/widgets/slider_value_selector.dart +++ b/lib/pages/routines/widgets/slider_value_selector.dart @@ -1,54 +1,142 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:flutter/services.dart'; import 'package:syncrow_web/pages/routines/widgets/condition_toggle.dart'; import 'package:syncrow_web/pages/routines/widgets/function_slider.dart'; import 'package:syncrow_web/pages/routines/widgets/value_display.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class SliderValueSelector extends StatelessWidget { - final String selectedFunction; - final DeviceFunctionData functionData; - final AllDevicesModel? device; + final String? currentCondition; final String dialogType; final (double, double) sliderRange; final String displayedValue; final Object? initialValue; final void Function(String condition) onConditionChanged; final void Function(double value) onSliderChanged; + final String unit; const SliderValueSelector({ - required this.selectedFunction, - required this.functionData, - required this.device, required this.dialogType, required this.sliderRange, required this.displayedValue, required this.initialValue, required this.onConditionChanged, required this.onSliderChanged, + required this.currentCondition, + required this.unit, super.key, }); @override Widget build(BuildContext context) { - return Column( - spacing: 16, + if (dialogType == 'IF') { + return Column( + spacing: 16, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ConditionToggle( + currentCondition: currentCondition, + onChanged: onConditionChanged, + ), + ValueDisplay( + value: initialValue, + label: displayedValue, + unit: unit, + ), + FunctionSlider( + initialValue: initialValue, + range: sliderRange, + onChanged: onSliderChanged, + ), + ], + ); + } + + return Row( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ - ConditionToggle( - currentCondition: functionData.condition, - onChanged: onConditionChanged, - ), - ValueDisplay( - value: initialValue, - label: displayedValue, - ), - FunctionSlider( - initialValue: initialValue, - range: sliderRange, - onChanged: onSliderChanged, + const Spacer(), + Expanded( + flex: 2, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + onChanged: (value) => onSliderChanged(double.tryParse(value) ?? 0), + expands: false, + onTapOutside: (_) => FocusScope.of(context).unfocus(), + initialValue: displayedValue, + style: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + inputFormatters: [ + RangeInputFormatter(min: sliderRange.$1, max: sliderRange.$2), + ], + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(8), + ), + filled: true, + fillColor: ColorsManager.textFieldGreyColor.withOpacity(0.5), + suffixText: unit, + hintStyle: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ), + const SizedBox(height: 8), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Min: ${sliderRange.$1}', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.lightGrayColor, + ), + ), + const Spacer(), + Text( + 'Max: ${sliderRange.$2}', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.lightGrayColor, + ), + ), + ], + ), + ], + ), ), + const Spacer(), ], ); } } + +class RangeInputFormatter extends TextInputFormatter { + const RangeInputFormatter({required this.min, required this.max}); + + final double min; + final double max; + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + final text = newValue.text; + if (text.isEmpty) { + return newValue; + } + + final value = double.tryParse(text); + if (value == null || value < min || value > max) { + return oldValue; + } + + return newValue; + } +} diff --git a/lib/pages/routines/widgets/value_display.dart b/lib/pages/routines/widgets/value_display.dart index e9b76325..a47fb5b4 100644 --- a/lib/pages/routines/widgets/value_display.dart +++ b/lib/pages/routines/widgets/value_display.dart @@ -5,11 +5,13 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; class ValueDisplay extends StatelessWidget { final dynamic value; final String label; + final String unit; const ValueDisplay({ required this.value, required this.label, super.key, + required this.unit, }); @override @@ -21,7 +23,7 @@ class ValueDisplay extends StatelessWidget { borderRadius: BorderRadius.circular(10), ), child: Text( - label, + '$label$unit', style: context.textTheme.headlineMedium!.copyWith( color: ColorsManager.primaryColorWithOpacity, ),