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/scenesPlayIconCheck.svg b/assets/icons/scenesPlayIconCheck.svg new file mode 100644 index 00000000..9e81869a --- /dev/null +++ b/assets/icons/scenesPlayIconCheck.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/main.dart b/lib/main.dart index f2f640e4..8eb6ce38 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/firebase_options_prod.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; @@ -33,7 +34,6 @@ Future main() async { } class MyApp extends StatelessWidget { - MyApp({super.key}); final GoRouter _router = GoRouter( @@ -55,6 +55,9 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ + BlocProvider( + create: (context) => CreateRoutineBloc(), + ), BlocProvider( create: (context) => HomeBloc()..add(const FetchUserInfo())), BlocProvider( diff --git a/lib/main_dev.dart b/lib/main_dev.dart index 9d00ebf7..a42d5d07 100644 --- a/lib/main_dev.dart +++ b/lib/main_dev.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/firebase_options_dev.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; @@ -55,6 +56,9 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ + BlocProvider( + create: (context) => CreateRoutineBloc(), + ), BlocProvider( create: (context) => HomeBloc()..add(const FetchUserInfo())), BlocProvider( diff --git a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart index 7ed3a377..05e82f1f 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart @@ -43,8 +43,7 @@ class DeviceManagementBloc final projectUuid = await ProjectManager.getProjectUUID() ?? ''; if (spaceBloc.state.selectedCommunities.isEmpty) { - devices = await DevicesManagementApi() - .fetchDevices('', '', projectUuid ); + devices = await DevicesManagementApi().fetchDevices('', '', projectUuid); } else { for (var community in spaceBloc.state.selectedCommunities) { List spacesList = diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index 45af9751..f41b5a4a 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -3,6 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; +import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routines/view/routines_view.dart'; @@ -39,10 +41,15 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { backgroundColor: null, ), onPressed: () { + BlocProvider.of(context) + .add(const ResetSelectedEvent()); + context .read() .add(const TriggerSwitchTabsEvent(isRoutineTab: false)); - context.read().add(FetchDevices(context)); + context + .read() + .add(FetchDevices(context)); }, child: Text( 'Devices', @@ -60,6 +67,9 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { backgroundColor: null, ), onPressed: () { + BlocProvider.of(context) + .add(const ResetSelectedEvent()); + context .read() .add(const TriggerSwitchTabsEvent(isRoutineTab: true)); 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/bloc/create_routine_bloc/create_routine_bloc.dart b/lib/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart new file mode 100644 index 00000000..5a8e5590 --- /dev/null +++ b/lib/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart @@ -0,0 +1,51 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_state.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/services/space_mana_api.dart'; + +class CreateRoutineBloc extends Bloc { + CreateRoutineBloc() : super(const CreateRoutineInitial()) { + on(_fetchSpaceOnlyWithDevices); + on(saveSpaceIdCommunityId); + on(resetSelected); + } + + String selectedSpaceId = ''; + String selectedCommunityId = ''; + + List spacesOnlyWithDevices = []; + + Future _fetchSpaceOnlyWithDevices( + SpaceOnlyWithDevicesEvent event, Emitter emit) async { + emit(const SpaceWithDeviceLoadingState()); + + try { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + + spacesOnlyWithDevices = await CommunitySpaceManagementApi() + .getSpaceOnlyWithDevices( + communityId: event.communityID, projectId: projectUuid); + + emit(SpaceWithDeviceLoadedState(spacesOnlyWithDevices)); + } catch (e) { + emit(SpaceTreeErrorState('Error loading communities and spaces: $e')); + } + } + + saveSpaceIdCommunityId( + SaveCommunityIdAndSpaceIdEvent event, Emitter emit) { + emit(const SpaceWithDeviceLoadingState()); + selectedSpaceId = event.spaceID!; + selectedCommunityId = event.communityID!; + emit(const SelectedState()); + } + + resetSelected(ResetSelectedEvent event, Emitter emit) { + emit(const SpaceWithDeviceLoadingState()); + selectedSpaceId = ''; + selectedCommunityId = ''; + emit(const ResetSelectedState()); + } +} diff --git a/lib/pages/routines/bloc/create_routine_bloc/create_routine_event.dart b/lib/pages/routines/bloc/create_routine_bloc/create_routine_event.dart new file mode 100644 index 00000000..24e620c0 --- /dev/null +++ b/lib/pages/routines/bloc/create_routine_bloc/create_routine_event.dart @@ -0,0 +1,43 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; + +abstract class CreateRoutineEvent extends Equatable { + const CreateRoutineEvent(); + + @override + List get props => []; +} + +class AddToIfContainer extends CreateRoutineEvent { + final SpaceModel spaceModel; + + const AddToIfContainer(this.spaceModel); + + @override + List get props => [spaceModel]; +} + +class SpaceOnlyWithDevicesEvent extends CreateRoutineEvent { + final String communityID; + const SpaceOnlyWithDevicesEvent(this.communityID); + + @override + List get props => [communityID]; +} + +class SaveCommunityIdAndSpaceIdEvent extends CreateRoutineEvent { + final String? communityID; + final String? spaceID; + + const SaveCommunityIdAndSpaceIdEvent({this.communityID, this.spaceID}); + + @override + List get props => [communityID!, spaceID!]; +} + +class ResetSelectedEvent extends CreateRoutineEvent { + const ResetSelectedEvent(); + + @override + List get props => []; +} diff --git a/lib/pages/routines/bloc/create_routine_bloc/create_routine_state.dart b/lib/pages/routines/bloc/create_routine_bloc/create_routine_state.dart new file mode 100644 index 00000000..4911304b --- /dev/null +++ b/lib/pages/routines/bloc/create_routine_bloc/create_routine_state.dart @@ -0,0 +1,46 @@ + +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; + +abstract class CreateRoutineState extends Equatable { + const CreateRoutineState(); + + @override + List get props => []; +} + +class CreateRoutineInitial extends CreateRoutineState { + const CreateRoutineInitial(); +} + +class SpaceWithDeviceLoadingState extends CreateRoutineState { + const SpaceWithDeviceLoadingState(); +} + +class SpaceWithDeviceLoadedState extends CreateRoutineState { + final List spaces; + + const SpaceWithDeviceLoadedState(this.spaces); + + @override + List get props => [spaces]; +} + +class SpaceTreeErrorState extends CreateRoutineState { + final String errorMessage; + + const SpaceTreeErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class SelectedState extends CreateRoutineState { + const SelectedState(); +} + + +class ResetSelectedState extends CreateRoutineState { + const ResetSelectedState(); +} + diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index 361810c0..2664d026 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -1,10 +1,13 @@ import 'dart:async'; +import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart'; +import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.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/delay/delay_fucntions.dart'; @@ -51,13 +54,18 @@ class RoutineBloc extends Bloc { on(_triggerSwitchTabsEvent); on(_createNewRoutineViewEvent); on(_resetErrorMessage); + on(_onSceneTrigger); + on(_onUpdateAutomationStatus); } + String selectedSpaceId = ''; + String selectedCommunityId = ''; 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()); @@ -83,8 +91,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; @@ -93,18 +101,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; @@ -115,22 +126,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); @@ -140,13 +155,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)); @@ -155,19 +172,30 @@ 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)); List scenes = []; try { - final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - BuildContext context = NavigationService.navigatorKey.currentContext!; - var spaceBloc = context.read(); - for (var communityId in spaceBloc.state.selectedCommunities) { - List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; - for (var spaceId in spacesList) { - scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid)); + var createRoutineBloc = context.read(); + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + if (createRoutineBloc.selectedSpaceId == '' && + createRoutineBloc.selectedCommunityId == '') { + var spaceBloc = context.read(); + for (var communityId in spaceBloc.state.selectedCommunities) { + List spacesList = + spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + for (var spaceId in spacesList) { + scenes.addAll( + await SceneApi.getScenes(spaceId, communityId, projectUuid)); + } } + } else { + scenes.addAll(await SceneApi.getScenes( + createRoutineBloc.selectedSpaceId, + createRoutineBloc.selectedCommunityId, + projectUuid)); } emit(state.copyWith( @@ -184,19 +212,32 @@ 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)); List automations = []; final projectId = await ProjectManager.getProjectUUID() ?? ''; + BuildContext context = NavigationService.navigatorKey.currentContext!; + var createRoutineBloc = context.read(); try { - BuildContext context = NavigationService.navigatorKey.currentContext!; - var spaceBloc = context.read(); - for (var communityId in spaceBloc.state.selectedCommunities) { - List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; - for (var spaceId in spacesList) { - automations.addAll(await SceneApi.getAutomation(spaceId, communityId, projectId)); + + if (createRoutineBloc.selectedSpaceId == '' && + createRoutineBloc.selectedCommunityId == '') { + var spaceBloc = context.read(); + for (var communityId in spaceBloc.state.selectedCommunities) { + List spacesList = + spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + for (var spaceId in spacesList) { + automations.addAll( + await SceneApi.getAutomation(spaceId, communityId, projectId)); + } } + } else { + automations.addAll(await SceneApi.getAutomation( + createRoutineBloc.selectedSpaceId, + createRoutineBloc.selectedCommunityId, + projectId)); } emit(state.copyWith( automations: automations, @@ -212,14 +253,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)); } @@ -233,7 +276,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)) { @@ -246,7 +290,8 @@ class RoutineBloc extends Bloc { if (_isLastActionDelay(state.thenItems)) { emit(state.copyWith( - errorMessage: 'A delay condition cannot be the only or the last action', + errorMessage: + 'A delay condition cannot be the only or the last action', isLoading: false, )); return; @@ -292,10 +337,10 @@ class RoutineBloc extends Bloc { }).toList(); BuildContext context = NavigationService.navigatorKey.currentContext!; - var spaceBloc = context.read(); + var createRoutineBloc = context.read(); final createSceneModel = CreateSceneModel( - spaceUuid: spaceBloc.state.selectedSpaces[0], + spaceUuid: createRoutineBloc.selectedSpaceId, iconId: state.selectedIcon ?? '', showInDevice: true, sceneName: state.routineName ?? '', @@ -322,10 +367,10 @@ class RoutineBloc extends Bloc { } } - Future _onCreateAutomation(CreateAutomationEvent event, Emitter emit) async { + Future _onCreateAutomation( + CreateAutomationEvent event, Emitter emit) async { try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - if (state.routineName == null || state.routineName!.isEmpty) { emit(state.copyWith( errorMessage: 'Automation name is required', @@ -345,7 +390,8 @@ class RoutineBloc extends Bloc { if (_isLastActionDelay(state.thenItems)) { emit(state.copyWith( - errorMessage: 'A delay condition cannot be the only or 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'); @@ -421,10 +467,10 @@ class RoutineBloc extends Bloc { }); }).toList(); BuildContext context = NavigationService.navigatorKey.currentContext!; - var spaceBloc = context.read(); + var createRoutineBloc = context.read(); final createAutomationModel = CreateAutomationModel( - spaceUuid: spaceBloc.state.selectedSpaces[0], + spaceUuid: createRoutineBloc.selectedSpaceId, automationName: state.routineName ?? '', decisionExpr: state.selectedAutomationOperator, effectiveTime: EffectiveTime( @@ -436,7 +482,8 @@ class RoutineBloc extends Bloc { actions: actions, ); - final result = await SceneApi.createAutomation(createAutomationModel, projectUuid); + final result = + await SceneApi.createAutomation(createAutomationModel, projectUuid); if (result['success']) { add(ResetRoutineState()); add(const LoadAutomation()); @@ -457,17 +504,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); @@ -478,7 +529,8 @@ class RoutineBloc extends Bloc { isAutomation: false, isTabToRun: false)); } else { - emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith( + ifItems: ifItems, selectedFunctions: selectedFunctions)); } } } @@ -490,18 +542,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, @@ -534,7 +591,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; @@ -570,8 +628,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), }; @@ -614,7 +675,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, @@ -662,10 +724,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' @@ -700,7 +764,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] = []; } @@ -772,7 +837,8 @@ class RoutineBloc extends Bloc { } } - FutureOr _onResetRoutineState(ResetRoutineState event, Emitter emit) { + FutureOr _onResetRoutineState( + ResetRoutineState event, Emitter emit) { emit(state.copyWith( ifItems: [], thenItems: [], @@ -795,7 +861,6 @@ class RoutineBloc extends Bloc { isUpdate: false, createRoutineView: false)); } - FutureOr _deleteScene(DeleteScene event, Emitter emit) async { try { final projectId = await ProjectManager.getProjectUUID() ?? ''; @@ -812,6 +877,17 @@ class RoutineBloc extends Bloc { automationId: state.automationId ?? '', projectId: projectId); } + // var createRoutineBloc = context.read(); + // if (state.isTabToRun) { + // await SceneApi.deleteScene( + // unitUuid: createRoutineBloc.selectedSpaceId, + // sceneId: state.sceneId ?? ''); + // } else { + // await SceneApi.deleteAutomation( + // projectId: projectId, + // unitUuid: createRoutineBloc.selectedSpaceId, + // automationId: state.automationId ?? ''); + // } add(const LoadScenes()); add(const LoadAutomation()); @@ -824,7 +900,7 @@ class RoutineBloc extends Bloc { )); } } - + // FutureOr _deleteAutomation(DeleteAutomation event, Emitter emit) { // try { // emit(state.copyWith(isLoading: true)); @@ -839,21 +915,31 @@ 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 projectUuid = await ProjectManager.getProjectUUID() ?? ''; - List devices = []; - BuildContext context = NavigationService.navigatorKey.currentContext!; + var createRoutineBloc = context.read(); var spaceBloc = context.read(); - for (var communityId in spaceBloc.state.selectedCommunities) { - List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; - for (var spaceId in spacesList) { - devices - .addAll(await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid)); + + if (createRoutineBloc.selectedSpaceId == '' && + createRoutineBloc.selectedCommunityId == '') { + for (var communityId in spaceBloc.state.selectedCommunities) { + List spacesList = + spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + for (var spaceId in spacesList) { + devices.addAll(await DevicesManagementApi() + .fetchDevices(communityId, spaceId, projectUuid)); + } } + } else { + devices.addAll(await DevicesManagementApi().fetchDevices( + createRoutineBloc.selectedCommunityId, + createRoutineBloc.selectedSpaceId, + projectUuid)); } emit(state.copyWith(isLoading: false, devices: devices)); @@ -862,7 +948,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)) { @@ -876,7 +963,8 @@ class RoutineBloc extends Bloc { if (_isLastActionDelay(state.thenItems)) { emit(state.copyWith( - errorMessage: 'A delay condition cannot be the only or the last action', + errorMessage: + 'A delay condition cannot be the only or the last action', isLoading: false, )); return; @@ -929,7 +1017,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()); @@ -948,10 +1037,9 @@ class RoutineBloc extends Bloc { } } - FutureOr _onUpdateAutomation(UpdateAutomation event, Emitter emit) async { + FutureOr _onUpdateAutomation( + UpdateAutomation event, Emitter emit) async { try { - final projectId = await ProjectManager.getProjectUUID() ?? ''; - if (state.routineName == null || state.routineName!.isEmpty) { emit(state.copyWith( errorMessage: 'Automation name is required', @@ -1046,10 +1134,10 @@ class RoutineBloc extends Bloc { }).toList(); BuildContext context = NavigationService.navigatorKey.currentContext!; - var spaceBloc = context.read(); + var spaceBloc = context.read(); final createAutomationModel = CreateAutomationModel( - spaceUuid: spaceBloc.state.selectedSpaces[0], + spaceUuid: spaceBloc.selectedSpaceId, automationName: state.routineName ?? '', decisionExpr: state.selectedAutomationOperator, effectiveTime: EffectiveTime( @@ -1060,7 +1148,7 @@ class RoutineBloc extends Bloc { conditions: conditions, actions: actions, ); - + final projectId = await ProjectManager.getProjectUUID() ?? ''; final result = await SceneApi.updateAutomation( createAutomationModel, state.automationId ?? '', projectId); @@ -1086,7 +1174,6 @@ class RoutineBloc extends Bloc { GetAutomationDetails event, Emitter emit) async { try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - emit(state.copyWith( isLoading: true, isUpdate: true, @@ -1166,13 +1253,15 @@ class RoutineBloc extends Bloc { ), ); - final deviceId = - action.actionExecutor == 'delay' ? '${action.entityId}_delay' : action.entityId; + final deviceId = action.actionExecutor == 'delay' + ? '${action.entityId}_delay' + : action.entityId; if (!deviceThenCards.containsKey(deviceId)) { deviceThenCards[deviceId] = { 'entityId': action.entityId, - 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'deviceId': + action.actionExecutor == 'delay' ? 'delay' : action.entityId, 'uniqueCustomId': const Uuid().v4(), 'title': action.actionExecutor == 'delay' ? 'Delay' @@ -1203,7 +1292,8 @@ class RoutineBloc extends Bloc { updatedFunctions[uniqueCustomId] = []; } - 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) { @@ -1245,10 +1335,14 @@ class RoutineBloc extends Bloc { } } - final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList(); + final ifItems = deviceIfCards.values + .where((card) => card['type'] == 'condition') + .toList(); final thenItems = deviceThenCards.values .where((card) => - card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene') + card['type'] == 'action' || + card['type'] == 'automation' || + card['type'] == 'scene') .toList(); emit(state.copyWith( @@ -1269,4 +1363,77 @@ class RoutineBloc extends Bloc { )); } } + + Future _onSceneTrigger( + SceneTrigger event, Emitter emit) async { + emit(state.copyWith(loadingSceneId: event.sceneId)); + + try { + final success = await SceneApi.triggerScene(event.sceneId!); + + if (success) { + emit(state.copyWith( + loadingSceneId: null, + // Add success state if needed + )); + // Optional: Add delay to show success feedback + await Future.delayed(const Duration(milliseconds: 500)); + } else { + emit(state.copyWith( + loadingSceneId: null, + errorMessage: 'Trigger failed', + )); + } + } catch (e) { + emit(state.copyWith( + loadingSceneId: null, + errorMessage: 'Trigger error: ${e.toString()}', + )); + } + } + + Future _onUpdateAutomationStatus( + UpdateAutomationStatus event, Emitter emit) async { + // Create a new set safely + final currentLoadingIds = state.loadingAutomationIds; + final newLoadingIds = {...currentLoadingIds!}..add(event.automationId); + + emit(state.copyWith(loadingAutomationIds: newLoadingIds)); + + try { + final projectId = await ProjectManager.getProjectUUID() ?? ''; + final success = await SceneApi.updateAutomationStatus( + event.automationId, event.automationStatusUpdate, projectId); + + if (success) { + final updatedAutomations = await SceneApi.getAutomationByUnitId( + event.automationStatusUpdate.spaceUuid, + event.communityId, + projectId); + + // Remove from loading set safely + final updatedLoadingIds = {...state.loadingAutomationIds!} + ..remove(event.automationId); + + emit(state.copyWith( + automations: updatedAutomations, + loadingAutomationIds: updatedLoadingIds, + )); + } else { + final updatedLoadingIds = {...state.loadingAutomationIds!} + ..remove(event.automationId); + emit(state.copyWith( + loadingAutomationIds: updatedLoadingIds, + errorMessage: 'Update failed', + )); + } + } catch (e) { + final updatedLoadingIds = {...state.loadingAutomationIds!} + ..remove(event.automationId); + emit(state.copyWith( + loadingAutomationIds: updatedLoadingIds, + errorMessage: 'Update error: ${e.toString()}', + )); + } + } } diff --git a/lib/pages/routines/bloc/routine_bloc/routine_event.dart b/lib/pages/routines/bloc/routine_bloc/routine_event.dart index c1b2a23f..b532235d 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_event.dart @@ -210,3 +210,28 @@ class ResetRoutineState extends RoutineEvent {} class ClearFunctions extends RoutineEvent {} class ResetErrorMessage extends RoutineEvent {} + + + + +class SceneTrigger extends RoutineEvent { + final String? sceneId; + final String? name; + + const SceneTrigger({this.sceneId, this.name}); + + @override + List get props => [sceneId!,name!]; +} + +//updateAutomationStatus +class UpdateAutomationStatus extends RoutineEvent { + 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/routine_bloc/routine_state.dart b/lib/pages/routines/bloc/routine_bloc/routine_state.dart index 4dc26a01..228d6af5 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_state.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_state.dart @@ -1,6 +1,7 @@ part of 'routine_bloc.dart'; class RoutineState extends Equatable { + final String? loadingSceneId; final List> ifItems; final List> thenItems; final List> availableCards; @@ -25,6 +26,7 @@ class RoutineState extends Equatable { // final String? automationActionExecutor; final bool routineTab; final bool createRoutineView; + final Set? loadingAutomationIds; // Track loading automations const RoutineState( {this.ifItems = const [], @@ -47,12 +49,16 @@ class RoutineState extends Equatable { this.sceneId, this.automationId, this.isUpdate, + this.loadingAutomationIds = const {}, // Initialize with empty set + this.loadingSceneId, this.devices = const [], // this.automationActionExecutor, this.routineTab = false, this.createRoutineView = false}); RoutineState copyWith({ + String? loadingSceneId, + Set? loadingAutomationIds, List>? ifItems, List>? thenItems, List? scenes, @@ -79,6 +85,8 @@ class RoutineState extends Equatable { bool? createRoutineView, }) { return RoutineState( + loadingSceneId: loadingSceneId, + loadingAutomationIds: loadingAutomationIds ?? this.loadingAutomationIds, ifItems: ifItems ?? this.ifItems, thenItems: thenItems ?? this.thenItems, scenes: scenes ?? this.scenes, @@ -109,6 +117,7 @@ class RoutineState extends Equatable { @override List get props => [ + loadingAutomationIds, ifItems, thenItems, scenes, @@ -134,3 +143,38 @@ class RoutineState extends Equatable { createRoutineView ]; } + +class SceneInitial extends RoutineState {} + +class SceneLoading extends RoutineState {} + +class SceneLoaded extends RoutineState { + final List? scenesOrAutomation; + const SceneLoaded({this.scenesOrAutomation}); + @override + List get props => [ + scenesOrAutomation, + ]; +} + +class SceneError extends RoutineState { + final String message; + + const SceneError({required this.message}); + + @override + List get props => [message]; +} + +class SceneTriggerSuccess extends RoutineState { + final String sceneName; + + const SceneTriggerSuccess(this.sceneName); + + @override + List get props => [sceneName]; +} + +class UpdateAutomationStatusLoading extends RoutineState { + const UpdateAutomationStatusLoading(); +} diff --git a/lib/pages/routines/create_new_routines/commu_dropdown.dart b/lib/pages/routines/create_new_routines/commu_dropdown.dart new file mode 100644 index 00000000..13e2a437 --- /dev/null +++ b/lib/pages/routines/create_new_routines/commu_dropdown.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class CommunityDropdown extends StatelessWidget { + final String? selectedValue; + final Function(String?) onChanged; + + const CommunityDropdown({ + Key? key, + required this.selectedValue, + required this.onChanged, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Community", + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + color: ColorsManager.blackColor, + ), + ), + const SizedBox(height: 8), + BlocBuilder( + builder: (context, state) { + List communities = state.isSearching + ? state.filteredCommunity + : state.communityList; + + return SizedBox( + child: DropdownButtonFormField( + dropdownColor: ColorsManager.whiteColors, + value: selectedValue, + items: communities.map((community) { + return DropdownMenuItem( + value: community.uuid, + child: Text(' ${community.name}'), + ); + }).toList(), + onChanged: onChanged, + icon: const SizedBox.shrink(), + borderRadius: const BorderRadius.all(Radius.circular(10)), + hint: Padding( + padding: EdgeInsets.only(left: 10), + child: Text( + "Please Select", + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.textGray, + ), + ), + ), + decoration: inputTextFormDeco().copyWith( + contentPadding: EdgeInsets.zero, + suffixIcon: Container( + padding: EdgeInsets.zero, + width: 70, + height: 45, + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: const BorderRadius.only( + bottomRight: Radius.circular(10), + topRight: Radius.circular(10), + ), + border: Border.all( + color: ColorsManager.textGray, + width: 1.0, + ), + ), + child: const Center( + child: Icon( + Icons.keyboard_arrow_down, + color: ColorsManager.textGray, + ), + ), + ), + ), + ), + ); + }, + ), + ], + ), + ); + } +} diff --git a/lib/pages/routines/create_new_routines/create_new_routines.dart b/lib/pages/routines/create_new_routines/create_new_routines.dart new file mode 100644 index 00000000..5d1021f2 --- /dev/null +++ b/lib/pages/routines/create_new_routines/create_new_routines.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_state.dart'; +import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/create_new_routines/commu_dropdown.dart'; +import 'package:syncrow_web/pages/routines/create_new_routines/space_dropdown.dart'; + +import 'package:syncrow_web/utils/color_manager.dart'; + +class CreateNewRoutinesDialog extends StatefulWidget { + const CreateNewRoutinesDialog({Key? key}) : super(key: key); + + @override + State createState() => + _CreateNewRoutinesDialogState(); +} + +class _CreateNewRoutinesDialogState extends State { + String? _selectedCommunity; + String? _selectedSpace; + void _fetchSpaces(String communityId) { + context + .read() + .add(SpaceOnlyWithDevicesEvent(communityId)); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final _bloc = BlocProvider.of(context); + final spaces = _bloc.spacesOnlyWithDevices; + final isLoading = state is SpaceWithDeviceLoadingState; + + String spaceHint = 'Select a community first'; + + if (_selectedCommunity != null) { + if (isLoading) { + spaceHint = 'Loading spaces...'; + } else if (spaces.isEmpty) { + spaceHint = 'No spaces available'; + } else { + spaceHint = 'Select Space'; + } + } + + return AlertDialog( + backgroundColor: Colors.white, + insetPadding: EdgeInsets.zero, + contentPadding: EdgeInsets.zero, + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + title: Text( + 'Create New Routines', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColor, + ), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Divider(), + CommunityDropdown( + selectedValue: _selectedCommunity, + onChanged: (String? newValue) { + setState(() { + _selectedCommunity = newValue; + _selectedSpace = null; + }); + if (newValue != null) { + _fetchSpaces(newValue); + } + }, + ), + const SizedBox(height: 16), + SpaceDropdown( + hintMessage: spaceHint, + spaces: spaces, + selectedValue: _selectedSpace, + onChanged: (String? newValue) { + setState(() { + _selectedSpace = newValue; + }); + }, + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 20, + right: 20, + ), + child: TextButton( + onPressed: () { + + Navigator.of(context).pop(); + }, + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + color: ColorsManager.blackColor, + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 20, + right: 20, + ), + child: TextButton( + onPressed: + _selectedCommunity != null && _selectedSpace != null + ? () { + Navigator.of(context).pop({ + 'community': _selectedCommunity, + 'space': _selectedSpace, + }); + } + : null, + child: Text( + 'Next', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.w400, + fontSize: 14, + color: _selectedCommunity != null && + _selectedSpace != null + ? ColorsManager.blueColor + : Colors.blue.shade100, + ), + ), + ), + ), + ], + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/pages/routines/create_new_routines/space_dropdown.dart b/lib/pages/routines/create_new_routines/space_dropdown.dart new file mode 100644 index 00000000..f207c736 --- /dev/null +++ b/lib/pages/routines/create_new_routines/space_dropdown.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class SpaceDropdown extends StatelessWidget { + final List spaces; + final String? selectedValue; + final Function(String?)? onChanged; + final String hintMessage; + + const SpaceDropdown({ + Key? key, + required this.spaces, + required this.selectedValue, + required this.onChanged, + required this.hintMessage, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Space", + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.w400, + fontSize: 13, + color: ColorsManager.blackColor, + ), + ), + const SizedBox(height: 8), + DropdownButtonFormField( + value: selectedValue, + items: spaces.map((space) { + return DropdownMenuItem( + value: space.uuid, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + ' ${space.name}', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + color: ColorsManager.blackColor, + ), + ), + Text( + ' ${space.lastThreeParents}', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + ), + ), + ], + )); + }).toList(), + onChanged: onChanged, + icon: const SizedBox.shrink(), + borderRadius: const BorderRadius.all(Radius.circular(10)), + hint: Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + hintMessage, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.textGray, + ), + ), + ), + decoration: inputTextFormDeco().copyWith( + contentPadding: EdgeInsets.zero, + suffixIcon: Container( + width: 70, + height: 45, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: const BorderRadius.only( + bottomRight: Radius.circular(10), + topRight: Radius.circular(10), + ), + border: Border.all( + color: ColorsManager.textGray, + width: 1.0, + ), + ), + child: const Icon( + Icons.keyboard_arrow_down, + color: ColorsManager.textGray, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/routines/models/routine_model.dart b/lib/pages/routines/models/routine_model.dart index bb3e117b..2f7c2a24 100644 --- a/lib/pages/routines/models/routine_model.dart +++ b/lib/pages/routines/models/routine_model.dart @@ -9,6 +9,9 @@ class ScenesModel { final String status; final String type; final String? icon; + final String spaceName; + final String spaceId; + final String communityId; ScenesModel({ required this.id, @@ -16,6 +19,9 @@ class ScenesModel { required this.name, required this.status, required this.type, + required this.spaceName, + required this.spaceId, + required this.communityId, this.icon, }); @@ -41,6 +47,9 @@ class ScenesModel { name: json["name"] ?? '', status: json["status"] ?? '', type: json["type"] ?? '', + spaceName: json["spaceName"] ?? '', + spaceId: json["spaceId"] ?? '', + communityId: json["communityId"] ?? '', icon: isAutomation == true ? Assets.automation : (json["icon"] as String?), ); @@ -52,5 +61,8 @@ class ScenesModel { "name": name, "status": status, "type": type, + "spaceName": spaceName, + "spaceId": spaceId, + "communityId": communityId, }; } diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index b0efb4a9..962b3b89 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/create_new_routines/create_new_routines.dart'; import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart'; import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart'; -import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -16,10 +18,23 @@ class RoutinesView extends StatefulWidget { } class _RoutinesViewState extends State { - @override - void initState() { - super.initState(); - // context.read().add(FetchDevicesInRoutine()); + void _handleRoutineCreation(BuildContext context) async { + final result = await showDialog>( + context: context, + builder: (context) => const CreateNewRoutinesDialog(), + ); + + if (result == null) return; + final communityId = result['community']; + final spaceId = result['space']; + final _bloc = BlocProvider.of(context); + final routineBloc = context.read(); + _bloc.add(SaveCommunityIdAndSpaceIdEvent( + communityID: communityId, spaceID: spaceId)); + await Future.delayed(const Duration(seconds: 1)); + routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true)); + await Future.delayed(const Duration(milliseconds:500)); + _bloc.add(const ResetSelectedEvent()); } @override @@ -29,73 +44,57 @@ class _RoutinesViewState extends State { if (state.createRoutineView) { return const CreateNewRoutineView(); } + return Row( children: [ - Expanded(child: SpaceTreeView( - onSelect: () { - context.read() + Expanded( + child: SpaceTreeView( + onSelect: () => context.read() ..add(const LoadScenes()) - ..add(const LoadAutomation()); - }, - )), + ..add(const LoadAutomation()), + ), + ), Expanded( flex: 4, - child: ListView(children: [ - Container( - padding: const EdgeInsets.all(16), - height: MediaQuery.sizeOf(context).height, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Create New Routines", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox( - height: 10, - ), - RoutineViewCard( - onTap: () { - 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), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.read().state.selectedSpaces.isEmpty - ? 'Please select a space' - : 'Please select only one space to proceed'), - ), - ); - // CustomSnackBar.redSnackBar( - // context.read().state.selectedSpaces.isEmpty - // ? 'Please select a space' - // : 'Please select only one space to proceed'); - } - }, - icon: Icons.add, - textString: '', - ), - const SizedBox( - height: 15, - ), - const Expanded(child: FetchRoutineScenesAutomation()), - ], + child: ListView( + children: [ + Container( + padding: const EdgeInsets.all(16), + height: MediaQuery.sizeOf(context).height, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Create New Routines", + style: + Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + RoutineViewCard( + isLoading: false, + onChanged: (v) {}, + status: '', + spaceId: '', + automationId: '', + communityId: '', + sceneId: '', + cardType: '', + spaceName: '', + onTap: () => _handleRoutineCreation(context), + icon: Icons.add, + textString: '', + ), + const SizedBox(height: 15), + const Expanded(child: FetchRoutineScenesAutomation()), + ], + ), ), - ), - ]), - ), + ], + ), + ) ], ); }, 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..483500b1 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 @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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/routines/widgets/main_routine_view/routine_view_card.dart'; -import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.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'; @@ -12,7 +12,8 @@ class FetchRoutineScenesAutomation extends StatefulWidget { const FetchRoutineScenesAutomation({super.key}); @override - State createState() => _FetchRoutineScenesState(); + State createState() => + _FetchRoutineScenesState(); } class _FetchRoutineScenesState extends State @@ -45,46 +46,70 @@ class _FetchRoutineScenesState extends State ), const SizedBox(height: 10), if (state.scenes.isEmpty) - Text( - "No scenes found", - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, + Expanded( + child: Text( + "No scenes found", + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), ), ), if (state.scenes.isNotEmpty) ConstrainedBox( constraints: BoxConstraints( - maxHeight: isSmallScreenSize(context) ? 160 : 170, - maxWidth: MediaQuery.sizeOf(context).width * 0.7), + maxHeight: isSmallScreenSize(context) ? 190 : 200, + maxWidth: MediaQuery.sizeOf(context).width * 0.8), child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: state.scenes.length, - itemBuilder: (context, index) => Padding( - padding: EdgeInsets.only( - right: isSmallScreenSize(context) ? 4.0 : 8.0, - ), - child: RoutineViewCard( - onTap: () { - BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(createRoutineView: true), - ); - context.read().add( - GetSceneDetails( - sceneId: state.scenes[index].id, - isTabToRun: true, - isUpdate: true, - ), + scrollDirection: Axis.horizontal, + itemCount: state.scenes.length, + itemBuilder: (context, index) { + final scene = state.scenes[index]; + final isLoading = + state.loadingSceneId == scene.id; + + return Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) ? 4.0 : 8.0, + ), + child: RoutineViewCard( + isLoading: isLoading, + sceneOnTap: () { + context.read().add( + SceneTrigger( + sceneId: scene.id, + name: scene.name)); + }, + status: state.scenes[index].status, + communityId: + state.scenes[index].communityId ?? '', + spaceId: state.scenes[index].spaceId, + sceneId: state.scenes[index].sceneTuyaId!, + automationId: state.scenes[index].id, + cardType: 'scenes', + spaceName: state.scenes[index].spaceName, + onTap: () { + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent( + createRoutineView: true), ); - }, - textString: state.scenes[index].name, - icon: state.scenes[index].icon ?? Assets.logoHorizontal, - isFromScenes: true, - iconInBytes: state.scenes[index].iconInBytes, - ), - ), - ), + context.read().add( + GetSceneDetails( + sceneId: state.scenes[index].id, + isTabToRun: true, + isUpdate: true, + ), + ); + }, + textString: state.scenes[index].name, + icon: state.scenes[index].icon ?? + Assets.logoHorizontal, + isFromScenes: true, + iconInBytes: state.scenes[index].iconInBytes, + ), + ); + }), ), - const SizedBox(height: 15), + const SizedBox(height: 10), Text( "Automations", style: Theme.of(context).textTheme.titleLarge?.copyWith( @@ -92,43 +117,77 @@ class _FetchRoutineScenesState extends State fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 10), + const SizedBox(height: 5), if (state.automations.isEmpty) - Text( - "No automations found", - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, + Expanded( + child: Text( + "No automations found", + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), ), ), if (state.automations.isNotEmpty) ConstrainedBox( constraints: BoxConstraints( - maxHeight: isSmallScreenSize(context) ? 160 : 170, + maxHeight: isSmallScreenSize(context) ? 185 : 192, maxWidth: MediaQuery.sizeOf(context).width * 0.7), child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: state.automations.length, - itemBuilder: (context, index) => Padding( - padding: EdgeInsets.only( - right: isSmallScreenSize(context) ? 4.0 : 8.0, - ), - child: RoutineViewCard( - onTap: () { - BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(createRoutineView: true), - ); - context.read().add( - GetAutomationDetails( - automationId: state.automations[index].id, - isAutomation: true, - isUpdate: true), + scrollDirection: Axis.horizontal, + itemCount: state.automations.length, + itemBuilder: (context, index) { + final isLoading = state.automations! + .contains(state.automations[index].id); + + return Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) ? 4.0 : 8.0, + ), + child: RoutineViewCard( + isLoading: isLoading, + onChanged: (v) { + // BlocProvider.of(context) + context.read().add( + UpdateAutomationStatus( + automationId: + state.automations[index].id, + automationStatusUpdate: + AutomationStatusUpdate( + spaceUuid: state + .automations[index] + .spaceId, + isEnable: v), + communityId: state + .automations[index].communityId, + ), + ); + }, + status: state.automations[index].status, + communityId: '', + spaceId: state.automations[index].spaceId, + sceneId: '', + automationId: state.automations[index].id, + cardType: 'automations', + spaceName: state.scenes[index].spaceName, + onTap: () { + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent( + createRoutineView: true), ); - }, - textString: state.automations[index].name, - icon: state.automations[index].icon ?? Assets.automation, - ), - ), - ), + context.read().add( + GetAutomationDetails( + automationId: + state.automations[index].id, + isAutomation: true, + isUpdate: true), + ); + }, + textString: state.automations[index].name, + 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..345a86c8 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,5 @@ +import 'dart:async'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -6,37 +8,78 @@ import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { +class RoutineViewCard extends StatefulWidget with HelperResponsiveLayout { const RoutineViewCard({ super.key, required this.onTap, + this.sceneOnTap, required this.icon, required this.textString, + required this.spaceName, + required this.cardType, this.isFromScenes, this.iconInBytes, + required this.sceneId, + required this.communityId, + required this.spaceId, + required this.automationId, + required this.status, + this.onChanged, + required this.isLoading, }); final Function() onTap; + final Function()? sceneOnTap; + final dynamic icon; final String textString; + final String spaceName; + final String cardType; + final String sceneId; + final String spaceId; + final String status; + final bool isLoading; + + final void Function(bool)? onChanged; + final String automationId; + final String communityId; + final bool? isFromScenes; final Uint8List? iconInBytes; + @override + State createState() => _RoutineViewCardState(); +} + +class _RoutineViewCardState extends State { + bool _showTemporaryCheck = false; + + void _handleSceneTap() { + if (!_showTemporaryCheck) { + setState(() => _showTemporaryCheck = true); + widget.sceneOnTap?.call(); + Timer(const Duration(seconds: 3), () { + if (mounted) setState(() => _showTemporaryCheck = false); + }); + } + } + @override Widget build(BuildContext context) { - final double cardWidth = isSmallScreenSize(context) + // Use widget. instead of just + final double cardWidth = widget.isSmallScreenSize(context) ? 120 - : isMediumScreenSize(context) + : widget.isMediumScreenSize(context) ? 135 : 150; - final double cardHeight = isSmallScreenSize(context) ? 160 : 170; + final double cardHeight = widget.isSmallScreenSize(context) ? 190 : 200; - final double iconSize = isSmallScreenSize(context) - ? 50 - : isMediumScreenSize(context) - ? 60 - : 70; + final double iconSize = widget.isSmallScreenSize(context) + ? 70 + : widget.isMediumScreenSize(context) + ? 80 + : 90; return ConstrainedBox( constraints: BoxConstraints( @@ -50,69 +93,147 @@ 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, + widget.cardType != '' + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (widget.isFromScenes ?? false) + InkWell( + onTap: _handleSceneTap, + child: SvgPicture.asset( + _showTemporaryCheck + ? Assets.scenesPlayIconCheck + : Assets.scenesPlayIcon, 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: Text( - textString, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontSize: isSmallScreenSize(context) ? 10 : 12, - ), + ) + else if (widget.isLoading) + const SizedBox( + width: 49, + height: 20, + child: Center( + child: SizedBox( + width: 16, + height: 16, + child: + CircularProgressIndicator(strokeWidth: 2), + ), + ), + ) + else + CupertinoSwitch( + activeColor: ColorsManager.primaryColor, + value: widget.status == 'enable', + onChanged: widget.onChanged, + ) + ], + ) + : const SizedBox(), + InkWell( + onTap: widget.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: (widget.isFromScenes ?? false) + ? (widget.iconInBytes != null && + widget.iconInBytes?.isNotEmpty == true) + ? Image.memory( + widget.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, + ) + : (widget.icon is String && + widget.icon.endsWith('.svg')) + ? SvgPicture.asset( + widget.icon, + fit: BoxFit.contain, + ) + : Icon( + widget.icon, + color: ColorsManager.dialogBlueTitle, + size: widget.isSmallScreenSize(context) + ? 30 + : 40, + ), + ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: Column( + children: [ + Text( + widget.textString, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: + widget.isSmallScreenSize(context) ? 10 : 12, + ), + ), + if (widget.spaceName != '') + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.spaceLocationIcon, + fit: BoxFit.contain, + ), + Text( + widget.spaceName, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: + context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: + widget.isSmallScreenSize(context) + ? 10 + : 12, + ), + ), + ], + ), + ], + ), + ), + ], ), ), ], diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index 71d365ca..6e744a29 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -14,6 +14,7 @@ class SpaceModel { String? icon; final String? spaceTuyaUuid; String name; + String? lastThreeParents; final bool isPrivate; final String? invitationCode; SpaceModel? parent; @@ -33,6 +34,7 @@ class SpaceModel { SpaceModel({ this.uuid, String? internalId, + this.lastThreeParents, this.spaceTuyaUuid, required this.icon, required this.name, @@ -67,6 +69,7 @@ class SpaceModel { internalId: internalId, uuid: json['uuid'] ?? '', name: json['spaceName'], + lastThreeParents: json['lastThreeParents'], isPrivate: json['isPrivate'] ?? false, invitationCode: json['invitationCode'], subspaces: (json['subspaces'] as List?) @@ -125,6 +128,7 @@ class SpaceModel { 'uuid': uuid ?? '', 'spaceTuyaUuid': spaceTuyaUuid, 'name': name, + 'lastThreeParents': lastThreeParents, 'isPrivate': isPrivate, 'invitationCode': invitationCode, 'parent': parent?.uuid, diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 25a0177f..604abaf4 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -23,8 +23,9 @@ class DevicesManagementApi { : ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId), showServerMessage: true, expectedResponseModel: (json) { - List jsonData = - communityId.isNotEmpty && spaceId.isNotEmpty ? json['data'] : json; + List jsonData = communityId.isNotEmpty && spaceId.isNotEmpty + ? json['data'] + : json; List devicesList = jsonData.map((jsonItem) { return AllDevicesModel.fromJson(jsonItem); }).toList(); @@ -33,7 +34,7 @@ class DevicesManagementApi { ); return response; } catch (e) { - debugPrint('Error fetching $e'); + debugPrint('fetchDevices Error fetching $e'); return []; } } @@ -92,7 +93,8 @@ class DevicesManagementApi { } } - Future deviceBatchControl(List uuids, String code, dynamic value) async { + Future deviceBatchControl( + List uuids, String code, dynamic value) async { try { final body = { 'devicesUuid': uuids, @@ -116,7 +118,8 @@ class DevicesManagementApi { } } - static Future> getDevicesByGatewayId(String gatewayId) async { + static Future> getDevicesByGatewayId( + String gatewayId) async { final response = await HTTPService().get( path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId), showServerMessage: false, @@ -150,7 +153,9 @@ class DevicesManagementApi { String code, ) async { final response = await HTTPService().get( - path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code), + path: ApiEndpoints.getDeviceLogs + .replaceAll('{uuid}', uuid) + .replaceAll('{code}', code), showServerMessage: false, expectedResponseModel: (json) { return DeviceReport.fromJson(json); @@ -223,7 +228,8 @@ class DevicesManagementApi { } } - Future addScheduleRecord(ScheduleEntry sendSchedule, String uuid) async { + Future addScheduleRecord( + ScheduleEntry sendSchedule, String uuid) async { try { final response = await HTTPService().post( path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), @@ -240,7 +246,8 @@ class DevicesManagementApi { } } - Future> getDeviceSchedules(String uuid, String category) async { + Future> getDeviceSchedules( + String uuid, String category) async { try { final response = await HTTPService().get( path: ApiEndpoints.getScheduleByDeviceId @@ -263,7 +270,9 @@ class DevicesManagementApi { } Future updateScheduleRecord( - {required bool enable, required String uuid, required String scheduleId}) async { + {required bool enable, + required String uuid, + required String scheduleId}) async { try { final response = await HTTPService().put( path: ApiEndpoints.updateScheduleByDeviceId @@ -284,7 +293,8 @@ class DevicesManagementApi { } } - Future editScheduleRecord(String uuid, ScheduleEntry newSchedule) async { + Future editScheduleRecord( + String uuid, ScheduleEntry newSchedule) async { try { final response = await HTTPService().put( path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 56a29aa1..eaa09e27 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'; @@ -11,7 +12,8 @@ class SceneApi { static final HTTPService _httpService = HTTPService(); // //create scene - static Future> createScene(CreateSceneModel createSceneModel) async { + static Future> createScene( + CreateSceneModel createSceneModel) async { try { debugPrint('create scene model: ${createSceneModel.toMap()}'); final response = await _httpService.post( @@ -36,7 +38,8 @@ class SceneApi { CreateAutomationModel createAutomationModel, String projectId) async { try { final response = await _httpService.post( - path: ApiEndpoints.createAutomation.replaceAll('{projectId}', projectId), + path: + ApiEndpoints.createAutomation.replaceAll('{projectId}', projectId), body: createAutomationModel.toMap(), showServerMessage: false, expectedResponseModel: (json) { @@ -68,7 +71,8 @@ class SceneApi { //get scenes by community id and space id - static Future> getScenes(String spaceId, String communityId, String projectId, + static Future> getScenes( + String spaceId, String communityId, String projectId, {showInDevice = false}) async { try { final response = await _httpService.get( @@ -154,7 +158,8 @@ class SceneApi { try { final response = await _httpService.put( path: ApiEndpoints.updateScene.replaceAll('{sceneId}', sceneId), - body: createSceneModel.toJson(sceneId.isNotEmpty == true ? sceneId : null), + body: createSceneModel + .toJson(sceneId.isNotEmpty == true ? sceneId : null), expectedResponseModel: (json) { return json; }, @@ -166,14 +171,15 @@ class SceneApi { } //update automation - static updateAutomation( - CreateAutomationModel createAutomationModel, String automationId, String projectId) async { + static updateAutomation(CreateAutomationModel createAutomationModel, + String automationId, String projectId) async { try { final response = await _httpService.put( path: ApiEndpoints.updateAutomation .replaceAll('{automationId}', automationId) .replaceAll('{projectId}', projectId), - body: createAutomationModel.toJson(automationId.isNotEmpty == true ? automationId : null), + body: createAutomationModel + .toJson(automationId.isNotEmpty == true ? automationId : null), expectedResponseModel: (json) { return json; }, @@ -190,7 +196,8 @@ class SceneApi { final response = await _httpService.get( path: ApiEndpoints.getScene.replaceAll('{sceneId}', sceneId), showServerMessage: false, - expectedResponseModel: (json) => RoutineDetailsModel.fromMap(json['data']), + expectedResponseModel: (json) => + RoutineDetailsModel.fromMap(json['data']), ); return response; } catch (e) { @@ -199,7 +206,8 @@ class SceneApi { } //delete Scene - static Future deleteScene({required String unitUuid, required String sceneId}) async { + static Future deleteScene( + {required String unitUuid, required String sceneId}) async { try { final response = await _httpService.delete( path: ApiEndpoints.deleteScene @@ -216,7 +224,9 @@ class SceneApi { // delete automation static Future deleteAutomation( - {required String unitUuid, required String automationId, required String projectId}) async { + {required String unitUuid, + required String automationId, + required String projectId}) async { try { final response = await _httpService.delete( path: ApiEndpoints.deleteAutomation @@ -230,4 +240,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/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 4eced226..f18a8da8 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -302,4 +302,24 @@ class CommunitySpaceManagementApi { return []; } } + Future> getSpaceOnlyWithDevices( + {String? communityId, String? projectId}) async { + try { + final response = await HTTPService().get( + path: ApiEndpoints.spaceOnlyWithDevices + .replaceAll('{communityId}', communityId!) + .replaceAll('{projectId}', projectId!), + expectedResponseModel: (json) { + final spaceModels = (json['data'] as List) + .map((spaceJson) => SpaceModel.fromJson(spaceJson)) + .toList(); + return spaceModels; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching space hierarchy: $e'); + return []; + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 71e0fc55..be972392 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -9,15 +9,19 @@ abstract class ApiEndpoints { static const String sendOtp = '/authentication/user/send-otp'; static const String verifyOtp = '/authentication/user/verify-otp'; static const String getRegion = '/region'; - static const String visitorPassword = '/projects/{projectId}/visitor-password'; - static const String getDevices = '/projects/{projectId}/visitor-password/devices'; + static const String visitorPassword = + '/projects/{projectId}/visitor-password'; + static const String getDevices = + '/projects/{projectId}/visitor-password/devices'; - static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time'; + static const String sendOnlineOneTime = + '/visitor-password/temporary-password/online/one-time'; static const String sendOnlineMultipleTime = '/visitor-password/temporary-password/online/multiple-time'; //offline Password - static const String sendOffLineOneTime = '/visitor-password/temporary-password/offline/one-time'; + static const String sendOffLineOneTime = + '/visitor-password/temporary-password/offline/one-time'; static const String sendOffLineMultipleTime = '/visitor-password/temporary-password/offline/multiple-time'; @@ -39,32 +43,45 @@ abstract class ApiEndpoints { static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; // Space Module - static const String createSpace = '/projects/{projectId}/communities/{communityId}/spaces'; - static const String listSpaces = '/projects/{projectId}/communities/{communityId}/spaces'; + static const String createSpace = + '/projects/{projectId}/communities/{communityId}/spaces'; + static const String listSpaces = + '/projects/{projectId}/communities/{communityId}/spaces'; static const String deleteSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; static const String updateSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; - static const String getSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; - static const String getSpaceHierarchy = '/projects/{projectId}/communities/{communityId}/spaces'; + static const String getSpace = + '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}'; + static const String getSpaceHierarchy = + '/projects/{projectId}/communities/{communityId}/spaces'; // Community Module static const String createCommunity = '/projects/{projectId}/communities'; static const String getCommunityList = '/projects/{projectId}/communities'; - static const String getCommunityById = '/projects/{projectId}/communities/{communityId}'; - static const String updateCommunity = '/projects/{projectId}/communities/{communityId}'; - static const String deleteCommunity = '/projects/{projectId}communities/{communityId}'; - static const String getUserCommunities = '/projects/{projectId}/communities/user/{userUuid}'; - static const String createUserCommunity = '/projects/{projectId}/communities/user'; + static const String getCommunityById = + '/projects/{projectId}/communities/{communityId}'; + static const String updateCommunity = + '/projects/{projectId}/communities/{communityId}'; + static const String deleteCommunity = + '/projects/{projectId}communities/{communityId}'; + static const String getUserCommunities = + '/projects/{projectId}/communities/user/{userUuid}'; + static const String createUserCommunity = + '/projects/{projectId}/communities/user'; static const String getDeviceLogsByDate = '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; static const String scheduleByDeviceId = '/schedule/{deviceUuid}'; - static const String getScheduleByDeviceId = '/schedule/{deviceUuid}?category={category}'; - static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}'; - static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}'; + static const String getScheduleByDeviceId = + '/schedule/{deviceUuid}?category={category}'; + static const String deleteScheduleByDeviceId = + '/schedule/{deviceUuid}/{scheduleUuid}'; + static const String updateScheduleByDeviceId = + '/schedule/enable/{deviceUuid}'; static const String factoryReset = '/device/factory/reset/{deviceUuid}'; - static const String powerClamp = '/device/{powerClampUuid}/power-clamp/status'; + static const String powerClamp = + '/device/{powerClampUuid}/power-clamp/status'; //product static const String listProducts = '/products'; @@ -76,27 +93,33 @@ abstract class ApiEndpoints { static const String createAutomation = '/projects/{projectId}/automations'; static const String getUnitScenes = '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; - static const String getAutomationDetails = '/projects/{projectId}/automations/{automationId}'; + static const String getAutomationDetails = + '/projects/{projectId}/automations/{automationId}'; static const String getScene = '/scene/tap-to-run/{sceneId}'; static const String deleteScene = '/scene/tap-to-run/{sceneId}'; - static const String deleteAutomation = '/projects/{projectId}/automations/{automationId}'; + static const String deleteAutomation = + '/projects/{projectId}/automations/{automationId}'; static const String updateScene = '/scene/tap-to-run/{sceneId}'; - static const String updateAutomation = '/projects/{projectId}/automations/{automationId}'; + static const String updateAutomation = + '/projects/{projectId}/automations/{automationId}'; //space model static const String listSpaceModels = '/projects/{projectId}/space-models'; static const String createSpaceModel = '/projects/{projectId}/space-models'; - static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}'; - static const String updateSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}'; + static const String getSpaceModel = + '/projects/{projectId}/space-models/{spaceModelUuid}'; + static const String updateSpaceModel = + '/projects/{projectId}/space-models/{spaceModelUuid}'; //tag static const String listTags = '/projects/{projectId}/tags'; static const String linkSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}/spaces/link'; - static const String validateSpaceModel = '/projects/{projectId}/spaces/validate'; + static const String validateSpaceModel = + '/projects/{projectId}/spaces/validate'; static const String roleTypes = '/role/types'; static const String permission = '/permission/{roleUuid}'; @@ -107,8 +130,16 @@ abstract class ApiEndpoints { static const String getUserById = '/projects/{projectId}/user/{userUuid}'; static const String editUser = '/invite-user/{inviteUserUuid}'; static const String deleteUser = '/invite-user/{inviteUserUuid}'; - static const String changeUserStatus = '/invite-user/{invitedUserUuid}/disable'; + static const String changeUserStatus = + '/invite-user/{invitedUserUuid}/disable'; 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'; + static const String spaceOnlyWithDevices = + '/projects/{projectId}/communities/{communityId}/spaces?onlyWithDevices=true'; } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index b7a0115f..0e7b0bd2 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -406,6 +406,9 @@ 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/spaseLocationIcon.svg'; + static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.svg'; + static const String scenesPlayIconCheck = + 'assets/icons/scenesPlayIconCheck.svg'; }