diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b87628bd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "automations" + ] +} \ No newline at end of file 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 b37ff36c..367a4fe8 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -4,6 +4,9 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart' import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/enum/device_types.dart'; @@ -71,7 +74,6 @@ class AllDevicesModel { int? batteryLevel; String? productName; List? spaces; - List? _deviceFunctions; AllDevicesModel({ this.room, @@ -104,8 +106,12 @@ class AllDevicesModel { this.spaces, }); AllDevicesModel.fromJson(Map json) { - room = (json['room'] != null && (json['room'] is Map)) ? DevicesModelRoom.fromJson(json['room']) : null; - unit = (json['unit'] != null && (json['unit'] is Map)) ? DevicesModelUnit.fromJson(json['unit']) : null; + room = (json['room'] != null && (json['room'] is Map)) + ? DevicesModelRoom.fromJson(json['room']) + : null; + unit = (json['unit'] != null && (json['unit'] is Map)) + ? DevicesModelUnit.fromJson(json['unit']) + : null; community = (json['community'] != null && (json['community'] is Map)) ? DeviceCommunityModel.fromJson(json['community']) : null; @@ -134,7 +140,9 @@ class AllDevicesModel { batteryLevel = int.tryParse(json['battery']?.toString() ?? ''); productName = json['productName']?.toString(); if (json['spaces'] != null && json['spaces'] is List) { - spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList(); + spaces = (json['spaces'] as List) + .map((space) => DeviceSpaceModel.fromJson(space)) + .toList(); } } @@ -182,7 +190,8 @@ SOS String tempIcon = ''; if (type == DeviceType.LightBulb) { tempIcon = Assets.lightBulb; - } else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) { + } else if (type == DeviceType.CeilingSensor || + type == DeviceType.WallSensor) { tempIcon = Assets.sensors; } else if (type == DeviceType.AC) { tempIcon = Assets.ac; @@ -218,11 +227,11 @@ SOS return tempIcon; } - List get deviceFunctions { - _deviceFunctions ??= _getDeviceFunctions(); - return _deviceFunctions!; + List get functions { + return _getDeviceFunctions(); } + //! Functions for Devices Types List _getDeviceFunctions() { switch (productType) { case 'AC': @@ -234,7 +243,38 @@ SOS ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? ''), ]; - // other product types + case '1G': + return [ + OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''), + OneGangCountdownFunction( + deviceId: uuid ?? '', deviceName: name ?? ''), + ]; + + case '2G': + return [ + TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangCountdown1Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangCountdown2Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ]; + + case '3G': + return [ + ThreeGangSwitch1Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangSwitch2Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangSwitch3Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangCountdown1Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangCountdown2Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangCountdown3Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ]; default: return []; } diff --git a/lib/pages/routiens/helper/ac_helper.dart b/lib/pages/routiens/helper/ac_helper.dart index 6cdb415d..4c216a89 100644 --- a/lib/pages/routiens/helper/ac_helper.dart +++ b/lib/pages/routiens/helper/ac_helper.dart @@ -1,154 +1,39 @@ 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/routiens/bloc/routine_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; -mixin ACHelper { - Future?> showACFunctionsDialog(BuildContext context, List> functions) { +class ACHelper { + static Future?> showACFunctionsDialog( + BuildContext context, + List> functions, + ) async { List acFunctions = functions.whereType().toList(); String? selectedFunction; - dynamic selectedValue; + dynamic selectedValue = 20; + String? selectedCondition = "=="; + List _selectedConditions = [false, true, false]; - return showDialog( + return showDialog?>( context: context, builder: (BuildContext context) { return StatefulBuilder( builder: (context, setState) { return AlertDialog( contentPadding: EdgeInsets.zero, - content: Container( - width: selectedFunction != null ? 600 : 360, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - padding: const EdgeInsets.only(top: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'AC Functions', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - fontWeight: FontWeight.bold, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 50), - child: Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - ), - Flexible( - child: Row( - children: [ - Expanded( - child: ListView.separated( - shrinkWrap: true, - itemCount: acFunctions.length, - separatorBuilder: (context, index) => Divider(), - itemBuilder: (context, index) { - final function = acFunctions[index]; - return ListTile( - leading: Image.asset(function.icon, width: 24, height: 24), - title: Text(function.operationName), - trailing: Icon(Icons.arrow_forward_ios), - onTap: () { - setState(() { - selectedFunction = function.code; - selectedValue = null; - }); - }, - ); - }, - ), - ), - if (selectedFunction != null) - Container( - width: 1, - color: ColorsManager.greyColor, - ), - if (selectedFunction != null) - Expanded( - child: ListView.separated( - shrinkWrap: true, - itemCount: acFunctions - .firstWhere((f) => f.code == selectedFunction) - .getOperationalValues() - .length, - separatorBuilder: (context, index) => Divider(), - itemBuilder: (context, index) { - final operationalValue = acFunctions.firstWhere((f) => f.code == selectedFunction) - ..getOperationalValues()[index]; - return ListTile( - leading: Image.asset(operationalValue.icon, width: 24, height: 24), - title: Text(operationalValue.getOperationalValues()[index].description), - trailing: Radio( - value: operationalValue.getOperationalValues()[index].value, - groupValue: selectedValue, - onChanged: (value) { - setState(() { - selectedValue = value; - }); - }, - ), - ); - }, - ), - ), - ], - ), - ), - Container( - height: 1, - width: double.infinity, - color: ColorsManager.greyColor, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: Center( - child: Text( - 'Cancel', - style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: ColorsManager.greyColor), - ), - ), - ), - Container( - height: 50, - width: 1, - color: ColorsManager.greyColor, - ), - GestureDetector( - onTap: () { - // Handle the confirmation action here - Navigator.pop(context, { - 'function': selectedFunction, - 'value': selectedValue, - }); - }, - child: Center( - child: Text( - 'Confirm', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ), - ), - ], - ), - ], - ), + content: _buildDialogContent( + context, + setState, + acFunctions, + selectedFunction, + selectedValue, + selectedCondition, + _selectedConditions, + (fn) => selectedFunction = fn, + (val) => selectedValue = val, + (cond) => selectedCondition = cond, ), ); }, @@ -157,34 +42,370 @@ mixin ACHelper { ); } - void handleACDeviceDrop(BuildContext context, Map data) { - final device = data['device'] as AllDevicesModel; - final acFunctions = device.deviceFunctions; - - showACFunctionsDialog(context, acFunctions).then((result) { - if (result != null) { - _addACDeviceToRoutine(context, data, result); - } - }); + /// Build dialog content for AC functions dialog + static Widget _buildDialogContent( + BuildContext context, + StateSetter setState, + List acFunctions, + String? selectedFunction, + dynamic selectedValue, + String? selectedCondition, + List selectedConditions, + Function(String?) onFunctionSelected, + Function(dynamic) onValueSelected, + Function(String?) onConditionSelected, + ) { + 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: [ + _buildDialogHeader(context), + Flexible( + child: Row( + children: [ + _buildFunctionsList( + context, + setState, + acFunctions, + selectedFunction, + onFunctionSelected, + ), + if (selectedFunction != null) + _buildValueSelector( + context, + setState, + selectedFunction, + selectedValue, + selectedCondition, + selectedConditions, + onValueSelected, + onConditionSelected, + acFunctions, + ), + ], + ), + ), + _buildDialogFooter( + context, + selectedFunction, + selectedValue, + selectedCondition, + ), + ], + ), + ); } - void handleNonACDeviceDrop(BuildContext context, Map data) { - context.read().add(AddToThenContainer(data)); + /// Build header for AC functions dialog + static Widget _buildDialogHeader(BuildContext context) { + return Column( + children: [ + Text( + 'AC Condition', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 50), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + ], + ); } - void _addACDeviceToRoutine(BuildContext context, Map deviceData, Map functionData) { - final updatedData = { - ...deviceData, - 'function': functionData['function'], - 'value': functionData['value'], - }; - - context.read().add(AddToThenContainer(updatedData)); - - _logACFunctionSelection(functionData); + /// Build functions list for AC functions dialog + static Widget _buildFunctionsList( + BuildContext context, + StateSetter setState, + List acFunctions, + String? selectedFunction, + Function(String?) onFunctionSelected, + ) { + return Expanded( + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: acFunctions.length, + separatorBuilder: (context, index) => const Divider( + color: ColorsManager.dividerColor, + ), + itemBuilder: (context, index) { + final function = acFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + 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: () => setState(() => onFunctionSelected(function.code)), + ); + }, + ), + ); } - void _logACFunctionSelection(Map functionData) { - print('Selected AC function: ${functionData['function']}, Value: ${functionData['value']}'); + /// Build value selector for AC functions dialog + static Widget _buildValueSelector( + BuildContext context, + StateSetter setState, + String selectedFunction, + dynamic selectedValue, + String? selectedCondition, + List selectedConditions, + Function(dynamic) onValueSelected, + Function(String?) onConditionSelected, + List acFunctions, + ) { + if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { + return Expanded( + child: _buildTemperatureSelector( + context, + setState, + selectedValue, + selectedCondition, + selectedConditions, + onValueSelected, + onConditionSelected, + ), + ); + } + + final selectedFn = + acFunctions.firstWhere((f) => f.code == selectedFunction); + final values = selectedFn.getOperationalValues(); + return Expanded( + child: _buildOperationalValuesList( + context, + setState, + values, + selectedValue, + onValueSelected, + ), + ); + } + + /// Build temperature selector for AC functions dialog + static Widget _buildTemperatureSelector( + BuildContext context, + StateSetter setState, + dynamic selectedValue, + String? selectedCondition, + List selectedConditions, + Function(dynamic) onValueSelected, + Function(String?) onConditionSelected, + ) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildConditionToggle( + context, + setState, + selectedConditions, + onConditionSelected, + ), + const SizedBox(height: 20), + _buildTemperatureDisplay(context, selectedValue), + const SizedBox(height: 20), + _buildTemperatureSlider( + context, + setState, + selectedValue, + onValueSelected, + ), + ], + ); + } + + /// Build condition toggle for AC functions dialog + static Widget _buildConditionToggle( + BuildContext context, + StateSetter setState, + List selectedConditions, + Function(String?) onConditionSelected, + ) { + return ToggleButtons( + onPressed: (int index) { + setState(() { + for (int i = 0; i < selectedConditions.length; i++) { + selectedConditions[i] = i == index; + } + onConditionSelected(index == 0 + ? "<" + : index == 1 + ? "==" + : ">"); + }); + }, + 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: selectedConditions, + children: const [Text("<"), Text("="), Text(">")], + ); + } + + /// Build temperature display for AC functions dialog + static Widget _buildTemperatureDisplay( + BuildContext context, dynamic selectedValue) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '${selectedValue ?? 20}°C', + style: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ); + } + + static Widget _buildTemperatureSlider( + BuildContext context, + StateSetter setState, + dynamic selectedValue, + Function(dynamic) onValueSelected, + ) { + final currentValue = selectedValue is int ? selectedValue.toDouble() : 20.0; + return Slider( + value: currentValue, + min: 16, + max: 30, + divisions: 14, + label: '${currentValue.toInt()}°C', + onChanged: (value) { + setState(() => onValueSelected(value.toInt())); + }, + ); + } + + static Widget _buildOperationalValuesList( + BuildContext context, + StateSetter setState, + List values, + dynamic selectedValue, + Function(dynamic) onValueSelected, + ) { + return ListView.builder( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (newValue) { + setState(() => onValueSelected(newValue)); + }, + ), + ); + }, + ); + } + + static Widget _buildDialogFooter( + BuildContext context, + String? selectedFunction, + dynamic selectedValue, + String? selectedCondition, + ) { + return Container( + decoration: const BoxDecoration( + border: Border( + top: BorderSide( + color: ColorsManager.greyColor, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildFooterButton( + context, + 'Cancel', + selectedFunction != null ? 299 : 179, + () => Navigator.pop(context), + ), + _buildFooterButton( + context, + 'Confirm', + selectedFunction != null ? 299 : 179, + selectedFunction != null && selectedValue != null + ? () => Navigator.pop(context, { + 'function': selectedFunction, + 'value': selectedValue, + 'condition': selectedCondition ?? "==", + }) + : null, + ), + ], + ), + ); + } + + static Widget _buildFooterButton( + BuildContext context, + String text, + double width, + VoidCallback? onTap, + ) { + return GestureDetector( + onTap: onTap, + child: SizedBox( + height: 50, + width: width, + child: Center( + child: Text( + text, + style: context.textTheme.bodyMedium!.copyWith( + color: onTap != null + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, + ), + ), + ), + ), + ); } } diff --git a/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart new file mode 100644 index 00000000..0046db8a --- /dev/null +++ b/lib/pages/routiens/helper/dialog_helper/device_dialog_helper.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/routiens/helper/ac_helper.dart'; +import 'package:syncrow_web/pages/routiens/helper/one_gang_switch_helper.dart'; +import 'package:syncrow_web/pages/routiens/helper/three_gang_switch_helper.dart'; +import 'package:syncrow_web/pages/routiens/helper/two_gang_switch_helper.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; + +class DeviceDialogHelper { + static Future?> showDeviceDialog( + BuildContext context, + Map data, + ) async { + final functions = data['functions'] as List; + + try { + final result = await _getDialogForDeviceType( + context, + data['productType'], + functions, + ); + + if (result != null) { + return {...data, ...result}; + } + } catch (e) { + debugPrint('Error: $e'); + } + + return null; + } + + static Future?> _getDialogForDeviceType( + BuildContext context, + String productType, + List functions, + ) async { + switch (productType) { + case 'AC': + return ACHelper.showACFunctionsDialog(context, functions); + case '1G': + return OneGangSwitchHelper.showSwitchFunctionsDialog( + context, functions); + case '2G': + return TwoGangSwitchHelper.showSwitchFunctionsDialog( + context, functions); + case '3G': + return ThreeGangSwitchHelper.showSwitchFunctionsDialog( + context, functions); + default: + return null; + } + } +} diff --git a/lib/pages/routiens/helper/one_gang_switch_helper.dart b/lib/pages/routiens/helper/one_gang_switch_helper.dart new file mode 100644 index 00000000..30f25a96 --- /dev/null +++ b/lib/pages/routiens/helper/one_gang_switch_helper.dart @@ -0,0 +1,206 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class OneGangSwitchHelper { + static Future?> showSwitchFunctionsDialog( + BuildContext context, List> functions) async { + List> switchFunctions = functions + .where( + (f) => f is OneGangSwitchFunction || f is OneGangCountdownFunction) + .toList(); + String? selectedFunction; + dynamic selectedValue; + + return showDialog?>( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: 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: [ + Text( + '1 Gang Light Switch Condition', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, horizontal: 50), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + Flexible( + child: Row( + children: [ + Expanded( + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: switchFunctions.length, + separatorBuilder: (context, index) => + const Divider( + color: ColorsManager.dividerColor, + ), + itemBuilder: (context, index) { + final function = switchFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + 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: () { + setState(() { + selectedFunction = function.code; + selectedValue = null; + }); + }, + ); + }, + ), + ), + if (selectedFunction != null) + Expanded( + child: Builder( + builder: (context) { + final selectedFn = switchFunctions.firstWhere( + (f) => f.code == selectedFunction) + as BaseSwitchFunction; + final values = + selectedFn.getOperationalValues(); + return ListView.builder( + shrinkWrap: false, + physics: + const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (newValue) { + setState(() { + selectedValue = newValue; + }); + }, + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Container( + height: 50, + width: selectedFunction != null ? 299 : 179, + decoration: const BoxDecoration( + border: Border( + right: + BorderSide(color: ColorsManager.greyColor), + ), + ), + child: Center( + child: Text( + 'Cancel', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + ), + GestureDetector( + onTap: () { + if (selectedFunction != null && + selectedValue != null) { + Navigator.pop(context, { + 'function': selectedFunction, + 'value': selectedValue, + }); + } + }, + child: SizedBox( + height: 50, + width: selectedFunction != null ? 299 : 179, + child: Center( + child: Text( + 'Confirm', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + ColorsManager.primaryColorWithOpacity, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/pages/routiens/helper/three_gang_switch_helper.dart b/lib/pages/routiens/helper/three_gang_switch_helper.dart new file mode 100644 index 00000000..89aa2d95 --- /dev/null +++ b/lib/pages/routiens/helper/three_gang_switch_helper.dart @@ -0,0 +1,211 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class ThreeGangSwitchHelper { + static Future?> showSwitchFunctionsDialog( + BuildContext context, List> functions) async { + List> switchFunctions = functions + .where((f) => + f is ThreeGangSwitch1Function || + f is ThreeGangSwitch2Function || + f is ThreeGangSwitch3Function || + f is ThreeGangCountdown1Function || + f is ThreeGangCountdown2Function || + f is ThreeGangCountdown3Function) + .toList(); + String? selectedFunction; + dynamic selectedValue; + + return showDialog?>( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: 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: [ + Text( + '3 Gangs Light Switch Condition', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, horizontal: 50), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + Flexible( + child: Row( + children: [ + Expanded( + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: switchFunctions.length, + separatorBuilder: (context, index) => + const Divider( + color: ColorsManager.dividerColor, + ), + itemBuilder: (context, index) { + final function = switchFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + 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: () { + setState(() { + selectedFunction = function.code; + selectedValue = null; + }); + }, + ); + }, + ), + ), + if (selectedFunction != null) + Expanded( + child: Builder( + builder: (context) { + final selectedFn = switchFunctions.firstWhere( + (f) => f.code == selectedFunction) + as BaseSwitchFunction; + final values = + selectedFn.getOperationalValues(); + return ListView.builder( + shrinkWrap: false, + physics: + const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (newValue) { + setState(() { + selectedValue = newValue; + }); + }, + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Container( + height: 50, + width: selectedFunction != null ? 299 : 179, + decoration: const BoxDecoration( + border: Border( + right: + BorderSide(color: ColorsManager.greyColor), + ), + ), + child: Center( + child: Text( + 'Cancel', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + ), + GestureDetector( + onTap: () { + if (selectedFunction != null && + selectedValue != null) { + Navigator.pop(context, { + 'function': selectedFunction, + 'value': selectedValue, + }); + } + }, + child: SizedBox( + height: 50, + width: selectedFunction != null ? 299 : 179, + child: Center( + child: Text( + 'Confirm', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + ColorsManager.primaryColorWithOpacity, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/pages/routiens/helper/two_gang_switch_helper.dart b/lib/pages/routiens/helper/two_gang_switch_helper.dart new file mode 100644 index 00000000..1d271ac7 --- /dev/null +++ b/lib/pages/routiens/helper/two_gang_switch_helper.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class TwoGangSwitchHelper { + static Future?> showSwitchFunctionsDialog( + BuildContext context, List> functions) async { + List> switchFunctions = functions + .where((f) => + f is TwoGangSwitch1Function || + f is TwoGangSwitch2Function || + f is TwoGangCountdown1Function || + f is TwoGangCountdown2Function) + .toList(); + String? selectedFunction; + dynamic selectedValue; + + return showDialog?>( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: 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: [ + Text( + '2 Gangs Light Switch Condition', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, horizontal: 50), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + Flexible( + child: Row( + children: [ + Expanded( + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: switchFunctions.length, + separatorBuilder: (context, index) => + const Divider( + color: ColorsManager.dividerColor, + ), + itemBuilder: (context, index) { + final function = switchFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + 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: () { + setState(() { + selectedFunction = function.code; + selectedValue = null; + }); + }, + ); + }, + ), + ), + if (selectedFunction != null) + Expanded( + child: Builder( + builder: (context) { + final selectedFn = switchFunctions.firstWhere( + (f) => f.code == selectedFunction) + as BaseSwitchFunction; + final values = + selectedFn.getOperationalValues(); + return ListView.builder( + shrinkWrap: false, + physics: + const AlwaysScrollableScrollPhysics(), + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (newValue) { + setState(() { + selectedValue = newValue; + }); + }, + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Container( + height: 50, + width: selectedFunction != null ? 299 : 179, + decoration: const BoxDecoration( + border: Border( + right: + BorderSide(color: ColorsManager.greyColor), + ), + ), + child: Center( + child: Text( + 'Cancel', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + ), + GestureDetector( + onTap: () { + if (selectedFunction != null && + selectedValue != null) { + Navigator.pop(context, { + 'function': selectedFunction, + 'value': selectedValue, + }); + } + }, + child: SizedBox( + height: 50, + width: selectedFunction != null ? 299 : 179, + child: Center( + child: Text( + 'Confirm', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + ColorsManager.primaryColorWithOpacity, + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/pages/routiens/models/gang_switches/base_switch_function.dart b/lib/pages/routiens/models/gang_switches/base_switch_function.dart new file mode 100644 index 00000000..3c89bebd --- /dev/null +++ b/lib/pages/routiens/models/gang_switches/base_switch_function.dart @@ -0,0 +1,17 @@ +import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; + +abstract class BaseSwitchFunction extends DeviceFunction { + BaseSwitchFunction({ + required super.deviceId, + required super.deviceName, + required super.code, + required super.operationName, + required super.icon, + }); + + @override + bool execute(bool currentStatus, dynamic newValue); + + List getOperationalValues(); +} diff --git a/lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart b/lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart new file mode 100644 index 00000000..32bc436e --- /dev/null +++ b/lib/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart @@ -0,0 +1,57 @@ +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class OneGangSwitchFunction extends BaseSwitchFunction { + OneGangSwitchFunction({required super.deviceId, required super.deviceName}) + : super( + code: 'switch_1', + operationName: 'Light Switch', + icon: Assets.assetsAcPower, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SwitchOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class OneGangCountdownFunction extends BaseSwitchFunction { + OneGangCountdownFunction({required super.deviceId, required super.deviceName}) + : super( + code: 'countdown_1', + operationName: 'Light Countdown', + icon: Assets.assetsLightCountdown, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; +} diff --git a/lib/pages/routiens/models/gang_switches/switch_operational_value.dart b/lib/pages/routiens/models/gang_switches/switch_operational_value.dart new file mode 100644 index 00000000..eabd4a35 --- /dev/null +++ b/lib/pages/routiens/models/gang_switches/switch_operational_value.dart @@ -0,0 +1,17 @@ +class SwitchOperationalValue { + final String icon; + final String description; + final dynamic value; + final double? minValue; + final double? maxValue; + final double? stepValue; + + SwitchOperationalValue({ + required this.icon, + required this.value, + this.description = '', + this.minValue, + this.maxValue, + this.stepValue, + }); +} diff --git a/lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart b/lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart new file mode 100644 index 00000000..8ff186ef --- /dev/null +++ b/lib/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart @@ -0,0 +1,168 @@ +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class ThreeGangSwitch1Function extends BaseSwitchFunction { + ThreeGangSwitch1Function({required super.deviceId, required super.deviceName}) + : super( + code: 'switch_1', + operationName: 'Light 1 Switch', + icon: Assets.assetsAcPower, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SwitchOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class ThreeGangCountdown1Function extends BaseSwitchFunction { + ThreeGangCountdown1Function( + {required super.deviceId, required super.deviceName}) + : super( + code: 'countdown_1', + operationName: 'Light 1 Countdown', + icon: Assets.assetsLightCountdown, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; +} + +class ThreeGangSwitch2Function extends BaseSwitchFunction { + ThreeGangSwitch2Function({required super.deviceId, required super.deviceName}) + : super( + code: 'switch_2', + operationName: 'Light 2 Switch', + icon: Assets.assetsAcPower, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SwitchOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class ThreeGangCountdown2Function extends BaseSwitchFunction { + ThreeGangCountdown2Function( + {required super.deviceId, required super.deviceName}) + : super( + code: 'countdown_2', + operationName: 'Light 2 Countdown', + icon: Assets.assetsLightCountdown, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; +} + +class ThreeGangSwitch3Function extends BaseSwitchFunction { + ThreeGangSwitch3Function({required super.deviceId, required super.deviceName}) + : super( + code: 'switch_3', + operationName: 'Light 3 Switch', + icon: Assets.assetsAcPower, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SwitchOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class ThreeGangCountdown3Function extends BaseSwitchFunction { + ThreeGangCountdown3Function( + {required super.deviceId, required super.deviceName}) + : super( + code: 'countdown_3', + operationName: 'Light 3 Countdown', + icon: Assets.assetsLightCountdown, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; +} diff --git a/lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart b/lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart new file mode 100644 index 00000000..7408d9d0 --- /dev/null +++ b/lib/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart @@ -0,0 +1,115 @@ +import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class TwoGangSwitch1Function extends BaseSwitchFunction { + TwoGangSwitch1Function({required super.deviceId, required super.deviceName}) + : super( + code: 'switch_1', + operationName: 'Light 1 Switch', + icon: Assets.assetsAcPower, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SwitchOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class TwoGangSwitch2Function extends BaseSwitchFunction { + TwoGangSwitch2Function({required String deviceId, required String deviceName}) + : super( + deviceId: deviceId, + deviceName: deviceName, + code: 'switch_2', + operationName: 'Light 2 Switch', + icon: Assets.assetsAcPower, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SwitchOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class TwoGangCountdown1Function extends BaseSwitchFunction { + TwoGangCountdown1Function( + {required super.deviceId, required super.deviceName}) + : super( + code: 'countdown_1', + operationName: 'Light 1 Countdown', + icon: Assets.assetsLightCountdown, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; +} + +class TwoGangCountdown2Function extends BaseSwitchFunction { + TwoGangCountdown2Function( + {required super.deviceId, required super.deviceName}) + : super( + code: 'countdown_2', + operationName: 'Light 2 Countdown', + icon: Assets.assetsLightCountdown, + ); + + @override + bool execute(bool currentStatus, dynamic newValue) { + return newValue as bool; + } + + @override + List getOperationalValues() => [ + SwitchOperationalValue( + icon: '', + description: "sec", + value: 0.0, + minValue: 0, + maxValue: 43200, + stepValue: 1, + ), + ]; +} diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 2e7d1a0e..8ed4afff 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -11,6 +11,7 @@ class DraggableCard extends StatelessWidget { this.titleColor, this.isDragged = false, this.isDisabled = false, + this.deviceData, }); final String imagePath; @@ -18,11 +19,17 @@ class DraggableCard extends StatelessWidget { final Color? titleColor; final bool isDragged; final bool isDisabled; + final Map? deviceData; @override Widget build(BuildContext context) { - Widget card = Draggable>( - data: {'key': UniqueKey().toString(), 'imagePath': imagePath, 'title': title}, + Widget card = Draggable>( + data: deviceData ?? + { + 'key': UniqueKey().toString(), + 'imagePath': imagePath, + 'title': title, + }, feedback: Transform.rotate( angle: -0.1, child: _buildCardContent(context), diff --git a/lib/pages/routiens/widgets/if_container.dart b/lib/pages/routiens/widgets/if_container.dart index 069f77b4..ba0d67ab 100644 --- a/lib/pages/routiens/widgets/if_container.dart +++ b/lib/pages/routiens/widgets/if_container.dart @@ -1,18 +1,17 @@ -// lib/pages/routiens/widgets/if_container.dart - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; class IfContainer extends StatelessWidget { - const IfContainer({Key? key}) : super(key: key); + const IfContainer({super.key}); @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return DragTarget>( + return DragTarget>( builder: (context, candidateData, rejectedData) { return Container( width: double.infinity, @@ -20,7 +19,9 @@ class IfContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('IF', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const Text('IF', + style: + TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), Wrap( spacing: 8, @@ -37,8 +38,16 @@ class IfContainer extends StatelessWidget { ), ); }, - onAccept: (data) { - context.read().add(AddToIfContainer(data)); + onWillAccept: (data) => data != null, + onAccept: (data) async { + final result = + await DeviceDialogHelper.showDeviceDialog(context, data); + if (result != null) { + context.read().add(AddToIfContainer(result)); + } else if (!['AC', '1G', '2G', '3G'] + .contains(data['productType'])) { + context.read().add(AddToIfContainer(data)); + } }, ); }, diff --git a/lib/pages/routiens/widgets/routine_devices.dart b/lib/pages/routiens/widgets/routine_devices.dart index 12d8492e..c845af19 100644 --- a/lib/pages/routiens/widgets/routine_devices.dart +++ b/lib/pages/routiens/widgets/routine_devices.dart @@ -29,11 +29,18 @@ class RoutineDevices extends StatelessWidget { spacing: 10, runSpacing: 10, children: deviceList.asMap().entries.map((entry) { - final index = entry.key; final device = entry.value; return DraggableCard( imagePath: device.getDefaultIcon(device.productType), title: device.name ?? '', + deviceData: { + 'key': UniqueKey().toString(), + 'imagePath': device.getDefaultIcon(device.productType), + 'title': device.name ?? '', + 'deviceId': device.uuid, + 'productType': device.productType, + 'functions': device.functions, + }, ); }).toList(), ); diff --git a/lib/pages/routiens/widgets/scenes_and_automations.dart b/lib/pages/routiens/widgets/scenes_and_automations.dart index 7c90305b..b455b2f5 100644 --- a/lib/pages/routiens/widgets/scenes_and_automations.dart +++ b/lib/pages/routiens/widgets/scenes_and_automations.dart @@ -14,10 +14,10 @@ class ScenesAndAutomations extends StatelessWidget { return BlocProvider( create: (context) => RoutineBloc() ..add( - LoadScenes(spaceId), + const LoadScenes(spaceId), ) ..add( - LoadAutomation(spaceId), + const LoadAutomation(spaceId), ), child: BlocBuilder( builder: (context, state) { @@ -27,11 +27,10 @@ class ScenesAndAutomations extends StatelessWidget { spacing: 10, runSpacing: 10, children: scenes.asMap().entries.map((entry) { - final index = entry.key; final scene = entry.value; return DraggableCard( imagePath: Assets.logo, - title: scene.name ?? '', + title: scene.name, ); }).toList(), ); diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index bc72bd9f..9bd7fd00 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -3,16 +3,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; class ThenContainer extends StatelessWidget { - const ThenContainer({Key? key}) : super(key: key); + const ThenContainer({super.key}); @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return DragTarget>( + return DragTarget>( builder: (context, candidateData, rejectedData) { return Container( padding: const EdgeInsets.all(16), @@ -20,7 +21,9 @@ class ThenContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('THEN', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const Text('THEN', + style: + TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), Wrap( spacing: 8, @@ -37,8 +40,16 @@ class ThenContainer extends StatelessWidget { ), ); }, - onAccept: (data) { - context.read().add(AddToThenContainer(data)); + onWillAccept: (data) => data != null, + onAccept: (data) async { + final result = + await DeviceDialogHelper.showDeviceDialog(context, data); + if (result != null) { + context.read().add(AddToThenContainer(result)); + } else if (!['AC', '1G', '2G', '3G'] + .contains(data['productType'])) { + context.read().add(AddToThenContainer(data)); + } }, ); }, diff --git a/macos/DerivedData/Runner/Logs/Build/.dat.nosync1585.W9c579 b/macos/DerivedData/Runner/Logs/Build/.dat.nosync1585.W9c579 new file mode 100644 index 00000000..309af193 --- /dev/null +++ b/macos/DerivedData/Runner/Logs/Build/.dat.nosync1585.W9c579 @@ -0,0 +1,95 @@ + + + + + logFormatVersion + 11 + logs + + DEC061F9-9521-4D0C-959C-43A07F62CC12 + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + DEC061F9-9521-4D0C-959C-43A07F62CC12.xcactivitylog + hasPrimaryLog + + primaryObservable + + highLevelStatus + S + totalNumberOfAnalyzerIssues + 0 + totalNumberOfErrors + 0 + totalNumberOfTestFailures + 0 + totalNumberOfWarnings + 0 + + schemeIdentifier-containerName + Runner project + schemeIdentifier-schemeName + Flutter Assemble + schemeIdentifier-sharedScheme + 1 + signature + Cleaning workspace Runner with scheme Flutter Assemble + timeStartedRecording + 752000674.27645695 + timeStoppedRecording + 752000674.42918503 + title + Cleaning workspace Runner with scheme Flutter Assemble + uniqueIdentifier + DEC061F9-9521-4D0C-959C-43A07F62CC12 + + FB42CDDD-C79D-4D4B-891A-12C476DFCB10 + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + FB42CDDD-C79D-4D4B-891A-12C476DFCB10.xcactivitylog + hasPrimaryLog + + primaryObservable + + highLevelStatus + S + totalNumberOfAnalyzerIssues + 0 + totalNumberOfErrors + 0 + totalNumberOfTestFailures + 0 + totalNumberOfWarnings + 0 + + schemeIdentifier-containerName + Runner project + schemeIdentifier-schemeName + Runner + schemeIdentifier-sharedScheme + 1 + signature + Cleaning workspace Runner with scheme Runner + timeStartedRecording + 752000674.90370798 + timeStoppedRecording + 752000675.05962098 + title + Cleaning workspace Runner with scheme Runner + uniqueIdentifier + FB42CDDD-C79D-4D4B-891A-12C476DFCB10 + + + + diff --git a/macos/DerivedData/Runner/Logs/Build/DEC061F9-9521-4D0C-959C-43A07F62CC12.xcactivitylog b/macos/DerivedData/Runner/Logs/Build/DEC061F9-9521-4D0C-959C-43A07F62CC12.xcactivitylog new file mode 100644 index 00000000..c811e6cb Binary files /dev/null and b/macos/DerivedData/Runner/Logs/Build/DEC061F9-9521-4D0C-959C-43A07F62CC12.xcactivitylog differ diff --git a/macos/DerivedData/Runner/Logs/Build/FB42CDDD-C79D-4D4B-891A-12C476DFCB10.xcactivitylog b/macos/DerivedData/Runner/Logs/Build/FB42CDDD-C79D-4D4B-891A-12C476DFCB10.xcactivitylog new file mode 100644 index 00000000..b9ccd504 Binary files /dev/null and b/macos/DerivedData/Runner/Logs/Build/FB42CDDD-C79D-4D4B-891A-12C476DFCB10.xcactivitylog differ diff --git a/macos/DerivedData/Runner/Logs/Build/LogStoreManifest.plist b/macos/DerivedData/Runner/Logs/Build/LogStoreManifest.plist new file mode 100644 index 00000000..0c8e2d35 --- /dev/null +++ b/macos/DerivedData/Runner/Logs/Build/LogStoreManifest.plist @@ -0,0 +1,53 @@ + + + + + logFormatVersion + 11 + logs + + DEC061F9-9521-4D0C-959C-43A07F62CC12 + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + DEC061F9-9521-4D0C-959C-43A07F62CC12.xcactivitylog + hasPrimaryLog + + primaryObservable + + highLevelStatus + S + totalNumberOfAnalyzerIssues + 0 + totalNumberOfErrors + 0 + totalNumberOfTestFailures + 0 + totalNumberOfWarnings + 0 + + schemeIdentifier-containerName + Runner project + schemeIdentifier-schemeName + Flutter Assemble + schemeIdentifier-sharedScheme + 1 + signature + Cleaning workspace Runner with scheme Flutter Assemble + timeStartedRecording + 752000674.27645695 + timeStoppedRecording + 752000674.42918503 + title + Cleaning workspace Runner with scheme Flutter Assemble + uniqueIdentifier + DEC061F9-9521-4D0C-959C-43A07F62CC12 + + + + diff --git a/macos/DerivedData/Runner/Logs/Launch/LogStoreManifest.plist b/macos/DerivedData/Runner/Logs/Launch/LogStoreManifest.plist new file mode 100644 index 00000000..f38de442 --- /dev/null +++ b/macos/DerivedData/Runner/Logs/Launch/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/macos/DerivedData/Runner/Logs/Localization/LogStoreManifest.plist b/macos/DerivedData/Runner/Logs/Localization/LogStoreManifest.plist new file mode 100644 index 00000000..f38de442 --- /dev/null +++ b/macos/DerivedData/Runner/Logs/Localization/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/macos/DerivedData/Runner/Logs/Package/LogStoreManifest.plist b/macos/DerivedData/Runner/Logs/Package/LogStoreManifest.plist new file mode 100644 index 00000000..f38de442 --- /dev/null +++ b/macos/DerivedData/Runner/Logs/Package/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/macos/DerivedData/Runner/Logs/Test/LogStoreManifest.plist b/macos/DerivedData/Runner/Logs/Test/LogStoreManifest.plist new file mode 100644 index 00000000..f38de442 --- /dev/null +++ b/macos/DerivedData/Runner/Logs/Test/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/macos/DerivedData/Runner/info.plist b/macos/DerivedData/Runner/info.plist new file mode 100644 index 00000000..3594c152 --- /dev/null +++ b/macos/DerivedData/Runner/info.plist @@ -0,0 +1,10 @@ + + + + + LastAccessedDate + 2024-10-30T17:04:35Z + WorkspacePath + /Users/akmz/Developer/web/syncrow-web/web/macos/Runner.xcworkspace + + diff --git a/pubspec.yaml b/pubspec.yaml index 9e35e58f..8dc6b6bc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,6 +76,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/icons/automation_functions/ + - assets/icons/functions_icons/ - assets/icons/routine/ - assets/icons/ - assets/images/