diff --git a/lib/main.dart b/lib/main.dart index 975578cf..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'; @@ -54,7 +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 76261c6a..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,7 +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..0ed0e7c2 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'; @@ -23,6 +25,9 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { create: (context) => DeviceManagementBloc()..add(FetchDevices(context)), ), + BlocProvider( + create: (context) => CreateRoutineBloc(), + ), ], child: WebScaffold( appBarTitle: Text( @@ -39,10 +44,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 +70,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/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 d4f61bae..ef9ca848 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -1,11 +1,14 @@ + 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'; @@ -13,9 +16,14 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/routine_details_model.dart'; import 'package:syncrow_web/pages/routines/models/routine_model.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/routines_api.dart'; +import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/constants/strings_manager.dart'; +import 'package:syncrow_web/utils/constants/temp_const.dart'; +import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; import 'package:syncrow_web/utils/navigation_service.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:uuid/uuid.dart'; @@ -52,15 +60,18 @@ class RoutineBloc extends Bloc { on(_triggerSwitchTabsEvent); on(_createNewRoutineViewEvent); on(_resetErrorMessage); - on(_onSceneTrigger); + 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()); @@ -86,8 +97,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; @@ -96,18 +107,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; @@ -118,22 +132,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); @@ -143,13 +161,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)); @@ -158,19 +178,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( @@ -187,19 +218,31 @@ 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, @@ -215,14 +258,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)); } @@ -236,7 +281,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)) { @@ -249,7 +295,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; @@ -295,10 +342,10 @@ class RoutineBloc extends Bloc { }).toList(); BuildContext context = NavigationService.navigatorKey.currentContext!; - var spaceBloc = context.read(); + var spaceBloc = context.read(); final createSceneModel = CreateSceneModel( - spaceUuid: spaceBloc.state.selectedSpaces[0], + spaceUuid: spaceBloc.selectedSpaceId, iconId: state.selectedIcon ?? '', showInDevice: true, sceneName: state.routineName ?? '', @@ -325,10 +372,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', @@ -348,7 +395,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'); @@ -424,10 +472,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( @@ -439,7 +487,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()); @@ -460,17 +509,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); @@ -481,7 +534,8 @@ class RoutineBloc extends Bloc { isAutomation: false, isTabToRun: false)); } else { - emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith( + ifItems: ifItems, selectedFunctions: selectedFunctions)); } } } @@ -493,18 +547,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, @@ -537,7 +596,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; @@ -573,8 +633,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), }; @@ -617,7 +680,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, @@ -665,10 +729,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' @@ -703,7 +769,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] = []; } @@ -775,7 +842,8 @@ class RoutineBloc extends Bloc { } } - FutureOr _onResetRoutineState(ResetRoutineState event, Emitter emit) { + FutureOr _onResetRoutineState( + ResetRoutineState event, Emitter emit) { emit(state.copyWith( ifItems: [], thenItems: [], @@ -799,21 +867,22 @@ class RoutineBloc extends Bloc { createRoutineView: false)); } - FutureOr _deleteScene(DeleteScene event, Emitter emit) async { + FutureOr _deleteScene( + DeleteScene event, Emitter emit) async { try { - final projectId = await ProjectManager.getProjectUUID() ?? ''; - emit(state.copyWith(isLoading: true)); + final projectId = await ProjectManager.getProjectUUID() ?? ''; BuildContext context = NavigationService.navigatorKey.currentContext!; - var spaceBloc = context.read(); + var createRoutineBloc = context.read(); if (state.isTabToRun) { await SceneApi.deleteScene( - unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? ''); + unitUuid: createRoutineBloc.selectedSpaceId, + sceneId: state.sceneId ?? ''); } else { await SceneApi.deleteAutomation( - unitUuid: spaceBloc.state.selectedSpaces[0], - automationId: state.automationId ?? '', - projectId: projectId); + projectId: projectId, + unitUuid: createRoutineBloc.selectedSpaceId, + automationId: state.automationId ?? ''); } add(const LoadScenes()); @@ -842,21 +911,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)); @@ -865,7 +944,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)) { @@ -879,7 +959,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; @@ -932,7 +1013,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()); @@ -951,10 +1033,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', @@ -1049,10 +1130,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( @@ -1063,7 +1144,7 @@ class RoutineBloc extends Bloc { conditions: conditions, actions: actions, ); - + final projectId = await ProjectManager.getProjectUUID() ?? ''; final result = await SceneApi.updateAutomation( createAutomationModel, state.automationId ?? '', projectId); @@ -1089,7 +1170,6 @@ class RoutineBloc extends Bloc { GetAutomationDetails event, Emitter emit) async { try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - emit(state.copyWith( isLoading: true, isUpdate: true, @@ -1169,13 +1249,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' @@ -1206,7 +1288,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) { @@ -1248,10 +1331,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( @@ -1301,8 +1388,6 @@ class RoutineBloc extends Bloc { } } - - Future _onUpdateAutomationStatus( UpdateAutomationStatus event, Emitter emit) async { // Create a new set safely 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/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index 3c140a4e..f21a2ad7 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(milliseconds: 500)); + routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true)); + await Future.delayed(const Duration(milliseconds: 500)); + BlocProvider.of(context).add(const ResetSelectedEvent()); } @override @@ -29,96 +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( - isLoading: false, - onChanged: (v) {}, - status: '', - spaceId: '', - automationId: '', - communityId: '', - sceneId: '', - cardType: '', - spaceName: '', - 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 809c462c..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 @@ -109,7 +109,7 @@ class _FetchRoutineScenesState extends State ); }), ), - const SizedBox(height: 15), + const SizedBox(height: 10), Text( "Automations", style: Theme.of(context).textTheme.titleLarge?.copyWith( @@ -117,7 +117,7 @@ class _FetchRoutineScenesState extends State fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 10), + const SizedBox(height: 5), if (state.automations.isEmpty) Expanded( child: Text( @@ -130,7 +130,7 @@ class _FetchRoutineScenesState extends State if (state.automations.isNotEmpty) ConstrainedBox( constraints: BoxConstraints( - maxHeight: isSmallScreenSize(context) ? 190 : 200, + maxHeight: isSmallScreenSize(context) ? 185 : 192, maxWidth: MediaQuery.sizeOf(context).width * 0.7), child: ListView.builder( scrollDirection: Axis.horizontal, @@ -149,16 +149,17 @@ class _FetchRoutineScenesState extends State // BlocProvider.of(context) context.read().add( UpdateAutomationStatus( - automationId: - state.automations[index].id, - automationStatusUpdate: - AutomationStatusUpdate( - spaceUuid: state - .automations[index] - .spaceId, - isEnable: v), - communityId: - 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9'), + automationId: + state.automations[index].id, + automationStatusUpdate: + AutomationStatusUpdate( + spaceUuid: state + .automations[index] + .spaceId, + isEnable: v), + communityId: state + .automations[index].communityId, + ), ); }, status: state.automations[index].status, 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/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 00449836..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,13 +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 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'; }