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
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<FunctionBloc>().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',
_ => '',
};
}
}

View File

@ -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',
_ => '',
};
}

View File

@ -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;
}
}

View File

@ -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,
),