diff --git a/lib/features/scene/bloc/create_scene/create_scene_bloc.dart b/lib/features/scene/bloc/create_scene/create_scene_bloc.dart index 60bfc17..de6886e 100644 --- a/lib/features/scene/bloc/create_scene/create_scene_bloc.dart +++ b/lib/features/scene/bloc/create_scene/create_scene_bloc.dart @@ -362,6 +362,7 @@ class CreateSceneBloc extends Bloc tasksList = List.from( getTaskListFunctionsFromApi( actions: response.actions, isAutomation: false)); + emit(AddSceneTask( automationTasksList: automationTasksList, tasksList: tasksList, diff --git a/lib/features/scene/bloc/scene_bloc/scene_bloc.dart b/lib/features/scene/bloc/scene_bloc/scene_bloc.dart index c68f4e9..047e9cb 100644 --- a/lib/features/scene/bloc/scene_bloc/scene_bloc.dart +++ b/lib/features/scene/bloc/scene_bloc/scene_bloc.dart @@ -13,6 +13,7 @@ class SceneBloc extends Bloc { on(_onLoadScenes); on(_onLoadAutomation); on(_onSceneTrigger); + on(_onUpdateAutomationStatus); } List scenes = []; @@ -26,7 +27,7 @@ class SceneBloc extends Bloc { scenes = await SceneApi.getScenesByUnitId(event.unitId); emit(SceneLoaded(scenes, automationList)); } else { - const SceneError(message: ''); + emit(const SceneError(message: 'Unit ID is empty')); } } catch (e) { emit(const SceneError(message: 'Something went wrong')); @@ -53,8 +54,11 @@ class SceneBloc extends Bloc { SceneTrigger event, Emitter emit) async { final currentState = state; if (currentState is SceneLoaded) { - emit(SceneLoaded(currentState.scenes, currentState.automationList, - loadingSceneId: event.sceneId)); + emit(SceneLoaded( + currentState.scenes, + currentState.automationList, + loadingSceneId: event.sceneId, + )); try { final success = await SceneApi.triggerScene(event.sceneId); @@ -69,4 +73,39 @@ class SceneBloc extends Bloc { } } } + + Future _onUpdateAutomationStatus( + UpdateAutomationStatus event, Emitter emit) async { + final currentState = state; + if (currentState is SceneLoaded) { + final newLoadingStates = + Map.from(currentState.loadingStates) + ..[event.automationId] = true; + + emit(SceneLoaded( + currentState.scenes, + currentState.automationList, + loadingStates: newLoadingStates, + )); + + try { + final success = await SceneApi.updateAutomationStatus( + event.automationId, event.automationStatusUpdate); + if (success) { + automationList = await SceneApi.getAutomationByUnitId( + event.automationStatusUpdate.unitUuid); + newLoadingStates[event.automationId] = false; + emit(SceneLoaded( + currentState.scenes, + automationList, + loadingStates: newLoadingStates, + )); + } else { + emit(const SceneError(message: 'Something went wrong')); + } + } catch (e) { + emit(const SceneError(message: 'Something went wrong')); + } + } + } } diff --git a/lib/features/scene/bloc/scene_bloc/scene_event.dart b/lib/features/scene/bloc/scene_bloc/scene_event.dart index 5a22fd5..d580ed4 100644 --- a/lib/features/scene/bloc/scene_bloc/scene_event.dart +++ b/lib/features/scene/bloc/scene_bloc/scene_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/scene/model/update_automation.dart'; abstract class SceneEvent extends Equatable { const SceneEvent(); @@ -34,3 +35,15 @@ class SceneTrigger extends SceneEvent { @override List get props => [sceneId]; } + +//updateAutomationStatus +class UpdateAutomationStatus extends SceneEvent { + final String automationId; + final AutomationStatusUpdate automationStatusUpdate; + + const UpdateAutomationStatus( + {required this.automationStatusUpdate, required this.automationId}); + + @override + List get props => [automationStatusUpdate]; +} diff --git a/lib/features/scene/bloc/scene_bloc/scene_state.dart b/lib/features/scene/bloc/scene_bloc/scene_state.dart index f6b42a3..0b6ac85 100644 --- a/lib/features/scene/bloc/scene_bloc/scene_state.dart +++ b/lib/features/scene/bloc/scene_bloc/scene_state.dart @@ -15,11 +15,14 @@ class SceneLoaded extends SceneState { final List scenes; final List automationList; final String? loadingSceneId; + final Map loadingStates; - const SceneLoaded(this.scenes, this.automationList, {this.loadingSceneId}); + const SceneLoaded(this.scenes, this.automationList, + {this.loadingSceneId, this.loadingStates = const {}}); @override - List get props => [scenes, loadingSceneId, automationList]; + List get props => + [scenes, loadingSceneId, automationList, loadingStates]; } class SceneError extends SceneState { @@ -39,3 +42,7 @@ class SceneTriggerSuccess extends SceneState { @override List get props => [sceneName]; } + +class UpdateAutomationStatusLoading extends SceneState { + const UpdateAutomationStatusLoading(); +} diff --git a/lib/features/scene/helper/scene_logic_helper.dart b/lib/features/scene/helper/scene_logic_helper.dart index 328e089..b1d3086 100644 --- a/lib/features/scene/helper/scene_logic_helper.dart +++ b/lib/features/scene/helper/scene_logic_helper.dart @@ -66,36 +66,40 @@ mixin SceneLogicHelper { ); }, ), - actions: List.generate( - actions.length, - (index) { - final task = actions[index]; - if (task.deviceId == 'delay') { + actions: [ + ...List.generate( + actions.length, + (index) { + final task = actions[index]; + if (task.deviceId == 'delay') { + return CreateSceneAction( + entityId: actions[index].deviceId, + actionExecutor: 'delay', + executorProperty: CreateSceneExecutorProperty( + functionCode: '', + functionValue: '', + delaySeconds: task.functionValue, + ), + ); + } return CreateSceneAction( - entityId: actions[index].deviceId, - actionExecutor: 'delay', + entityId: task.deviceId, + actionExecutor: 'device_issue', executorProperty: CreateSceneExecutorProperty( - functionCode: '', - functionValue: '', - delaySeconds: task.functionValue, + functionCode: task.code, + functionValue: task.functionValue, + delaySeconds: 0, ), ); - } - return CreateSceneAction( - entityId: task.deviceId, - actionExecutor: 'device_issue', - executorProperty: CreateSceneExecutorProperty( - functionCode: task.code, - functionValue: task.functionValue, - delaySeconds: 0, - ), - ); - }, - )..add(CreateSceneAction( - entityId: smartSceneBloc.smartSceneEnable?.entityId ?? '', - actionExecutor: - smartSceneBloc.smartSceneEnable?.actionExecutor ?? '', - executorProperty: null)), + }, + ), + if (smartSceneBloc.smartSceneEnable != null) + CreateSceneAction( + entityId: smartSceneBloc.smartSceneEnable?.entityId ?? '', + actionExecutor: + smartSceneBloc.smartSceneEnable?.actionExecutor ?? '', + executorProperty: null) + ], ); sceneBloc.add(CreateSceneWithTasksEvent( createSceneModel: null, @@ -121,36 +125,40 @@ mixin SceneLogicHelper { unitUuid: HomeCubit.getInstance().selectedSpace!.id ?? '', sceneName: sceneName.text, decisionExpr: 'and', - actions: List.generate( - actions.length, - (index) { - final task = actions[index]; - if (task.deviceId == 'delay') { + actions: [ + ...List.generate( + actions.length, + (index) { + final task = actions[index]; + if (task.deviceId == 'delay') { + return CreateSceneAction( + entityId: actions[index].deviceId, + actionExecutor: 'delay', + executorProperty: CreateSceneExecutorProperty( + functionCode: '', + functionValue: '', + delaySeconds: task.functionValue, + ), + ); + } return CreateSceneAction( - entityId: actions[index].deviceId, - actionExecutor: 'delay', + entityId: task.deviceId, + actionExecutor: 'device_issue', executorProperty: CreateSceneExecutorProperty( - functionCode: '', - functionValue: '', - delaySeconds: task.functionValue, + functionCode: task.code, + functionValue: task.functionValue, + delaySeconds: 0, ), ); - } - return CreateSceneAction( - entityId: task.deviceId, - actionExecutor: 'device_issue', - executorProperty: CreateSceneExecutorProperty( - functionCode: task.code, - functionValue: task.functionValue, - delaySeconds: 0, - ), - ); - }, - )..add(CreateSceneAction( - entityId: smartSceneBloc.smartSceneEnable?.entityId ?? '', - actionExecutor: - smartSceneBloc.smartSceneEnable?.actionExecutor ?? '', - executorProperty: null)), + }, + ), + if (smartSceneBloc.smartSceneEnable != null) + CreateSceneAction( + entityId: smartSceneBloc.smartSceneEnable?.entityId ?? '', + actionExecutor: + smartSceneBloc.smartSceneEnable?.actionExecutor ?? '', + executorProperty: null) + ], ); sceneBloc.add(CreateSceneWithTasksEvent( createSceneModel: createSceneModel, diff --git a/lib/features/scene/model/scene_details_model.dart b/lib/features/scene/model/scene_details_model.dart index 04b9827..3c40476 100644 --- a/lib/features/scene/model/scene_details_model.dart +++ b/lib/features/scene/model/scene_details_model.dart @@ -32,11 +32,15 @@ class SceneDetailsModel { name: json["name"], status: json["status"], type: json["type"], - actions: - List.from(json["actions"].map((x) => Action.fromJson(x))), + actions: (json["actions"] as List) + .map((x) => Action.fromJson(x)) + .where((x) => x != null) + .toList() + .cast(), conditions: json["conditions"] != null - ? List.from( - json["conditions"].map((x) => Condition.fromJson(x))) + ? (json["conditions"] as List) + .map((x) => Condition.fromJson(x)) + .toList() : null, decisionExpr: json["decisionExpr"], effectiveTime: json["effectiveTime"] != null @@ -69,15 +73,19 @@ class Action { required this.executorProperty, }); - factory Action.fromRawJson(String str) => Action.fromJson(json.decode(str)); - String toRawJson() => json.encode(toJson()); - factory Action.fromJson(Map json) => Action( - actionExecutor: json["actionExecutor"], - entityId: json["entityId"], - executorProperty: ExecutorProperty.fromJson(json["executorProperty"]), - ); + static Action? fromJson(Map json) { + if (json["executorProperty"] == null) { + return null; // Return null if executorProperty is not present + } + + return Action( + actionExecutor: json["actionExecutor"], + entityId: json["entityId"], + executorProperty: ExecutorProperty.fromJson(json["executorProperty"]), + ); + } Map toJson() => { "actionExecutor": actionExecutor, diff --git a/lib/features/scene/model/update_automation.dart b/lib/features/scene/model/update_automation.dart new file mode 100644 index 0000000..d15d191 --- /dev/null +++ b/lib/features/scene/model/update_automation.dart @@ -0,0 +1,38 @@ +import 'dart:convert'; + +class AutomationStatusUpdate { + final String unitUuid; + final bool isEnable; + + AutomationStatusUpdate({ + required this.unitUuid, + required this.isEnable, + }); + + factory AutomationStatusUpdate.fromRawJson(String str) => + AutomationStatusUpdate.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AutomationStatusUpdate.fromJson(Map json) => + AutomationStatusUpdate( + unitUuid: json["unitUuid"], + isEnable: json["isEnable"], + ); + + Map toJson() => { + "unitUuid": unitUuid, + "isEnable": isEnable, + }; + + factory AutomationStatusUpdate.fromMap(Map map) => + AutomationStatusUpdate( + unitUuid: map["unitUuid"], + isEnable: map["isEnable"], + ); + + Map toMap() => { + "unitUuid": unitUuid, + "isEnable": isEnable, + }; +} diff --git a/lib/features/scene/view/scene_view.dart b/lib/features/scene/view/scene_view.dart index f181c8b..544b267 100644 --- a/lib/features/scene/view/scene_view.dart +++ b/lib/features/scene/view/scene_view.dart @@ -98,7 +98,9 @@ class SceneView extends StatelessWidget { scenes: scenes, loadingSceneId: state.loadingSceneId, - disablePLayButton: false, + disablePlayButton: false, + loadingStates: state + .loadingStates, // Add this line ) : const Center( child: BodyMedium( @@ -129,7 +131,9 @@ class SceneView extends StatelessWidget { scenes: automationList, loadingSceneId: state.loadingSceneId, - disablePLayButton: true, + disablePlayButton: true, + loadingStates: state + .loadingStates, // Add this line ) : const Center( child: BodyMedium( diff --git a/lib/features/scene/widgets/scene_view_widget/scene_grid_view.dart b/lib/features/scene/widgets/scene_view_widget/scene_grid_view.dart index 6004028..f690fbb 100644 --- a/lib/features/scene/widgets/scene_view_widget/scene_grid_view.dart +++ b/lib/features/scene/widgets/scene_view_widget/scene_grid_view.dart @@ -4,12 +4,15 @@ import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_item. class SceneGrid extends StatelessWidget { final List scenes; final String? loadingSceneId; - final bool disablePLayButton; + final bool disablePlayButton; + final Map loadingStates; + const SceneGrid({ required this.scenes, required this.loadingSceneId, + required this.disablePlayButton, + required this.loadingStates, super.key, - required this.disablePLayButton, }); @override @@ -27,10 +30,14 @@ class SceneGrid extends StatelessWidget { itemBuilder: (context, index) { final scene = scenes[index]; final isLoading = loadingSceneId == scene.id; + final isSwitchLoading = loadingStates[scene.id] ?? false; + return SceneItem( - scene: scene, - isLoading: isLoading, - disablePLayButton: disablePLayButton); + scene: scene, + isLoading: isLoading, + disablePlayButton: disablePlayButton, + isSwitchLoading: isSwitchLoading, + ); }, ); } diff --git a/lib/features/scene/widgets/scene_view_widget/scene_item.dart b/lib/features/scene/widgets/scene_view_widget/scene_item.dart index 894f662..0dc4fff 100644 --- a/lib/features/scene/widgets/scene_view_widget/scene_item.dart +++ b/lib/features/scene/widgets/scene_view_widget/scene_item.dart @@ -1,11 +1,14 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart'; import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart'; import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart'; import 'package:syncrow_app/features/scene/model/scenes_model.dart'; import 'package:syncrow_app/features/scene/model/scene_settings_route_arguments.dart'; +import 'package:syncrow_app/features/scene/model/update_automation.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; @@ -16,13 +19,15 @@ import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; class SceneItem extends StatelessWidget { final ScenesModel scene; final bool isLoading; - final bool disablePLayButton; + final bool isSwitchLoading; + final bool disablePlayButton; const SceneItem({ required this.scene, required this.isLoading, super.key, - required this.disablePLayButton, + required this.disablePlayButton, + required this.isSwitchLoading, }); @override @@ -33,14 +38,14 @@ class SceneItem extends StatelessWidget { context, Routes.sceneTasksRoute, arguments: SceneSettingsRouteArguments( - sceneType: disablePLayButton == false + sceneType: disablePlayButton == false ? CreateSceneEnum.tabToRun.name : CreateSceneEnum.deviceStatusChanges.name, sceneId: scene.id, sceneName: scene.name, ), ); - if (disablePLayButton == false) { + if (disablePlayButton == false) { BlocProvider.of(context) .add(const SceneTypeEvent(CreateSceneEnum.tabToRun)); BlocProvider.of(context).add( @@ -66,26 +71,45 @@ class SceneItem extends StatelessWidget { Assets.assetsIconsLogo, fit: BoxFit.fill, ), - Visibility( - visible: disablePLayButton == false, - child: IconButton( - padding: EdgeInsets.zero, - onPressed: () { - context - .read() - .add(SceneTrigger(scene.id, scene.name)); - }, - icon: isLoading - ? const Center( - child: CircularProgressIndicator(), + disablePlayButton == false + ? IconButton( + padding: EdgeInsets.zero, + onPressed: () { + context + .read() + .add(SceneTrigger(scene.id, scene.name)); + }, + icon: isLoading + ? const Center( + child: CircularProgressIndicator(), + ) + : const Icon( + Icons.play_circle, + size: 40, + color: ColorsManager.greyColor, + ), + ) + : isSwitchLoading + ? Center( + child: CircularProgressIndicator( + color: ColorsManager.primaryColorWithOpacity, + ), ) - : const Icon( - Icons.play_circle, - size: 40, - color: ColorsManager.greyColor, + : CupertinoSwitch( + activeColor: ColorsManager.primaryColor, + value: scene.status == 'enable' ? true : false, + onChanged: (value) { + context.read().add( + UpdateAutomationStatus( + automationStatusUpdate: + AutomationStatusUpdate( + isEnable: value, + unitUuid: HomeCubit.getInstance() + .selectedSpace! + .id!), + automationId: scene.id)); + }, ), - ), - ), ], ), const SizedBox(height: 10), diff --git a/lib/services/api/api_links_endpoints.dart b/lib/services/api/api_links_endpoints.dart index 8e22e5d..c3c2e8c 100644 --- a/lib/services/api/api_links_endpoints.dart +++ b/lib/services/api/api_links_endpoints.dart @@ -81,7 +81,8 @@ abstract class ApiEndpoints { static const String controlGroup = '/group/control'; //GET static const String groupBySpace = '/group/{unitUuid}'; - static const String devicesByGroupName = '/group/{unitUuid}/devices/{groupName}'; + static const String devicesByGroupName = + '/group/{unitUuid}/devices/{groupName}'; static const String groupByUuid = '/group/{groupUuid}'; //DELETE @@ -93,7 +94,8 @@ abstract class ApiEndpoints { static const String addDeviceToRoom = '/device/room'; static const String addDeviceToGroup = '/device/group'; static const String controlDevice = '/device/{deviceUuid}/control'; - static const String firmwareDevice = '/device/{deviceUuid}/firmware/{firmwareVersion}'; + static const String firmwareDevice = + '/device/{deviceUuid}/firmware/{firmwareVersion}'; static const String getDevicesByUserId = '/device/user/{userId}'; static const String getDevicesByUnitId = '/device/unit/{unitUuid}'; @@ -102,7 +104,8 @@ abstract class ApiEndpoints { static const String deviceByUuid = '/device/{deviceUuid}'; static const String deviceFunctions = '/device/{deviceUuid}/functions'; static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices'; - static const String deviceFunctionsStatus = '/device/{deviceUuid}/functions/status'; + static const String deviceFunctionsStatus = + '/device/{deviceUuid}/functions/status'; ///Device Permission Module //POST @@ -127,22 +130,29 @@ abstract class ApiEndpoints { static const String getUnitAutomation = '/automation/{unitUuid}'; - static const String getAutomationDetails = '/automation/details/{automationId}'; + static const String getAutomationDetails = + '/automation/details/{automationId}'; /// PUT static const String updateScene = '/scene/tap-to-run/{sceneId}'; static const String updateAutomation = '/automation/{automationId}'; + static const String updateAutomationStatus = + '/automation/status/{automationId}'; + /// DELETE static const String deleteScene = '/scene/tap-to-run/{unitUuid}/{sceneId}'; - static const String deleteAutomation = '/automation/{unitUuid}/{automationId}'; + static const String deleteAutomation = + '/automation/{unitUuid}/{automationId}'; //////////////////////Door Lock ////////////////////// //online - static const String addTemporaryPassword = '/door-lock/temporary-password/online/{doorLockUuid}'; - static const String getTemporaryPassword = '/door-lock/temporary-password/online/{doorLockUuid}'; + static const String addTemporaryPassword = + '/door-lock/temporary-password/online/{doorLockUuid}'; + static const String getTemporaryPassword = + '/door-lock/temporary-password/online/{doorLockUuid}'; //one-time offline static const String addOneTimeTemporaryPassword = diff --git a/lib/services/api/scene_api.dart b/lib/services/api/scene_api.dart index 6233b30..e26920c 100644 --- a/lib/services/api/scene_api.dart +++ b/lib/services/api/scene_api.dart @@ -2,6 +2,7 @@ import 'package:syncrow_app/features/scene/model/create_automation_model.dart'; import 'package:syncrow_app/features/scene/model/create_scene_model.dart'; import 'package:syncrow_app/features/scene/model/scene_details_model.dart'; import 'package:syncrow_app/features/scene/model/scenes_model.dart'; +import 'package:syncrow_app/features/scene/model/update_automation.dart'; import 'package:syncrow_app/services/api/api_links_endpoints.dart'; import 'package:syncrow_app/services/api/http_service.dart'; @@ -115,6 +116,22 @@ class SceneApi { } } + //updateAutomationStatus + static Future updateAutomationStatus(String automationId, + AutomationStatusUpdate createAutomationEnable) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.updateAutomationStatus + .replaceAll('{automationId}', automationId), + body: createAutomationEnable.toMap(), + expectedResponseModel: (json) => json['success'], + ); + return response; + } catch (e) { + rethrow; + } + } + //getScene static Future getSceneDetails(String sceneId) async {