From c0d53fdf5c36e2b0e28aaa9ad0e11a7e64701590 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 19 Mar 2025 12:24:55 +0300 Subject: [PATCH] Update Automation Status --- assets/icons/scenesPlayIcon.svg | 17 ++ assets/icons/spaseLocationIcon.svg | 4 + .../automation_scene_trigger_bloc.dart | 124 +++++++++++++ .../automation_scene_trigger_event.dart | 55 ++++++ .../automation_scene_trigger_status.dart | 51 +++++ .../automation_status_update.dart | 39 ++++ lib/pages/routines/view/routines_view.dart | 31 +++- .../fetch_routine_scenes_automation.dart | 22 ++- .../main_routine_view/routine_view_card.dart | 174 ++++++++++++------ lib/services/routines_api.dart | 56 ++++++ lib/utils/constants/api_const.dart | 5 + lib/utils/constants/assets.dart | 5 +- 12 files changed, 512 insertions(+), 71 deletions(-) create mode 100644 assets/icons/scenesPlayIcon.svg create mode 100644 assets/icons/spaseLocationIcon.svg create mode 100644 lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_bloc.dart create mode 100644 lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart create mode 100644 lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart create mode 100644 lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart diff --git a/assets/icons/scenesPlayIcon.svg b/assets/icons/scenesPlayIcon.svg new file mode 100644 index 00000000..6417b0e6 --- /dev/null +++ b/assets/icons/scenesPlayIcon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/spaseLocationIcon.svg b/assets/icons/spaseLocationIcon.svg new file mode 100644 index 00000000..2c3f0ad9 --- /dev/null +++ b/assets/icons/spaseLocationIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_bloc.dart b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_bloc.dart new file mode 100644 index 00000000..b76b06e0 --- /dev/null +++ b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_bloc.dart @@ -0,0 +1,124 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/auth/model/project_model.dart'; +import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart'; +import 'package:syncrow_web/pages/routines/models/routine_model.dart'; +import 'package:syncrow_web/services/routines_api.dart'; + +class AutomationSceneTriggerBloc extends Bloc { + AutomationSceneTriggerBloc() : super(AutomationSceneInitial()) { + // on(_onLoadScenes); + // on(_onLoadAutomation); + on(_onSceneTrigger); + on(_onUpdateAutomationStatus); + } + + List scenes = []; + List automationList = []; + + // Future _onLoadScenes(LoadScenes event, Emitter emit) async { + // emit(SceneLoading()); + + // try { + // Project? project = HomeCubit.getInstance().project; + + // if (event.unitId.isNotEmpty) { + // scenes = await SceneApi.getScenesByUnitId(event.unitId, + // event.unit.community.uuid, project?.uuid ?? TempConst.projectIdDev, + // showInDevice: event.showInDevice); + // emit(SceneLoaded(scenes, automationList)); + // } else { + // emit(const SceneError(message: 'Unit ID is empty')); + // } + // } catch (e) { + // emit(const SceneError(message: 'Something went wrong')); + // } + // } + + // Future _onLoadAutomation( + // LoadAutomation event, Emitter emit) async { + // emit(SceneLoading()); + + // try { + // Project? project = HomeCubit.getInstance().project; + + // if (event.unitId.isNotEmpty) { + // automationList = await SceneApi.getAutomationByUnitId( + // event.unitId, event.communityId, project?.uuid ?? ''); + // emit(SceneLoaded(scenes, automationList)); + // } else { + // emit(const SceneError(message: 'Unit ID is empty')); + // } + // } catch (e) { + // emit(const SceneError(message: 'Something went wrong')); + // } + // } + + Future _onSceneTrigger( + SceneTrigger event, Emitter emit) async { + final currentState = state; + if (currentState is AutomationSceneLoaded) { + emit(AutomationSceneLoaded( + currentState.scenes, + currentState.automationList, + loadingSceneId: event.sceneId, + )); + + try { + final success = await SceneApi.triggerScene(event.sceneId); + if (success) { + emit(SceneTriggerSuccess(event.name)); + emit(AutomationSceneLoaded(currentState.scenes, currentState.automationList)); + } else { + emit(const AutomationSceneError(message: 'Something went wrong')); + } + } catch (e) { + emit(const AutomationSceneError(message: 'Something went wrong')); + } + } + } + + Future _onUpdateAutomationStatus( + UpdateAutomationStatus event, Emitter emit) async { + final currentState = state; + if (currentState is AutomationSceneLoaded) { + final newLoadingStates = + Map.from(currentState.loadingStates) + ..[event.automationId] = true; + + emit(AutomationSceneLoaded( + currentState.scenes, + currentState.automationList, + loadingStates: newLoadingStates, + )); + + try { + Project? project = HomeCubit.getInstance().project; + + final success = await SceneApi.updateAutomationStatus( + event.automationId, + event.automationStatusUpdate, + project?.uuid ?? ''); + if (success) { + automationList = await SceneApi.getAutomationByUnitId( + event.automationStatusUpdate.spaceUuid, + event.communityId, + project?.uuid ?? ''); + newLoadingStates[event.automationId] = false; + emit(AutomationSceneLoaded( + currentState.scenes, + automationList, + loadingStates: newLoadingStates, + )); + } else { + emit(const AutomationSceneError(message: 'Something went wrong')); + } + } catch (e) { + emit(const AutomationSceneError(message: 'Something went wrong')); + } + } + } +} diff --git a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart new file mode 100644 index 00000000..b34eb5ed --- /dev/null +++ b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; + +abstract class AutomationSceneTriggerEvent extends Equatable { + const AutomationSceneTriggerEvent(); + + @override + List get props => []; +} + +class AutomationSceneScenes extends AutomationSceneTriggerEvent { + final String unitId; + final bool showInDevice; + final SpaceModel unit; + + const AutomationSceneScenes(this.unitId, this.unit, {this.showInDevice = false}); + + @override + List get props => [unitId, showInDevice]; +} + +class AutomationSceneAutomation extends AutomationSceneTriggerEvent { + final String unitId; + final String communityId; + + + const AutomationSceneAutomation(this.unitId, this.communityId); + + @override + List get props => [unitId, communityId]; +} + +class SceneTrigger extends AutomationSceneTriggerEvent { + final String sceneId; + final String name; + + const SceneTrigger(this.sceneId, this.name); + + @override + List get props => [sceneId]; +} + +//updateAutomationStatus +class UpdateAutomationStatus extends AutomationSceneTriggerEvent { + final String automationId; + final AutomationStatusUpdate automationStatusUpdate; + final String communityId; + + const UpdateAutomationStatus({required this.automationStatusUpdate, required this.automationId, required this.communityId}); + + @override + List get props => [automationStatusUpdate]; +} diff --git a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart new file mode 100644 index 00000000..6753372f --- /dev/null +++ b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart @@ -0,0 +1,51 @@ + + +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/routines/models/routine_model.dart'; + +abstract class AutomationSceneTriggerStatus extends Equatable { + const AutomationSceneTriggerStatus(); + + @override + List get props => []; +} + +class AutomationSceneInitial extends AutomationSceneTriggerStatus {} + +class AutomationSceneLoading extends AutomationSceneTriggerStatus {} + +class AutomationSceneLoaded extends AutomationSceneTriggerStatus { + final List scenes; + final List automationList; + final String? loadingSceneId; + final Map loadingStates; + + const AutomationSceneLoaded(this.scenes, this.automationList, + {this.loadingSceneId, this.loadingStates = const {}}); + + @override + List get props => + [scenes, loadingSceneId, automationList, loadingStates]; +} + +class AutomationSceneError extends AutomationSceneTriggerStatus { + final String message; + + const AutomationSceneError({required this.message}); + + @override + List get props => [message]; +} + +class SceneTriggerSuccess extends AutomationSceneTriggerStatus { + final String sceneName; + + const SceneTriggerSuccess(this.sceneName); + + @override + List get props => [sceneName]; +} + +class UpdateAutomationStatusLoading extends AutomationSceneTriggerStatus { + const UpdateAutomationStatusLoading(); +} diff --git a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart new file mode 100644 index 00000000..c664c2c4 --- /dev/null +++ b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart @@ -0,0 +1,39 @@ + +import 'dart:convert'; + +class AutomationStatusUpdate { + final String spaceUuid; + final bool isEnable; + + AutomationStatusUpdate({ + required this.spaceUuid, + required this.isEnable, + }); + + factory AutomationStatusUpdate.fromRawJson(String str) => + AutomationStatusUpdate.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AutomationStatusUpdate.fromJson(Map json) => + AutomationStatusUpdate( + spaceUuid: json["spaceUuid"], + isEnable: json["isEnable"], + ); + + Map toJson() => { + "spaceUuid": spaceUuid, + "isEnable": isEnable, + }; + + factory AutomationStatusUpdate.fromMap(Map map) => + AutomationStatusUpdate( + spaceUuid: map["spaceUuid"], + isEnable: map["isEnable"], + ); + + Map toMap() => { + "spaceUuid": spaceUuid, + "isEnable": isEnable, + }; +} diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index b0efb4a9..de627a43 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -60,22 +60,39 @@ class _RoutinesViewState extends State { height: 10, ), RoutineViewCard( + isFromScenes: false, + cardType: '', + spaceName: '', onTap: () { - if (context.read().state.selectedCommunities.length == 1 && - context.read().state.selectedSpaces.length == 1) { + if (context + .read() + .state + .selectedCommunities + .length == + 1 && + context + .read() + .state + .selectedSpaces + .length == + 1) { context.read().add( (ResetRoutineState()), ); BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(createRoutineView: true), + const CreateNewRoutineViewEvent( + createRoutineView: true), ); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text( - context.read().state.selectedSpaces.isEmpty - ? 'Please select a space' - : 'Please select only one space to proceed'), + content: Text(context + .read() + .state + .selectedSpaces + .isEmpty + ? 'Please select a space' + : 'Please select only one space to proceed'), ), ); // CustomSnackBar.redSnackBar( diff --git a/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart b/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart index 718a78b9..08f4318e 100644 --- a/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart +++ b/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart @@ -12,7 +12,8 @@ class FetchRoutineScenesAutomation extends StatefulWidget { const FetchRoutineScenesAutomation({super.key}); @override - State createState() => _FetchRoutineScenesState(); + State createState() => + _FetchRoutineScenesState(); } class _FetchRoutineScenesState extends State @@ -64,9 +65,12 @@ class _FetchRoutineScenesState extends State right: isSmallScreenSize(context) ? 4.0 : 8.0, ), child: RoutineViewCard( + cardType: 'scenes', + spaceName: 'scenes', onTap: () { BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(createRoutineView: true), + const CreateNewRoutineViewEvent( + createRoutineView: true), ); context.read().add( GetSceneDetails( @@ -77,7 +81,8 @@ class _FetchRoutineScenesState extends State ); }, textString: state.scenes[index].name, - icon: state.scenes[index].icon ?? Assets.logoHorizontal, + icon: state.scenes[index].icon ?? + Assets.logoHorizontal, isFromScenes: true, iconInBytes: state.scenes[index].iconInBytes, ), @@ -113,19 +118,24 @@ class _FetchRoutineScenesState extends State right: isSmallScreenSize(context) ? 4.0 : 8.0, ), child: RoutineViewCard( + cardType: 'automations', + spaceName: 'automations', onTap: () { BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(createRoutineView: true), + const CreateNewRoutineViewEvent( + createRoutineView: true), ); context.read().add( GetAutomationDetails( - automationId: state.automations[index].id, + automationId: + state.automations[index].id, isAutomation: true, isUpdate: true), ); }, textString: state.automations[index].name, - icon: state.automations[index].icon ?? Assets.automation, + icon: state.automations[index].icon ?? + Assets.automation, ), ), ), diff --git a/lib/pages/routines/widgets/main_routine_view/routine_view_card.dart b/lib/pages/routines/widgets/main_routine_view/routine_view_card.dart index f8a2e358..fe94cf83 100644 --- a/lib/pages/routines/widgets/main_routine_view/routine_view_card.dart +++ b/lib/pages/routines/widgets/main_routine_view/routine_view_card.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -12,6 +13,8 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { required this.onTap, required this.icon, required this.textString, + required this.spaceName, + required this.cardType, this.isFromScenes, this.iconInBytes, }); @@ -19,6 +22,9 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { final Function() onTap; final dynamic icon; final String textString; + final String spaceName; + final String cardType; + final bool? isFromScenes; final Uint8List? iconInBytes; @@ -50,69 +56,125 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { ), color: ColorsManager.whiteColors, child: InkWell( - onTap: onTap, borderRadius: BorderRadius.circular(10), child: Padding( padding: const EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Center( - child: Container( - decoration: BoxDecoration( - color: ColorsManager.graysColor, - borderRadius: BorderRadius.circular(120), - border: Border.all( - color: ColorsManager.greyColor, - width: 2.0, - ), - ), - height: iconSize, - width: iconSize, - child: (isFromScenes ?? false) - ? (iconInBytes != null && iconInBytes?.isNotEmpty == true) - ? Image.memory( - iconInBytes!, - height: iconSize, - width: iconSize, - fit: BoxFit.contain, - errorBuilder: (context, error, stackTrace) => Image.asset( - Assets.logo, - height: iconSize, - width: iconSize, - fit: BoxFit.contain, - ), - ) - : Image.asset( - Assets.logo, - height: iconSize, - width: iconSize, - fit: BoxFit.contain, - ) - : (icon is String && icon.endsWith('.svg')) - ? SvgPicture.asset( - icon, - fit: BoxFit.contain, - ) - : Icon( - icon, - color: ColorsManager.dialogBlueTitle, - size: isSmallScreenSize(context) ? 30 : 40, - ), - ), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + (isFromScenes ?? false) + ? InkWell( + onTap: () {}, + child: SvgPicture.asset( + Assets.scenesPlayIcon, + fit: BoxFit.contain, + ), + ) + : CupertinoSwitch( + activeColor: ColorsManager.primaryColor, + value: false, + onChanged: (value) {}, + ) + ], ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 3), - child: Text( - textString, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontSize: isSmallScreenSize(context) ? 10 : 12, - ), + InkWell( + onTap: onTap, + child: Column( + children: [ + Center( + child: Container( + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: BorderRadius.circular(120), + border: Border.all( + color: ColorsManager.greyColor, + width: 2.0, + ), + ), + height: iconSize, + width: iconSize, + child: (isFromScenes ?? false) + ? (iconInBytes != null && + iconInBytes?.isNotEmpty == true) + ? Image.memory( + iconInBytes!, + height: iconSize, + width: iconSize, + fit: BoxFit.contain, + errorBuilder: + (context, error, stackTrace) => + Image.asset( + Assets.logo, + height: iconSize, + width: iconSize, + fit: BoxFit.contain, + ), + ) + : Image.asset( + Assets.logo, + height: iconSize, + width: iconSize, + fit: BoxFit.contain, + ) + : (icon is String && icon.endsWith('.svg')) + ? SvgPicture.asset( + icon, + fit: BoxFit.contain, + ) + : Icon( + icon, + color: ColorsManager.dialogBlueTitle, + size: + isSmallScreenSize(context) ? 30 : 40, + ), + ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: Column( + children: [ + Text( + textString, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: isSmallScreenSize(context) ? 10 : 12, + ), + ), + if (spaceName != '') + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.spaceLocationIcon, + fit: BoxFit.contain, + ), + Text( + spaceName, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: + context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: + isSmallScreenSize(context) ? 10 : 12, + ), + ), + ], + ), + ], + ), + ), + ], ), ), ], diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 56a29aa1..cbef2bc3 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart'; import 'package:syncrow_web/pages/routines/models/icon_model.dart'; @@ -230,4 +231,59 @@ class SceneApi { rethrow; } } + + static Future updateAutomationStatus(String automationId, + AutomationStatusUpdate createAutomationEnable, String projectId) async { + try { + final response = await _httpService.patch( + path: ApiEndpoints.updateAutomationStatus + .replaceAll('{automationId}', automationId) + .replaceAll('{projectId}', projectId), + body: createAutomationEnable.toMap(), + expectedResponseModel: (json) => json['success'], + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future triggerScene(String sceneId) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.triggerScene.replaceAll('{sceneId}', sceneId), + showServerMessage: false, + expectedResponseModel: (json) => json['success'], + ); + return response; + } catch (e) { + rethrow; + } + } + static Future> getAutomationByUnitId( + String unitId, + String communityId, + String projectId, + ) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getUnitAutomation + .replaceAll('{unitUuid}', unitId) + .replaceAll('{communityId}', communityId) + .replaceAll('{projectId}', projectId), + showServerMessage: false, + expectedResponseModel: (json) { + List scenes = []; + for (var scene in json) { + scenes.add(ScenesModel.fromJson(scene)); + } + return scenes; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 71e0fc55..00449836 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -111,4 +111,9 @@ abstract class ApiEndpoints { static const String terms = '/terms'; static const String policy = '/policy'; static const String userAgreements = '/user/agreements/web/{userUuid}'; + static const String triggerScene = '/scene/tap-to-run/{sceneId}/trigger'; + static const String updateAutomationStatus = + '/projects/{projectId}/automations/{automationId}'; + static const String getUnitAutomation = + '/projects/{projectId}/communities/{communityId}/spaces/{unitUuid}/automations'; } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index b7a0115f..5d462f28 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -406,6 +406,7 @@ class Assets { static const String deleteSpaceLinkIcon = 'assets/icons/delete_space_link_icon.svg'; static const String spaceLinkIcon = 'assets/icons/space_link_icon.svg'; - static const String successIcon = 'assets/icons/success_icon.svg'; - + static const String successIcon = 'assets/icons/success_icon.svg'; + static const String spaceLocationIcon = 'assets/icons/spaceLocationIcon.svg'; + static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.svg'; }