mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 07:07:19 +00:00
Merge pull request #142 from SyncrowIOT/SP-1278-FE-Allow-Simple-Edit-Delete
Added Ceiling Presence Sensor Device To Routine
This commit is contained in:
33
lib/pages/routines/widgets/condition_toggle.dart
Normal file
33
lib/pages/routines/widgets/condition_toggle.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ConditionToggle extends StatelessWidget {
|
||||
final String? currentCondition;
|
||||
final void Function(String condition) onChanged;
|
||||
|
||||
const ConditionToggle({
|
||||
required this.onChanged,
|
||||
this.currentCondition,
|
||||
super.key,
|
||||
});
|
||||
|
||||
static const _conditions = ["<", "==", ">"];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ToggleButtons(
|
||||
onPressed: (index) => onChanged(_conditions[index]),
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
35
lib/pages/routines/widgets/function_slider.dart
Normal file
35
lib/pages/routines/widgets/function_slider.dart
Normal file
@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FunctionSlider extends StatelessWidget {
|
||||
final dynamic initialValue;
|
||||
final (double min, double max) range;
|
||||
final void Function(double value) onChanged;
|
||||
final double dividendOfRange;
|
||||
|
||||
const FunctionSlider({
|
||||
required this.onChanged,
|
||||
required this.initialValue,
|
||||
required this.range,
|
||||
required this.dividendOfRange,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final (min, max) = range;
|
||||
final bool isValidRange = max > min;
|
||||
final double value = initialValue is int
|
||||
? (initialValue as int).toDouble()
|
||||
: (initialValue as double);
|
||||
|
||||
final int? divisions = isValidRange ? ((max - min) / dividendOfRange).round() : null;
|
||||
|
||||
return Slider(
|
||||
value: value.clamp(min, max),
|
||||
min: min,
|
||||
max: max,
|
||||
divisions: divisions,
|
||||
onChanged: isValidRange ? onChanged : null,
|
||||
);
|
||||
}
|
||||
}
|
@ -27,8 +27,8 @@ class IfContainer extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('IF',
|
||||
style: TextStyle(
|
||||
fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
style:
|
||||
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
if (state.isAutomation && state.ifItems.isNotEmpty)
|
||||
AutomationOperatorSelector(
|
||||
selectedOperator: state.selectedAutomationOperator),
|
||||
@ -55,8 +55,8 @@ class IfContainer extends StatelessWidget {
|
||||
(index) => GestureDetector(
|
||||
onTap: () async {
|
||||
if (!state.isTabToRun) {
|
||||
final result = await DeviceDialogHelper
|
||||
.showDeviceDialog(
|
||||
final result =
|
||||
await DeviceDialogHelper.showDeviceDialog(
|
||||
context: context,
|
||||
data: state.ifItems[index],
|
||||
removeComparetors: false,
|
||||
@ -71,8 +71,9 @@ class IfContainer extends StatelessWidget {
|
||||
'1G',
|
||||
'2G',
|
||||
'3G',
|
||||
'WPS'
|
||||
'WPS',
|
||||
'GW',
|
||||
'CPS',
|
||||
].contains(
|
||||
state.ifItems[index]['productType'])) {
|
||||
context.read<RoutineBloc>().add(
|
||||
@ -82,8 +83,7 @@ class IfContainer extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
child: DraggableCard(
|
||||
imagePath:
|
||||
state.ifItems[index]['imagePath'] ?? '',
|
||||
imagePath: state.ifItems[index]['imagePath'] ?? '',
|
||||
title: state.ifItems[index]['title'] ?? '',
|
||||
deviceData: state.ifItems[index],
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@ -91,12 +91,11 @@ class IfContainer extends StatelessWidget {
|
||||
isFromThen: false,
|
||||
isFromIf: true,
|
||||
onRemove: () {
|
||||
context.read<RoutineBloc>().add(
|
||||
RemoveDragCard(
|
||||
index: index,
|
||||
isFromThen: false,
|
||||
key: state.ifItems[index]
|
||||
['uniqueCustomId']));
|
||||
context.read<RoutineBloc>().add(RemoveDragCard(
|
||||
index: index,
|
||||
isFromThen: false,
|
||||
key: state.ifItems[index]
|
||||
['uniqueCustomId']));
|
||||
},
|
||||
),
|
||||
)),
|
||||
@ -116,9 +115,7 @@ class IfContainer extends StatelessWidget {
|
||||
|
||||
if (!state.isTabToRun) {
|
||||
if (mutableData['deviceId'] == 'tab_to_run') {
|
||||
context
|
||||
.read<RoutineBloc>()
|
||||
.add(AddToIfContainer(mutableData, true));
|
||||
context.read<RoutineBloc>().add(AddToIfContainer(mutableData, true));
|
||||
} else {
|
||||
final result = await DeviceDialogHelper.showDeviceDialog(
|
||||
dialogType: 'IF',
|
||||
@ -130,7 +127,7 @@ class IfContainer extends StatelessWidget {
|
||||
context
|
||||
.read<RoutineBloc>()
|
||||
.add(AddToIfContainer(mutableData, false));
|
||||
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW']
|
||||
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS']
|
||||
.contains(mutableData['productType'])) {
|
||||
context
|
||||
.read<RoutineBloc>()
|
||||
|
@ -17,7 +17,7 @@ class _RoutineDevicesState extends State<RoutineDevices> {
|
||||
context.read<RoutineBloc>().add(FetchDevicesInRoutine());
|
||||
}
|
||||
|
||||
static const _allowedProductTypes = {'AC', '1G', '2G', '3G', 'WPS', 'GW'};
|
||||
static const _allowedProductTypes = {'AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS'};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -0,0 +1,219 @@
|
||||
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/bloc/routine_bloc/routine_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/dialog_footer.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_value_selector.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart';
|
||||
|
||||
class CeilingSensorDialog extends StatefulWidget {
|
||||
const CeilingSensorDialog({
|
||||
required this.uniqueCustomId,
|
||||
required this.functions,
|
||||
required this.deviceSelectedFunctions,
|
||||
required this.device,
|
||||
required this.dialogType,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String? uniqueCustomId;
|
||||
final List<DeviceFunction> functions;
|
||||
final List<DeviceFunctionData> deviceSelectedFunctions;
|
||||
final AllDevicesModel? device;
|
||||
final String dialogType;
|
||||
|
||||
@override
|
||||
State<CeilingSensorDialog> createState() => _CeilingSensorDialogState();
|
||||
}
|
||||
|
||||
class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
|
||||
late final List<CpsFunctions> _cpsFunctions;
|
||||
late final String _dialogHeaderText;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_cpsFunctions = widget.functions.whereType<CpsFunctions>().where((function) {
|
||||
if (widget.dialogType == 'THEN') {
|
||||
return function.type == 'THEN' || function.type == 'BOTH';
|
||||
}
|
||||
return function.type == 'IF' || function.type == 'BOTH';
|
||||
}).toList();
|
||||
|
||||
final isIfDialog = widget.dialogType == 'IF';
|
||||
_dialogHeaderText = isIfDialog ? 'Conditions' : 'Functions';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
|
||||
builder: (context, state) {
|
||||
final selectedFunction = state.selectedFunction;
|
||||
|
||||
return Container(
|
||||
width: selectedFunction != null ? 600 : 360,
|
||||
height: 450,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DialogHeader('Presence Sensor $_dialogHeaderText'),
|
||||
Expanded(child: _buildMainContent(context, state)),
|
||||
DialogFooter(
|
||||
onCancel: () => Navigator.pop(context),
|
||||
onConfirm: state.addedFunctions.isNotEmpty
|
||||
? () {
|
||||
final functions = _updateValuesForAddedFunctions(
|
||||
state.addedFunctions,
|
||||
);
|
||||
context.read<RoutineBloc>().add(
|
||||
AddFunctionToRoutine(
|
||||
functions,
|
||||
'${widget.uniqueCustomId}',
|
||||
),
|
||||
);
|
||||
|
||||
Navigator.pop(context, {
|
||||
'deviceId': widget.functions.first.deviceId,
|
||||
});
|
||||
}
|
||||
: null,
|
||||
isConfirmEnabled: selectedFunction != null,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMainContent(BuildContext context, FunctionBlocState state) {
|
||||
final selectedFunction = state.selectedFunction;
|
||||
final selectedOperationName = state.selectedOperationName;
|
||||
final selectedFunctionData = state.addedFunctions.firstWhere(
|
||||
(f) => f.functionCode == selectedFunction,
|
||||
orElse: () => DeviceFunctionData(
|
||||
entityId: '',
|
||||
functionCode: selectedFunction ?? '',
|
||||
operationName: '',
|
||||
value: null,
|
||||
),
|
||||
);
|
||||
final selectedCpsFunctions = _cpsFunctions.firstWhere(
|
||||
(f) => f.code == selectedFunction,
|
||||
orElse: () => CpsMovementFunctions(
|
||||
deviceId: '',
|
||||
deviceName: '',
|
||||
type: '',
|
||||
),
|
||||
);
|
||||
final operations = selectedCpsFunctions.getOperationalValues();
|
||||
final isSensitivityFunction = selectedFunction == 'sensitivity';
|
||||
final isToggleFunction = isSensitivityFunction
|
||||
? widget.dialogType == 'THEN'
|
||||
: CeilingSensorHelper.toggleCodes.contains(selectedFunction);
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
CpsFunctionsList(cpsFunctions: _cpsFunctions),
|
||||
if (state.selectedFunction != null)
|
||||
Expanded(
|
||||
child: isToggleFunction
|
||||
? CpsDialogValueSelector(
|
||||
operations: operations,
|
||||
selectedFunction: selectedFunction ?? '',
|
||||
selectedFunctionData: selectedFunctionData,
|
||||
cpsFunctions: _cpsFunctions,
|
||||
operationName: selectedOperationName ?? '',
|
||||
device: widget.device,
|
||||
)
|
||||
: CpsDialogSliderSelector(
|
||||
operations: operations,
|
||||
selectedFunction: selectedFunction ?? '',
|
||||
selectedFunctionData: selectedFunctionData,
|
||||
cpsFunctions: _cpsFunctions,
|
||||
operationName: selectedOperationName ?? '',
|
||||
device: widget.device,
|
||||
dialogType: widget.dialogType,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static const _mappableSteppedFunctions = <String>{
|
||||
'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',
|
||||
};
|
||||
|
||||
List<DeviceFunctionData> _updateValuesForAddedFunctions(
|
||||
List<DeviceFunctionData> addedFunctions,
|
||||
) {
|
||||
return addedFunctions.map((function) {
|
||||
final shouldMapValue = _mappableSteppedFunctions.contains(
|
||||
function.functionCode,
|
||||
);
|
||||
if (shouldMapValue) {
|
||||
final mappedValue = _mapSteppedValue(
|
||||
value: function.value,
|
||||
inputStep: CpsSliderHelpers.dividendOfRange(function.functionCode),
|
||||
inputRange: CpsSliderHelpers.sliderRange(function.functionCode),
|
||||
outputRange: CpsSliderHelpers.mappedRange(function.functionCode),
|
||||
);
|
||||
return DeviceFunctionData(
|
||||
value: mappedValue,
|
||||
entityId: function.entityId,
|
||||
functionCode: function.functionCode,
|
||||
operationName: function.operationName,
|
||||
condition: function.condition,
|
||||
actionExecutor: function.actionExecutor,
|
||||
valueDescription: function.valueDescription,
|
||||
);
|
||||
}
|
||||
return function;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
int _mapSteppedValue({
|
||||
required (double min, double max) inputRange,
|
||||
required double inputStep,
|
||||
required (double min, double max, double dividend) outputRange,
|
||||
required double value,
|
||||
}) {
|
||||
final (inputMin, inputMax) = inputRange;
|
||||
final (outputMin, outputMax, outputStep) = outputRange;
|
||||
|
||||
final clampedValue = value.clamp(inputMin, inputMax);
|
||||
|
||||
final stepsFromMin = ((clampedValue - inputMin) / inputStep).round();
|
||||
|
||||
final mappedValue = outputMin + (stepsFromMin * outputStep);
|
||||
|
||||
return mappedValue.clamp(outputMin, outputMax).round();
|
||||
}
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
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/models/ceiling_presence_sensor_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart';
|
||||
|
||||
abstract final class CeilingSensorHelper {
|
||||
const CeilingSensorHelper._();
|
||||
|
||||
static Future<Map<String, dynamic>?> showCeilingSensorDialog({
|
||||
required BuildContext context,
|
||||
required String? uniqueCustomId,
|
||||
required List<DeviceFunction> functions,
|
||||
required List<DeviceFunctionData> deviceSelectedFunctions,
|
||||
required AllDevicesModel? device,
|
||||
required String dialogType,
|
||||
}) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => BlocProvider(
|
||||
create: (context) => FunctionBloc()
|
||||
..add(
|
||||
InitializeFunctions(deviceSelectedFunctions),
|
||||
),
|
||||
child: CeilingSensorDialog(
|
||||
uniqueCustomId: uniqueCustomId,
|
||||
functions: functions,
|
||||
deviceSelectedFunctions: deviceSelectedFunctions,
|
||||
device: device,
|
||||
dialogType: dialogType,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static List<DeviceFunction<CpsOperationalValue>> getCeilingSensorFunctions({
|
||||
required String uuid,
|
||||
required String name,
|
||||
}) {
|
||||
return [
|
||||
CpsRadarSwitchFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsSpatialParameterSwitchFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsSensitivityFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsMovingSpeedFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'IF',
|
||||
),
|
||||
CpsSpatialStaticValueFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'IF',
|
||||
),
|
||||
CpsSpatialMotionValueFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'IF',
|
||||
),
|
||||
CpsMaxDistanceOfDetectionFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsMaxDistanceOfStaticDetectionFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsDetectionRangeFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'IF',
|
||||
),
|
||||
CpsDistanceOfMovingObjectsFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'IF',
|
||||
),
|
||||
CpsPresenceJudgementThrsholdFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsMotionAmplitudeTriggerThresholdFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsPerpetualBoundaryFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsMotionTriggerBoundaryFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsMotionTriggerTimeFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsMotionToStaticTimeFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsEnteringNoBodyStateTimeFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsSelfTestResultFunctions(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'IF',
|
||||
),
|
||||
CpsNobodyTimeFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsMovementFunctions(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'IF',
|
||||
),
|
||||
CpsCustomModeFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsSpaceTypeFunctions(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'BOTH',
|
||||
),
|
||||
CpsPresenceStatusFunctions(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'IF',
|
||||
),
|
||||
CpsSportsParaFunction(
|
||||
deviceName: name,
|
||||
deviceId: uuid,
|
||||
type: 'IF',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
static const toggleCodes = <String>{
|
||||
'radar_switch',
|
||||
'space_para_switch',
|
||||
'checking_result',
|
||||
'nobody_time',
|
||||
'body_movement',
|
||||
'custom_mode',
|
||||
'scene',
|
||||
'presence_state',
|
||||
};
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
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/models/ceiling_presence_sensor_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.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';
|
||||
|
||||
class CpsDialogSliderSelector extends StatelessWidget {
|
||||
const CpsDialogSliderSelector({
|
||||
required this.operations,
|
||||
required this.selectedFunction,
|
||||
required this.selectedFunctionData,
|
||||
required this.cpsFunctions,
|
||||
required this.device,
|
||||
required this.operationName,
|
||||
required this.dialogType,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<CpsOperationalValue> operations;
|
||||
final String selectedFunction;
|
||||
final DeviceFunctionData selectedFunctionData;
|
||||
final List<CpsFunctions> cpsFunctions;
|
||||
final AllDevicesModel? device;
|
||||
final String operationName;
|
||||
final String dialogType;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliderValueSelector(
|
||||
currentCondition: selectedFunctionData.condition,
|
||||
dialogType: dialogType,
|
||||
sliderRange: CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode),
|
||||
displayedValue: CpsSliderHelpers.displayText(
|
||||
value: selectedFunctionData.value,
|
||||
functionCode: selectedFunctionData.functionCode,
|
||||
),
|
||||
initialValue: selectedFunctionData.value ?? 0,
|
||||
unit: CpsSliderHelpers.unit(selectedFunctionData.functionCode),
|
||||
onConditionChanged: (condition) => context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectedFunction,
|
||||
operationName: operationName,
|
||||
condition: condition,
|
||||
value: selectedFunctionData.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
onSliderChanged: (value) => context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectedFunction,
|
||||
operationName: operationName,
|
||||
value: double.parse(value.toStringAsFixed(2)),
|
||||
condition: selectedFunctionData.condition,
|
||||
),
|
||||
),
|
||||
),
|
||||
dividendOfRange: CpsSliderHelpers.dividendOfRange(
|
||||
selectedFunctionData.functionCode,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
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/models/ceiling_presence_sensor_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialog_selection_list_tile.dart';
|
||||
|
||||
class CpsDialogValueSelector extends StatelessWidget {
|
||||
const CpsDialogValueSelector({
|
||||
required this.operations,
|
||||
required this.selectedFunction,
|
||||
required this.selectedFunctionData,
|
||||
required this.cpsFunctions,
|
||||
required this.device,
|
||||
required this.operationName,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<CpsOperationalValue> operations;
|
||||
final String selectedFunction;
|
||||
final DeviceFunctionData? selectedFunctionData;
|
||||
final List<CpsFunctions> cpsFunctions;
|
||||
final AllDevicesModel? device;
|
||||
final String operationName;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
itemCount: operations.length,
|
||||
itemBuilder: (context, index) {
|
||||
final operation = operations[index];
|
||||
final isSelected = selectedFunctionData?.value == operation.value;
|
||||
return RoutineDialogSelectionListTile(
|
||||
iconPath: operation.icon,
|
||||
description: operation.description,
|
||||
isSelected: isSelected,
|
||||
onTap: () {
|
||||
if (!isSelected) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectedFunction,
|
||||
operationName: operationName,
|
||||
value: operation.value,
|
||||
condition: selectedFunctionData?.condition,
|
||||
valueDescription: selectedFunctionData?.valueDescription,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
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/widgets/routine_dialog_function_list_tile.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class CpsFunctionsList extends StatelessWidget {
|
||||
const CpsFunctionsList({required this.cpsFunctions, super.key});
|
||||
|
||||
final List<CpsFunctions> cpsFunctions;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 360,
|
||||
child: ListView.separated(
|
||||
shrinkWrap: false,
|
||||
itemCount: cpsFunctions.length,
|
||||
separatorBuilder: (context, index) => const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 40.0),
|
||||
child: Divider(color: ColorsManager.dividerColor),
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final function = cpsFunctions[index];
|
||||
return RoutineDialogFunctionListTile(
|
||||
iconPath: function.icon,
|
||||
operationName: function.operationName,
|
||||
onTap: () => context.read<FunctionBloc>().add(
|
||||
SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
abstract final class CpsSliderHelpers {
|
||||
static (double min, double max, double step) mappedRange(String functionCode) {
|
||||
final (defaultMin, defaultMax) = sliderRange(functionCode);
|
||||
final defaultDivdidend = dividendOfRange(functionCode);
|
||||
return switch (functionCode) {
|
||||
'static_max_dis' => (0, 500, 50),
|
||||
'presence_reference' => (0, 255, 5),
|
||||
'moving_reference' => (0, 255, 5),
|
||||
'perceptual_boundary' => (0, 500, 50),
|
||||
'moving_boundary' => (0, 500, 50),
|
||||
'moving_rigger_time' => (0, 2000, 100),
|
||||
'moving_static_time' => (0, 60000, 1000),
|
||||
'none_body_time' => (0, 300000, 5000),
|
||||
'moving_max_dis' => (0, 500, 50),
|
||||
'moving_range' => (0, 255, 5),
|
||||
'presence_range' => (0, 255, 5),
|
||||
_ => (defaultMin, defaultMax, defaultDivdidend),
|
||||
};
|
||||
}
|
||||
|
||||
static (double min, double max) sliderRange(String functionCode) =>
|
||||
switch (functionCode) {
|
||||
'moving_speed' => (0, 32),
|
||||
'sensitivity' => (0, 10),
|
||||
'space_static_val' => (0, 255),
|
||||
'space_move_val' => (0, 255),
|
||||
'moving_max_dis' => (0, 10),
|
||||
'static_max_dis' => (0, 10),
|
||||
'moving_range' => (0, 25.5),
|
||||
'presence_range' => (0, 25.5),
|
||||
'presence_judgement_threshold' => (0, 255),
|
||||
'motion_amplitude_trigger_threshold' => (0, 255),
|
||||
'perceptual_boundary' => (0, 5),
|
||||
'moving_boundary' => (0, 5),
|
||||
'moving_rigger_time' => (0, 2),
|
||||
'moving_static_time' => (0, 50),
|
||||
'none_body_time' => (0, 300),
|
||||
'sports_para' => (0, 100),
|
||||
_ => (0, 300),
|
||||
};
|
||||
|
||||
static double dividendOfRange(String functionCode) => switch (functionCode) {
|
||||
'presence_reference' => 5,
|
||||
'moving_reference' => 5,
|
||||
'moving_max_dis' => 0.5,
|
||||
'static_max_dis' => 0.5,
|
||||
'moving_range' => 0.1,
|
||||
'presence_range' => 0.1,
|
||||
'perceptual_boundary' => 0.5,
|
||||
'moving_boundary' => 0.5,
|
||||
'moving_rigger_time' => 0.1,
|
||||
'moving_static_time' => 1.0,
|
||||
'none_body_time' => 5.0,
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
static String unit(String functionCode) => switch (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',
|
||||
_ => '',
|
||||
};
|
||||
|
||||
static String displayText({
|
||||
required dynamic value,
|
||||
required String functionCode,
|
||||
}) {
|
||||
final parsedValue = double.tryParse('$value');
|
||||
|
||||
return switch (functionCode) {
|
||||
'moving_max_dis' ||
|
||||
'static_max_dis' ||
|
||||
'moving_range' ||
|
||||
'presence_range' ||
|
||||
'perceptual_boundary' ||
|
||||
'moving_boundary' =>
|
||||
parsedValue?.toStringAsFixed(1) ?? '0',
|
||||
'moving_rigger_time' => parsedValue?.toStringAsFixed(2) ?? '0',
|
||||
'moving_static_time' ||
|
||||
'none_body_time' =>
|
||||
parsedValue?.toStringAsFixed(2) ?? '0',
|
||||
_ => '${parsedValue?.toStringAsFixed(0) ?? 0}',
|
||||
};
|
||||
}
|
||||
}
|
@ -1,17 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
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/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.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/wall_sensor/time_wheel.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
|
||||
|
||||
class WallPresenceSensor extends StatefulWidget {
|
||||
final List<DeviceFunction> functions;
|
||||
@ -63,8 +62,7 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_wpsFunctions =
|
||||
widget.functions.whereType<WpsFunctions>().where((function) {
|
||||
_wpsFunctions = widget.functions.whereType<WpsFunctions>().where((function) {
|
||||
if (widget.dialogType == 'THEN') {
|
||||
return function.type == 'THEN' || function.type == 'BOTH';
|
||||
}
|
||||
@ -176,10 +174,10 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
|
||||
);
|
||||
|
||||
return Expanded(
|
||||
child: _ValueSelector(
|
||||
child: WpsValueSelectorWidget(
|
||||
selectedFunction: selectedFunction,
|
||||
functionData: functionData,
|
||||
acFunctions: _wpsFunctions,
|
||||
wpsFunctions: _wpsFunctions,
|
||||
device: widget.device,
|
||||
dialogType: widget.dialogType!,
|
||||
removeComparators: widget.removeComparetors,
|
||||
@ -208,342 +206,3 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ValueSelector extends StatelessWidget {
|
||||
final String selectedFunction;
|
||||
final DeviceFunctionData functionData;
|
||||
final List<WpsFunctions> acFunctions;
|
||||
final AllDevicesModel? device;
|
||||
final String dialogType;
|
||||
final bool removeComparators;
|
||||
|
||||
const _ValueSelector({
|
||||
required this.selectedFunction,
|
||||
required this.functionData,
|
||||
required this.acFunctions,
|
||||
required this.device,
|
||||
required this.dialogType,
|
||||
required this.removeComparators,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedFn =
|
||||
acFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final values = selectedFn.getOperationalValues();
|
||||
|
||||
if (_isSliderFunction(selectedFunction)) {
|
||||
return _SliderValueSelector(
|
||||
selectedFunction: selectedFunction,
|
||||
functionData: functionData,
|
||||
device: device,
|
||||
dialogType: dialogType,
|
||||
);
|
||||
}
|
||||
|
||||
return _OperationalValuesList(
|
||||
values: values,
|
||||
selectedValue: functionData.value,
|
||||
device: device,
|
||||
operationName: selectedFn.operationName,
|
||||
selectCode: selectedFunction,
|
||||
);
|
||||
}
|
||||
|
||||
bool _isSliderFunction(String function) =>
|
||||
['dis_current', 'presence_time', 'illuminance_value'].contains(function);
|
||||
}
|
||||
|
||||
class _SliderValueSelector extends StatelessWidget {
|
||||
final String selectedFunction;
|
||||
final DeviceFunctionData functionData;
|
||||
final AllDevicesModel? device;
|
||||
final String dialogType;
|
||||
|
||||
const _SliderValueSelector({
|
||||
required this.selectedFunction,
|
||||
required this.functionData,
|
||||
required this.device,
|
||||
required this.dialogType,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final initialValue = functionData.value ?? 250;
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
_ConditionToggle(
|
||||
currentCondition: functionData.condition,
|
||||
selectCode: selectedFunction,
|
||||
device: device,
|
||||
operationName: functionData.operationName,
|
||||
selectedValue: functionData.value,
|
||||
),
|
||||
_ValueDisplay(
|
||||
value: initialValue,
|
||||
functionCode: selectedFunction,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_FunctionSlider(
|
||||
initialValue: initialValue,
|
||||
functionCode: selectedFunction,
|
||||
functionData: functionData,
|
||||
device: device,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ConditionToggle extends StatelessWidget {
|
||||
final String? currentCondition;
|
||||
final String selectCode;
|
||||
final AllDevicesModel? device;
|
||||
final String operationName;
|
||||
final dynamic selectedValue;
|
||||
|
||||
const _ConditionToggle({
|
||||
this.currentCondition,
|
||||
required this.selectCode,
|
||||
this.device,
|
||||
required this.operationName,
|
||||
this.selectedValue,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const conditions = ["<", "==", ">"];
|
||||
return ToggleButtons(
|
||||
onPressed: (index) => _updateCondition(context, conditions[index]),
|
||||
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(),
|
||||
);
|
||||
}
|
||||
|
||||
void _updateCondition(BuildContext context, String condition) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectCode,
|
||||
operationName: operationName,
|
||||
condition: condition,
|
||||
value: selectedValue,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ValueDisplay extends StatelessWidget {
|
||||
final dynamic value;
|
||||
final String functionCode;
|
||||
|
||||
const _ValueDisplay({
|
||||
required this.value,
|
||||
required this.functionCode,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
_getDisplayText(),
|
||||
style: context.textTheme.headlineMedium!.copyWith(
|
||||
color: ColorsManager.primaryColorWithOpacity,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getDisplayText() {
|
||||
final intValue = (value as num?)?.toInt() ?? 0;
|
||||
switch (functionCode) {
|
||||
case 'presence_time':
|
||||
return '$intValue Min';
|
||||
case 'dis_current':
|
||||
return '$intValue CM';
|
||||
case 'illuminance_value':
|
||||
return '$intValue Lux';
|
||||
default:
|
||||
return '$intValue';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _FunctionSlider extends StatelessWidget {
|
||||
final dynamic initialValue;
|
||||
final String functionCode;
|
||||
final DeviceFunctionData functionData;
|
||||
final AllDevicesModel? device;
|
||||
|
||||
const _FunctionSlider({
|
||||
required this.initialValue,
|
||||
required this.functionCode,
|
||||
required this.functionData,
|
||||
required this.device,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final (min, max) = _getSliderRange();
|
||||
return Slider(
|
||||
value: initialValue is int ? initialValue.toDouble() : min,
|
||||
min: min,
|
||||
max: max,
|
||||
divisions: (max - min).toInt(),
|
||||
onChanged: (value) => _updateValue(context, value.toInt()),
|
||||
);
|
||||
}
|
||||
|
||||
(double, double) _getSliderRange() {
|
||||
switch (functionCode) {
|
||||
case 'presence_time':
|
||||
return (0, 65535);
|
||||
case 'dis_current':
|
||||
return (1, 600);
|
||||
case 'illuminance_value':
|
||||
return (0, 10000);
|
||||
default:
|
||||
return (200, 300);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateValue(BuildContext context, int value) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: functionCode,
|
||||
operationName: functionData.operationName,
|
||||
value: value,
|
||||
condition: functionData.condition,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _OperationalValuesList extends StatelessWidget {
|
||||
final List<WpsOperationalValue> values;
|
||||
final dynamic selectedValue;
|
||||
final AllDevicesModel? device;
|
||||
final String operationName;
|
||||
final String selectCode;
|
||||
|
||||
const _OperationalValuesList({
|
||||
required this.values,
|
||||
required this.selectedValue,
|
||||
required this.device,
|
||||
required this.operationName,
|
||||
required this.selectCode,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return operationName == 'Nobody Time'
|
||||
? _buildTimeWheel(context)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.all(20),
|
||||
itemCount: values.length,
|
||||
itemBuilder: (context, index) =>
|
||||
_buildValueItem(context, values[index]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTimeWheel(BuildContext context) {
|
||||
final currentTotalSeconds = selectedValue as int? ?? 0;
|
||||
return TimeWheelPicker(
|
||||
initialHours: currentTotalSeconds ~/ 3600,
|
||||
initialMinutes: (currentTotalSeconds % 3600) ~/ 60,
|
||||
initialSeconds: currentTotalSeconds % 60,
|
||||
onTimeChanged: (h, m, s) => _updateTotalSeconds(context, h, m, s),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildValueItem(BuildContext context, WpsOperationalValue value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildValueIcon(context, value),
|
||||
Expanded(child: _buildValueDescription(value)),
|
||||
_buildValueRadio(context, value),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildValueIcon(context, WpsOperationalValue value) {
|
||||
return Column(
|
||||
children: [
|
||||
if (_shouldShowTextDescription)
|
||||
Text(value.description.replaceAll("cm", '')),
|
||||
SvgPicture.asset(value.icon, width: 25, height: 25),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool get _shouldShowTextDescription =>
|
||||
operationName == 'Far Detection' ||
|
||||
operationName == 'Motionless Detection Sensitivity';
|
||||
|
||||
Widget _buildValueDescription(WpsOperationalValue value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text(value.description),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildValueRadio(context, WpsOperationalValue value) {
|
||||
return Radio<dynamic>(
|
||||
value: value.value,
|
||||
groupValue: selectedValue,
|
||||
onChanged: (_) => _selectValue(context, value.value),
|
||||
);
|
||||
}
|
||||
|
||||
void _selectValue(BuildContext context, dynamic value) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectCode,
|
||||
operationName: operationName,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _updateTotalSeconds(BuildContext context, int h, int m, int s) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectCode,
|
||||
operationName: operationName,
|
||||
value: h * 3600 + m * 60 + s,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,114 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
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/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart';
|
||||
|
||||
class WpsOperationalValuesList extends StatelessWidget {
|
||||
final List<WpsOperationalValue> values;
|
||||
final dynamic selectedValue;
|
||||
final AllDevicesModel? device;
|
||||
final String operationName;
|
||||
final String selectCode;
|
||||
|
||||
const WpsOperationalValuesList({
|
||||
required this.values,
|
||||
required this.selectedValue,
|
||||
required this.device,
|
||||
required this.operationName,
|
||||
required this.selectCode,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return operationName == 'Nobody Time'
|
||||
? _buildTimeWheel(context)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.all(20),
|
||||
itemCount: values.length,
|
||||
itemBuilder: (context, index) => _buildValueItem(context, values[index]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTimeWheel(BuildContext context) {
|
||||
final currentTotalSeconds = selectedValue as int? ?? 0;
|
||||
return TimeWheelPicker(
|
||||
initialHours: currentTotalSeconds ~/ 3600,
|
||||
initialMinutes: (currentTotalSeconds % 3600) ~/ 60,
|
||||
initialSeconds: currentTotalSeconds % 60,
|
||||
onTimeChanged: (h, m, s) => _updateTotalSeconds(context, h, m, s),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildValueItem(BuildContext context, WpsOperationalValue value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildValueIcon(context, value),
|
||||
Expanded(child: _buildValueDescription(value)),
|
||||
_buildValueRadio(context, value),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildValueIcon(context, WpsOperationalValue value) {
|
||||
return Column(
|
||||
children: [
|
||||
if (_shouldShowTextDescription) Text(value.description.replaceAll("cm", '')),
|
||||
SvgPicture.asset(value.icon, width: 25, height: 25),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool get _shouldShowTextDescription =>
|
||||
operationName == 'Far Detection' ||
|
||||
operationName == 'Motionless Detection Sensitivity';
|
||||
|
||||
Widget _buildValueDescription(WpsOperationalValue value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text(value.description),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildValueRadio(context, WpsOperationalValue value) {
|
||||
return Radio<dynamic>(
|
||||
value: value.value,
|
||||
groupValue: selectedValue,
|
||||
onChanged: (_) => _selectValue(context, value.value),
|
||||
);
|
||||
}
|
||||
|
||||
void _selectValue(BuildContext context, dynamic value) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectCode,
|
||||
operationName: operationName,
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _updateTotalSeconds(BuildContext context, int h, int m, int s) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectCode,
|
||||
operationName: operationName,
|
||||
value: h * 3600 + m * 60 + s,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
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/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.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;
|
||||
final DeviceFunctionData functionData;
|
||||
final List<WpsFunctions> wpsFunctions;
|
||||
final AllDevicesModel? device;
|
||||
final String dialogType;
|
||||
final bool removeComparators;
|
||||
|
||||
const WpsValueSelectorWidget({
|
||||
required this.selectedFunction,
|
||||
required this.functionData,
|
||||
required this.wpsFunctions,
|
||||
required this.device,
|
||||
required this.dialogType,
|
||||
required this.removeComparators,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedFn = wpsFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||
final values = selectedFn.getOperationalValues();
|
||||
|
||||
if (_isSliderFunction(selectedFunction)) {
|
||||
return SliderValueSelector(
|
||||
currentCondition: functionData.condition,
|
||||
dialogType: dialogType,
|
||||
sliderRange: sliderRange,
|
||||
displayedValue: getDisplayText,
|
||||
initialValue: functionData.value ?? 250,
|
||||
onConditionChanged: (condition) => context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectedFunction,
|
||||
operationName: functionData.operationName,
|
||||
condition: condition,
|
||||
value: functionData.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
onSliderChanged: (value) => context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
functionData: DeviceFunctionData(
|
||||
entityId: device?.uuid ?? '',
|
||||
functionCode: selectedFunction,
|
||||
operationName: functionData.operationName,
|
||||
value: value.toInt(),
|
||||
condition: functionData.condition,
|
||||
),
|
||||
),
|
||||
),
|
||||
unit: _unit,
|
||||
dividendOfRange: 1,
|
||||
);
|
||||
}
|
||||
|
||||
return WpsOperationalValuesList(
|
||||
values: values,
|
||||
selectedValue: functionData.value,
|
||||
device: device,
|
||||
operationName: selectedFn.operationName,
|
||||
selectCode: selectedFunction,
|
||||
);
|
||||
}
|
||||
|
||||
bool _isSliderFunction(String function) =>
|
||||
['dis_current', 'presence_time', 'illuminance_value'].contains(function);
|
||||
|
||||
(double, double) get sliderRange => switch (functionData.functionCode) {
|
||||
'presence_time' => (0, 65535),
|
||||
'dis_current' => (0, 600),
|
||||
'illuminance_value' => (0, 10000),
|
||||
_ => (200, 300),
|
||||
};
|
||||
|
||||
String get getDisplayText {
|
||||
final intValue = int.tryParse('${functionData.value ?? ''}');
|
||||
return switch (functionData.functionCode) {
|
||||
'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',
|
||||
_ => '',
|
||||
};
|
||||
}
|
81
lib/pages/routines/widgets/slider_value_selector.dart
Normal file
81
lib/pages/routines/widgets/slider_value_selector.dart
Normal file
@ -0,0 +1,81 @@
|
||||
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/function_slider.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/value_display.dart';
|
||||
|
||||
class SliderValueSelector extends StatelessWidget {
|
||||
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;
|
||||
final double dividendOfRange;
|
||||
|
||||
const SliderValueSelector({
|
||||
required this.dialogType,
|
||||
required this.sliderRange,
|
||||
required this.displayedValue,
|
||||
required this.initialValue,
|
||||
required this.onConditionChanged,
|
||||
required this.onSliderChanged,
|
||||
required this.currentCondition,
|
||||
required this.unit,
|
||||
required this.dividendOfRange,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
spacing: 16,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (dialogType == 'IF')
|
||||
ConditionToggle(
|
||||
currentCondition: currentCondition,
|
||||
onChanged: onConditionChanged,
|
||||
),
|
||||
ValueDisplay(
|
||||
value: initialValue,
|
||||
label: displayedValue,
|
||||
unit: unit,
|
||||
),
|
||||
FunctionSlider(
|
||||
initialValue: initialValue,
|
||||
range: sliderRange,
|
||||
onChanged: onSliderChanged,
|
||||
dividendOfRange: dividendOfRange,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -3,10 +3,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/automation_dialog.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/delay_dialog.dart';
|
||||
import 'package:syncrow_web/pages/routines/helper/dialog_helper/device_dialog_helper.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/automation_dialog.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/delay_dialog.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
@ -27,8 +27,7 @@ class ThenContainer extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('THEN',
|
||||
style: TextStyle(
|
||||
fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 16),
|
||||
state.isLoading && state.isUpdate == true
|
||||
? const Center(
|
||||
@ -41,12 +40,11 @@ class ThenContainer extends StatelessWidget {
|
||||
state.thenItems.length,
|
||||
(index) => GestureDetector(
|
||||
onTap: () async {
|
||||
if (state.thenItems[index]
|
||||
['deviceId'] ==
|
||||
if (state.thenItems[index]['deviceId'] ==
|
||||
'delay') {
|
||||
final result = await DelayHelper
|
||||
.showDelayPickerDialog(context,
|
||||
state.thenItems[index]);
|
||||
.showDelayPickerDialog(
|
||||
context, state.thenItems[index]);
|
||||
|
||||
if (result != null) {
|
||||
context
|
||||
@ -66,17 +64,14 @@ class ThenContainer extends StatelessWidget {
|
||||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
AutomationDialog(
|
||||
automationName:
|
||||
state.thenItems[index]
|
||||
['name'] ??
|
||||
'Automation',
|
||||
automationId:
|
||||
state.thenItems[index]
|
||||
['deviceId'] ??
|
||||
'',
|
||||
uniqueCustomId:
|
||||
state.thenItems[index]
|
||||
['uniqueCustomId'],
|
||||
automationName: state.thenItems[index]
|
||||
['name'] ??
|
||||
'Automation',
|
||||
automationId: state.thenItems[index]
|
||||
['deviceId'] ??
|
||||
'',
|
||||
uniqueCustomId: state.thenItems[index]
|
||||
['uniqueCustomId'],
|
||||
),
|
||||
);
|
||||
|
||||
@ -85,13 +80,11 @@ class ThenContainer extends StatelessWidget {
|
||||
.read<RoutineBloc>()
|
||||
.add(AddToThenContainer({
|
||||
...state.thenItems[index],
|
||||
'imagePath':
|
||||
Assets.automation,
|
||||
'title':
|
||||
'imagePath': Assets.automation,
|
||||
'title': state.thenItems[index]
|
||||
['name'] ??
|
||||
state.thenItems[index]
|
||||
['name'] ??
|
||||
state.thenItems[index]
|
||||
['title'],
|
||||
['title'],
|
||||
}));
|
||||
}
|
||||
return;
|
||||
@ -114,9 +107,10 @@ class ThenContainer extends StatelessWidget {
|
||||
'2G',
|
||||
'3G',
|
||||
'WPS',
|
||||
'CPS',
|
||||
"GW",
|
||||
].contains(state.thenItems[index]
|
||||
['productType'])) {
|
||||
].contains(
|
||||
state.thenItems[index]['productType'])) {
|
||||
context.read<RoutineBloc>().add(
|
||||
AddToThenContainer(
|
||||
state.thenItems[index]));
|
||||
@ -126,9 +120,7 @@ class ThenContainer extends StatelessWidget {
|
||||
imagePath: state.thenItems[index]
|
||||
['imagePath'] ??
|
||||
'',
|
||||
title: state.thenItems[index]
|
||||
['title'] ??
|
||||
'',
|
||||
title: state.thenItems[index]['title'] ?? '',
|
||||
deviceData: state.thenItems[index],
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 8),
|
||||
@ -165,8 +157,8 @@ class ThenContainer extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (mutableData['type'] == 'automation') {
|
||||
int index = state.thenItems.indexWhere(
|
||||
(item) => item['deviceId'] == mutableData['deviceId']);
|
||||
int index = state.thenItems
|
||||
.indexWhere((item) => item['deviceId'] == mutableData['deviceId']);
|
||||
if (index != -1) {
|
||||
return;
|
||||
}
|
||||
@ -191,8 +183,8 @@ class ThenContainer extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (mutableData['type'] == 'tap_to_run' && state.isAutomation) {
|
||||
int index = state.thenItems.indexWhere(
|
||||
(item) => item['deviceId'] == mutableData['deviceId']);
|
||||
int index = state.thenItems
|
||||
.indexWhere((item) => item['deviceId'] == mutableData['deviceId']);
|
||||
if (index != -1) {
|
||||
return;
|
||||
}
|
||||
@ -230,7 +222,7 @@ class ThenContainer extends StatelessWidget {
|
||||
dialogType: "THEN");
|
||||
if (result != null) {
|
||||
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
|
||||
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW']
|
||||
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS']
|
||||
.contains(mutableData['productType'])) {
|
||||
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
|
||||
}
|
||||
|
33
lib/pages/routines/widgets/value_display.dart
Normal file
33
lib/pages/routines/widgets/value_display.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
'$label $unit ',
|
||||
style: context.textTheme.headlineMedium!.copyWith(
|
||||
color: ColorsManager.primaryColorWithOpacity,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user