From 7accf1d4c85770cb152eaaca114c4566330c900c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 13:07:53 +0300 Subject: [PATCH 01/18] SP-1366- Add Gateway Device Card to Devices Section. --- lib/pages/routines/widgets/routine_devices.dart | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/pages/routines/widgets/routine_devices.dart b/lib/pages/routines/widgets/routine_devices.dart index 2d3f7236..ba5756e0 100644 --- a/lib/pages/routines/widgets/routine_devices.dart +++ b/lib/pages/routines/widgets/routine_devices.dart @@ -1,6 +1,5 @@ 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/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart'; @@ -32,14 +31,10 @@ class _RoutineDevicesState extends State { } }); - List deviceList = state.devices - .where((device) => - device.productType == 'AC' || - device.productType == '1G' || - device.productType == '2G' || - device.productType == '3G' || - device.productType == 'WPS') - .toList(); + final deviceList = state.devices.where((device) { + const allowedProductTypes = {'AC', '1G', '2G', '3G', 'WPS', 'GW'}; + return allowedProductTypes.contains(device.productType); + }).toList(); return Wrap( spacing: 10, @@ -63,7 +58,7 @@ class _RoutineDevicesState extends State { 'uniqueCustomId': '', }, ) - : Container(); + : const SizedBox.shrink(); } else { return DraggableCard( imagePath: device.getDefaultIcon(device.productType), From 46f318734ad7f137ff5a5e67efb5839184f510f3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 15:06:11 +0300 Subject: [PATCH 02/18] Add SVG icons for active bell and gear --- assets/icons/active_bell.svg | 21 +++++++++++++++++++++ assets/icons/gear.svg | 3 +++ lib/utils/constants/assets.dart | 3 +++ 3 files changed, 27 insertions(+) create mode 100644 assets/icons/active_bell.svg create mode 100644 assets/icons/gear.svg diff --git a/assets/icons/active_bell.svg b/assets/icons/active_bell.svg new file mode 100644 index 00000000..3887ead5 --- /dev/null +++ b/assets/icons/active_bell.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/gear.svg b/assets/icons/gear.svg new file mode 100644 index 00000000..02dbab04 --- /dev/null +++ b/assets/icons/gear.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index e81512ff..d9788ee8 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -426,5 +426,8 @@ class Assets { static const String motionDetectionSensitivityValueIcon = 'assets/icons/motion_detection_sensitivity_value_icon.svg'; static const String presenceTimeIcon = 'assets/icons/presence_time_icon.svg'; static const String IlluminanceIcon = 'assets/icons/Illuminance_icon.svg'; + static const String gear = 'assets/icons/gear.svg'; + static const String activeBell='assets/icons/active_bell.svg'; + } From 9d4395e204b3a1b43d0f46676ae85c44ef78fb39 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 15:08:01 +0300 Subject: [PATCH 03/18] Added gateway product type to `IfContainer`. --- lib/pages/routines/widgets/if_container.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pages/routines/widgets/if_container.dart b/lib/pages/routines/widgets/if_container.dart index eebf3fb7..b65e99c7 100644 --- a/lib/pages/routines/widgets/if_container.dart +++ b/lib/pages/routines/widgets/if_container.dart @@ -72,6 +72,7 @@ class IfContainer extends StatelessWidget { '2G', '3G', 'WPS' + 'GW', ].contains( state.ifItems[index]['productType'])) { context.read().add( @@ -129,7 +130,7 @@ class IfContainer extends StatelessWidget { context .read() .add(AddToIfContainer(mutableData, false)); - } else if (!['AC', '1G', '2G', '3G', 'WPS'] + } else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW'] .contains(mutableData['productType'])) { context .read() From 8a244dcd239ac31763b8265b54812227341ff79f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 15:08:15 +0300 Subject: [PATCH 04/18] Created `GatewayModel`. --- .../gateway/model/gateway_model.dart | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 lib/pages/device_managment/gateway/model/gateway_model.dart diff --git a/lib/pages/device_managment/gateway/model/gateway_model.dart b/lib/pages/device_managment/gateway/model/gateway_model.dart new file mode 100644 index 00000000..8e8d00f9 --- /dev/null +++ b/lib/pages/device_managment/gateway/model/gateway_model.dart @@ -0,0 +1,40 @@ +class GatewayModel { + final String uuid; + final bool switchAlarmSound; + final String masterState; + final bool factoryReset; + final String alarmActive; + + GatewayModel({ + required this.uuid, + required this.switchAlarmSound, + required this.masterState, + required this.factoryReset, + required this.alarmActive, + }); + + factory GatewayModel.fromJson(Map json) { + final status = json['status'] as List; + + final switchAlarmSound = status.firstWhere( + (item) => item['code'] == 'switch_alarm_sound', + )['value'] as bool; + final masterState = status.firstWhere( + (item) => item['code'] == 'master_state', + )['value'] as String; + final factoryReset = status.firstWhere( + (item) => item['code'] == 'factory_reset', + )['value'] as bool; + final alarmActive = status.firstWhere( + (item) => item['code'] == 'alarm_active', + )['value'] as String; + + return GatewayModel( + uuid: json['uuid'] as String, + switchAlarmSound: switchAlarmSound, + masterState: masterState, + factoryReset: factoryReset, + alarmActive: alarmActive, + ); + } +} From b0846b2fefc8ed6546e3b45864446fcaa4d3aca1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 15:08:33 +0300 Subject: [PATCH 05/18] Add Gateway operational value classes and implementations --- lib/pages/routines/models/gateway.dart | 108 +++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 lib/pages/routines/models/gateway.dart diff --git a/lib/pages/routines/models/gateway.dart b/lib/pages/routines/models/gateway.dart new file mode 100644 index 00000000..be257a8b --- /dev/null +++ b/lib/pages/routines/models/gateway.dart @@ -0,0 +1,108 @@ +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class GatewayOperationalValue { + final String icon; + final String description; + final dynamic value; + + GatewayOperationalValue({ + required this.icon, + required this.description, + required this.value, + }); +} + +abstract class GatewayFunctions extends DeviceFunction { + final String type; + + GatewayFunctions({ + required super.deviceId, + required super.deviceName, + required super.code, + required super.operationName, + required super.icon, + required this.type, + }); + + List getOperationalValues(); +} + +final class GatewaySwitchAlarmSound extends GatewayFunctions { + GatewaySwitchAlarmSound({ + required super.deviceId, + required super.deviceName, + required super.type, + super.code = '', + super.operationName = 'Switch Alarm Sound', + super.icon = Assets.activeBell, + }); + + @override + List getOperationalValues() => [ + GatewayOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + GatewayOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +final class GatewayMasterState extends GatewayFunctions { + GatewayMasterState({ + required super.deviceId, + required super.deviceName, + required super.type, + super.code = '', + super.operationName = 'Master State', + super.icon = Assets.gear, + }); + + @override + List getOperationalValues() { + return [ + GatewayOperationalValue( + icon: Assets.assetsAcPower, + description: "Normal", + value: true, + ), + GatewayOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "Alarm", + value: false, + ), + ]; + } +} + +final class GatewayFactoryReset extends GatewayFunctions { + GatewayFactoryReset({ + required super.deviceId, + required super.deviceName, + required super.type, + super.code = '', + super.operationName = 'Factory Reset', + super.icon = Assets.factoryReset, + }); + + @override + List getOperationalValues() { + return [ + GatewayOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + GatewayOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; + } +} From b6752683fdc29c531921748be319b6d1d03b1574 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 15:08:53 +0300 Subject: [PATCH 06/18] Add Gateway dialog and functions integration --- .../all_devices/models/devices_model.dart | 19 +++ .../dialog_helper/device_dialog_helper.dart | 25 ++- .../gateway/gateway_dialog.dart | 29 ++++ .../gateway/gateway_if_dialog.dart | 144 ++++++++++++++++++ 4 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index 2663d931..d6999a98 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.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/three_gang_switch/three_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart'; +import 'package:syncrow_web/pages/routines/models/gateway.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/enum/device_types.dart'; @@ -314,6 +315,24 @@ SOS // FarDetectionSliderFunction( // deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN') ]; + case 'GW': + return [ + GatewaySwitchAlarmSound( + deviceId: uuid ?? '', + deviceName: name ?? '', + type: 'IF', + ), + GatewayMasterState( + deviceId: uuid ?? '', + deviceName: name ?? '', + type: 'IF', + ), + GatewayFactoryReset( + deviceId: uuid ?? '', + deviceName: name ?? '', + type: 'IF', + ), + ]; default: return []; } diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index 48eaedb9..a94b6740 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -1,11 +1,12 @@ 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/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ac_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart'; -import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart'; class DeviceDialogHelper { @@ -57,12 +58,13 @@ class DeviceDialogHelper { switch (productType) { case 'AC': return ACHelper.showACFunctionsDialog( - context, - functions, - data['device'], - deviceSelectedFunctions, - data['uniqueCustomId'], - removeComparetors); + context, + functions, + data['device'], + deviceSelectedFunctions, + data['uniqueCustomId'], + removeComparetors, + ); case '1G': return OneGangSwitchHelper.showSwitchFunctionsDialog( @@ -97,6 +99,15 @@ class DeviceDialogHelper { deviceSelectedFunctions: deviceSelectedFunctions, uniqueCustomId: data['uniqueCustomId'], removeComparetors: removeComparetors); + case 'GW': + return GatewayHelper.showGatewayFunctionsDialog( + dialogType: dialogType, + context: context, + functions: functions, + uniqueCustomId: data['uniqueCustomId'], + deviceSelectedFunctions: deviceSelectedFunctions, + ); + default: return null; } diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart new file mode 100644 index 00000000..fe7379fc --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart @@ -0,0 +1,29 @@ +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/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart'; + +final class GatewayHelper { + static Future?> showGatewayFunctionsDialog({ + required String dialogType, + required BuildContext context, + required List functions, + required String? uniqueCustomId, + required List deviceSelectedFunctions, + }) async { + return showDialog( + context: context, + builder: (context) => BlocProvider( + create: (context) => FunctionBloc() + ..add( + InitializeFunctions(deviceSelectedFunctions), + ), + child: GatewayIfDialog( + uniqueCustomId: uniqueCustomId, + functions: functions, + deviceSelectedFunctions: deviceSelectedFunctions), + ), + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart new file mode 100644 index 00000000..ecaf5ba9 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.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/gateway.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/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class GatewayIfDialog extends StatefulWidget { + const GatewayIfDialog({ + required this.uniqueCustomId, + required this.functions, + required this.deviceSelectedFunctions, + super.key, + }); + + final String? uniqueCustomId; + final List functions; + final List deviceSelectedFunctions; + + @override + State createState() => _GatewayIfDialogState(); +} + +class _GatewayIfDialogState extends State { + late final List _gatewayFunctions; + + @override + void initState() { + super.initState(); + _gatewayFunctions = widget.functions + .whereType() + .where((function) => function.type == 'IF' || function.type == 'BOTH') + .toList(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: BlocBuilder( + 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: [ + const DialogHeader('Gateway Conditions'), + Expanded(child: _buildMainContent(context, state)), + _buildDialogFooter(context, state), + ], + ), + ); + }, + ), + ); + } + + Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) { + return DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: state.addedFunctions.isNotEmpty + ? () { + context.read().add( + AddFunctionToRoutine( + state.addedFunctions, + widget.uniqueCustomId ?? '-1', + ), + ); + Navigator.pop( + context, + {'deviceId': widget.functions.firstOrNull?.deviceId}, + ); + } + : null, + isConfirmEnabled: state.selectedFunction != null, + ); + } + + Widget _buildMainContent(BuildContext context, FunctionBlocState state) { + return Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildFunctionList(context), + // if (state.selectedFunction != null) _buildValueSelector(context, state), + ], + ); + } + + Widget _buildFunctionList(BuildContext context) { + return SizedBox( + width: 360, + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: _gatewayFunctions.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), + child: Divider(color: ColorsManager.dividerColor), + ), + itemBuilder: (context, index) { + final function = _gatewayFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + width: 24, + height: 24, + placeholderBuilder: (context) => const SizedBox( + width: 24, + height: 24, + ), + ), + title: Text( + function.operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: () => context.read().add( + SelectFunction( + functionCode: function.code, + operationName: function.operationName, + ), + ), + ); + }, + ), + ); + } +} From 006bd4c8aebea218f71a47b762b342c276e9edb7 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 16:20:22 +0300 Subject: [PATCH 07/18] =?UTF-8?q?SP-1364/=20Gateway=20Conditions=20Popup?= =?UTF-8?q?=20(for=20=E2=80=9CIf=E2=80=9D=20section)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../all_devices/models/devices_model.dart | 6 +- lib/pages/routines/models/gateway.dart | 10 +- .../gateway/gateway_if_dialog.dart | 111 +++++++++++++++++- 3 files changed, 113 insertions(+), 14 deletions(-) diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index d6999a98..a71de8ce 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -320,17 +320,17 @@ SOS GatewaySwitchAlarmSound( deviceId: uuid ?? '', deviceName: name ?? '', - type: 'IF', + type: 'BOTH', ), GatewayMasterState( deviceId: uuid ?? '', deviceName: name ?? '', - type: 'IF', + type: 'BOTH', ), GatewayFactoryReset( deviceId: uuid ?? '', deviceName: name ?? '', - type: 'IF', + type: 'BOTH', ), ]; default: diff --git a/lib/pages/routines/models/gateway.dart b/lib/pages/routines/models/gateway.dart index be257a8b..daeebd53 100644 --- a/lib/pages/routines/models/gateway.dart +++ b/lib/pages/routines/models/gateway.dart @@ -33,7 +33,7 @@ final class GatewaySwitchAlarmSound extends GatewayFunctions { required super.deviceId, required super.deviceName, required super.type, - super.code = '', + super.code = 'switch_alarm_sound', super.operationName = 'Switch Alarm Sound', super.icon = Assets.activeBell, }); @@ -58,7 +58,7 @@ final class GatewayMasterState extends GatewayFunctions { required super.deviceId, required super.deviceName, required super.type, - super.code = '', + super.code = 'master_state', super.operationName = 'Master State', super.icon = Assets.gear, }); @@ -69,12 +69,12 @@ final class GatewayMasterState extends GatewayFunctions { GatewayOperationalValue( icon: Assets.assetsAcPower, description: "Normal", - value: true, + value: 'Normal', ), GatewayOperationalValue( icon: Assets.assetsAcPowerOFF, description: "Alarm", - value: false, + value: 'Alarm', ), ]; } @@ -85,7 +85,7 @@ final class GatewayFactoryReset extends GatewayFunctions { required super.deviceId, required super.deviceName, required super.type, - super.code = '', + super.code = 'factory_reset', super.operationName = 'Factory Reset', super.icon = Assets.factoryReset, }); diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart index ecaf5ba9..457110b7 100644 --- a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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/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'; @@ -32,10 +33,7 @@ class _GatewayIfDialogState extends State { @override void initState() { super.initState(); - _gatewayFunctions = widget.functions - .whereType() - .where((function) => function.type == 'IF' || function.type == 'BOTH') - .toList(); + _gatewayFunctions = widget.functions.whereType().toList(); } @override @@ -89,11 +87,32 @@ class _GatewayIfDialogState extends State { } 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, + ), + ); + return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildFunctionList(context), - // if (state.selectedFunction != null) _buildValueSelector(context, state), + if (state.selectedFunction != null) + Expanded( + child: _buildValueSelector( + context: context, + selectedFunction: selectedFunction ?? '', + selectedFunctionData: selectedFunctionData, + acFunctions: _gatewayFunctions, + operationName: selectedOperationName ?? '', + ), + ), ], ); } @@ -103,7 +122,6 @@ class _GatewayIfDialogState extends State { width: 360, child: ListView.separated( shrinkWrap: false, - physics: const AlwaysScrollableScrollPhysics(), itemCount: _gatewayFunctions.length, separatorBuilder: (context, index) => const Padding( padding: EdgeInsets.symmetric(horizontal: 40.0), @@ -141,4 +159,85 @@ class _GatewayIfDialogState extends State { ), ); } + + static Widget _buildValueSelector({ + required BuildContext context, + required String selectedFunction, + required DeviceFunctionData? selectedFunctionData, + required List acFunctions, + AllDevicesModel? device, + required String operationName, + }) { + final selectedGatewayFunctions = acFunctions.firstWhere( + (f) => f.code == selectedFunction, + ); + final values = selectedGatewayFunctions.getOperationalValues(); + + return _buildOperationalValuesList( + context: context, + values: values, + selectedValue: selectedFunctionData?.value, + device: device, + operationName: operationName, + selectCode: selectedFunction, + selectedFunctionData: selectedFunctionData, + ); + } + + static Widget _buildOperationalValuesList({ + required BuildContext context, + required List values, + required dynamic selectedValue, + AllDevicesModel? device, + required String operationName, + required String selectCode, + DeviceFunctionData? selectedFunctionData, + }) { + return ListView.builder( + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + final isSelected = selectedValue == value.value; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + placeholderBuilder: (context) => Container( + width: 24, + height: 24, + color: Colors.transparent, + ), + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Icon( + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + size: 24, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, + ), + onTap: () { + if (!isSelected) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value.value, + condition: selectedFunctionData?.condition, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + } + }, + ); + }, + ); + } } From 970f7ed16f18356b5b95ededf039f0bd69d03103 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 16:41:34 +0300 Subject: [PATCH 08/18] SP-1365 --- .../helper/dialog_helper/device_dialog_helper.dart | 9 ++++----- .../widgets/routine_dialogs/gateway/gateway_dialog.dart | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index a94b6740..1fac903e 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -50,10 +50,10 @@ class DeviceDialogHelper { final deviceSelectedFunctions = routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; - if (removeComparetors && data['productType'] != 'WPS') { - //remove the current temp function in the 'if container' - functions.removeAt(3); - } + // if (removeComparetors && data['productType'] != 'WPS') { + // //remove the current temp function in the 'if container' + // functions.removeAt(3); + // } switch (productType) { case 'AC': @@ -101,7 +101,6 @@ class DeviceDialogHelper { removeComparetors: removeComparetors); case 'GW': return GatewayHelper.showGatewayFunctionsDialog( - dialogType: dialogType, context: context, functions: functions, uniqueCustomId: data['uniqueCustomId'], diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart index fe7379fc..b9638cd9 100644 --- a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart @@ -6,7 +6,6 @@ import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gatew final class GatewayHelper { static Future?> showGatewayFunctionsDialog({ - required String dialogType, required BuildContext context, required List functions, required String? uniqueCustomId, From 3a0c8edf8601e0a79c0ff90d7827d8e6bb9938fc Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 16:42:31 +0300 Subject: [PATCH 09/18] =?UTF-8?q?SP-1365/=20Gateway=20Functions=20Popup=20?= =?UTF-8?q?(for=20=E2=80=9CThen=E2=80=9D=20section).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/routines/widgets/then_container.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pages/routines/widgets/then_container.dart b/lib/pages/routines/widgets/then_container.dart index a4d0461d..e0828721 100644 --- a/lib/pages/routines/widgets/then_container.dart +++ b/lib/pages/routines/widgets/then_container.dart @@ -113,7 +113,8 @@ class ThenContainer extends StatelessWidget { '1G', '2G', '3G', - 'WPS' + 'WPS', + "GW", ].contains(state.thenItems[index] ['productType'])) { context.read().add( @@ -229,7 +230,7 @@ class ThenContainer extends StatelessWidget { dialogType: "THEN"); if (result != null) { context.read().add(AddToThenContainer(mutableData)); - } else if (!['AC', '1G', '2G', '3G', 'WPS'] + } else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW'] .contains(mutableData['productType'])) { context.read().add(AddToThenContainer(mutableData)); } From a242377ea645b19eb9d9233f5568d9ee0f8268e9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 09:46:45 +0300 Subject: [PATCH 10/18] Fully refactored `CreateRoutine/Gateway` feature. --- .../dialog_helper/device_dialog_helper.dart | 3 +- .../routine_dialog_function_list_tile.dart | 42 +++ .../routine_dialog_selection_list_tile.dart | 47 ++++ .../gateway/gateway_dialog.dart | 143 +++++++++-- .../gateway_dialog_value_selector.dart | 58 +++++ .../gateway/gateway_functions_list.dart | 43 ++++ .../gateway/gateway_helper.dart | 34 +++ .../gateway/gateway_if_dialog.dart | 243 ------------------ 8 files changed, 350 insertions(+), 263 deletions(-) create mode 100644 lib/pages/routines/widgets/routine_dialog_function_list_tile.dart create mode 100644 lib/pages/routines/widgets/routine_dialog_selection_list_tile.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog_value_selector.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/gateway/gateway_functions_list.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart delete mode 100644 lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index 1fac903e..f029677e 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -3,7 +3,7 @@ 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/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ac_dialog.dart'; -import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart'; @@ -105,6 +105,7 @@ class DeviceDialogHelper { functions: functions, uniqueCustomId: data['uniqueCustomId'], deviceSelectedFunctions: deviceSelectedFunctions, + device: data['device'], ); default: diff --git a/lib/pages/routines/widgets/routine_dialog_function_list_tile.dart b/lib/pages/routines/widgets/routine_dialog_function_list_tile.dart new file mode 100644 index 00000000..3e98a06e --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialog_function_list_tile.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class RoutineDialogFunctionListTile extends StatelessWidget { + const RoutineDialogFunctionListTile({ + super.key, + required this.iconPath, + required this.operationName, + required this.onTap, + }); + + final String iconPath; + final String operationName; + final void Function() onTap; + + @override + Widget build(BuildContext context) { + return ListTile( + leading: SvgPicture.asset( + iconPath, + width: 24, + height: 24, + placeholderBuilder: (context) => const SizedBox( + width: 24, + height: 24, + ), + ), + title: Text( + operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: onTap, + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialog_selection_list_tile.dart b/lib/pages/routines/widgets/routine_dialog_selection_list_tile.dart new file mode 100644 index 00000000..b661e591 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialog_selection_list_tile.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class RoutineDialogSelectionListTile extends StatelessWidget { + const RoutineDialogSelectionListTile({ + required this.iconPath, + required this.description, + required this.isSelected, + required this.onTap, + super.key, + }); + + final bool isSelected; + final String iconPath; + final String description; + final void Function() onTap; + + @override + Widget build(BuildContext context) { + return ListTile( + leading: SvgPicture.asset( + iconPath, + width: 24, + height: 24, + placeholderBuilder: (context) => Container( + width: 24, + height: 24, + color: Colors.transparent, + ), + ), + title: Text( + description, + style: context.textTheme.bodyMedium, + ), + trailing: Icon( + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + size: 24, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, + ), + onTap: onTap, + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart index b9638cd9..fc7189f2 100644 --- a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart @@ -1,28 +1,133 @@ 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/device_functions.dart'; -import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart'; +import 'package:syncrow_web/pages/routines/models/gateway.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/gateway/gateway_dialog_value_selector.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_functions_list.dart'; -final class GatewayHelper { - static Future?> showGatewayFunctionsDialog({ - required BuildContext context, - required List functions, - required String? uniqueCustomId, - required List deviceSelectedFunctions, - }) async { - return showDialog( - context: context, - builder: (context) => BlocProvider( - create: (context) => FunctionBloc() - ..add( - InitializeFunctions(deviceSelectedFunctions), - ), - child: GatewayIfDialog( - uniqueCustomId: uniqueCustomId, - functions: functions, - deviceSelectedFunctions: deviceSelectedFunctions), +class GatewayDialog extends StatefulWidget { + const GatewayDialog({ + required this.uniqueCustomId, + required this.functions, + required this.deviceSelectedFunctions, + required this.device, + super.key, + }); + + final String? uniqueCustomId; + final List functions; + final List deviceSelectedFunctions; + final AllDevicesModel? device; + + @override + State createState() => _GatewayDialogState(); +} + +class _GatewayDialogState extends State { + late final List _gatewayFunctions; + + @override + void initState() { + super.initState(); + _gatewayFunctions = widget.functions.whereType().toList(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: BlocBuilder( + 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: [ + const DialogHeader('Gateway Conditions'), + Expanded(child: _buildMainContent(context, state)), + _buildDialogFooter(context, state), + ], + ), + ); + }, ), ); } + + 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 selectedGatewayFunctions = _gatewayFunctions.firstWhere( + (f) => f.code == selectedFunction, + orElse: () => GatewaySwitchAlarmSound( + code: selectedFunction ?? '', + deviceId: '', + deviceName: '', + operationName: '', + icon: '', + type: '', + ), + ); + final operations = selectedGatewayFunctions.getOperationalValues(); + + return Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + GatewayFunctionsList(gatewayFunctions: _gatewayFunctions), + if (state.selectedFunction != null) + Expanded( + child: GatewayDialogValueSelector( + operations: operations, + selectedFunction: selectedFunction ?? '', + selectedFunctionData: selectedFunctionData, + gatewayFunctions: _gatewayFunctions, + operationName: selectedOperationName ?? '', + device: widget.device, + ), + ), + ], + ); + } + + Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) { + return DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: state.addedFunctions.isNotEmpty + ? () { + context.read().add( + AddFunctionToRoutine( + state.addedFunctions, + widget.uniqueCustomId ?? '-1', + ), + ); + Navigator.pop( + context, + {'deviceId': widget.functions.firstOrNull?.deviceId}, + ); + } + : null, + isConfirmEnabled: state.selectedFunction != null, + ); + } } diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog_value_selector.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog_value_selector.dart new file mode 100644 index 00000000..392c3012 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog_value_selector.dart @@ -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/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/gateway.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialog_selection_list_tile.dart'; + +class GatewayDialogValueSelector extends StatelessWidget { + const GatewayDialogValueSelector({ + required this.operations, + required this.selectedFunction, + required this.selectedFunctionData, + required this.gatewayFunctions, + required this.device, + required this.operationName, + super.key, + }); + + final List operations; + final String selectedFunction; + final DeviceFunctionData? selectedFunctionData; + final List gatewayFunctions; + 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().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: operationName, + value: operation.value, + condition: selectedFunctionData?.condition, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + } + }, + ); + }, + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_functions_list.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_functions_list.dart new file mode 100644 index 00000000..6d253dcb --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_functions_list.dart @@ -0,0 +1,43 @@ +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/gateway.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class GatewayFunctionsList extends StatelessWidget { + const GatewayFunctionsList({ + required this.gatewayFunctions, + super.key, + }); + + final List gatewayFunctions; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 360, + child: ListView.separated( + shrinkWrap: false, + itemCount: gatewayFunctions.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), + child: Divider(color: ColorsManager.dividerColor), + ), + itemBuilder: (context, index) { + final function = gatewayFunctions[index]; + return RoutineDialogFunctionListTile( + iconPath: function.icon, + operationName: function.operationName, + onTap: () => context.read().add( + SelectFunction( + functionCode: function.code, + operationName: function.operationName, + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart new file mode 100644 index 00000000..9a9351ca --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart @@ -0,0 +1,34 @@ +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/widgets/routine_dialogs/gateway/gateway_dialog.dart'; + +abstract final class GatewayHelper { + const GatewayHelper._(); + + static Future?> showGatewayFunctionsDialog({ + required BuildContext context, + required List functions, + required String? uniqueCustomId, + required List deviceSelectedFunctions, + required AllDevicesModel? device, + }) async { + return showDialog( + context: context, + builder: (context) => BlocProvider( + create: (context) => FunctionBloc() + ..add( + InitializeFunctions(deviceSelectedFunctions), + ), + child: GatewayDialog( + uniqueCustomId: uniqueCustomId, + functions: functions, + deviceSelectedFunctions: deviceSelectedFunctions, + device: device, + ), + ), + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart deleted file mode 100644 index 457110b7..00000000 --- a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart +++ /dev/null @@ -1,243 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.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/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/gateway.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/utils/color_manager.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; - -class GatewayIfDialog extends StatefulWidget { - const GatewayIfDialog({ - required this.uniqueCustomId, - required this.functions, - required this.deviceSelectedFunctions, - super.key, - }); - - final String? uniqueCustomId; - final List functions; - final List deviceSelectedFunctions; - - @override - State createState() => _GatewayIfDialogState(); -} - -class _GatewayIfDialogState extends State { - late final List _gatewayFunctions; - - @override - void initState() { - super.initState(); - _gatewayFunctions = widget.functions.whereType().toList(); - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - contentPadding: EdgeInsets.zero, - content: BlocBuilder( - 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: [ - const DialogHeader('Gateway Conditions'), - Expanded(child: _buildMainContent(context, state)), - _buildDialogFooter(context, state), - ], - ), - ); - }, - ), - ); - } - - Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) { - return DialogFooter( - onCancel: () => Navigator.pop(context), - onConfirm: state.addedFunctions.isNotEmpty - ? () { - context.read().add( - AddFunctionToRoutine( - state.addedFunctions, - widget.uniqueCustomId ?? '-1', - ), - ); - Navigator.pop( - context, - {'deviceId': widget.functions.firstOrNull?.deviceId}, - ); - } - : null, - isConfirmEnabled: state.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, - ), - ); - - return Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildFunctionList(context), - if (state.selectedFunction != null) - Expanded( - child: _buildValueSelector( - context: context, - selectedFunction: selectedFunction ?? '', - selectedFunctionData: selectedFunctionData, - acFunctions: _gatewayFunctions, - operationName: selectedOperationName ?? '', - ), - ), - ], - ); - } - - Widget _buildFunctionList(BuildContext context) { - return SizedBox( - width: 360, - child: ListView.separated( - shrinkWrap: false, - itemCount: _gatewayFunctions.length, - separatorBuilder: (context, index) => const Padding( - padding: EdgeInsets.symmetric(horizontal: 40.0), - child: Divider(color: ColorsManager.dividerColor), - ), - itemBuilder: (context, index) { - final function = _gatewayFunctions[index]; - return ListTile( - leading: SvgPicture.asset( - function.icon, - width: 24, - height: 24, - placeholderBuilder: (context) => const SizedBox( - width: 24, - height: 24, - ), - ), - title: Text( - function.operationName, - style: context.textTheme.bodyMedium, - ), - trailing: const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.textGray, - ), - onTap: () => context.read().add( - SelectFunction( - functionCode: function.code, - operationName: function.operationName, - ), - ), - ); - }, - ), - ); - } - - static Widget _buildValueSelector({ - required BuildContext context, - required String selectedFunction, - required DeviceFunctionData? selectedFunctionData, - required List acFunctions, - AllDevicesModel? device, - required String operationName, - }) { - final selectedGatewayFunctions = acFunctions.firstWhere( - (f) => f.code == selectedFunction, - ); - final values = selectedGatewayFunctions.getOperationalValues(); - - return _buildOperationalValuesList( - context: context, - values: values, - selectedValue: selectedFunctionData?.value, - device: device, - operationName: operationName, - selectCode: selectedFunction, - selectedFunctionData: selectedFunctionData, - ); - } - - static Widget _buildOperationalValuesList({ - required BuildContext context, - required List values, - required dynamic selectedValue, - AllDevicesModel? device, - required String operationName, - required String selectCode, - DeviceFunctionData? selectedFunctionData, - }) { - return ListView.builder( - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; - final isSelected = selectedValue == value.value; - return ListTile( - leading: SvgPicture.asset( - value.icon, - width: 24, - height: 24, - placeholderBuilder: (context) => Container( - width: 24, - height: 24, - color: Colors.transparent, - ), - ), - title: Text( - value.description, - style: context.textTheme.bodyMedium, - ), - trailing: Icon( - isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, - size: 24, - color: isSelected - ? ColorsManager.primaryColorWithOpacity - : ColorsManager.textGray, - ), - onTap: () { - if (!isSelected) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - value: value.value, - condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - } - }, - ); - }, - ); - } -} From 9b69ec31e9200df34cc3ce4aadad23219507894a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 09:57:18 +0300 Subject: [PATCH 11/18] Refactor RoutineDevices to use a class-level constant for allowed product types. --- lib/pages/routines/widgets/routine_devices.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/pages/routines/widgets/routine_devices.dart b/lib/pages/routines/widgets/routine_devices.dart index ba5756e0..f6ff0db9 100644 --- a/lib/pages/routines/widgets/routine_devices.dart +++ b/lib/pages/routines/widgets/routine_devices.dart @@ -17,6 +17,8 @@ class _RoutineDevicesState extends State { context.read().add(FetchDevicesInRoutine()); } + static const _allowedProductTypes = {'AC', '1G', '2G', '3G', 'WPS', 'GW'}; + @override Widget build(BuildContext context) { return BlocBuilder( @@ -31,10 +33,9 @@ class _RoutineDevicesState extends State { } }); - final deviceList = state.devices.where((device) { - const allowedProductTypes = {'AC', '1G', '2G', '3G', 'WPS', 'GW'}; - return allowedProductTypes.contains(device.productType); - }).toList(); + final deviceList = state.devices + .where((device) => _allowedProductTypes.contains(device.productType)) + .toList(); return Wrap( spacing: 10, From 774f21a55b1801d88f84efda14150bad2b2985fe Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 09:59:15 +0300 Subject: [PATCH 12/18] Refactor RoutineDevices to consolidate device data preparation in a single map to remove code duplication. --- .../routines/widgets/routine_devices.dart | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/lib/pages/routines/widgets/routine_devices.dart b/lib/pages/routines/widgets/routine_devices.dart index f6ff0db9..84fd44c0 100644 --- a/lib/pages/routines/widgets/routine_devices.dart +++ b/lib/pages/routines/widgets/routine_devices.dart @@ -42,37 +42,32 @@ class _RoutineDevicesState extends State { runSpacing: 10, children: deviceList.asMap().entries.map((entry) { final device = entry.value; + + final deviceData = { + 'device': device, + 'imagePath': device.getDefaultIcon(device.productType), + 'title': device.name ?? '', + 'deviceId': device.uuid, + 'productType': device.productType, + 'functions': device.functions, + 'uniqueCustomId': '', + }; + if (state.searchText != null && state.searchText!.isNotEmpty) { return device.name! .toLowerCase() .contains(state.searchText!.toLowerCase()) ? DraggableCard( - imagePath: device.getDefaultIcon(device.productType), - title: device.name ?? '', - deviceData: { - 'device': device, - 'imagePath': device.getDefaultIcon(device.productType), - 'title': device.name ?? '', - 'deviceId': device.uuid, - 'productType': device.productType, - 'functions': device.functions, - 'uniqueCustomId': '', - }, + imagePath: deviceData['imagePath'] as String, + title: deviceData['title'] as String, + deviceData: deviceData, ) : const SizedBox.shrink(); } else { return DraggableCard( - imagePath: device.getDefaultIcon(device.productType), - title: device.name ?? '', - deviceData: { - 'device': device, - 'imagePath': device.getDefaultIcon(device.productType), - 'title': device.name ?? '', - 'deviceId': device.uuid, - 'productType': device.productType, - 'functions': device.functions, - 'uniqueCustomId': '', - }, + imagePath: deviceData['imagePath'] as String, + title: deviceData['title'] as String, + deviceData: deviceData, ); } }).toList(), From 92aa574944021ce019a0293bece855c0da5da5cb Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 10:21:49 +0300 Subject: [PATCH 13/18] update `GatewayMasterState.getOperationalValues` to match what the api expects. --- lib/pages/routines/models/gateway.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/routines/models/gateway.dart b/lib/pages/routines/models/gateway.dart index daeebd53..dcb29148 100644 --- a/lib/pages/routines/models/gateway.dart +++ b/lib/pages/routines/models/gateway.dart @@ -69,12 +69,12 @@ final class GatewayMasterState extends GatewayFunctions { GatewayOperationalValue( icon: Assets.assetsAcPower, description: "Normal", - value: 'Normal', + value: 'normal', ), GatewayOperationalValue( icon: Assets.assetsAcPowerOFF, description: "Alarm", - value: 'Alarm', + value: 'alarm', ), ]; } From fda96025e9a88cf32f285aabede9bbb449010b6f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 10:41:38 +0300 Subject: [PATCH 14/18] uncommented code. --- .../helper/dialog_helper/device_dialog_helper.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index f029677e..d1146df9 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -50,10 +50,10 @@ class DeviceDialogHelper { final deviceSelectedFunctions = routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; - // if (removeComparetors && data['productType'] != 'WPS') { - // //remove the current temp function in the 'if container' - // functions.removeAt(3); - // } + if (removeComparetors && data['productType'] != 'WPS') { + //remove the current temp function in the 'if container' + functions.removeAt(3); + } switch (productType) { case 'AC': From 9c97e2879aa8facf645ba931bd33014ada56af5c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 10:57:10 +0300 Subject: [PATCH 15/18] Refactor constructor syntax for Gateway functions to use initializer list --- lib/pages/routines/models/gateway.dart | 27 ++++++++++--------- .../gateway/gateway_dialog.dart | 3 --- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/pages/routines/models/gateway.dart b/lib/pages/routines/models/gateway.dart index dcb29148..b1a70d2e 100644 --- a/lib/pages/routines/models/gateway.dart +++ b/lib/pages/routines/models/gateway.dart @@ -33,10 +33,11 @@ final class GatewaySwitchAlarmSound extends GatewayFunctions { required super.deviceId, required super.deviceName, required super.type, - super.code = 'switch_alarm_sound', - super.operationName = 'Switch Alarm Sound', - super.icon = Assets.activeBell, - }); + }) : super( + code: 'switch_alarm_sound', + operationName: 'Switch Alarm Sound', + icon: Assets.activeBell, + ); @override List getOperationalValues() => [ @@ -58,10 +59,11 @@ final class GatewayMasterState extends GatewayFunctions { required super.deviceId, required super.deviceName, required super.type, - super.code = 'master_state', - super.operationName = 'Master State', - super.icon = Assets.gear, - }); + }) : super( + code: 'master_state', + operationName: 'Master State', + icon: Assets.gear, + ); @override List getOperationalValues() { @@ -85,10 +87,11 @@ final class GatewayFactoryReset extends GatewayFunctions { required super.deviceId, required super.deviceName, required super.type, - super.code = 'factory_reset', - super.operationName = 'Factory Reset', - super.icon = Assets.factoryReset, - }); + }) : super( + code: 'factory_reset', + operationName: 'Factory Reset', + icon: Assets.factoryReset, + ); @override List getOperationalValues() { diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart index fc7189f2..364854ce 100644 --- a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart @@ -81,11 +81,8 @@ class _GatewayDialogState extends State { final selectedGatewayFunctions = _gatewayFunctions.firstWhere( (f) => f.code == selectedFunction, orElse: () => GatewaySwitchAlarmSound( - code: selectedFunction ?? '', deviceId: '', deviceName: '', - operationName: '', - icon: '', type: '', ), ); From c90b9a1912cd812db16648794878322285e79554 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 12:24:59 +0300 Subject: [PATCH 16/18] bugfix/ range index errorof automations in routines. --- .../main_routine_view/fetch_routine_scenes_automation.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart b/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart index 0a22208c..92a837b6 100644 --- a/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart +++ b/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart @@ -183,7 +183,7 @@ class _FetchRoutineScenesState extends State state.automations[index].id, cardType: 'automations', spaceName: - state.scenes[index].spaceName, + state.automations[index].spaceName, onTap: () { BlocProvider.of(context) .add( From 694a5a7dda87dd619eee4358055d28acf5c3ae04 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 12:32:44 +0300 Subject: [PATCH 17/18] Refactor GatewayModel.fromJson to use a loop for status parsing instead of firstWhere. --- .../gateway/model/gateway_model.dart | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/lib/pages/device_managment/gateway/model/gateway_model.dart b/lib/pages/device_managment/gateway/model/gateway_model.dart index 8e8d00f9..b9600fd7 100644 --- a/lib/pages/device_managment/gateway/model/gateway_model.dart +++ b/lib/pages/device_managment/gateway/model/gateway_model.dart @@ -14,27 +14,36 @@ class GatewayModel { }); factory GatewayModel.fromJson(Map json) { - final status = json['status'] as List; + final status = json['status'] as List? ?? []; - final switchAlarmSound = status.firstWhere( - (item) => item['code'] == 'switch_alarm_sound', - )['value'] as bool; - final masterState = status.firstWhere( - (item) => item['code'] == 'master_state', - )['value'] as String; - final factoryReset = status.firstWhere( - (item) => item['code'] == 'factory_reset', - )['value'] as bool; - final alarmActive = status.firstWhere( - (item) => item['code'] == 'alarm_active', - )['value'] as String; + bool? switchAlarmSound; + String? masterState; + bool? factoryReset; + String? alarmActive; + + for (final item in status) { + switch (item['code']) { + case 'switch_alarm_sound': + switchAlarmSound = item['value'] as bool; + break; + case 'master_state': + masterState = item['value'] as String; + break; + case 'factory_reset': + factoryReset = item['value'] as bool; + break; + case 'alarm_active': + alarmActive = item['value'] as String; + break; + } + } return GatewayModel( uuid: json['uuid'] as String, - switchAlarmSound: switchAlarmSound, - masterState: masterState, - factoryReset: factoryReset, - alarmActive: alarmActive, + switchAlarmSound: switchAlarmSound ?? false, + masterState: masterState ?? '', + factoryReset: factoryReset ?? false, + alarmActive: alarmActive ?? '', ); } } From 387ef99728382cf082f5f4a96859b15f420f9c98 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 12:33:20 +0300 Subject: [PATCH 18/18] fix: handle nullable uuid in GatewayModel.fromJson --- lib/pages/device_managment/gateway/model/gateway_model.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/device_managment/gateway/model/gateway_model.dart b/lib/pages/device_managment/gateway/model/gateway_model.dart index b9600fd7..a522aa00 100644 --- a/lib/pages/device_managment/gateway/model/gateway_model.dart +++ b/lib/pages/device_managment/gateway/model/gateway_model.dart @@ -39,7 +39,7 @@ class GatewayModel { } return GatewayModel( - uuid: json['uuid'] as String, + uuid: json['uuid'] as String? ?? '', switchAlarmSound: switchAlarmSound ?? false, masterState: masterState ?? '', factoryReset: factoryReset ?? false,