Refactor SliderValueSelector and ValueDisplay to include unit handling and improve UI consistency across sensor dialogs.

This commit is contained in:
Faris Armoush
2025-04-10 16:22:02 +03:00
parent fadb23d631
commit 74046c5aed
4 changed files with 145 additions and 39 deletions

View File

@ -29,13 +29,12 @@ class CpsDialogSliderSelector extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliderValueSelector( return SliderValueSelector(
selectedFunction: selectedFunction, currentCondition: selectedFunctionData.condition,
functionData: selectedFunctionData,
device: device,
dialogType: dialogType, dialogType: dialogType,
sliderRange: _sliderRange, sliderRange: _sliderRange,
displayedValue: _displayText, displayedValue: _displayText,
initialValue: selectedFunctionData.value ?? 0, initialValue: selectedFunctionData.value ?? 0,
unit: _unit,
onConditionChanged: (condition) => context.read<FunctionBloc>().add( onConditionChanged: (condition) => context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
@ -58,13 +57,10 @@ class CpsDialogSliderSelector extends StatelessWidget {
), ),
), ),
), ),
); );
} }
double get sliderStepper {
return 1;
}
(double, double) get _sliderRange => switch (selectedFunctionData.functionCode) { (double, double) get _sliderRange => switch (selectedFunctionData.functionCode) {
'moving_speed' => (0, 32), 'moving_speed' => (0, 32),
'space_static_val' => (0, 255), 'space_static_val' => (0, 255),
@ -94,12 +90,26 @@ class CpsDialogSliderSelector extends StatelessWidget {
'presence_range' || 'presence_range' ||
'perceptual_boundary' || 'perceptual_boundary' ||
'moving_boundary' => 'moving_boundary' =>
'${parsedValue?.toStringAsFixed(1) ?? '0'} M', '${parsedValue?.toStringAsFixed(1) ?? '0'}',
'moving_rigger_time' => '${parsedValue?.toStringAsFixed(3) ?? '0'} s', 'moving_rigger_time' => '${parsedValue?.toStringAsFixed(3) ?? '0'}',
'moving_static_time' || 'moving_static_time' ||
'none_body_time' => 'none_body_time' =>
'${parsedValue?.toStringAsFixed(0) ?? '0'} s', '${parsedValue?.toStringAsFixed(0) ?? '0'}',
_ => '${parsedValue ?? 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',
_ => '',
};
}
} }

View File

@ -32,9 +32,7 @@ class WpsValueSelectorWidget extends StatelessWidget {
if (_isSliderFunction(selectedFunction)) { if (_isSliderFunction(selectedFunction)) {
return SliderValueSelector( return SliderValueSelector(
selectedFunction: selectedFunction, currentCondition: functionData.condition,
functionData: functionData,
device: device,
dialogType: dialogType, dialogType: dialogType,
sliderRange: sliderRange, sliderRange: sliderRange,
displayedValue: getDisplayText, displayedValue: getDisplayText,
@ -61,6 +59,7 @@ class WpsValueSelectorWidget extends StatelessWidget {
), ),
), ),
), ),
unit: _unit,
); );
} }
@ -86,10 +85,17 @@ class WpsValueSelectorWidget extends StatelessWidget {
String get getDisplayText { String get getDisplayText {
final intValue = int.tryParse('${functionData.value ?? ''}'); final intValue = int.tryParse('${functionData.value ?? ''}');
return switch (functionData.functionCode) { return switch (functionData.functionCode) {
'presence_time' => '${intValue ?? '0'} Min', 'presence_time' => '${intValue ?? '0'}',
'dis_current' => '${intValue ?? '250'} CM', 'dis_current' => '${intValue ?? '250'}',
'illuminance_value' => '${intValue ?? '0'} Lux', 'illuminance_value' => '${intValue ?? '0'}',
_ => '$intValue', _ => '$intValue',
}; };
} }
String get _unit => switch (functionData.functionCode) {
'presence_time' => 'Min',
'dis_current' => 'CM',
'illuminance_value' => 'Lux',
_ => '',
};
} }

View File

@ -1,54 +1,142 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:flutter/services.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/condition_toggle.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/function_slider.dart';
import 'package:syncrow_web/pages/routines/widgets/value_display.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 { class SliderValueSelector extends StatelessWidget {
final String selectedFunction; final String? currentCondition;
final DeviceFunctionData functionData;
final AllDevicesModel? device;
final String dialogType; final String dialogType;
final (double, double) sliderRange; final (double, double) sliderRange;
final String displayedValue; final String displayedValue;
final Object? initialValue; final Object? initialValue;
final void Function(String condition) onConditionChanged; final void Function(String condition) onConditionChanged;
final void Function(double value) onSliderChanged; final void Function(double value) onSliderChanged;
final String unit;
const SliderValueSelector({ const SliderValueSelector({
required this.selectedFunction,
required this.functionData,
required this.device,
required this.dialogType, required this.dialogType,
required this.sliderRange, required this.sliderRange,
required this.displayedValue, required this.displayedValue,
required this.initialValue, required this.initialValue,
required this.onConditionChanged, required this.onConditionChanged,
required this.onSliderChanged, required this.onSliderChanged,
required this.currentCondition,
required this.unit,
super.key, super.key,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( if (dialogType == 'IF') {
spacing: 16, 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, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [ children: [
ConditionToggle( const Spacer(),
currentCondition: functionData.condition, Expanded(
onChanged: onConditionChanged, flex: 2,
), child: Column(
ValueDisplay( mainAxisSize: MainAxisSize.min,
value: initialValue, children: [
label: displayedValue, TextFormField(
), onChanged: (value) => onSliderChanged(double.tryParse(value) ?? 0),
FunctionSlider( expands: false,
initialValue: initialValue, onTapOutside: (_) => FocusScope.of(context).unfocus(),
range: sliderRange, initialValue: displayedValue,
onChanged: onSliderChanged, 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;
}
}

View File

@ -5,11 +5,13 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ValueDisplay extends StatelessWidget { class ValueDisplay extends StatelessWidget {
final dynamic value; final dynamic value;
final String label; final String label;
final String unit;
const ValueDisplay({ const ValueDisplay({
required this.value, required this.value,
required this.label, required this.label,
super.key, super.key,
required this.unit,
}); });
@override @override
@ -21,7 +23,7 @@ class ValueDisplay extends StatelessWidget {
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: Text( child: Text(
label, '$label$unit',
style: context.textTheme.headlineMedium!.copyWith( style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity, color: ColorsManager.primaryColorWithOpacity,
), ),