From 2f5c5d7da1cf0bdefa8562874075384c500d5c82 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 2 Dec 2024 16:49:44 +0300 Subject: [PATCH 1/3] rule_disable selected & CreateAutomation actionType --- .../bloc/routine_bloc/routine_bloc.dart | 189 ++++++++++++------ .../create_automation_model.dart | 4 + .../create_scene_model.dart | 5 + .../routine_dialogs/automation_dialog.dart | 62 +++--- 4 files changed, 169 insertions(+), 91 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 0b61e4e7..9cb3adb5 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -53,7 +53,8 @@ class RoutineBloc extends Bloc { TriggerSwitchTabsEvent event, Emitter emit, ) { - emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false)); + emit(state.copyWith( + routineTab: event.isRoutineTab, createRoutineView: false)); add(ResetRoutineState()); if (event.isRoutineTab) { add(const LoadScenes(spaceId, communityId)); @@ -72,8 +73,8 @@ class RoutineBloc extends Bloc { final updatedIfItems = List>.from(state.ifItems); // Find the index of the item in teh current itemsList - int index = - updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = updatedIfItems.indexWhere( + (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { updatedIfItems[index] = event.item; @@ -82,18 +83,21 @@ class RoutineBloc extends Bloc { } if (event.isTabToRun) { - emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); + emit(state.copyWith( + ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); } else { - emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); + emit(state.copyWith( + ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); } } - void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { + void _onAddToThenContainer( + AddToThenContainer event, Emitter emit) { final currentItems = List>.from(state.thenItems); // Find the index of the item in teh current itemsList - int index = - currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = currentItems.indexWhere( + (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { currentItems[index] = event.item; @@ -104,22 +108,26 @@ class RoutineBloc extends Bloc { emit(state.copyWith(thenItems: currentItems)); } - void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter emit) { + void _onAddFunctionsToRoutine( + AddFunctionToRoutine event, Emitter emit) { try { if (event.functions.isEmpty) return; - List selectedFunction = List.from(event.functions); + List selectedFunction = + List.from(event.functions); Map> currentSelectedFunctions = Map>.from(state.selectedFunctions); if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { List currentFunctions = - List.from(currentSelectedFunctions[event.uniqueCustomId] ?? []); + List.from( + currentSelectedFunctions[event.uniqueCustomId] ?? []); List functionCode = []; for (int i = 0; i < selectedFunction.length; i++) { for (int j = 0; j < currentFunctions.length; j++) { - if (selectedFunction[i].functionCode == currentFunctions[j].functionCode) { + if (selectedFunction[i].functionCode == + currentFunctions[j].functionCode) { currentFunctions[j] = selectedFunction[i]; if (!functionCode.contains(currentFunctions[j].functionCode)) { functionCode.add(currentFunctions[j].functionCode); @@ -129,13 +137,15 @@ class RoutineBloc extends Bloc { } for (int i = 0; i < functionCode.length; i++) { - selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]); + selectedFunction + .removeWhere((code) => code.functionCode == functionCode[i]); } - currentSelectedFunctions[event.uniqueCustomId] = List.from(currentFunctions) - ..addAll(selectedFunction); + currentSelectedFunctions[event.uniqueCustomId] = + List.from(currentFunctions)..addAll(selectedFunction); } else { - currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions); + currentSelectedFunctions[event.uniqueCustomId] = + List.from(event.functions); } emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); @@ -144,11 +154,13 @@ class RoutineBloc extends Bloc { } } - Future _onLoadScenes(LoadScenes event, Emitter emit) async { + Future _onLoadScenes( + LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { - final scenes = await SceneApi.getScenesByUnitId(event.unitId, event.communityId); + final scenes = + await SceneApi.getScenesByUnitId(event.unitId, event.communityId); emit(state.copyWith( scenes: scenes, isLoading: false, @@ -163,7 +175,8 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation( + LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -191,14 +204,16 @@ class RoutineBloc extends Bloc { } } - FutureOr _onSearchRoutines(SearchRoutines event, Emitter emit) async { + FutureOr _onSearchRoutines( + SearchRoutines event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); await Future.delayed(const Duration(seconds: 1)); emit(state.copyWith(isLoading: false, errorMessage: null)); emit(state.copyWith(searchText: event.query)); } - FutureOr _onAddSelectedIcon(AddSelectedIcon event, Emitter emit) { + FutureOr _onAddSelectedIcon( + AddSelectedIcon event, Emitter emit) { emit(state.copyWith(selectedIcon: event.icon)); } @@ -212,7 +227,8 @@ class RoutineBloc extends Bloc { return actions.last['deviceId'] == 'delay'; } - Future _onCreateScene(CreateSceneEvent event, Emitter emit) async { + Future _onCreateScene( + CreateSceneEvent event, Emitter emit) async { try { // Check if first action is delay if (_isFirstActionDelay(state.thenItems)) { @@ -238,6 +254,7 @@ class RoutineBloc extends Bloc { return functions.map((function) { if (function.functionCode == 'automation') { return CreateSceneAction( + actionType: 'automation', entityId: function.entityId, actionExecutor: function.value, executorProperty: null, @@ -257,6 +274,7 @@ class RoutineBloc extends Bloc { } return CreateSceneAction( + actionType: 'scene', entityId: function.entityId, actionExecutor: 'device_issue', executorProperty: CreateSceneExecutorProperty( @@ -296,7 +314,8 @@ class RoutineBloc extends Bloc { } } - Future _onCreateAutomation(CreateAutomationEvent event, Emitter emit) async { + Future _onCreateAutomation( + CreateAutomationEvent event, Emitter emit) async { try { if (state.routineName == null || state.routineName!.isEmpty) { emit(state.copyWith( @@ -353,6 +372,15 @@ class RoutineBloc extends Bloc { return functions.map((function) { if (function.functionCode == 'automation') { return AutomationAction( + actionType: 'automation', + entityId: function.entityId, + actionExecutor: function.value, + ); + } + + if (function.functionCode == 'tap_to_run') { + return AutomationAction( + actionType: 'scene', entityId: function.entityId, actionExecutor: function.value, ); @@ -411,17 +439,21 @@ class RoutineBloc extends Bloc { } } - FutureOr _onRemoveDragCard(RemoveDragCard event, Emitter emit) { + FutureOr _onRemoveDragCard( + RemoveDragCard event, Emitter emit) { if (event.isFromThen) { final thenItems = List>.from(state.thenItems); - final selectedFunctions = Map>.from(state.selectedFunctions); + final selectedFunctions = + Map>.from(state.selectedFunctions); thenItems.removeAt(event.index); selectedFunctions.remove(event.key); - emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith( + thenItems: thenItems, selectedFunctions: selectedFunctions)); } else { final ifItems = List>.from(state.ifItems); - final selectedFunctions = Map>.from(state.selectedFunctions); + final selectedFunctions = + Map>.from(state.selectedFunctions); ifItems.removeAt(event.index); selectedFunctions.remove(event.key); @@ -432,7 +464,8 @@ class RoutineBloc extends Bloc { isAutomation: false, isTabToRun: false)); } else { - emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith( + ifItems: ifItems, selectedFunctions: selectedFunctions)); } } } @@ -444,18 +477,23 @@ class RoutineBloc extends Bloc { )); } - FutureOr _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter emit) { + FutureOr _onEffectiveTimeEvent( + EffectiveTimePeriodEvent event, Emitter emit) { emit(state.copyWith(effectiveTime: event.effectiveTime)); } - FutureOr _onSetRoutineName(SetRoutineName event, Emitter emit) { + FutureOr _onSetRoutineName( + SetRoutineName event, Emitter emit) { emit(state.copyWith( routineName: event.name, )); } - (List>, List>, Map>) - _createCardData( + ( + List>, + List>, + Map> + ) _createCardData( List actions, List? conditions, Map> currentFunctions, @@ -488,7 +526,8 @@ class RoutineBloc extends Bloc { 'deviceId': condition.entityId, 'title': matchingDevice.name ?? condition.entityId, 'productType': condition.entityType, - 'imagePath': matchingDevice.getDefaultIcon(condition.entityType), + 'imagePath': + matchingDevice.getDefaultIcon(condition.entityType), }; final functions = matchingDevice.functions; @@ -524,8 +563,11 @@ class RoutineBloc extends Bloc { final cardData = { 'entityId': action.entityId, 'uniqueCustomId': const Uuid().v4(), - 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'title': action.actionExecutor == 'delay' ? 'Delay' : (matchingDevice.name ?? 'Device'), + 'deviceId': + action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'title': action.actionExecutor == 'delay' + ? 'Delay' + : (matchingDevice.name ?? 'Device'), 'productType': action.productType, 'imagePath': matchingDevice.getDefaultIcon(action.productType), }; @@ -568,7 +610,8 @@ class RoutineBloc extends Bloc { return (thenItems, ifItems, currentFunctions); } - Future _onGetSceneDetails(GetSceneDetails event, Emitter emit) async { + Future _onGetSceneDetails( + GetSceneDetails event, Emitter emit) async { try { emit(state.copyWith( isLoading: true, @@ -616,10 +659,12 @@ class RoutineBloc extends Bloc { if (!deviceCards.containsKey(deviceId)) { deviceCards[deviceId] = { 'entityId': action.entityId, - 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay' - ? const Uuid().v4() - : action.entityId, + 'deviceId': + action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'uniqueCustomId': + action.type == 'automation' || action.actionExecutor == 'delay' + ? const Uuid().v4() + : action.entityId, 'title': action.actionExecutor == 'delay' ? 'Delay' : action.type == 'automation' @@ -654,7 +699,8 @@ class RoutineBloc extends Bloc { ), ); emit(state.copyWith(automationActionExecutor: action.actionExecutor)); - } else if (action.executorProperty != null && action.actionExecutor != 'delay') { + } else if (action.executorProperty != null && + action.actionExecutor != 'delay') { if (!updatedFunctions.containsKey(uniqueCustomId)) { updatedFunctions[uniqueCustomId] = []; } @@ -739,7 +785,8 @@ class RoutineBloc extends Bloc { thenItems: [], )); - final automationDetails = await SceneApi.getAutomationDetails(event.automationId); + final automationDetails = + await SceneApi.getAutomationDetails(event.automationId); final List> thenItems; final List> ifItems; @@ -758,13 +805,16 @@ class RoutineBloc extends Bloc { final cardData = { 'entityId': condition.entityId, 'uniqueCustomId': const Uuid().v4(), - 'deviceId': condition.expr.statusCode == 'delay' ? 'delay' : condition.entityId, + 'deviceId': condition.expr.statusCode == 'delay' + ? 'delay' + : condition.entityId, 'title': condition.expr.statusCode == 'delay' ? 'Delay' : (matchingDevice?.name ?? 'Device'), 'productType': condition.productType, 'functions': matchingDevice?.functions ?? [], - 'imagePath': matchingDevice?.getDefaultIcon(condition.productType) ?? '', + 'imagePath': + matchingDevice?.getDefaultIcon(condition.productType) ?? '', 'device': matchingDevice, }; @@ -801,15 +851,19 @@ class RoutineBloc extends Bloc { final cardData = { 'entityId': action.entityId, 'uniqueCustomId': const Uuid().v4(), - 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'title': action.actionExecutor == 'delay' ? 'Delay' : (matchingDevice.name ?? 'Device'), + 'deviceId': + action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'title': action.actionExecutor == 'delay' + ? 'Delay' + : (matchingDevice.name ?? 'Device'), 'productType': action.productType, 'functions': matchingDevice.functions, 'imagePath': matchingDevice.getDefaultIcon(action.productType), 'device': matchingDevice, }; - if (action.executorProperty != null && action.actionExecutor != 'delay') { + if (action.executorProperty != null && + action.actionExecutor != 'delay') { final functions = matchingDevice.functions; final functionCode = action.executorProperty!.functionCode; for (var function in functions) { @@ -872,7 +926,8 @@ class RoutineBloc extends Bloc { } } - FutureOr _onResetRoutineState(ResetRoutineState event, Emitter emit) { + FutureOr _onResetRoutineState( + ResetRoutineState event, Emitter emit) { emit(state.copyWith( ifItems: [], thenItems: [], @@ -900,7 +955,8 @@ class RoutineBloc extends Bloc { if (state.isTabToRun) { SceneApi.deleteScene(unitUuid: spaceId, sceneId: state.sceneId ?? ''); } else { - SceneApi.deleteAutomation(unitUuid: spaceId, automationId: state.automationId ?? ''); + SceneApi.deleteAutomation( + unitUuid: spaceId, automationId: state.automationId ?? ''); } add(const LoadScenes(spaceId, communityId)); @@ -929,7 +985,8 @@ class RoutineBloc extends Bloc { // } // } - FutureOr _fetchDevices(FetchDevicesInRoutine event, Emitter emit) async { + FutureOr _fetchDevices( + FetchDevicesInRoutine event, Emitter emit) async { emit(state.copyWith(isLoading: true)); try { final devices = await DevicesManagementApi().fetchDevices(); @@ -940,7 +997,8 @@ class RoutineBloc extends Bloc { } } - FutureOr _onUpdateScene(UpdateScene event, Emitter emit) async { + FutureOr _onUpdateScene( + UpdateScene event, Emitter emit) async { try { // Check if first action is delay if (_isFirstActionDelay(state.thenItems)) { @@ -967,6 +1025,7 @@ class RoutineBloc extends Bloc { return functions.map((function) { if (function.functionCode == 'automation') { return CreateSceneAction( + actionType: 'automation', entityId: function.entityId, actionExecutor: function.value, executorProperty: null, @@ -1006,7 +1065,8 @@ class RoutineBloc extends Bloc { actions: actions, ); - final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? ''); + final result = + await SceneApi.updateScene(createSceneModel, state.sceneId ?? ''); if (result['success']) { add(ResetRoutineState()); add(const LoadScenes(spaceId, communityId)); @@ -1025,7 +1085,8 @@ class RoutineBloc extends Bloc { } } - FutureOr _onUpdateAutomation(UpdateAutomation event, Emitter emit) async { + FutureOr _onUpdateAutomation( + UpdateAutomation event, Emitter emit) async { if (_isFirstActionDelay(state.thenItems)) { emit(state.copyWith( errorMessage: 'Cannot have delay as the first action', @@ -1037,7 +1098,8 @@ class RoutineBloc extends Bloc { try { final conditions = state.ifItems .map((item) { - final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = + state.selectedFunctions[item['uniqueCustomId']] ?? []; if (functions.isEmpty) return null; final function = functions.first; @@ -1057,18 +1119,21 @@ class RoutineBloc extends Bloc { final actions = state.thenItems .map((item) { - final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = + state.selectedFunctions[item['uniqueCustomId']] ?? []; if (functions.isEmpty) return null; final function = functions.first; return AutomationAction( + actionType: 'automation', entityId: function.entityId, actionExecutor: function.actionExecutor, executorProperty: ExecutorProperty( functionCode: function.functionCode, functionValue: function.value, - delaySeconds: - function.functionCode == 'delay' ? (function.value as num).toInt() : null, + delaySeconds: function.functionCode == 'delay' + ? (function.value as num).toInt() + : null, ), ); }) @@ -1079,13 +1144,14 @@ class RoutineBloc extends Bloc { spaceUuid: spaceId, automationName: state.routineName ?? '', decisionExpr: state.selectedAutomationOperator, - effectiveTime: state.effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), + effectiveTime: + state.effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), conditions: conditions, actions: actions, ); - final result = - await SceneApi.updateAutomation(createAutomationModel, state.automationId ?? ''); + final result = await SceneApi.updateAutomation( + createAutomationModel, state.automationId ?? ''); if (result['success']) { add(ResetRoutineState()); @@ -1107,6 +1173,7 @@ class RoutineBloc extends Bloc { FutureOr _onSetAutomationActionExecutor( SetAutomationActionExecutor event, Emitter emit) { - emit(state.copyWith(automationActionExecutor: event.automationActionExecutor)); + emit(state.copyWith( + automationActionExecutor: event.automationActionExecutor)); } } diff --git a/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart b/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart index 295328e4..d76c34de 100644 --- a/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart +++ b/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart @@ -137,11 +137,13 @@ class ConditionExpr { class AutomationAction { String entityId; + String? actionType; String actionExecutor; ExecutorProperty? executorProperty; AutomationAction({ required this.entityId, + this.actionType, required this.actionExecutor, this.executorProperty, }); @@ -151,11 +153,13 @@ class AutomationAction { 'entityId': entityId, 'actionExecutor': actionExecutor, 'executorProperty': executorProperty?.toMap(), + 'actionType': actionType }; } factory AutomationAction.fromMap(Map map) { return AutomationAction( + actionType: map['actionType'], entityId: map['entityId'] ?? '', actionExecutor: map['actionExecutor'] ?? '', executorProperty: map['executorProperty'] != null diff --git a/lib/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart b/lib/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart index c669aa9a..81ee1096 100644 --- a/lib/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart +++ b/lib/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart @@ -95,10 +95,12 @@ class CreateSceneModel { class CreateSceneAction { String entityId; + String? actionType; String actionExecutor; CreateSceneExecutorProperty? executorProperty; CreateSceneAction({ + this.actionType, required this.entityId, required this.actionExecutor, required this.executorProperty, @@ -110,6 +112,7 @@ class CreateSceneAction { CreateSceneExecutorProperty? executorProperty, }) { return CreateSceneAction( + actionType: actionType ?? this.actionType, entityId: entityId ?? this.entityId, actionExecutor: actionExecutor ?? this.actionExecutor, executorProperty: executorProperty ?? this.executorProperty, @@ -125,6 +128,7 @@ class CreateSceneAction { }; } else { return { + "actionType": actionType, 'entityId': entityId, 'actionExecutor': actionExecutor, }; @@ -133,6 +137,7 @@ class CreateSceneAction { factory CreateSceneAction.fromMap(Map map) { return CreateSceneAction( + actionType: map['actionType'], entityId: map['entityId'] ?? '', actionExecutor: map['actionExecutor'] ?? '', executorProperty: diff --git a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart index 16a181b6..2950c0cc 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart @@ -23,7 +23,7 @@ class AutomationDialog extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final isEnabled = state.automationActionExecutor == 'rule_enable'; + final String? initialSelection = state.automationActionExecutor; return Dialog( shape: @@ -40,14 +40,14 @@ class AutomationDialog extends StatelessWidget { leading: SvgPicture.asset(Assets.acPower, width: 24, height: 24), title: const Text('Enable'), - trailing: Radio( - value: true, - groupValue: isEnabled, - onChanged: (bool? value) { - if (value == true) { + trailing: Radio( + value: 'rule_enable', + groupValue: initialSelection, + onChanged: (String? value) { + if (value != null) { context.read().add( - const SetAutomationActionExecutor( - automationActionExecutor: 'rule_enable', + SetAutomationActionExecutor( + automationActionExecutor: value, ), ); } @@ -58,14 +58,14 @@ class AutomationDialog extends StatelessWidget { leading: SvgPicture.asset(Assets.acPowerOff, width: 24, height: 24), title: const Text('Disable'), - trailing: Radio( - value: false, - groupValue: isEnabled, - onChanged: (bool? value) { - if (value == false) { + trailing: Radio( + value: 'rule_disable', + groupValue: initialSelection, + onChanged: (String? value) { + if (value != null) { context.read().add( - const SetAutomationActionExecutor( - automationActionExecutor: 'rule_disable', + SetAutomationActionExecutor( + automationActionExecutor: value, ), ); } @@ -75,23 +75,25 @@ class AutomationDialog extends StatelessWidget { const SizedBox(height: 16), DialogFooter( onConfirm: () { - context.read().add( - AddFunctionToRoutine( - [ - DeviceFunctionData( - entityId: automationId, - functionCode: 'automation', - value: state.automationActionExecutor, - operationName: 'Automation', - ), - ], - uniqueCustomId, - ), - ); - Navigator.of(context).pop(true); + if (state.automationActionExecutor != null) { + context.read().add( + AddFunctionToRoutine( + [ + DeviceFunctionData( + entityId: automationId, + functionCode: 'automation', + value: state.automationActionExecutor, + operationName: 'Automation', + ), + ], + uniqueCustomId, + ), + ); + Navigator.of(context).pop(true); + } }, onCancel: () => Navigator.of(context).pop(false), - isConfirmEnabled: true, + isConfirmEnabled: state.automationActionExecutor != null, dialogWidth: 400, ), ], From 4e94d2df8916986e557a02254e841af6a0418633 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 3 Dec 2024 00:02:52 +0300 Subject: [PATCH 2/3] Bug fixes --- .../bloc/routine_bloc/routine_bloc.dart | 261 ++++++++---------- .../bloc/routine_bloc/routine_event.dart | 2 + .../routiens/helper/save_routine_helper.dart | 53 ++-- lib/pages/routiens/widgets/dragable_card.dart | 28 +- .../widgets/routine_search_and_buttons.dart | 9 + .../routiens/widgets/then_container.dart | 110 +++----- lib/utils/snack_bar.dart | 36 ++- 7 files changed, 245 insertions(+), 254 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 9cb3adb5..ace64856 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -13,6 +13,7 @@ import 'package:syncrow_web/pages/routiens/models/routine_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/routines_api.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:uuid/uuid.dart'; part 'routine_event.dart'; @@ -47,14 +48,14 @@ class RoutineBloc extends Bloc { on(_onSetAutomationActionExecutor); on(_triggerSwitchTabsEvent); on(_createNewRoutineViewEvent); + on(_resetErrorMessage); } FutureOr _triggerSwitchTabsEvent( TriggerSwitchTabsEvent event, Emitter emit, ) { - emit(state.copyWith( - routineTab: event.isRoutineTab, createRoutineView: false)); + emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false)); add(ResetRoutineState()); if (event.isRoutineTab) { add(const LoadScenes(spaceId, communityId)); @@ -62,6 +63,13 @@ class RoutineBloc extends Bloc { } } + _resetErrorMessage( + ResetErrorMessage event, + Emitter emit, + ) { + emit(state.copyWith(errorMessage: '')); + } + FutureOr _createNewRoutineViewEvent( CreateNewRoutineViewEvent event, Emitter emit, @@ -73,8 +81,8 @@ class RoutineBloc extends Bloc { final updatedIfItems = List>.from(state.ifItems); // Find the index of the item in teh current itemsList - int index = updatedIfItems.indexWhere( - (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = + updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { updatedIfItems[index] = event.item; @@ -83,21 +91,18 @@ class RoutineBloc extends Bloc { } if (event.isTabToRun) { - emit(state.copyWith( - ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); + emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); } else { - emit(state.copyWith( - ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); + emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); } } - void _onAddToThenContainer( - AddToThenContainer event, Emitter emit) { + void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { final currentItems = List>.from(state.thenItems); // Find the index of the item in teh current itemsList - int index = currentItems.indexWhere( - (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = + currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { currentItems[index] = event.item; @@ -108,26 +113,22 @@ class RoutineBloc extends Bloc { emit(state.copyWith(thenItems: currentItems)); } - void _onAddFunctionsToRoutine( - AddFunctionToRoutine event, Emitter emit) { + void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter emit) { try { if (event.functions.isEmpty) return; - List selectedFunction = - List.from(event.functions); + List selectedFunction = List.from(event.functions); Map> currentSelectedFunctions = Map>.from(state.selectedFunctions); if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { List currentFunctions = - List.from( - currentSelectedFunctions[event.uniqueCustomId] ?? []); + List.from(currentSelectedFunctions[event.uniqueCustomId] ?? []); List functionCode = []; for (int i = 0; i < selectedFunction.length; i++) { for (int j = 0; j < currentFunctions.length; j++) { - if (selectedFunction[i].functionCode == - currentFunctions[j].functionCode) { + if (selectedFunction[i].functionCode == currentFunctions[j].functionCode) { currentFunctions[j] = selectedFunction[i]; if (!functionCode.contains(currentFunctions[j].functionCode)) { functionCode.add(currentFunctions[j].functionCode); @@ -137,15 +138,13 @@ class RoutineBloc extends Bloc { } for (int i = 0; i < functionCode.length; i++) { - selectedFunction - .removeWhere((code) => code.functionCode == functionCode[i]); + selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]); } - currentSelectedFunctions[event.uniqueCustomId] = - List.from(currentFunctions)..addAll(selectedFunction); + currentSelectedFunctions[event.uniqueCustomId] = List.from(currentFunctions) + ..addAll(selectedFunction); } else { - currentSelectedFunctions[event.uniqueCustomId] = - List.from(event.functions); + currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions); } emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); @@ -154,13 +153,11 @@ class RoutineBloc extends Bloc { } } - Future _onLoadScenes( - LoadScenes event, Emitter emit) async { + Future _onLoadScenes(LoadScenes event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { - final scenes = - await SceneApi.getScenesByUnitId(event.unitId, event.communityId); + final scenes = await SceneApi.getScenesByUnitId(event.unitId, event.communityId); emit(state.copyWith( scenes: scenes, isLoading: false, @@ -175,8 +172,7 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation( - LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); try { @@ -204,44 +200,41 @@ class RoutineBloc extends Bloc { } } - FutureOr _onSearchRoutines( - SearchRoutines event, Emitter emit) async { + FutureOr _onSearchRoutines(SearchRoutines event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); await Future.delayed(const Duration(seconds: 1)); emit(state.copyWith(isLoading: false, errorMessage: null)); emit(state.copyWith(searchText: event.query)); } - FutureOr _onAddSelectedIcon( - AddSelectedIcon event, Emitter emit) { + FutureOr _onAddSelectedIcon(AddSelectedIcon event, Emitter emit) { emit(state.copyWith(selectedIcon: event.icon)); } - bool _isFirstActionDelay(List> actions) { - if (actions.isEmpty) return false; - return actions.first['deviceId'] == 'delay'; - } + // bool _isFirstActionDelay(List> actions) { + // if (actions.isEmpty) return false; + // return actions.first['deviceId'] == 'delay'; + // } bool _isLastActionDelay(List> actions) { if (actions.isEmpty) return false; return actions.last['deviceId'] == 'delay'; } - Future _onCreateScene( - CreateSceneEvent event, Emitter emit) async { + Future _onCreateScene(CreateSceneEvent event, Emitter emit) async { try { // Check if first action is delay - if (_isFirstActionDelay(state.thenItems)) { - emit(state.copyWith( - errorMessage: 'Cannot have delay as the first action', - isLoading: false, - )); - return; - } + // if (_isFirstActionDelay(state.thenItems)) { + // emit(state.copyWith( + // errorMessage: 'Cannot have delay as the first action', + // isLoading: false, + // )); + // return; + // } if (_isLastActionDelay(state.thenItems)) { emit(state.copyWith( - errorMessage: 'Cannot have delay as the last action', + errorMessage: 'A delay condition cannot be the only or the last action', isLoading: false, )); return; @@ -314,28 +307,31 @@ class RoutineBloc extends Bloc { } } - Future _onCreateAutomation( - CreateAutomationEvent event, Emitter emit) async { + Future _onCreateAutomation(CreateAutomationEvent event, Emitter emit) async { try { if (state.routineName == null || state.routineName!.isEmpty) { emit(state.copyWith( errorMessage: 'Automation name is required', )); + CustomSnackBar.redSnackBar('Automation name is required'); return; } - if (_isFirstActionDelay(state.thenItems)) { - emit(state.copyWith( - errorMessage: 'Cannot have delay as the first action', - isLoading: false, - )); - return; - } + // if (_isFirstActionDelay(state.thenItems)) { + // emit(state.copyWith( + // errorMessage: 'Cannot have delay as the first action', + // isLoading: false, + // )); + // CustomSnackBar.redSnackBar('Cannot have delay as the first action'); + + // return; + // } if (_isLastActionDelay(state.thenItems)) { emit(state.copyWith( - errorMessage: 'Cannot have delay as the last action', + errorMessage: 'A delay condition cannot be the only or the last action', isLoading: false, )); + CustomSnackBar.redSnackBar('Cannot have delay as the last action'); return; } emit(state.copyWith(isLoading: true, errorMessage: null)); @@ -430,30 +426,28 @@ class RoutineBloc extends Bloc { isLoading: false, errorMessage: result['message'], )); + CustomSnackBar.redSnackBar('Something went wrong'); } } catch (e) { emit(state.copyWith( isLoading: false, errorMessage: 'Something went wrong', )); + CustomSnackBar.redSnackBar('Something went wrong'); } } - FutureOr _onRemoveDragCard( - RemoveDragCard event, Emitter emit) { + FutureOr _onRemoveDragCard(RemoveDragCard event, Emitter emit) { if (event.isFromThen) { final thenItems = List>.from(state.thenItems); - final selectedFunctions = - Map>.from(state.selectedFunctions); + final selectedFunctions = Map>.from(state.selectedFunctions); thenItems.removeAt(event.index); selectedFunctions.remove(event.key); - emit(state.copyWith( - thenItems: thenItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions)); } else { final ifItems = List>.from(state.ifItems); - final selectedFunctions = - Map>.from(state.selectedFunctions); + final selectedFunctions = Map>.from(state.selectedFunctions); ifItems.removeAt(event.index); selectedFunctions.remove(event.key); @@ -464,8 +458,7 @@ class RoutineBloc extends Bloc { isAutomation: false, isTabToRun: false)); } else { - emit(state.copyWith( - ifItems: ifItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); } } } @@ -477,23 +470,18 @@ class RoutineBloc extends Bloc { )); } - FutureOr _onEffectiveTimeEvent( - EffectiveTimePeriodEvent event, Emitter emit) { + FutureOr _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter emit) { emit(state.copyWith(effectiveTime: event.effectiveTime)); } - FutureOr _onSetRoutineName( - SetRoutineName event, Emitter emit) { + FutureOr _onSetRoutineName(SetRoutineName event, Emitter emit) { emit(state.copyWith( routineName: event.name, )); } - ( - List>, - List>, - Map> - ) _createCardData( + (List>, List>, Map>) + _createCardData( List actions, List? conditions, Map> currentFunctions, @@ -526,8 +514,7 @@ class RoutineBloc extends Bloc { 'deviceId': condition.entityId, 'title': matchingDevice.name ?? condition.entityId, 'productType': condition.entityType, - 'imagePath': - matchingDevice.getDefaultIcon(condition.entityType), + 'imagePath': matchingDevice.getDefaultIcon(condition.entityType), }; final functions = matchingDevice.functions; @@ -563,11 +550,8 @@ class RoutineBloc extends Bloc { final cardData = { 'entityId': action.entityId, 'uniqueCustomId': const Uuid().v4(), - 'deviceId': - action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'title': action.actionExecutor == 'delay' - ? 'Delay' - : (matchingDevice.name ?? 'Device'), + 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'title': action.actionExecutor == 'delay' ? 'Delay' : (matchingDevice.name ?? 'Device'), 'productType': action.productType, 'imagePath': matchingDevice.getDefaultIcon(action.productType), }; @@ -610,8 +594,7 @@ class RoutineBloc extends Bloc { return (thenItems, ifItems, currentFunctions); } - Future _onGetSceneDetails( - GetSceneDetails event, Emitter emit) async { + Future _onGetSceneDetails(GetSceneDetails event, Emitter emit) async { try { emit(state.copyWith( isLoading: true, @@ -659,12 +642,10 @@ class RoutineBloc extends Bloc { if (!deviceCards.containsKey(deviceId)) { deviceCards[deviceId] = { 'entityId': action.entityId, - 'deviceId': - action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'uniqueCustomId': - action.type == 'automation' || action.actionExecutor == 'delay' - ? const Uuid().v4() - : action.entityId, + 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay' + ? const Uuid().v4() + : action.entityId, 'title': action.actionExecutor == 'delay' ? 'Delay' : action.type == 'automation' @@ -699,8 +680,7 @@ class RoutineBloc extends Bloc { ), ); emit(state.copyWith(automationActionExecutor: action.actionExecutor)); - } else if (action.executorProperty != null && - action.actionExecutor != 'delay') { + } else if (action.executorProperty != null && action.actionExecutor != 'delay') { if (!updatedFunctions.containsKey(uniqueCustomId)) { updatedFunctions[uniqueCustomId] = []; } @@ -785,8 +765,7 @@ class RoutineBloc extends Bloc { thenItems: [], )); - final automationDetails = - await SceneApi.getAutomationDetails(event.automationId); + final automationDetails = await SceneApi.getAutomationDetails(event.automationId); final List> thenItems; final List> ifItems; @@ -805,16 +784,13 @@ class RoutineBloc extends Bloc { final cardData = { 'entityId': condition.entityId, 'uniqueCustomId': const Uuid().v4(), - 'deviceId': condition.expr.statusCode == 'delay' - ? 'delay' - : condition.entityId, + 'deviceId': condition.expr.statusCode == 'delay' ? 'delay' : condition.entityId, 'title': condition.expr.statusCode == 'delay' ? 'Delay' : (matchingDevice?.name ?? 'Device'), 'productType': condition.productType, 'functions': matchingDevice?.functions ?? [], - 'imagePath': - matchingDevice?.getDefaultIcon(condition.productType) ?? '', + 'imagePath': matchingDevice?.getDefaultIcon(condition.productType) ?? '', 'device': matchingDevice, }; @@ -851,19 +827,15 @@ class RoutineBloc extends Bloc { final cardData = { 'entityId': action.entityId, 'uniqueCustomId': const Uuid().v4(), - 'deviceId': - action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'title': action.actionExecutor == 'delay' - ? 'Delay' - : (matchingDevice.name ?? 'Device'), + 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'title': action.actionExecutor == 'delay' ? 'Delay' : (matchingDevice.name ?? 'Device'), 'productType': action.productType, 'functions': matchingDevice.functions, 'imagePath': matchingDevice.getDefaultIcon(action.productType), 'device': matchingDevice, }; - if (action.executorProperty != null && - action.actionExecutor != 'delay') { + if (action.executorProperty != null && action.actionExecutor != 'delay') { final functions = matchingDevice.functions; final functionCode = action.executorProperty!.functionCode; for (var function in functions) { @@ -926,8 +898,7 @@ class RoutineBloc extends Bloc { } } - FutureOr _onResetRoutineState( - ResetRoutineState event, Emitter emit) { + FutureOr _onResetRoutineState(ResetRoutineState event, Emitter emit) { emit(state.copyWith( ifItems: [], thenItems: [], @@ -955,8 +926,7 @@ class RoutineBloc extends Bloc { if (state.isTabToRun) { SceneApi.deleteScene(unitUuid: spaceId, sceneId: state.sceneId ?? ''); } else { - SceneApi.deleteAutomation( - unitUuid: spaceId, automationId: state.automationId ?? ''); + SceneApi.deleteAutomation(unitUuid: spaceId, automationId: state.automationId ?? ''); } add(const LoadScenes(spaceId, communityId)); @@ -985,8 +955,7 @@ class RoutineBloc extends Bloc { // } // } - FutureOr _fetchDevices( - FetchDevicesInRoutine event, Emitter emit) async { + FutureOr _fetchDevices(FetchDevicesInRoutine event, Emitter emit) async { emit(state.copyWith(isLoading: true)); try { final devices = await DevicesManagementApi().fetchDevices(); @@ -997,22 +966,21 @@ class RoutineBloc extends Bloc { } } - FutureOr _onUpdateScene( - UpdateScene event, Emitter emit) async { + FutureOr _onUpdateScene(UpdateScene event, Emitter emit) async { try { // Check if first action is delay - if (_isFirstActionDelay(state.thenItems)) { - emit(state.copyWith( - errorMessage: 'Cannot have delay as the first action', - isLoading: false, - )); + // if (_isFirstActionDelay(state.thenItems)) { + // emit(state.copyWith( + // errorMessage: 'Cannot have delay as the first action', + // isLoading: false, + // )); - return; - } + // return; + // } if (_isLastActionDelay(state.thenItems)) { emit(state.copyWith( - errorMessage: 'Cannot have delay as the last action', + errorMessage: 'A delay condition cannot be the only or the last action', isLoading: false, )); return; @@ -1065,8 +1033,7 @@ class RoutineBloc extends Bloc { actions: actions, ); - final result = - await SceneApi.updateScene(createSceneModel, state.sceneId ?? ''); + final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? ''); if (result['success']) { add(ResetRoutineState()); add(const LoadScenes(spaceId, communityId)); @@ -1085,21 +1052,19 @@ class RoutineBloc extends Bloc { } } - FutureOr _onUpdateAutomation( - UpdateAutomation event, Emitter emit) async { - if (_isFirstActionDelay(state.thenItems)) { - emit(state.copyWith( - errorMessage: 'Cannot have delay as the first action', - isLoading: false, - )); - return; - } + FutureOr _onUpdateAutomation(UpdateAutomation event, Emitter emit) async { + // if (_isFirstActionDelay(state.thenItems)) { + // emit(state.copyWith( + // errorMessage: 'Cannot have delay as the first action', + // isLoading: false, + // )); + // return; + // } emit(state.copyWith(isLoading: true)); try { final conditions = state.ifItems .map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; if (functions.isEmpty) return null; final function = functions.first; @@ -1119,8 +1084,7 @@ class RoutineBloc extends Bloc { final actions = state.thenItems .map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; if (functions.isEmpty) return null; final function = functions.first; @@ -1131,9 +1095,8 @@ class RoutineBloc extends Bloc { executorProperty: ExecutorProperty( functionCode: function.functionCode, functionValue: function.value, - delaySeconds: function.functionCode == 'delay' - ? (function.value as num).toInt() - : null, + delaySeconds: + function.functionCode == 'delay' ? (function.value as num).toInt() : null, ), ); }) @@ -1144,14 +1107,13 @@ class RoutineBloc extends Bloc { spaceUuid: spaceId, automationName: state.routineName ?? '', decisionExpr: state.selectedAutomationOperator, - effectiveTime: - state.effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), + effectiveTime: state.effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), conditions: conditions, actions: actions, ); - final result = await SceneApi.updateAutomation( - createAutomationModel, state.automationId ?? ''); + final result = + await SceneApi.updateAutomation(createAutomationModel, state.automationId ?? ''); if (result['success']) { add(ResetRoutineState()); @@ -1173,7 +1135,6 @@ class RoutineBloc extends Bloc { FutureOr _onSetAutomationActionExecutor( SetAutomationActionExecutor event, Emitter emit) { - emit(state.copyWith( - automationActionExecutor: event.automationActionExecutor)); + emit(state.copyWith(automationActionExecutor: event.automationActionExecutor)); } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 1f703c92..f3d35eb8 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -206,3 +206,5 @@ class FetchDevicesInRoutine extends RoutineEvent {} class ResetRoutineState extends RoutineEvent {} class ClearFunctions extends RoutineEvent {} + +class ResetErrorMessage extends RoutineEvent {} diff --git a/lib/pages/routiens/helper/save_routine_helper.dart b/lib/pages/routiens/helper/save_routine_helper.dart index 3bd5a5d2..6a725145 100644 --- a/lib/pages/routiens/helper/save_routine_helper.dart +++ b/lib/pages/routiens/helper/save_routine_helper.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -6,6 +8,7 @@ import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class SaveRoutineHelper { static Future showSaveRoutineDialog(BuildContext context) async { @@ -98,18 +101,29 @@ class SaveRoutineHelper { final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; return ListTile( - leading: SvgPicture.asset( - item['imagePath'], - width: 22, - height: 22, + leading: item['type'] == 'tap_to_run' + ? Image.memory( + base64Decode(item['icon']), + width: 22, + height: 22, + ) + : SvgPicture.asset( + item['imagePath'], + width: 22, + height: 22, + ), + title: Text( + item['title'], + style: context.textTheme.bodySmall?.copyWith( + fontSize: 14, + color: ColorsManager.grayColor, + ), ), - title: - Text(item['title'], style: const TextStyle(fontSize: 14)), subtitle: Wrap( children: functions .map((f) => Text( '${f.operationName}: ${f.value}, ', - style: const TextStyle( + style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.grayColor, fontSize: 8), overflow: TextOverflow.ellipsis, maxLines: 3, @@ -124,17 +138,17 @@ class SaveRoutineHelper { ], ), ), - if (state.errorMessage != null || state.errorMessage!.isNotEmpty) - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - state.errorMessage!, - style: const TextStyle(color: Colors.red), - ), - ), + // if (state.errorMessage != null || state.errorMessage!.isNotEmpty) + // Padding( + // padding: const EdgeInsets.all(8.0), + // child: Text( + // state.errorMessage!, + // style: const TextStyle(color: Colors.red), + // ), + // ), DialogFooter( onCancel: () => Navigator.pop(context), - onConfirm: () { + onConfirm: () async { if (state.isAutomation) { if (state.automationId != null) { context.read().add(const UpdateAutomation()); @@ -148,10 +162,9 @@ class SaveRoutineHelper { context.read().add(const CreateSceneEvent()); } } - if (context.read().state.errorMessage == null || - context.read().state.errorMessage!.isEmpty) { - Navigator.pop(context); - } + // if (state.errorMessage == null || state.errorMessage!.isEmpty) { + Navigator.pop(context); + // } }, isConfirmEnabled: true, ), diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 1eddf841..abea6593 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -32,15 +32,13 @@ class DraggableCard extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final deviceFunctions = - state.selectedFunctions[deviceData['uniqueCustomId']] ?? []; + final deviceFunctions = state.selectedFunctions[deviceData['uniqueCustomId']] ?? []; return Draggable>( data: deviceData, feedback: Transform.rotate( angle: -0.1, - child: - _buildCardContent(context, deviceFunctions, padding: padding), + child: _buildCardContent(context, deviceFunctions, padding: padding), ), childWhenDragging: _buildGreyContainer(), child: _buildCardContent(context, deviceFunctions, padding: padding), @@ -49,8 +47,7 @@ class DraggableCard extends StatelessWidget { ); } - Widget _buildCardContent( - BuildContext context, List deviceFunctions, + Widget _buildCardContent(BuildContext context, List deviceFunctions, {EdgeInsetsGeometry? padding}) { return Stack( children: [ @@ -79,17 +76,13 @@ class DraggableCard extends StatelessWidget { ), ), padding: const EdgeInsets.all(8), - child: imagePath.contains('.svg') - ? SvgPicture.asset( - imagePath, + child: deviceData['type'] == 'tap_to_run' + ? Image.memory( + base64Decode(deviceData['icon']), ) - : imagePath.contains('.png') - ? Image.asset( - imagePath, - ) - : Image.memory( - base64Decode(imagePath), - ), + : SvgPicture.asset( + imagePath, + ), ), const SizedBox(height: 8), Padding( @@ -152,8 +145,7 @@ class DraggableCard extends StatelessWidget { } String _formatFunctionValue(DeviceFunctionData function) { - if (function.functionCode == 'temp_set' || - function.functionCode == 'temp_current') { + if (function.functionCode == 'temp_set' || function.functionCode == 'temp_current') { return '${(function.value / 10).toStringAsFixed(0)}°C'; } else if (function.functionCode.contains('countdown')) { final seconds = function.value.toInt(); diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index 4f4165d5..f10694b6 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -43,6 +43,13 @@ class _RoutineSearchAndButtonsState extends State { return Wrap( runSpacing: 16, children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + state.errorMessage ?? '', + style: const TextStyle(color: Colors.red), + ), + ), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -214,6 +221,7 @@ class _RoutineSearchAndButtonsState extends State { } // final result = // await + BlocProvider.of(context).add(ResetErrorMessage()); SaveRoutineHelper.showSaveRoutineDialog(context); // if (result != null && result) { // BlocProvider.of(context).add( @@ -341,6 +349,7 @@ class _RoutineSearchAndButtonsState extends State { } // final result = // await + BlocProvider.of(context).add(ResetErrorMessage()); SaveRoutineHelper.showSaveRoutineDialog(context); // if (result != null && result) { // BlocProvider.of(context).add( diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index d959c372..44d4aba8 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -26,9 +26,7 @@ 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), state.isLoading && state.isUpdate == true ? const Center( @@ -41,17 +39,12 @@ class ThenContainer extends StatelessWidget { state.thenItems.length, (index) => GestureDetector( onTap: () async { - if (state.thenItems[index] - ['deviceId'] == - 'delay') { - final result = await DelayHelper - .showDelayPickerDialog(context, - state.thenItems[index]); + if (state.thenItems[index]['deviceId'] == 'delay') { + final result = await DelayHelper.showDelayPickerDialog( + context, state.thenItems[index]); if (result != null) { - context - .read() - .add(AddToThenContainer({ + context.read().add(AddToThenContainer({ ...state.thenItems[index], 'imagePath': Assets.delay, 'title': 'Delay', @@ -60,76 +53,57 @@ class ThenContainer extends StatelessWidget { return; } - if (state.thenItems[index]['type'] == - 'automation') { + if (state.thenItems[index]['type'] == 'automation') { final result = await showDialog( context: context, - builder: (BuildContext context) => - AutomationDialog( + builder: (BuildContext context) => AutomationDialog( automationName: - state.thenItems[index] - ['name'] ?? - 'Automation', + state.thenItems[index]['name'] ?? 'Automation', automationId: - state.thenItems[index] - ['deviceId'] ?? - '', - uniqueCustomId: - state.thenItems[index] - ['uniqueCustomId'], + state.thenItems[index]['deviceId'] ?? '', + uniqueCustomId: state.thenItems[index] + ['uniqueCustomId'], ), ); if (result != null) { - context - .read() - .add(AddToThenContainer({ + context.read().add(AddToThenContainer({ ...state.thenItems[index], - 'imagePath': - Assets.automation, - 'title': state - .thenItems[index]['name'], + 'imagePath': Assets.automation, + 'title': state.thenItems[index]['name'], })); } return; } - final result = await DeviceDialogHelper - .showDeviceDialog( - context, state.thenItems[index], - removeComparetors: true); + final result = await DeviceDialogHelper.showDeviceDialog( + context, state.thenItems[index], + removeComparetors: true); if (result != null) { - context.read().add( - AddToThenContainer( - state.thenItems[index])); + context + .read() + .add(AddToThenContainer(state.thenItems[index])); } else if (!['AC', '1G', '2G', '3G'] - .contains(state.thenItems[index] - ['productType'])) { - context.read().add( - AddToThenContainer( - state.thenItems[index])); + .contains(state.thenItems[index]['productType'])) { + context + .read() + .add(AddToThenContainer(state.thenItems[index])); } }, child: DraggableCard( - imagePath: state.thenItems[index] - ['imagePath'] ?? - '', - title: state.thenItems[index] - ['title'] ?? - '', + imagePath: state.thenItems[index]['imagePath'] ?? '', + title: state.thenItems[index]['title'] ?? '', deviceData: state.thenItems[index], - padding: const EdgeInsets.symmetric( - horizontal: 4, vertical: 8), + padding: + const EdgeInsets.symmetric(horizontal: 4, vertical: 8), isFromThen: true, isFromIf: false, onRemove: () { - context.read().add( - RemoveDragCard( - index: index, - isFromThen: true, - key: state.thenItems[index] - ['uniqueCustomId'])); + context.read().add(RemoveDragCard( + index: index, + isFromThen: true, + key: state.thenItems[index]['uniqueCustomId'])); }, ), ))), @@ -149,6 +123,12 @@ class ThenContainer extends StatelessWidget { } if (mutableData['type'] == 'automation') { + int index = + state.thenItems.indexWhere((item) => item['deviceId'] == mutableData['deviceId']); + if (index != -1) { + return; + } + final result = await showDialog( context: context, builder: (BuildContext context) => AutomationDialog( @@ -169,6 +149,11 @@ class ThenContainer extends StatelessWidget { } if (mutableData['type'] == 'tap_to_run' && state.isAutomation) { + int index = + state.thenItems.indexWhere((item) => item['deviceId'] == mutableData['deviceId']); + if (index != -1) { + return; + } context.read().add(AddToThenContainer({ ...mutableData, 'imagePath': Assets.logo, @@ -183,8 +168,7 @@ class ThenContainer extends StatelessWidget { } if (mutableData['deviceId'] == 'delay') { - final result = - await DelayHelper.showDelayPickerDialog(context, mutableData); + final result = await DelayHelper.showDelayPickerDialog(context, mutableData); if (result != null) { context.read().add(AddToThenContainer({ @@ -196,13 +180,11 @@ class ThenContainer extends StatelessWidget { return; } - final result = await DeviceDialogHelper.showDeviceDialog( - context, mutableData, + final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData, removeComparetors: true); if (result != null) { context.read().add(AddToThenContainer(mutableData)); - } else if (!['AC', '1G', '2G', '3G'] - .contains(mutableData['productType'])) { + } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) { context.read().add(AddToThenContainer(mutableData)); } }, diff --git a/lib/utils/snack_bar.dart b/lib/utils/snack_bar.dart index d50a4250..0a312e5a 100644 --- a/lib/utils/snack_bar.dart +++ b/lib/utils/snack_bar.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/navigation_service.dart'; class CustomSnackBar { @@ -11,6 +12,35 @@ class CustomSnackBar { } } + static redSnackBar(String message) { + final key = NavigationService.snackbarKey; + BuildContext? currentContext = key?.currentContext; + if (key != null && currentContext != null) { + final snackBar = SnackBar( + padding: const EdgeInsets.all(16), + backgroundColor: ColorsManager.red, + content: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + const Icon( + Icons.check_circle, + color: ColorsManager.whiteColors, + size: 32, + ), + const SizedBox( + width: 8, + ), + Text( + message, + style: Theme.of(currentContext) + .textTheme + .bodySmall! + .copyWith(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green), + ) + ]), + ); + key.currentState?.showSnackBar(snackBar); + } + } + static greenSnackBar(String message) { final key = NavigationService.snackbarKey; BuildContext? currentContext = key?.currentContext; @@ -29,8 +59,10 @@ class CustomSnackBar { ), Text( message, - style: Theme.of(currentContext).textTheme.bodySmall!.copyWith( - fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green), + style: Theme.of(currentContext) + .textTheme + .bodySmall! + .copyWith(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green), ) ]), ); From c58f2eb24e9cc6253958b2c5d5b0897f4c8fc69d Mon Sep 17 00:00:00 2001 From: ashraf_personal Date: Tue, 3 Dec 2024 00:29:59 +0300 Subject: [PATCH 3/3] fixed autoamtion update --- .../bloc/routine_bloc/routine_bloc.dart | 508 ++++++++++-------- .../bloc/routine_bloc/routine_state.dart | 19 +- .../create_automation_model.dart | 3 +- .../routine_dialogs/automation_dialog.dart | 166 +++--- .../routiens/widgets/then_container.dart | 9 +- 5 files changed, 399 insertions(+), 306 deletions(-) diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 9cb3adb5..673b439c 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -44,7 +44,6 @@ class RoutineBloc extends Bloc { on(_fetchDevices); on(_onUpdateScene); on(_onUpdateAutomation); - on(_onSetAutomationActionExecutor); on(_triggerSwitchTabsEvent); on(_createNewRoutineViewEvent); } @@ -368,21 +367,25 @@ class RoutineBloc extends Bloc { } final actions = state.thenItems.expand((item) { + if (item['type'] == 'tap_to_run') { + return [ + AutomationAction( + actionType: 'scene', + entityId: item['deviceId'], + actionExecutor: 'rule_trigger', + ), + ]; + } + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + return functions.map((function) { if (function.functionCode == 'automation') { return AutomationAction( actionType: 'automation', entityId: function.entityId, actionExecutor: function.value, - ); - } - - if (function.functionCode == 'tap_to_run') { - return AutomationAction( - actionType: 'scene', - entityId: function.entityId, - actionExecutor: function.value, + executorProperty: null, ); } @@ -698,7 +701,7 @@ class RoutineBloc extends Bloc { operationName: 'Automation', ), ); - emit(state.copyWith(automationActionExecutor: action.actionExecutor)); + // emit(state.copyWith(automationActionExecutor: action.actionExecutor)); } else if (action.executorProperty != null && action.actionExecutor != 'delay') { if (!updatedFunctions.containsKey(uniqueCustomId)) { @@ -772,160 +775,6 @@ class RoutineBloc extends Bloc { } } - Future _onGetAutomationDetails( - GetAutomationDetails event, Emitter emit) async { - try { - emit(state.copyWith( - isLoading: true, - isUpdate: true, - isTabToRun: false, - automationId: event.automationId, - isAutomation: true, - ifItems: [], - thenItems: [], - )); - - final automationDetails = - await SceneApi.getAutomationDetails(event.automationId); - - final List> thenItems; - final List> ifItems; - final Map> updatedFunctions = - Map>.from(state.selectedFunctions); - - ifItems = automationDetails.conditions?.map((condition) { - late AllDevicesModel? matchingDevice; - for (var device in state.devices) { - if (device.uuid == condition.entityId) { - matchingDevice = device; - break; - } - } - - final cardData = { - 'entityId': condition.entityId, - 'uniqueCustomId': const Uuid().v4(), - 'deviceId': condition.expr.statusCode == 'delay' - ? 'delay' - : condition.entityId, - 'title': condition.expr.statusCode == 'delay' - ? 'Delay' - : (matchingDevice?.name ?? 'Device'), - 'productType': condition.productType, - 'functions': matchingDevice?.functions ?? [], - 'imagePath': - matchingDevice?.getDefaultIcon(condition.productType) ?? '', - 'device': matchingDevice, - }; - - final functions = matchingDevice?.functions ?? []; - for (var function in functions) { - if (function.code == condition.expr.statusCode) { - updatedFunctions[cardData['uniqueCustomId'].toString()] = [ - DeviceFunctionData( - entityId: condition.entityId, - functionCode: condition.expr.statusCode, - value: condition.expr.statusValue, - operationName: function.operationName, - ), - ]; - break; - } - } - - return cardData; - }).toList() ?? - []; - - // Create then items from actions - thenItems = automationDetails.actions.map((action) { - final matchingDevice = state.devices.firstWhere( - (device) => device.uuid == action.entityId, - orElse: () => AllDevicesModel( - uuid: action.entityId, - name: action.entityId, - productType: action.productType, - ), - ); - - final cardData = { - 'entityId': action.entityId, - 'uniqueCustomId': const Uuid().v4(), - 'deviceId': - action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'title': action.actionExecutor == 'delay' - ? 'Delay' - : (matchingDevice.name ?? 'Device'), - 'productType': action.productType, - 'functions': matchingDevice.functions, - 'imagePath': matchingDevice.getDefaultIcon(action.productType), - 'device': matchingDevice, - }; - - if (action.executorProperty != null && - action.actionExecutor != 'delay') { - final functions = matchingDevice.functions; - final functionCode = action.executorProperty!.functionCode; - for (var function in functions) { - if (function.code == functionCode) { - updatedFunctions[cardData['uniqueCustomId'].toString()] = [ - DeviceFunctionData( - entityId: action.entityId, - functionCode: functionCode ?? '', - value: action.executorProperty!.functionValue, - operationName: function.operationName, - ), - ]; - break; - } - } - } else if (action.actionExecutor == 'delay') { - final delayFunction = DelayFunction( - deviceId: action.entityId, - deviceName: 'Delay', - ); - updatedFunctions[cardData['uniqueCustomId'].toString()] = [ - DeviceFunctionData( - entityId: action.entityId, - functionCode: 'delay', - value: action.executorProperty?.delaySeconds ?? 0, - operationName: delayFunction.operationName, - ), - ]; - } else if (action.actionExecutor == 'rule_disable' || - action.actionExecutor == 'rule_enable') { - updatedFunctions[cardData['uniqueCustomId'].toString()] = [ - DeviceFunctionData( - entityId: action.entityId, - functionCode: 'automation', - value: action.actionExecutor, - operationName: action.name ?? 'Automation', - ), - ]; - } - - return cardData; - }).toList(); - - emit(state.copyWith( - isLoading: false, - routineName: automationDetails.name, - selectedAutomationOperator: automationDetails.decisionExpr, - effectiveTime: automationDetails.effectiveTime, - isAutomation: true, - thenItems: thenItems, - ifItems: ifItems, - selectedFunctions: updatedFunctions, - automationId: automationDetails.automationId, - )); - } catch (e) { - emit(state.copyWith( - isLoading: false, - errorMessage: 'Failed to load automation details: $e', - )); - } - } - FutureOr _onResetRoutineState( ResetRoutineState event, Emitter emit) { emit(state.copyWith( @@ -1087,65 +936,112 @@ class RoutineBloc extends Bloc { FutureOr _onUpdateAutomation( UpdateAutomation event, Emitter emit) async { - if (_isFirstActionDelay(state.thenItems)) { - emit(state.copyWith( - errorMessage: 'Cannot have delay as the first action', - isLoading: false, - )); - return; - } - emit(state.copyWith(isLoading: true)); try { - final conditions = state.ifItems - .map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; - if (functions.isEmpty) return null; + if (state.routineName == null || state.routineName!.isEmpty) { + emit(state.copyWith( + errorMessage: 'Automation name is required', + )); + return; + } + if (_isFirstActionDelay(state.thenItems)) { + emit(state.copyWith( + errorMessage: 'Cannot have delay as the first action', + isLoading: false, + )); + return; + } - final function = functions.first; - return Condition( - code: state.ifItems.indexOf(item) + 1, - entityId: function.entityId, - entityType: item['productType'], - expr: ConditionExpr( - statusCode: function.functionCode, - statusValue: function.value, - comparator: function.condition ?? '==', - ), - ); - }) - .whereType() - .toList(); + if (_isLastActionDelay(state.thenItems)) { + emit(state.copyWith( + errorMessage: 'Cannot have delay as the last action', + isLoading: false, + )); + return; + } + emit(state.copyWith(isLoading: true, errorMessage: null)); - final actions = state.thenItems - .map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; - if (functions.isEmpty) return null; + final conditions = state.ifItems.expand((item) { + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + return functions.map((function) { + return Condition( + code: state.selectedFunctions[item['uniqueCustomId']]!.indexOf( + function, + ) + + 1, + entityId: function.entityId, + entityType: 'device_report', + expr: ConditionExpr( + statusCode: function.functionCode, + comparator: function.condition ?? '==', + statusValue: function.value, + ), + ); + }); + }).toList(); - final function = functions.first; + if (conditions.isEmpty) { + emit(state.copyWith( + isLoading: false, + errorMessage: 'At least one condition is required', + )); + return; + } + + final actions = state.thenItems.expand((item) { + if (item['type'] == 'tap_to_run' || item['type'] == 'scene') { + return [ + AutomationAction( + actionType: 'scene', + entityId: item['deviceId'], + actionExecutor: 'rule_trigger', + ), + ]; + } + + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + + return functions.map((function) { + if (function.functionCode == 'automation') { return AutomationAction( actionType: 'automation', entityId: function.entityId, - actionExecutor: function.actionExecutor, + actionExecutor: function.value, + executorProperty: null, + ); + } + + if (item['deviceId'] == 'delay') { + return AutomationAction( + entityId: function.entityId, + actionType: 'automation', + actionExecutor: 'delay', executorProperty: ExecutorProperty( - functionCode: function.functionCode, - functionValue: function.value, - delaySeconds: function.functionCode == 'delay' - ? (function.value as num).toInt() - : null, + delaySeconds: int.tryParse(function.value.toString()) ?? 0, ), ); - }) - .whereType() - .toList(); + } + + return AutomationAction( + entityId: function.entityId, + actionExecutor: 'device_issue', + actionType: 'automation', + executorProperty: ExecutorProperty( + functionCode: function.functionCode, + functionValue: function.value, + ), + ); + }); + }).toList(); final createAutomationModel = CreateAutomationModel( spaceUuid: spaceId, automationName: state.routineName ?? '', decisionExpr: state.selectedAutomationOperator, - effectiveTime: - state.effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), + effectiveTime: EffectiveTime( + start: state.effectiveTime?.start ?? '00:00', + end: state.effectiveTime?.end ?? '23:59', + loops: state.effectiveTime?.loops ?? '1111111', + ), conditions: conditions, actions: actions, ); @@ -1155,8 +1051,8 @@ class RoutineBloc extends Bloc { if (result['success']) { add(ResetRoutineState()); - add(const LoadScenes(spaceId, communityId)); add(const LoadAutomation(spaceId)); + add(const LoadScenes(spaceId, communityId)); } else { emit(state.copyWith( isLoading: false, @@ -1166,14 +1062,198 @@ class RoutineBloc extends Bloc { } catch (e) { emit(state.copyWith( isLoading: false, - errorMessage: 'Failed to update automation: $e', + errorMessage: 'Something went wrong', )); } } - FutureOr _onSetAutomationActionExecutor( - SetAutomationActionExecutor event, Emitter emit) { - emit(state.copyWith( - automationActionExecutor: event.automationActionExecutor)); + Future _onGetAutomationDetails( + GetAutomationDetails event, Emitter emit) async { + try { + emit(state.copyWith( + isLoading: true, + isUpdate: true, + isTabToRun: false, + automationId: event.automationId, + isAutomation: true, + ifItems: [], + thenItems: [], + )); + + final automationDetails = + await SceneApi.getAutomationDetails(event.automationId); + + final Map> deviceCards = {}; + final Map> updatedFunctions = + Map>.from(state.selectedFunctions); + + for (RoutineCondition condition in automationDetails.conditions ?? []) { + AllDevicesModel? matchingDevice = state.devices.firstWhere( + (device) => device.uuid == condition.entityId, + orElse: () => AllDevicesModel( + uuid: condition.entityId, + name: condition.entityId, + productType: condition.productType, + ), + ); + + final deviceId = condition.entityId; + + if (!deviceCards.containsKey(deviceId)) { + deviceCards[deviceId] = { + 'entityId': condition.entityId, + 'deviceId': condition.entityId, + 'uniqueCustomId': const Uuid().v4(), + 'title': matchingDevice.name ?? 'Device', + 'productType': condition.productType, + 'functions': matchingDevice.functions, + 'imagePath': matchingDevice.getDefaultIcon(condition.productType), + 'device': matchingDevice, + 'type': 'condition', + }; + } + + final cardData = deviceCards[deviceId]!; + final uniqueCustomId = cardData['uniqueCustomId'].toString(); + + if (!updatedFunctions.containsKey(uniqueCustomId)) { + updatedFunctions[uniqueCustomId] = []; + } + + final functions = matchingDevice.functions; + for (var function in functions) { + if (function.code == condition.expr.statusCode) { + updatedFunctions[uniqueCustomId]!.add( + DeviceFunctionData( + entityId: condition.entityId, + functionCode: condition.expr.statusCode, + value: condition.expr.statusValue, + operationName: function.operationName, + ), + ); + break; + } + } + } + + // Process actions (thenItems) + for (var action in automationDetails.actions) { + AllDevicesModel? matchingDevice = state.devices.firstWhere( + (device) => device.uuid == action.entityId, + orElse: () => AllDevicesModel( + uuid: action.entityId, + name: action.entityId, + productType: action.productType, + ), + ); + + final deviceId = action.actionExecutor == 'delay' + ? '${action.entityId}_delay' + : action.entityId; + + if (!deviceCards.containsKey(deviceId)) { + deviceCards[deviceId] = { + 'entityId': action.entityId, + 'deviceId': + action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'uniqueCustomId': const Uuid().v4(), + 'title': action.actionExecutor == 'delay' + ? 'Delay' + : (action.type == 'scene' || action.type == 'automation') + ? action.name + : (matchingDevice.name ?? 'Device'), + 'productType': action.productType, + 'functions': matchingDevice.functions, + 'imagePath': action.actionExecutor == 'delay' + ? Assets.delay + : action.type == 'automation' + ? Assets.automation + : matchingDevice.getDefaultIcon(action.productType), + 'device': matchingDevice, + 'type': action.type == 'scene' + ? 'scene' + : action.type == 'automation' + ? 'automation' + : 'action', + }; + } + + final cardData = deviceCards[deviceId]!; + final uniqueCustomId = cardData['uniqueCustomId'].toString(); + + if (!updatedFunctions.containsKey(uniqueCustomId)) { + updatedFunctions[uniqueCustomId] = []; + } + + if (action.executorProperty != null && + action.actionExecutor != 'delay') { + final functions = matchingDevice.functions; + final functionCode = action.executorProperty!.functionCode; + for (var function in functions) { + if (function.code == functionCode) { + updatedFunctions[uniqueCustomId]!.add( + DeviceFunctionData( + entityId: action.entityId, + functionCode: functionCode ?? '', + value: action.executorProperty!.functionValue, + operationName: function.operationName, + ), + ); + break; + } + } + } else if (action.actionExecutor == 'delay') { + final delayFunction = DelayFunction( + deviceId: action.entityId, + deviceName: 'Delay', + ); + updatedFunctions[uniqueCustomId]!.add( + DeviceFunctionData( + entityId: action.entityId, + functionCode: 'delay', + value: action.executorProperty?.delaySeconds ?? 0, + operationName: delayFunction.operationName, + ), + ); + } else if (action.actionExecutor == 'rule_disable' || + action.actionExecutor == 'rule_enable') { + updatedFunctions[uniqueCustomId]!.add( + DeviceFunctionData( + entityId: action.entityId, + functionCode: 'automation', + value: action.actionExecutor, + operationName: action.name ?? 'Automation', + ), + ); + } + } + + final ifItems = deviceCards.values + .where((card) => card['type'] == 'condition') + .toList(); + final thenItems = deviceCards.values + .where((card) => + card['type'] == 'action' || + card['type'] == 'automation' || + card['type'] == 'scene') + .toList(); + + emit(state.copyWith( + isLoading: false, + routineName: automationDetails.name, + selectedAutomationOperator: automationDetails.decisionExpr, + effectiveTime: automationDetails.effectiveTime, + isAutomation: true, + thenItems: thenItems, + ifItems: ifItems, + selectedFunctions: updatedFunctions, + automationId: automationDetails.automationId, + )); + } catch (e) { + emit(state.copyWith( + isLoading: false, + errorMessage: 'Failed to load automation details: $e', + )); + } } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart index e7dce718..4dc26a01 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart @@ -22,7 +22,7 @@ class RoutineState extends Equatable { final String? automationId; final bool? isUpdate; final List devices; - final String? automationActionExecutor; + // final String? automationActionExecutor; final bool routineTab; final bool createRoutineView; @@ -48,7 +48,7 @@ class RoutineState extends Equatable { this.automationId, this.isUpdate, this.devices = const [], - this.automationActionExecutor, + // this.automationActionExecutor, this.routineTab = false, this.createRoutineView = false}); @@ -73,7 +73,7 @@ class RoutineState extends Equatable { String? automationId, bool? isUpdate, List? devices, - String? automationActionExecutor, + // String? automationActionExecutor, TextEditingController? nameController, bool? routineTab, bool? createRoutineView, @@ -88,18 +88,21 @@ class RoutineState extends Equatable { errorMessage: errorMessage ?? this.errorMessage, routineName: routineName ?? this.routineName, selectedIcon: selectedIcon ?? this.selectedIcon, - loadScenesErrorMessage: loadScenesErrorMessage ?? this.loadScenesErrorMessage, - loadAutomationErrorMessage: loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, + loadScenesErrorMessage: + loadScenesErrorMessage ?? this.loadScenesErrorMessage, + loadAutomationErrorMessage: + loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, searchText: searchText ?? this.searchText, isTabToRun: isTabToRun ?? this.isTabToRun, isAutomation: isAutomation ?? this.isAutomation, - selectedAutomationOperator: selectedAutomationOperator ?? this.selectedAutomationOperator, + selectedAutomationOperator: + selectedAutomationOperator ?? this.selectedAutomationOperator, effectiveTime: effectiveTime ?? this.effectiveTime, sceneId: sceneId ?? this.sceneId, automationId: automationId ?? this.automationId, isUpdate: isUpdate ?? this.isUpdate, devices: devices ?? this.devices, - automationActionExecutor: automationActionExecutor ?? this.automationActionExecutor, + // automationActionExecutor: automationActionExecutor ?? this.automationActionExecutor, routineTab: routineTab ?? this.routineTab, createRoutineView: createRoutineView ?? this.createRoutineView); } @@ -126,7 +129,7 @@ class RoutineState extends Equatable { automationId, isUpdate, devices, - automationActionExecutor, + // automationActionExecutor, routineTab, createRoutineView ]; diff --git a/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart b/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart index d76c34de..467c492c 100644 --- a/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart +++ b/lib/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart @@ -152,7 +152,8 @@ class AutomationAction { return { 'entityId': entityId, 'actionExecutor': actionExecutor, - 'executorProperty': executorProperty?.toMap(), + if (executorProperty != null) + 'executorProperty': executorProperty?.toMap(), 'actionType': actionType }; } diff --git a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart index 2950c0cc..84ed8fdd 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart @@ -7,100 +7,106 @@ import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -class AutomationDialog extends StatelessWidget { +class AutomationDialog extends StatefulWidget { final String automationName; final String automationId; final String uniqueCustomId; + final String? passedAutomationActionExecutor; const AutomationDialog({ super.key, required this.automationName, required this.automationId, required this.uniqueCustomId, + this.passedAutomationActionExecutor, }); @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - final String? initialSelection = state.automationActionExecutor; + State createState() => _AutomationDialogState(); +} - return Dialog( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Container( - width: 400, - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - DialogHeader(automationName), - const SizedBox(height: 16), - ListTile( - leading: - SvgPicture.asset(Assets.acPower, width: 24, height: 24), - title: const Text('Enable'), - trailing: Radio( - value: 'rule_enable', - groupValue: initialSelection, - onChanged: (String? value) { - if (value != null) { - context.read().add( - SetAutomationActionExecutor( - automationActionExecutor: value, - ), - ); - } - }, - ), - ), - ListTile( - leading: SvgPicture.asset(Assets.acPowerOff, - width: 24, height: 24), - title: const Text('Disable'), - trailing: Radio( - value: 'rule_disable', - groupValue: initialSelection, - onChanged: (String? value) { - if (value != null) { - context.read().add( - SetAutomationActionExecutor( - automationActionExecutor: value, - ), - ); - } - }, - ), - ), - const SizedBox(height: 16), - DialogFooter( - onConfirm: () { - if (state.automationActionExecutor != null) { - context.read().add( - AddFunctionToRoutine( - [ - DeviceFunctionData( - entityId: automationId, - functionCode: 'automation', - value: state.automationActionExecutor, - operationName: 'Automation', - ), - ], - uniqueCustomId, - ), - ); - Navigator.of(context).pop(true); - } - }, - onCancel: () => Navigator.of(context).pop(false), - isConfirmEnabled: state.automationActionExecutor != null, - dialogWidth: 400, - ), - ], +class _AutomationDialogState extends State { + String? selectedAutomationActionExecutor; + + @override + void initState() { + super.initState(); + List? functions = context + .read() + .state + .selectedFunctions[widget.uniqueCustomId]; + for (DeviceFunctionData data in functions ?? []) { + if (data.entityId == widget.automationId) { + selectedAutomationActionExecutor = data.value; + } + } + } + + @override + Widget build(BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Container( + width: 400, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + DialogHeader(widget.automationName), + const SizedBox(height: 16), + ListTile( + leading: SvgPicture.asset(Assets.acPower, width: 24, height: 24), + title: const Text('Enable'), + trailing: Radio( + value: 'rule_enable', + groupValue: selectedAutomationActionExecutor, + onChanged: (String? value) { + setState(() { + selectedAutomationActionExecutor = 'rule_enable'; + }); + }), ), - ), - ); - }, + ListTile( + leading: + SvgPicture.asset(Assets.acPowerOff, width: 24, height: 24), + title: const Text('Disable'), + trailing: Radio( + value: 'rule_disable', + groupValue: selectedAutomationActionExecutor, + onChanged: (String? value) { + setState(() { + selectedAutomationActionExecutor = 'rule_disable'; + }); + }, + ), + ), + const SizedBox(height: 16), + DialogFooter( + onConfirm: () { + if (selectedAutomationActionExecutor != null) { + context.read().add( + AddFunctionToRoutine( + [ + DeviceFunctionData( + entityId: widget.automationId, + functionCode: 'automation', + value: selectedAutomationActionExecutor, + operationName: 'Automation', + ), + ], + widget.uniqueCustomId, + ), + ); + } + Navigator.of(context).pop(true); + }, + onCancel: () => Navigator.of(context).pop(), + isConfirmEnabled: selectedAutomationActionExecutor != null, + dialogWidth: 400, + ), + ], + ), + ), ); } } diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index d959c372..69a1c278 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -87,8 +87,11 @@ class ThenContainer extends StatelessWidget { ...state.thenItems[index], 'imagePath': Assets.automation, - 'title': state - .thenItems[index]['name'], + 'title': + state.thenItems[index] + ['name'] ?? + state.thenItems[index] + ['title'], })); } return; @@ -171,7 +174,7 @@ class ThenContainer extends StatelessWidget { if (mutableData['type'] == 'tap_to_run' && state.isAutomation) { context.read().add(AddToThenContainer({ ...mutableData, - 'imagePath': Assets.logo, + 'imagePath': mutableData['imagePath'] ?? Assets.logo, 'title': mutableData['name'], }));