From 16df75eb49322c7c7e634b9784f81886afd66cee Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 13 Mar 2025 13:14:31 +0400 Subject: [PATCH 001/238] fixed space update --- .../bloc/space_management_bloc.dart | 24 ++++++++++++++++--- .../bloc/space_management_event.dart | 8 ++++--- .../widgets/community_structure_widget.dart | 1 + 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 48208c87..b0f28459 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; @@ -457,7 +458,8 @@ class SpaceManagementBloc extends Bloc> saveSpacesHierarchically( - List spaces, String communityUuid) async { + BuildContext context, List spaces, String communityUuid) async { final orderedSpaces = flattenHierarchy(spaces); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + var spaceBloc = context.read(); + List communities = spaceBloc.state.communityList; + CommunityModel? selectedCommunity = communities.firstWhere( + (community) => community.uuid == communityUuid, + ); + if (selectedCommunity != null) { + print("community name ${selectedCommunity}, spaces ${selectedCommunity.spaces.length}"); + } final parentsToDelete = orderedSpaces.where((space) => space.status == SpaceStatus.deleted && @@ -532,7 +542,13 @@ class SpaceManagementBloc extends Bloc tagUpdates = []; - final prevSpace = await _api.getSpace(communityUuid, space.uuid!, projectUuid); + List matchedSpaces = + selectedCommunity.spaces.where((space) => space.uuid == space.uuid).toList(); + + if (matchedSpaces.isEmpty) continue; + + final prevSpace = matchedSpaces[0]; + final List subspaceUpdates = []; final List? prevSubspaces = prevSpace?.subspaces; final List? newSubspaces = space.subspaces; @@ -598,6 +614,8 @@ class SpaceManagementBloc extends Bloc spaces; final String communityUuid; + final BuildContext context; - const SaveSpacesEvent({ + const SaveSpacesEvent( + this.context, { required this.spaces, required this.communityUuid, }); @override - List get props => [spaces, communityUuid]; + List get props => [spaces, communityUuid, context]; } class UpdateSpacePositionEvent extends SpaceManagementEvent { @@ -170,4 +172,4 @@ class UpdateSpaceModelCache extends SpaceManagementEvent { class DeleteSpaceModelFromCache extends SpaceManagementEvent { final String deletedUuid; DeleteSpaceModelFromCache(this.deletedUuid); -} \ No newline at end of file +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 12d76452..56352432 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -460,6 +460,7 @@ class _CommunityStructureAreaState extends State { String communityUuid = widget.selectedCommunity!.uuid; context.read().add(SaveSpacesEvent( + context, spaces: spacesToSave, communityUuid: communityUuid, )); From 8ed6980264a921519473ba74d5be21286b9bf133 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 13 Mar 2025 13:51:52 +0400 Subject: [PATCH 002/238] resposnive text length of space model name --- .../widgets/space_model_card_widget.dart | 79 ++++++++----------- lib/utils/string_utils.dart | 6 ++ 2 files changed, 38 insertions(+), 47 deletions(-) create mode 100644 lib/utils/string_utils.dart diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index d028acba..4d72a419 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -18,8 +18,10 @@ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; +import 'package:syncrow_web/utils/string_utils.dart'; -class SpaceModelCardWidget extends StatelessWidget { +class SpaceModelCardWidget extends StatelessWidget with HelperResponsiveLayout { final SpaceTemplateModel model; final BuildContext? pageContext; final bool topActionsDisabled; @@ -76,14 +78,17 @@ class SpaceModelCardWidget extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - model.modelName, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: Colors.black, - fontWeight: FontWeight.bold, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + Expanded( + child: Text( + StringUtils.capitalizeFirstLetter(model.modelName), + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.bold, + fontSize: isSmallScreenSize(context) ? 13 : 20, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), ), if (!topActionsDisabled) Row( @@ -95,15 +100,12 @@ class SpaceModelCardWidget extends StatelessWidget { builder: (BuildContext dialogContext) { return BlocProvider( create: (_) => LinkSpaceToModelBloc(), - child: BlocListener( + child: BlocListener( listenWhen: (previous, current) { return previous != current; }, listener: (context, state) { - final _bloc = - BlocProvider.of( - context); + final _bloc = BlocProvider.of(context); if (state is SpaceModelLoading) { showDialog( context: context, @@ -111,19 +113,14 @@ class SpaceModelCardWidget extends StatelessWidget { builder: (BuildContext context) { return Dialog( shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular( - 20)), + borderRadius: BorderRadius.circular(20)), elevation: 10, backgroundColor: Colors.white, child: Padding( - padding: - const EdgeInsets.symmetric( - vertical: 30, - horizontal: 50), + padding: const EdgeInsets.symmetric( + vertical: 30, horizontal: 50), child: Column( - mainAxisSize: - MainAxisSize.min, + mainAxisSize: MainAxisSize.min, children: [ CustomLoadingIndicator(), ], @@ -132,19 +129,14 @@ class SpaceModelCardWidget extends StatelessWidget { ); }, ); - } else if (state - is AlreadyHaveLinkedState) { + } else if (state is AlreadyHaveLinkedState) { Navigator.of(dialogContext).pop(); - showOverwriteDialog( - context, _bloc, model); - } else if (state - is SpaceValidationSuccess) { + showOverwriteDialog(context, _bloc, model); + } else if (state is SpaceValidationSuccess) { _bloc.add(LinkSpaceModelEvent( - isOverWrite: false, - selectedSpaceMode: model.uuid)); + isOverWrite: false, selectedSpaceMode: model.uuid)); - Future.delayed( - const Duration(seconds: 1), () { + Future.delayed(const Duration(seconds: 1), () { Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop(); @@ -152,29 +144,23 @@ class SpaceModelCardWidget extends StatelessWidget { showDialog( context: context, - builder: - (BuildContext dialogContext) { + builder: (BuildContext dialogContext) { return const LinkingSuccessful(); }, ).then((v) { - Future.delayed( - const Duration(seconds: 2), () { + Future.delayed(const Duration(seconds: 2), () { Navigator.of(dialogContext).pop(); }); }); - } else if (state - is SpaceModelLinkSuccess) { + } else if (state is SpaceModelLinkSuccess) { Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop(); showDialog( context: context, barrierDismissible: false, - builder: (BuildContext - successDialogContext) { - Future.delayed( - const Duration(seconds: 2), () { - Navigator.of(successDialogContext) - .pop(); + builder: (BuildContext successDialogContext) { + Future.delayed(const Duration(seconds: 2), () { + Navigator.of(successDialogContext).pop(); }); return const LinkingSuccessful(); @@ -232,8 +218,7 @@ class SpaceModelCardWidget extends StatelessWidget { ), ), ), - if (productTagCount.isNotEmpty && - model.subspaceModels != null) + if (productTagCount.isNotEmpty && model.subspaceModels != null) Container( width: 1.0, color: ColorsManager.softGray, diff --git a/lib/utils/string_utils.dart b/lib/utils/string_utils.dart new file mode 100644 index 00000000..c8d8e2af --- /dev/null +++ b/lib/utils/string_utils.dart @@ -0,0 +1,6 @@ +class StringUtils { + static String capitalizeFirstLetter(String text) { + if (text.isEmpty) return text; + return text[0].toUpperCase() + text.substring(1); + } +} From c39c6937552ab4b580556731e64338138467b66f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 13 Mar 2025 14:37:39 +0400 Subject: [PATCH 003/238] remove unused import --- lib/services/routines_api.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 9360820c..4ca69546 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -6,7 +6,6 @@ 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/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; class SceneApi { static final HTTPService _httpService = HTTPService(); From 70d31f53513066d06b05d9d99d41fb0ac22810b4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 13 Mar 2025 14:51:09 +0400 Subject: [PATCH 004/238] removed print --- .../all_spaces/bloc/space_management_bloc.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index b0f28459..d675c680 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -518,9 +518,6 @@ class SpaceManagementBloc extends Bloc community.uuid == communityUuid, ); - if (selectedCommunity != null) { - print("community name ${selectedCommunity}, spaces ${selectedCommunity.spaces.length}"); - } final parentsToDelete = orderedSpaces.where((space) => space.status == SpaceStatus.deleted && @@ -614,8 +611,6 @@ class SpaceManagementBloc extends Bloc Date: Fri, 14 Mar 2025 11:50:18 +0400 Subject: [PATCH 005/238] updated endpoint of get automation by space --- .../bloc/routine_bloc/routine_bloc.dart | 196 ++++++------------ lib/services/routines_api.dart | 39 ++-- lib/utils/constants/api_const.dart | 2 +- 3 files changed, 85 insertions(+), 152 deletions(-) diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index 1baafb14..801daa1c 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -1,6 +1,5 @@ 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'; @@ -16,9 +15,6 @@ import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/routines_api.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -import 'package:syncrow_web/utils/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'; @@ -61,8 +57,7 @@ class RoutineBloc extends Bloc { TriggerSwitchTabsEvent event, Emitter emit, ) { - emit(state.copyWith( - routineTab: event.isRoutineTab, createRoutineView: false)); + emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false)); add(ResetRoutineState()); if (event.isRoutineTab) { add(const LoadScenes()); @@ -88,8 +83,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; @@ -98,21 +93,18 @@ class RoutineBloc extends Bloc { } if (event.isTabToRun) { - emit(state.copyWith( - ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); + emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); } else { - emit(state.copyWith( - ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); + emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); } } - void _onAddToThenContainer( - AddToThenContainer event, Emitter emit) { + void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { final currentItems = List>.from(state.thenItems); // Find the index of the item in teh current itemsList - int index = currentItems.indexWhere( - (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = + currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { currentItems[index] = event.item; @@ -123,26 +115,22 @@ class RoutineBloc extends Bloc { emit(state.copyWith(thenItems: currentItems)); } - void _onAddFunctionsToRoutine( - AddFunctionToRoutine event, Emitter emit) { + void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter emit) { try { if (event.functions.isEmpty) return; - List selectedFunction = - List.from(event.functions); + List selectedFunction = List.from(event.functions); Map> currentSelectedFunctions = Map>.from(state.selectedFunctions); if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { List currentFunctions = - List.from( - currentSelectedFunctions[event.uniqueCustomId] ?? []); + List.from(currentSelectedFunctions[event.uniqueCustomId] ?? []); List functionCode = []; for (int i = 0; i < selectedFunction.length; i++) { for (int j = 0; j < currentFunctions.length; j++) { - if (selectedFunction[i].functionCode == - currentFunctions[j].functionCode) { + if (selectedFunction[i].functionCode == currentFunctions[j].functionCode) { currentFunctions[j] = selectedFunction[i]; if (!functionCode.contains(currentFunctions[j].functionCode)) { functionCode.add(currentFunctions[j].functionCode); @@ -152,15 +140,13 @@ class RoutineBloc extends Bloc { } for (int i = 0; i < functionCode.length; i++) { - selectedFunction - .removeWhere((code) => code.functionCode == functionCode[i]); + selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]); } - currentSelectedFunctions[event.uniqueCustomId] = - List.from(currentFunctions)..addAll(selectedFunction); + currentSelectedFunctions[event.uniqueCustomId] = List.from(currentFunctions) + ..addAll(selectedFunction); } else { - currentSelectedFunctions[event.uniqueCustomId] = - List.from(event.functions); + currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions); } emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); @@ -169,8 +155,7 @@ 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 { @@ -179,11 +164,9 @@ class RoutineBloc extends Bloc { BuildContext context = NavigationService.navigatorKey.currentContext!; var spaceBloc = context.read(); for (var communityId in spaceBloc.state.selectedCommunities) { - List spacesList = - spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; for (var spaceId in spacesList) { - scenes.addAll( - await SceneApi.getScenes(spaceId, communityId, projectUuid)); + scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid)); } } @@ -201,19 +184,18 @@ 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() ?? ''; try { BuildContext context = NavigationService.navigatorKey.currentContext!; var spaceBloc = context.read(); for (var communityId in spaceBloc.state.selectedCommunities) { - List spacesList = - spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; for (var spaceId in spacesList) { - automations.addAll(await SceneApi.getAutomation(spaceId)); + automations.addAll(await SceneApi.getAutomation(spaceId, communityId, projectId)); } } emit(state.copyWith( @@ -230,16 +212,14 @@ 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)); } @@ -253,8 +233,7 @@ 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)) { @@ -267,8 +246,7 @@ 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; @@ -344,8 +322,7 @@ class RoutineBloc extends Bloc { } } - Future _onCreateAutomation( - CreateAutomationEvent event, Emitter emit) async { + Future _onCreateAutomation(CreateAutomationEvent event, Emitter emit) async { try { if (state.routineName == null || state.routineName!.isEmpty) { emit(state.copyWith( @@ -366,8 +343,7 @@ 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'); @@ -479,21 +455,17 @@ 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); @@ -504,8 +476,7 @@ class RoutineBloc extends Bloc { isAutomation: false, isTabToRun: false)); } else { - emit(state.copyWith( - ifItems: ifItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); } } } @@ -517,23 +488,18 @@ class RoutineBloc extends Bloc { )); } - FutureOr _onEffectiveTimeEvent( - EffectiveTimePeriodEvent event, Emitter emit) { + FutureOr _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter emit) { emit(state.copyWith(effectiveTime: event.effectiveTime)); } - FutureOr _onSetRoutineName( - SetRoutineName event, Emitter emit) { + FutureOr _onSetRoutineName(SetRoutineName event, Emitter emit) { emit(state.copyWith( routineName: event.name, )); } - ( - List>, - List>, - Map> - ) _createCardData( + (List>, List>, Map>) + _createCardData( List actions, List? conditions, Map> currentFunctions, @@ -566,8 +532,7 @@ class RoutineBloc extends Bloc { 'deviceId': condition.entityId, 'title': matchingDevice.name ?? condition.entityId, 'productType': condition.entityType, - 'imagePath': - matchingDevice.getDefaultIcon(condition.entityType), + 'imagePath': matchingDevice.getDefaultIcon(condition.entityType), }; final functions = matchingDevice.functions; @@ -603,11 +568,8 @@ class RoutineBloc extends Bloc { final cardData = { 'entityId': action.entityId, 'uniqueCustomId': const Uuid().v4(), - 'deviceId': - action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'title': action.actionExecutor == 'delay' - ? 'Delay' - : (matchingDevice.name ?? 'Device'), + 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'title': action.actionExecutor == 'delay' ? 'Delay' : (matchingDevice.name ?? 'Device'), 'productType': action.productType, 'imagePath': matchingDevice.getDefaultIcon(action.productType), }; @@ -650,8 +612,7 @@ class RoutineBloc extends Bloc { return (thenItems, ifItems, currentFunctions); } - Future _onGetSceneDetails( - GetSceneDetails event, Emitter emit) async { + Future _onGetSceneDetails(GetSceneDetails event, Emitter emit) async { try { emit(state.copyWith( isLoading: true, @@ -699,12 +660,10 @@ class RoutineBloc extends Bloc { if (!deviceCards.containsKey(deviceId)) { deviceCards[deviceId] = { 'entityId': action.entityId, - 'deviceId': - action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'uniqueCustomId': - action.type == 'automation' || action.actionExecutor == 'delay' - ? const Uuid().v4() - : action.entityId, + 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay' + ? const Uuid().v4() + : action.entityId, 'title': action.actionExecutor == 'delay' ? 'Delay' : action.type == 'automation' @@ -739,8 +698,7 @@ class RoutineBloc extends Bloc { ), ); // emit(state.copyWith(automationActionExecutor: action.actionExecutor)); - } else if (action.executorProperty != null && - action.actionExecutor != 'delay') { + } else if (action.executorProperty != null && action.actionExecutor != 'delay') { if (!updatedFunctions.containsKey(uniqueCustomId)) { updatedFunctions[uniqueCustomId] = []; } @@ -812,8 +770,7 @@ class RoutineBloc extends Bloc { } } - FutureOr _onResetRoutineState( - ResetRoutineState event, Emitter emit) { + FutureOr _onResetRoutineState(ResetRoutineState event, Emitter emit) { emit(state.copyWith( ifItems: [], thenItems: [], @@ -837,20 +794,17 @@ class RoutineBloc extends Bloc { createRoutineView: false)); } - FutureOr _deleteScene( - DeleteScene event, Emitter emit) async { + FutureOr _deleteScene(DeleteScene event, Emitter emit) async { try { emit(state.copyWith(isLoading: true)); BuildContext context = NavigationService.navigatorKey.currentContext!; var spaceBloc = context.read(); if (state.isTabToRun) { await SceneApi.deleteScene( - unitUuid: spaceBloc.state.selectedSpaces[0], - sceneId: state.sceneId ?? ''); + unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? ''); } else { await SceneApi.deleteAutomation( - unitUuid: spaceBloc.state.selectedSpaces[0], - automationId: state.automationId ?? ''); + unitUuid: spaceBloc.state.selectedSpaces[0], automationId: state.automationId ?? ''); } add(const LoadScenes()); @@ -879,8 +833,7 @@ class RoutineBloc extends Bloc { // } // } - FutureOr _fetchDevices( - FetchDevicesInRoutine event, Emitter emit) async { + FutureOr _fetchDevices(FetchDevicesInRoutine event, Emitter emit) async { emit(state.copyWith(isLoading: true)); try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; @@ -890,11 +843,10 @@ class RoutineBloc extends Bloc { BuildContext context = NavigationService.navigatorKey.currentContext!; var spaceBloc = context.read(); for (var communityId in spaceBloc.state.selectedCommunities) { - List spacesList = - spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; for (var spaceId in spacesList) { - devices.addAll(await DevicesManagementApi() - .fetchDevices(communityId, spaceId, projectUuid)); + devices + .addAll(await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid)); } } @@ -904,8 +856,7 @@ 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)) { @@ -919,8 +870,7 @@ 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; @@ -973,8 +923,7 @@ class RoutineBloc extends Bloc { actions: actions, ); - final result = - await SceneApi.updateScene(createSceneModel, state.sceneId ?? ''); + final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? ''); if (result['success']) { add(ResetRoutineState()); add(const LoadScenes()); @@ -993,8 +942,7 @@ class RoutineBloc extends Bloc { } } - FutureOr _onUpdateAutomation( - UpdateAutomation event, Emitter emit) async { + FutureOr _onUpdateAutomation(UpdateAutomation event, Emitter emit) async { try { if (state.routineName == null || state.routineName!.isEmpty) { emit(state.copyWith( @@ -1105,8 +1053,8 @@ class RoutineBloc extends Bloc { actions: actions, ); - final result = await SceneApi.updateAutomation( - createAutomationModel, state.automationId ?? ''); + final result = + await SceneApi.updateAutomation(createAutomationModel, state.automationId ?? ''); if (result['success']) { add(ResetRoutineState()); @@ -1139,8 +1087,7 @@ class RoutineBloc extends Bloc { thenItems: [], )); - final automationDetails = - await SceneApi.getAutomationDetails(event.automationId); + final automationDetails = await SceneApi.getAutomationDetails(event.automationId); final Map> deviceIfCards = {}; final Map> deviceThenCards = {}; @@ -1208,15 +1155,13 @@ 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' @@ -1247,8 +1192,7 @@ 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) { @@ -1290,14 +1234,10 @@ 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( diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 4ca69546..ea01a5b7 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -11,8 +11,7 @@ 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( @@ -69,8 +68,7 @@ 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( @@ -98,11 +96,14 @@ class SceneApi { //getAutomation - static Future> getAutomation(String spaceId) async { + static Future> getAutomation( + String spaceId, String communityId, String projectId) async { try { final response = await _httpService.get( - path: - ApiEndpoints.getSpaceAutomation.replaceAll('{spaceUuid}', spaceId), + path: ApiEndpoints.getSpaceAutomation + .replaceAll('{spaceUuid}', spaceId) + .replaceAll('{communityId}', communityId) + .replaceAll('{projectId}', projectId), showServerMessage: false, expectedResponseModel: (json) { List scenes = []; @@ -132,12 +133,10 @@ class SceneApi { // } //automation details - static Future getAutomationDetails( - String automationId) async { + static Future getAutomationDetails(String automationId) async { try { final response = await _httpService.get( - path: ApiEndpoints.getAutomationDetails - .replaceAll('{automationId}', automationId), + path: ApiEndpoints.getAutomationDetails.replaceAll('{automationId}', automationId), showServerMessage: false, expectedResponseModel: (json) => RoutineDetailsModel.fromMap(json), ); @@ -152,8 +151,7 @@ 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; }, @@ -165,14 +163,11 @@ class SceneApi { } //update automation - static updateAutomation( - CreateAutomationModel createAutomationModel, String automationId) async { + static updateAutomation(CreateAutomationModel createAutomationModel, String automationId) async { try { final response = await _httpService.put( - path: ApiEndpoints.updateAutomation - .replaceAll('{automationId}', automationId), - body: createAutomationModel - .toJson(automationId.isNotEmpty == true ? automationId : null), + path: ApiEndpoints.updateAutomation.replaceAll('{automationId}', automationId), + body: createAutomationModel.toJson(automationId.isNotEmpty == true ? automationId : null), expectedResponseModel: (json) { return json; }, @@ -189,8 +184,7 @@ 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,8 +193,7 @@ 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 diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 5aa188d9..4307699e 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -69,7 +69,7 @@ abstract class ApiEndpoints { //product static const String listProducts = '/products'; static const String getSpaceScenes = '/scene/tap-to-run/{spaceUuid}'; - static const String getSpaceAutomation = '/automation/{spaceUuid}'; + static const String getSpaceAutomation = '/projects/{projectId}communities/{communityId}/spaces/{spaceUuid}/automations'; static const String getIconScene = '/scene/icon'; static const String createScene = '/scene/tap-to-run'; static const String createAutomation = '/automation'; From 7ae3e4a933b174c4e97ac329a227f18a1b543c58 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 14 Mar 2025 11:54:47 +0400 Subject: [PATCH 006/238] updated endpoint for creating automations --- lib/pages/routines/bloc/routine_bloc/routine_bloc.dart | 4 +++- lib/services/routines_api.dart | 4 ++-- lib/utils/constants/api_const.dart | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index 801daa1c..767f40b2 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -324,6 +324,8 @@ class RoutineBloc extends Bloc { 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', @@ -434,7 +436,7 @@ class RoutineBloc extends Bloc { actions: actions, ); - final result = await SceneApi.createAutomation(createAutomationModel); + final result = await SceneApi.createAutomation(createAutomationModel, projectUuid); if (result['success']) { add(ResetRoutineState()); add(const LoadAutomation()); diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index ea01a5b7..fbc6c9ec 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -33,10 +33,10 @@ class SceneApi { // // create automation static Future> createAutomation( - CreateAutomationModel createAutomationModel) async { + CreateAutomationModel createAutomationModel, String projectId) async { try { final response = await _httpService.post( - path: ApiEndpoints.createAutomation, + path: ApiEndpoints.createAutomation.replaceAll('{projectId}', projectId), body: createAutomationModel.toMap(), showServerMessage: false, expectedResponseModel: (json) { diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 4307699e..b30b02e3 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -69,10 +69,10 @@ abstract class ApiEndpoints { //product static const String listProducts = '/products'; static const String getSpaceScenes = '/scene/tap-to-run/{spaceUuid}'; - static const String getSpaceAutomation = '/projects/{projectId}communities/{communityId}/spaces/{spaceUuid}/automations'; + static const String getSpaceAutomation = '/projects/{projectId}/communities/{communityId}/spaces/{spaceUuid}/automations'; static const String getIconScene = '/scene/icon'; static const String createScene = '/scene/tap-to-run'; - static const String createAutomation = '/automation'; + static const String createAutomation = '/projects/{projectId}/automations'; static const String getUnitScenes = '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; static const String getAutomationDetails = '/automation/details/{automationId}'; From 2a065efc0e10ab2845620c83aee7933e1aecfb34 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 14 Mar 2025 12:09:14 +0400 Subject: [PATCH 007/238] replaced endpoint for get automaiton by id --- lib/pages/routines/bloc/routine_bloc/routine_bloc.dart | 5 ++++- lib/services/routines_api.dart | 7 +++++-- lib/utils/constants/api_const.dart | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index 767f40b2..6d5b9357 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -1079,6 +1079,8 @@ class RoutineBloc extends Bloc { Future _onGetAutomationDetails( GetAutomationDetails event, Emitter emit) async { try { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + emit(state.copyWith( isLoading: true, isUpdate: true, @@ -1089,7 +1091,8 @@ class RoutineBloc extends Bloc { thenItems: [], )); - final automationDetails = await SceneApi.getAutomationDetails(event.automationId); + final automationDetails = + await SceneApi.getAutomationDetails(event.automationId, projectUuid); final Map> deviceIfCards = {}; final Map> deviceThenCards = {}; diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index fbc6c9ec..4c3b6893 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -133,10 +133,13 @@ class SceneApi { // } //automation details - static Future getAutomationDetails(String automationId) async { + static Future getAutomationDetails( + String automationId, String projectId) async { try { final response = await _httpService.get( - path: ApiEndpoints.getAutomationDetails.replaceAll('{automationId}', automationId), + path: ApiEndpoints.getAutomationDetails + .replaceAll('{automationId}', automationId) + .replaceAll('{projectId}', projectId), showServerMessage: false, expectedResponseModel: (json) => RoutineDetailsModel.fromMap(json), ); diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index b30b02e3..9f44fb38 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -75,7 +75,7 @@ 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 = '/automation/details/{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}'; From bc64efa557bc62e13a8636fdc48316ec396dd8ea Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 14 Mar 2025 12:12:15 +0400 Subject: [PATCH 008/238] updated delete automation endpoint --- lib/pages/routines/bloc/routine_bloc/routine_bloc.dart | 4 +++- lib/services/routines_api.dart | 4 ++-- lib/utils/constants/api_const.dart | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index 6d5b9357..b7e75847 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -798,6 +798,8 @@ class RoutineBloc extends Bloc { FutureOr _deleteScene(DeleteScene event, Emitter emit) async { try { + final projectId = await ProjectManager.getProjectUUID() ?? ''; + emit(state.copyWith(isLoading: true)); BuildContext context = NavigationService.navigatorKey.currentContext!; var spaceBloc = context.read(); @@ -806,7 +808,7 @@ class RoutineBloc extends Bloc { unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? ''); } else { await SceneApi.deleteAutomation( - unitUuid: spaceBloc.state.selectedSpaces[0], automationId: state.automationId ?? ''); + unitUuid: spaceBloc.state.selectedSpaces[0], automationId: state.automationId ?? '',projectId: projectId); } add(const LoadScenes()); diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 4c3b6893..c2593b4d 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -213,12 +213,12 @@ class SceneApi { // delete automation static Future deleteAutomation( - {required String unitUuid, required String automationId}) async { + {required String unitUuid, required String automationId, required String projectId}) async { try { final response = await _httpService.delete( path: ApiEndpoints.deleteAutomation .replaceAll('{automationId}', automationId) - .replaceAll('{unitUuid}', unitUuid), + .replaceAll('{projectId}', projectId), showServerMessage: false, expectedResponseModel: (json) => json['statusCode'] == 200, ); diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 9f44fb38..5a42cb47 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -79,7 +79,7 @@ abstract class ApiEndpoints { static const String getScene = '/scene/tap-to-run/{sceneId}'; static const String deleteScene = '/scene/tap-to-run/{sceneId}'; - static const String deleteAutomation = '/automation/{automationId}'; + static const String deleteAutomation = '/projects/{projectId}/automations/{automationId}'; static const String updateScene = '/scene/tap-to-run/{sceneId}'; static const String updateAutomation = '/automation/{automationId}'; From e0486deecb4510c242172f85ec3861a5665dcda9 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 14 Mar 2025 12:16:32 +0400 Subject: [PATCH 009/238] changed endpoint for automation update --- lib/pages/routines/bloc/routine_bloc/routine_bloc.dart | 10 +++++++--- lib/services/routines_api.dart | 7 +++++-- lib/utils/constants/api_const.dart | 5 +++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index b7e75847..361810c0 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -808,7 +808,9 @@ class RoutineBloc extends Bloc { unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? ''); } else { await SceneApi.deleteAutomation( - unitUuid: spaceBloc.state.selectedSpaces[0], automationId: state.automationId ?? '',projectId: projectId); + unitUuid: spaceBloc.state.selectedSpaces[0], + automationId: state.automationId ?? '', + projectId: projectId); } add(const LoadScenes()); @@ -948,6 +950,8 @@ class RoutineBloc extends Bloc { 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', @@ -1057,8 +1061,8 @@ class RoutineBloc extends Bloc { actions: actions, ); - final result = - await SceneApi.updateAutomation(createAutomationModel, state.automationId ?? ''); + final result = await SceneApi.updateAutomation( + createAutomationModel, state.automationId ?? '', projectId); if (result['success']) { add(ResetRoutineState()); diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index c2593b4d..56a29aa1 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -166,10 +166,13 @@ class SceneApi { } //update automation - static updateAutomation(CreateAutomationModel createAutomationModel, String automationId) async { + static updateAutomation( + CreateAutomationModel createAutomationModel, String automationId, String projectId) async { try { final response = await _httpService.put( - path: ApiEndpoints.updateAutomation.replaceAll('{automationId}', automationId), + path: ApiEndpoints.updateAutomation + .replaceAll('{automationId}', automationId) + .replaceAll('{projectId}', projectId), body: createAutomationModel.toJson(automationId.isNotEmpty == true ? automationId : null), expectedResponseModel: (json) { return json; diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 5a42cb47..71e0fc55 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -69,7 +69,8 @@ abstract class ApiEndpoints { //product static const String listProducts = '/products'; static const String getSpaceScenes = '/scene/tap-to-run/{spaceUuid}'; - static const String getSpaceAutomation = '/projects/{projectId}/communities/{communityId}/spaces/{spaceUuid}/automations'; + static const String getSpaceAutomation = + '/projects/{projectId}/communities/{communityId}/spaces/{spaceUuid}/automations'; static const String getIconScene = '/scene/icon'; static const String createScene = '/scene/tap-to-run'; static const String createAutomation = '/projects/{projectId}/automations'; @@ -82,7 +83,7 @@ abstract class ApiEndpoints { static const String deleteAutomation = '/projects/{projectId}/automations/{automationId}'; static const String updateScene = '/scene/tap-to-run/{sceneId}'; - static const String updateAutomation = '/automation/{automationId}'; + static const String updateAutomation = '/projects/{projectId}/automations/{automationId}'; //space model static const String listSpaceModels = '/projects/{projectId}/space-models'; From a5a37f384152e918f099299d1e345369c2acde0a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 14 Mar 2025 14:10:03 +0400 Subject: [PATCH 010/238] append space id to list --- lib/pages/space_tree/view/space_tree_view.dart | 5 +++-- .../link_space_model/bloc/link_space_to_model_bloc.dart | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index 92bc858d..63926852 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -37,8 +37,9 @@ class _SpaceTreeViewState extends State { List list = state.isSearching ? state.filteredCommunity : state.communityList; return Container( height: MediaQuery.sizeOf(context).height, - color: ColorsManager.whiteColors, - decoration: widget.isSide == true ? subSectionContainerDecoration : null, + decoration: widget.isSide == true + ? subSectionContainerDecoration.copyWith(color: ColorsManager.whiteColors) + : const BoxDecoration(color: ColorsManager.whiteColors), child: state is SpaceTreeLoadingState ? const Center(child: CircularProgressIndicator()) : Column( diff --git a/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart index c789c2a9..5b85e17e 100644 --- a/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart +++ b/lib/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart @@ -28,10 +28,11 @@ class LinkSpaceToModelBloc try { BuildContext context = NavigationService.navigatorKey.currentContext!; var spaceBloc = context.read(); + spacesListIds.clear(); for (var communityId in spaceBloc.state.selectedCommunities) { List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; - spacesListIds = spacesList; + spacesListIds.addAll(spacesList); } hasSelectedSpaces = spaceBloc.state.selectedCommunities.any((communityId) { From 3750fa8329dcef2e17fa516461ca2258cbdd62e7 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 14 Mar 2025 15:02:22 +0400 Subject: [PATCH 011/238] removed left and right button --- .../all_spaces/widgets/space_card_widget.dart | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/space_card_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_card_widget.dart index 0a78da52..f3a476b2 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/space_card_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/space_card_widget.dart @@ -47,18 +47,6 @@ class SpaceCardWidget extends StatelessWidget { children: [ buildSpaceContainer(index), // Build the space container if (isHovered) ...[ - PlusButtonWidget( - index: index, - direction: 'left', - offset: const Offset(-21, 20), - onButtonTap: onButtonTap, - ), - PlusButtonWidget( - index: index, - direction: 'right', - offset: const Offset(140, 20), - onButtonTap: onButtonTap, - ), PlusButtonWidget( index: index, direction: 'down', From 8c15fffd42d11e87b7c55cd0726d7a6fc32438e6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 19 Mar 2025 12:25:37 +0400 Subject: [PATCH 012/238] duplicate --- .../widgets/community_structure_widget.dart | 175 +++++++++++------- .../helper/space_helper.dart | 30 ++- 2 files changed, 122 insertions(+), 83 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 56352432..70b86074 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -301,11 +301,18 @@ class _CommunityStructureAreaState extends State { SpaceTemplateModel? spaceModel, List? subspaces, List? tags) { setState(() { // Set the first space in the center or use passed position - Offset centerPosition = position ?? ConnectionHelper.getCenterPosition(screenSize); + Offset newPosition; + if (parentIndex != null) { + newPosition = + getBalancedChildPosition(spaces[parentIndex]); // Ensure balanced position + } else { + newPosition = position ?? ConnectionHelper.getCenterPosition(screenSize); + } + SpaceModel newSpace = SpaceModel( name: name, icon: icon, - position: centerPosition, + position: newPosition, isPrivate: false, children: [], status: SpaceStatus.newSpace, @@ -425,7 +432,7 @@ class _CommunityStructureAreaState extends State { Connection( startSpace: parent, endSpace: child, - direction: child.incomingConnection?.direction ?? "down", + direction: "down", ), ); @@ -522,6 +529,38 @@ class _CommunityStructureAreaState extends State { ); } + Offset getBalancedChildPosition(SpaceModel parent) { + int totalSiblings = parent.children.length + 1; + double totalWidth = (totalSiblings - 1) * 250; // Horizontal spacing + double startX = parent.position.dx - (totalWidth / 2); + + Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180); + + // Check for overlaps & adjust + while (spaces.any((s) => (s.position - position).distance < 250)) { + position = Offset(position.dx + 250, position.dy); + } + + return position; + } + + void realignTree() { + void updatePositions(SpaceModel node, double x, double y) { + node.position = Offset(x, y); + + int numChildren = node.children.length; + double childStartX = x - ((numChildren - 1) * 250) / 2; + + for (int i = 0; i < numChildren; i++) { + updatePositions(node.children[i], childStartX + (i * 250), y + 180); + } + } + + if (spaces.isNotEmpty) { + updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy); + } + } + void _onDuplicate(BuildContext parentContext) { final screenWidth = MediaQuery.of(context).size.width; @@ -604,29 +643,57 @@ class _CommunityStructureAreaState extends State { void _duplicateSpace(SpaceModel space) { final Map originalToDuplicate = {}; - const double horizontalGap = 200.0; - const double verticalGap = 100.0; + double horizontalGap = 250.0; // Increased spacing + double verticalGap = 180.0; // Adjusted for better visualization - SpaceModel duplicateRecursive( - SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) { - Offset newPosition = Offset(parentPosition.dx + horizontalGap, original.position.dy); + print("🟢 Duplicating: ${space.name}"); - while (spaces.any((s) => - (s.position - newPosition).distance < horizontalGap && s.status != SpaceStatus.deleted)) { - newPosition += Offset(horizontalGap, 0); + /// **Find a new position ensuring no overlap** + Offset getBalancedChildPosition(SpaceModel parent) { + int totalSiblings = parent.children.length + 1; + double totalWidth = (totalSiblings - 1) * horizontalGap; + double startX = parent.position.dx - (totalWidth / 2); + Offset position = Offset( + startX + (parent.children.length * horizontalGap), parent.position.dy + verticalGap); + + // **Check for overlaps & adjust** + while (spaces.any((s) => (s.position - position).distance < horizontalGap)) { + position = Offset(position.dx + horizontalGap, position.dy); } + print("🔹 New position for ${parent.name}: (${position.dx}, ${position.dy})"); + return position; + } + + /// **Realign the entire tree after duplication** + void realignTree() { + void updatePositions(SpaceModel node, double x, double y) { + node.position = Offset(x, y); + print("✅ Adjusted ${node.name} to (${x}, ${y})"); + + int numChildren = node.children.length; + double childStartX = x - ((numChildren - 1) * horizontalGap) / 2; + + for (int i = 0; i < numChildren; i++) { + updatePositions(node.children[i], childStartX + (i * horizontalGap), y + verticalGap); + } + } + + if (spaces.isNotEmpty) { + print("🔄 Realigning tree..."); + updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy); + } + } + + /// **Recursive duplication logic** + SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) { + Offset newPosition = duplicatedParent == null + ? Offset(original.position.dx + horizontalGap, original.position.dy) + : getBalancedChildPosition(duplicatedParent); + final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); - - final List? duplicatedSubspaces; - final List? duplicatedTags; - if (original.spaceModel != null) { - duplicatedTags = []; - duplicatedSubspaces = []; - } else { - duplicatedTags = original.tags; - duplicatedSubspaces = original.subspaces; - } + print( + "🟡 Duplicating ${original.name} → ${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})"); final duplicated = SpaceModel( name: duplicatedName, @@ -637,12 +704,10 @@ class _CommunityStructureAreaState extends State { status: SpaceStatus.newSpace, parent: duplicatedParent, spaceModel: original.spaceModel, - subspaces: duplicatedSubspaces, - tags: duplicatedTags, + subspaces: original.subspaces, + tags: original.tags, ); - originalToDuplicate[original] = duplicated; - setState(() { spaces.add(duplicated); _updateNodePosition(duplicated, duplicated.position); @@ -651,60 +716,42 @@ class _CommunityStructureAreaState extends State { final newConnection = Connection( startSpace: duplicatedParent, endSpace: duplicated, - direction: original.incomingConnection?.direction ?? 'down', + direction: "down", ); connections.add(newConnection); duplicated.incomingConnection = newConnection; duplicatedParent.addOutgoingConnection(newConnection); + duplicatedParent.children.add(duplicated); + print("🔗 Created connection: ${duplicatedParent.name} → ${duplicated.name}"); } - if (original.parent != null && duplicatedParent == null) { - final originalParent = original.parent!; - final duplicatedParent = originalToDuplicate[originalParent] ?? originalParent; - - final parentConnection = Connection( - startSpace: duplicatedParent, - endSpace: duplicated, - direction: original.incomingConnection?.direction ?? "down", - ); - - connections.add(parentConnection); - duplicated.incomingConnection = parentConnection; - duplicatedParent.addOutgoingConnection(parentConnection); - } + // **Recalculate the whole tree to avoid overlaps** + realignTree(); }); - final childrenWithDownDirection = original.children - .where((child) => - child.incomingConnection?.direction == "down" && child.status != SpaceStatus.deleted) - .toList(); - - Offset childStartPosition = childrenWithDownDirection.length == 1 - ? duplicated.position - : newPosition + Offset(0, verticalGap); - - for (final child in original.children) { - final isDownDirection = child.incomingConnection?.direction == "down" ?? false; - - if (isDownDirection && childrenWithDownDirection.length == 1) { - childStartPosition = duplicated.position + Offset(0, verticalGap); - } else if (!isDownDirection) { - childStartPosition = duplicated.position + Offset(horizontalGap, 0); - } - - final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated); - duplicated.children.add(duplicatedChild); - childStartPosition += Offset(0, verticalGap); + // Recursively duplicate children + for (var child in original.children) { + duplicateRecursive(child, duplicated); } return duplicated; } + /// **Handle root duplication** if (space.parent == null) { - duplicateRecursive(space, space.position, null); + print("🟠 Duplicating root node: ${space.name}"); + SpaceModel duplicatedRoot = duplicateRecursive(space, null); + + setState(() { + spaces.add(duplicatedRoot); + realignTree(); + }); + + print("✅ Root duplication successful: ${duplicatedRoot.name}"); } else { - final duplicatedParent = originalToDuplicate[space.parent!] ?? space.parent!; - duplicateRecursive(space, space.position, duplicatedParent); + duplicateRecursive(space, space.parent); } + + print("🟢 Finished duplication process for: ${space.name}"); } } diff --git a/lib/pages/spaces_management/helper/space_helper.dart b/lib/pages/spaces_management/helper/space_helper.dart index 126306cd..4049eb7e 100644 --- a/lib/pages/spaces_management/helper/space_helper.dart +++ b/lib/pages/spaces_management/helper/space_helper.dart @@ -2,8 +2,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; class SpaceHelper { - static SpaceModel? findSpaceByUuid( - String? uuid, List communities) { + static SpaceModel? findSpaceByUuid(String? uuid, List communities) { for (var community in communities) { for (var space in community.spaces) { if (space.uuid == uuid) return space; @@ -12,8 +11,7 @@ class SpaceHelper { return null; } - static SpaceModel? findSpaceByInternalId( - String? internalId, List spaces) { + static SpaceModel? findSpaceByInternalId(String? internalId, List spaces) { if (internalId != null) { for (var space in spaces) { if (space.internalId == internalId) return space; @@ -23,8 +21,7 @@ class SpaceHelper { return null; } - static String generateUniqueSpaceName( - String originalName, List spaces) { + static String generateUniqueSpaceName(String originalName, List spaces) { final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim(); int maxNumber = 0; @@ -54,13 +51,10 @@ class SpaceHelper { return space == selectedSpace || selectedSpace.parent?.internalId == space.internalId || - selectedSpace.children - ?.any((child) => child.internalId == space.internalId) == - true; + selectedSpace.children?.any((child) => child.internalId == space.internalId) == true; } - static bool isNameConflict( - String value, SpaceModel? parentSpace, SpaceModel? editSpace) { + static bool isNameConflict(String value, SpaceModel? parentSpace, SpaceModel? editSpace) { final siblings = parentSpace?.children .where((child) => child.internalId != editSpace?.internalId) .toList() ?? @@ -71,19 +65,17 @@ class SpaceHelper { .toList() ?? []; - final editSiblingConflict = - editSiblings.any((child) => child.name == value); + final editSiblingConflict = editSiblings.any((child) => child.name == value); final siblingConflict = siblings.any((child) => child.name == value); - final parentConflict = parentSpace?.name == value && - parentSpace?.internalId != editSpace?.internalId; + final parentConflict = + parentSpace?.name == value && parentSpace?.internalId != editSpace?.internalId; - final parentOfEditSpaceConflict = editSpace?.parent?.name == value && - editSpace?.parent?.internalId != editSpace?.internalId; + final parentOfEditSpaceConflict = + editSpace?.parent?.name == value && editSpace?.parent?.internalId != editSpace?.internalId; - final childConflict = - editSpace?.children.any((child) => child.name == value) ?? false; + final childConflict = editSpace?.children.any((child) => child.name == value) ?? false; return siblingConflict || parentConflict || From c0d53fdf5c36e2b0e28aaa9ad0e11a7e64701590 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 19 Mar 2025 12:24:55 +0300 Subject: [PATCH 013/238] Update Automation Status --- assets/icons/scenesPlayIcon.svg | 17 ++ assets/icons/spaseLocationIcon.svg | 4 + .../automation_scene_trigger_bloc.dart | 124 +++++++++++++ .../automation_scene_trigger_event.dart | 55 ++++++ .../automation_scene_trigger_status.dart | 51 +++++ .../automation_status_update.dart | 39 ++++ lib/pages/routines/view/routines_view.dart | 31 +++- .../fetch_routine_scenes_automation.dart | 22 ++- .../main_routine_view/routine_view_card.dart | 174 ++++++++++++------ lib/services/routines_api.dart | 56 ++++++ lib/utils/constants/api_const.dart | 5 + lib/utils/constants/assets.dart | 5 +- 12 files changed, 512 insertions(+), 71 deletions(-) create mode 100644 assets/icons/scenesPlayIcon.svg create mode 100644 assets/icons/spaseLocationIcon.svg create mode 100644 lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_bloc.dart create mode 100644 lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart create mode 100644 lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart create mode 100644 lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart diff --git a/assets/icons/scenesPlayIcon.svg b/assets/icons/scenesPlayIcon.svg new file mode 100644 index 00000000..6417b0e6 --- /dev/null +++ b/assets/icons/scenesPlayIcon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/spaseLocationIcon.svg b/assets/icons/spaseLocationIcon.svg new file mode 100644 index 00000000..2c3f0ad9 --- /dev/null +++ b/assets/icons/spaseLocationIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_bloc.dart b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_bloc.dart new file mode 100644 index 00000000..b76b06e0 --- /dev/null +++ b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_bloc.dart @@ -0,0 +1,124 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/auth/model/project_model.dart'; +import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart'; +import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart'; +import 'package:syncrow_web/pages/routines/models/routine_model.dart'; +import 'package:syncrow_web/services/routines_api.dart'; + +class AutomationSceneTriggerBloc extends Bloc { + AutomationSceneTriggerBloc() : super(AutomationSceneInitial()) { + // on(_onLoadScenes); + // on(_onLoadAutomation); + on(_onSceneTrigger); + on(_onUpdateAutomationStatus); + } + + List scenes = []; + List automationList = []; + + // Future _onLoadScenes(LoadScenes event, Emitter emit) async { + // emit(SceneLoading()); + + // try { + // Project? project = HomeCubit.getInstance().project; + + // if (event.unitId.isNotEmpty) { + // scenes = await SceneApi.getScenesByUnitId(event.unitId, + // event.unit.community.uuid, project?.uuid ?? TempConst.projectIdDev, + // showInDevice: event.showInDevice); + // emit(SceneLoaded(scenes, automationList)); + // } else { + // emit(const SceneError(message: 'Unit ID is empty')); + // } + // } catch (e) { + // emit(const SceneError(message: 'Something went wrong')); + // } + // } + + // Future _onLoadAutomation( + // LoadAutomation event, Emitter emit) async { + // emit(SceneLoading()); + + // try { + // Project? project = HomeCubit.getInstance().project; + + // if (event.unitId.isNotEmpty) { + // automationList = await SceneApi.getAutomationByUnitId( + // event.unitId, event.communityId, project?.uuid ?? ''); + // emit(SceneLoaded(scenes, automationList)); + // } else { + // emit(const SceneError(message: 'Unit ID is empty')); + // } + // } catch (e) { + // emit(const SceneError(message: 'Something went wrong')); + // } + // } + + Future _onSceneTrigger( + SceneTrigger event, Emitter emit) async { + final currentState = state; + if (currentState is AutomationSceneLoaded) { + emit(AutomationSceneLoaded( + currentState.scenes, + currentState.automationList, + loadingSceneId: event.sceneId, + )); + + try { + final success = await SceneApi.triggerScene(event.sceneId); + if (success) { + emit(SceneTriggerSuccess(event.name)); + emit(AutomationSceneLoaded(currentState.scenes, currentState.automationList)); + } else { + emit(const AutomationSceneError(message: 'Something went wrong')); + } + } catch (e) { + emit(const AutomationSceneError(message: 'Something went wrong')); + } + } + } + + Future _onUpdateAutomationStatus( + UpdateAutomationStatus event, Emitter emit) async { + final currentState = state; + if (currentState is AutomationSceneLoaded) { + final newLoadingStates = + Map.from(currentState.loadingStates) + ..[event.automationId] = true; + + emit(AutomationSceneLoaded( + currentState.scenes, + currentState.automationList, + loadingStates: newLoadingStates, + )); + + try { + Project? project = HomeCubit.getInstance().project; + + final success = await SceneApi.updateAutomationStatus( + event.automationId, + event.automationStatusUpdate, + project?.uuid ?? ''); + if (success) { + automationList = await SceneApi.getAutomationByUnitId( + event.automationStatusUpdate.spaceUuid, + event.communityId, + project?.uuid ?? ''); + newLoadingStates[event.automationId] = false; + emit(AutomationSceneLoaded( + currentState.scenes, + automationList, + loadingStates: newLoadingStates, + )); + } else { + emit(const AutomationSceneError(message: 'Something went wrong')); + } + } catch (e) { + emit(const AutomationSceneError(message: 'Something went wrong')); + } + } + } +} diff --git a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart new file mode 100644 index 00000000..b34eb5ed --- /dev/null +++ b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; + +abstract class AutomationSceneTriggerEvent extends Equatable { + const AutomationSceneTriggerEvent(); + + @override + List get props => []; +} + +class AutomationSceneScenes extends AutomationSceneTriggerEvent { + final String unitId; + final bool showInDevice; + final SpaceModel unit; + + const AutomationSceneScenes(this.unitId, this.unit, {this.showInDevice = false}); + + @override + List get props => [unitId, showInDevice]; +} + +class AutomationSceneAutomation extends AutomationSceneTriggerEvent { + final String unitId; + final String communityId; + + + const AutomationSceneAutomation(this.unitId, this.communityId); + + @override + List get props => [unitId, communityId]; +} + +class SceneTrigger extends AutomationSceneTriggerEvent { + final String sceneId; + final String name; + + const SceneTrigger(this.sceneId, this.name); + + @override + List get props => [sceneId]; +} + +//updateAutomationStatus +class UpdateAutomationStatus extends AutomationSceneTriggerEvent { + final String automationId; + final AutomationStatusUpdate automationStatusUpdate; + final String communityId; + + const UpdateAutomationStatus({required this.automationStatusUpdate, required this.automationId, required this.communityId}); + + @override + List get props => [automationStatusUpdate]; +} diff --git a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart new file mode 100644 index 00000000..6753372f --- /dev/null +++ b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart @@ -0,0 +1,51 @@ + + +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/routines/models/routine_model.dart'; + +abstract class AutomationSceneTriggerStatus extends Equatable { + const AutomationSceneTriggerStatus(); + + @override + List get props => []; +} + +class AutomationSceneInitial extends AutomationSceneTriggerStatus {} + +class AutomationSceneLoading extends AutomationSceneTriggerStatus {} + +class AutomationSceneLoaded extends AutomationSceneTriggerStatus { + final List scenes; + final List automationList; + final String? loadingSceneId; + final Map loadingStates; + + const AutomationSceneLoaded(this.scenes, this.automationList, + {this.loadingSceneId, this.loadingStates = const {}}); + + @override + List get props => + [scenes, loadingSceneId, automationList, loadingStates]; +} + +class AutomationSceneError extends AutomationSceneTriggerStatus { + final String message; + + const AutomationSceneError({required this.message}); + + @override + List get props => [message]; +} + +class SceneTriggerSuccess extends AutomationSceneTriggerStatus { + final String sceneName; + + const SceneTriggerSuccess(this.sceneName); + + @override + List get props => [sceneName]; +} + +class UpdateAutomationStatusLoading extends AutomationSceneTriggerStatus { + const UpdateAutomationStatusLoading(); +} diff --git a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart new file mode 100644 index 00000000..c664c2c4 --- /dev/null +++ b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart @@ -0,0 +1,39 @@ + +import 'dart:convert'; + +class AutomationStatusUpdate { + final String spaceUuid; + final bool isEnable; + + AutomationStatusUpdate({ + required this.spaceUuid, + required this.isEnable, + }); + + factory AutomationStatusUpdate.fromRawJson(String str) => + AutomationStatusUpdate.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AutomationStatusUpdate.fromJson(Map json) => + AutomationStatusUpdate( + spaceUuid: json["spaceUuid"], + isEnable: json["isEnable"], + ); + + Map toJson() => { + "spaceUuid": spaceUuid, + "isEnable": isEnable, + }; + + factory AutomationStatusUpdate.fromMap(Map map) => + AutomationStatusUpdate( + spaceUuid: map["spaceUuid"], + isEnable: map["isEnable"], + ); + + Map toMap() => { + "spaceUuid": spaceUuid, + "isEnable": isEnable, + }; +} diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index b0efb4a9..de627a43 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -60,22 +60,39 @@ class _RoutinesViewState extends State { height: 10, ), RoutineViewCard( + isFromScenes: false, + cardType: '', + spaceName: '', onTap: () { - if (context.read().state.selectedCommunities.length == 1 && - context.read().state.selectedSpaces.length == 1) { + if (context + .read() + .state + .selectedCommunities + .length == + 1 && + context + .read() + .state + .selectedSpaces + .length == + 1) { context.read().add( (ResetRoutineState()), ); BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(createRoutineView: true), + const CreateNewRoutineViewEvent( + createRoutineView: true), ); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text( - context.read().state.selectedSpaces.isEmpty - ? 'Please select a space' - : 'Please select only one space to proceed'), + content: Text(context + .read() + .state + .selectedSpaces + .isEmpty + ? 'Please select a space' + : 'Please select only one space to proceed'), ), ); // CustomSnackBar.redSnackBar( diff --git a/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart b/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart index 718a78b9..08f4318e 100644 --- a/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart +++ b/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart @@ -12,7 +12,8 @@ class FetchRoutineScenesAutomation extends StatefulWidget { const FetchRoutineScenesAutomation({super.key}); @override - State createState() => _FetchRoutineScenesState(); + State createState() => + _FetchRoutineScenesState(); } class _FetchRoutineScenesState extends State @@ -64,9 +65,12 @@ class _FetchRoutineScenesState extends State right: isSmallScreenSize(context) ? 4.0 : 8.0, ), child: RoutineViewCard( + cardType: 'scenes', + spaceName: 'scenes', onTap: () { BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(createRoutineView: true), + const CreateNewRoutineViewEvent( + createRoutineView: true), ); context.read().add( GetSceneDetails( @@ -77,7 +81,8 @@ class _FetchRoutineScenesState extends State ); }, textString: state.scenes[index].name, - icon: state.scenes[index].icon ?? Assets.logoHorizontal, + icon: state.scenes[index].icon ?? + Assets.logoHorizontal, isFromScenes: true, iconInBytes: state.scenes[index].iconInBytes, ), @@ -113,19 +118,24 @@ class _FetchRoutineScenesState extends State right: isSmallScreenSize(context) ? 4.0 : 8.0, ), child: RoutineViewCard( + cardType: 'automations', + spaceName: 'automations', onTap: () { BlocProvider.of(context).add( - const CreateNewRoutineViewEvent(createRoutineView: true), + const CreateNewRoutineViewEvent( + createRoutineView: true), ); context.read().add( GetAutomationDetails( - automationId: state.automations[index].id, + automationId: + state.automations[index].id, isAutomation: true, isUpdate: true), ); }, textString: state.automations[index].name, - icon: state.automations[index].icon ?? Assets.automation, + icon: state.automations[index].icon ?? + Assets.automation, ), ), ), diff --git a/lib/pages/routines/widgets/main_routine_view/routine_view_card.dart b/lib/pages/routines/widgets/main_routine_view/routine_view_card.dart index f8a2e358..fe94cf83 100644 --- a/lib/pages/routines/widgets/main_routine_view/routine_view_card.dart +++ b/lib/pages/routines/widgets/main_routine_view/routine_view_card.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -12,6 +13,8 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { required this.onTap, required this.icon, required this.textString, + required this.spaceName, + required this.cardType, this.isFromScenes, this.iconInBytes, }); @@ -19,6 +22,9 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { final Function() onTap; final dynamic icon; final String textString; + final String spaceName; + final String cardType; + final bool? isFromScenes; final Uint8List? iconInBytes; @@ -50,69 +56,125 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { ), color: ColorsManager.whiteColors, child: InkWell( - onTap: onTap, borderRadius: BorderRadius.circular(10), child: Padding( padding: const EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Center( - child: Container( - decoration: BoxDecoration( - color: ColorsManager.graysColor, - borderRadius: BorderRadius.circular(120), - border: Border.all( - color: ColorsManager.greyColor, - width: 2.0, - ), - ), - height: iconSize, - width: iconSize, - child: (isFromScenes ?? false) - ? (iconInBytes != null && iconInBytes?.isNotEmpty == true) - ? Image.memory( - iconInBytes!, - height: iconSize, - width: iconSize, - fit: BoxFit.contain, - errorBuilder: (context, error, stackTrace) => Image.asset( - Assets.logo, - height: iconSize, - width: iconSize, - fit: BoxFit.contain, - ), - ) - : Image.asset( - Assets.logo, - height: iconSize, - width: iconSize, - fit: BoxFit.contain, - ) - : (icon is String && icon.endsWith('.svg')) - ? SvgPicture.asset( - icon, - fit: BoxFit.contain, - ) - : Icon( - icon, - color: ColorsManager.dialogBlueTitle, - size: isSmallScreenSize(context) ? 30 : 40, - ), - ), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + (isFromScenes ?? false) + ? InkWell( + onTap: () {}, + child: SvgPicture.asset( + Assets.scenesPlayIcon, + fit: BoxFit.contain, + ), + ) + : CupertinoSwitch( + activeColor: ColorsManager.primaryColor, + value: false, + onChanged: (value) {}, + ) + ], ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 3), - child: Text( - textString, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontSize: isSmallScreenSize(context) ? 10 : 12, - ), + InkWell( + onTap: onTap, + child: Column( + children: [ + Center( + child: Container( + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: BorderRadius.circular(120), + border: Border.all( + color: ColorsManager.greyColor, + width: 2.0, + ), + ), + height: iconSize, + width: iconSize, + child: (isFromScenes ?? false) + ? (iconInBytes != null && + iconInBytes?.isNotEmpty == true) + ? Image.memory( + iconInBytes!, + height: iconSize, + width: iconSize, + fit: BoxFit.contain, + errorBuilder: + (context, error, stackTrace) => + Image.asset( + Assets.logo, + height: iconSize, + width: iconSize, + fit: BoxFit.contain, + ), + ) + : Image.asset( + Assets.logo, + height: iconSize, + width: iconSize, + fit: BoxFit.contain, + ) + : (icon is String && icon.endsWith('.svg')) + ? SvgPicture.asset( + icon, + fit: BoxFit.contain, + ) + : Icon( + icon, + color: ColorsManager.dialogBlueTitle, + size: + isSmallScreenSize(context) ? 30 : 40, + ), + ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: Column( + children: [ + Text( + textString, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: isSmallScreenSize(context) ? 10 : 12, + ), + ), + if (spaceName != '') + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.spaceLocationIcon, + fit: BoxFit.contain, + ), + Text( + spaceName, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: + context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: + isSmallScreenSize(context) ? 10 : 12, + ), + ), + ], + ), + ], + ), + ), + ], ), ), ], diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 56a29aa1..cbef2bc3 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart'; import 'package:syncrow_web/pages/routines/models/icon_model.dart'; @@ -230,4 +231,59 @@ class SceneApi { rethrow; } } + + static Future updateAutomationStatus(String automationId, + AutomationStatusUpdate createAutomationEnable, String projectId) async { + try { + final response = await _httpService.patch( + path: ApiEndpoints.updateAutomationStatus + .replaceAll('{automationId}', automationId) + .replaceAll('{projectId}', projectId), + body: createAutomationEnable.toMap(), + expectedResponseModel: (json) => json['success'], + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future triggerScene(String sceneId) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.triggerScene.replaceAll('{sceneId}', sceneId), + showServerMessage: false, + expectedResponseModel: (json) => json['success'], + ); + return response; + } catch (e) { + rethrow; + } + } + static Future> getAutomationByUnitId( + String unitId, + String communityId, + String projectId, + ) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getUnitAutomation + .replaceAll('{unitUuid}', unitId) + .replaceAll('{communityId}', communityId) + .replaceAll('{projectId}', projectId), + showServerMessage: false, + expectedResponseModel: (json) { + List scenes = []; + for (var scene in json) { + scenes.add(ScenesModel.fromJson(scene)); + } + return scenes; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 71e0fc55..00449836 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -111,4 +111,9 @@ abstract class ApiEndpoints { static const String terms = '/terms'; static const String policy = '/policy'; static const String userAgreements = '/user/agreements/web/{userUuid}'; + static const String triggerScene = '/scene/tap-to-run/{sceneId}/trigger'; + static const String updateAutomationStatus = + '/projects/{projectId}/automations/{automationId}'; + static const String getUnitAutomation = + '/projects/{projectId}/communities/{communityId}/spaces/{unitUuid}/automations'; } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index b7a0115f..5d462f28 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -406,6 +406,7 @@ class Assets { static const String deleteSpaceLinkIcon = 'assets/icons/delete_space_link_icon.svg'; static const String spaceLinkIcon = 'assets/icons/space_link_icon.svg'; - static const String successIcon = 'assets/icons/success_icon.svg'; - + static const String successIcon = 'assets/icons/success_icon.svg'; + static const String spaceLocationIcon = 'assets/icons/spaceLocationIcon.svg'; + static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.svg'; } From 2d3345c1d961710ec13d8672149dc6b60f98d0bc Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 19 Mar 2025 21:48:44 +0400 Subject: [PATCH 014/238] fixed issue in subspace updatw --- .../bloc/space_management_bloc.dart | 58 +++++-------------- 1 file changed, 15 insertions(+), 43 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index d675c680..56949a19 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -262,27 +262,6 @@ class SpaceManagementBloc extends Bloc updatedCommunities = await Future.wait( - communities.map((community) async { - List spaces = await _fetchSpacesForCommunity(community.uuid); - return CommunityModel( - uuid: community.uuid, - createdAt: community.createdAt, - updatedAt: community.updatedAt, - name: community.name, - description: community.description, - spaces: spaces, - region: community.region, - ); - }).toList(), - ); - - communities = updatedCommunities; - } - emit(BlankState( spaceModels: prevSpaceModels, communities: communities, @@ -540,7 +519,7 @@ class SpaceManagementBloc extends Bloc tagUpdates = []; List matchedSpaces = - selectedCommunity.spaces.where((space) => space.uuid == space.uuid).toList(); + findMatchingSpaces(selectedCommunity.spaces, space.uuid!); if (matchedSpaces.isEmpty) continue; @@ -696,27 +675,6 @@ class SpaceManagementBloc extends Bloc updatedCommunities = await Future.wait( - communities.map((community) async { - List spaces = await _fetchSpacesForCommunity(community.uuid); - return CommunityModel( - uuid: community.uuid, - createdAt: community.createdAt, - updatedAt: community.updatedAt, - name: community.name, - description: community.description, - spaces: spaces, - region: community.region, - ); - }).toList(), - ); - - communities = updatedCommunities; - } - emit(SpaceModelLoaded( communities: communities, products: _cachedProducts ?? [], @@ -799,4 +757,18 @@ class SpaceManagementBloc extends Bloc findMatchingSpaces(List spaces, String targetUuid) { + List matched = []; + + for (var space in spaces) { + if (space.uuid == targetUuid) { + matched.add(space); + } + matched + .addAll(findMatchingSpaces(space.children, targetUuid)); // Recursively search in children + } + + return matched; + } } From 7e1c2ba71295e4b3ed995e8b28c9c8cca3b71810 Mon Sep 17 00:00:00 2001 From: mohammad Date: Fri, 21 Mar 2025 17:14:05 +0300 Subject: [PATCH 015/238] routines and automation Toggle --- assets/icons/scenesPlayIconCheck.svg | 30 +++ lib/main.dart | 2 +- lib/main_dev.dart | 1 + .../automation_scene_trigger_bloc.dart | 124 ------------ .../automation_scene_trigger_event.dart | 55 ------ .../automation_scene_trigger_status.dart | 51 ----- .../bloc/routine_bloc/routine_bloc.dart | 78 ++++++++ .../bloc/routine_bloc/routine_event.dart | 25 +++ .../bloc/routine_bloc/routine_state.dart | 44 +++++ lib/pages/routines/models/routine_model.dart | 12 ++ lib/pages/routines/view/routines_view.dart | 8 +- .../fetch_routine_scenes_automation.dart | 184 +++++++++++------- .../main_routine_view/routine_view_card.dart | 145 ++++++++++---- lib/services/routines_api.dart | 35 ++-- lib/utils/constants/assets.dart | 4 +- 15 files changed, 441 insertions(+), 357 deletions(-) create mode 100644 assets/icons/scenesPlayIconCheck.svg delete mode 100644 lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_bloc.dart delete mode 100644 lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart delete mode 100644 lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart 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/lib/main.dart b/lib/main.dart index f2f640e4..975578cf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -33,7 +33,6 @@ Future main() async { } class MyApp extends StatelessWidget { - MyApp({super.key}); final GoRouter _router = GoRouter( @@ -55,6 +54,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ + BlocProvider( create: (context) => HomeBloc()..add(const FetchUserInfo())), BlocProvider( diff --git a/lib/main_dev.dart b/lib/main_dev.dart index 9d00ebf7..76261c6a 100644 --- a/lib/main_dev.dart +++ b/lib/main_dev.dart @@ -55,6 +55,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ + BlocProvider( create: (context) => HomeBloc()..add(const FetchUserInfo())), BlocProvider( diff --git a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_bloc.dart b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_bloc.dart deleted file mode 100644 index b76b06e0..00000000 --- a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_bloc.dart +++ /dev/null @@ -1,124 +0,0 @@ -import 'dart:async'; - -import 'package:equatable/equatable.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/auth/model/project_model.dart'; -import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart'; -import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart'; -import 'package:syncrow_web/pages/routines/models/routine_model.dart'; -import 'package:syncrow_web/services/routines_api.dart'; - -class AutomationSceneTriggerBloc extends Bloc { - AutomationSceneTriggerBloc() : super(AutomationSceneInitial()) { - // on(_onLoadScenes); - // on(_onLoadAutomation); - on(_onSceneTrigger); - on(_onUpdateAutomationStatus); - } - - List scenes = []; - List automationList = []; - - // Future _onLoadScenes(LoadScenes event, Emitter emit) async { - // emit(SceneLoading()); - - // try { - // Project? project = HomeCubit.getInstance().project; - - // if (event.unitId.isNotEmpty) { - // scenes = await SceneApi.getScenesByUnitId(event.unitId, - // event.unit.community.uuid, project?.uuid ?? TempConst.projectIdDev, - // showInDevice: event.showInDevice); - // emit(SceneLoaded(scenes, automationList)); - // } else { - // emit(const SceneError(message: 'Unit ID is empty')); - // } - // } catch (e) { - // emit(const SceneError(message: 'Something went wrong')); - // } - // } - - // Future _onLoadAutomation( - // LoadAutomation event, Emitter emit) async { - // emit(SceneLoading()); - - // try { - // Project? project = HomeCubit.getInstance().project; - - // if (event.unitId.isNotEmpty) { - // automationList = await SceneApi.getAutomationByUnitId( - // event.unitId, event.communityId, project?.uuid ?? ''); - // emit(SceneLoaded(scenes, automationList)); - // } else { - // emit(const SceneError(message: 'Unit ID is empty')); - // } - // } catch (e) { - // emit(const SceneError(message: 'Something went wrong')); - // } - // } - - Future _onSceneTrigger( - SceneTrigger event, Emitter emit) async { - final currentState = state; - if (currentState is AutomationSceneLoaded) { - emit(AutomationSceneLoaded( - currentState.scenes, - currentState.automationList, - loadingSceneId: event.sceneId, - )); - - try { - final success = await SceneApi.triggerScene(event.sceneId); - if (success) { - emit(SceneTriggerSuccess(event.name)); - emit(AutomationSceneLoaded(currentState.scenes, currentState.automationList)); - } else { - emit(const AutomationSceneError(message: 'Something went wrong')); - } - } catch (e) { - emit(const AutomationSceneError(message: 'Something went wrong')); - } - } - } - - Future _onUpdateAutomationStatus( - UpdateAutomationStatus event, Emitter emit) async { - final currentState = state; - if (currentState is AutomationSceneLoaded) { - final newLoadingStates = - Map.from(currentState.loadingStates) - ..[event.automationId] = true; - - emit(AutomationSceneLoaded( - currentState.scenes, - currentState.automationList, - loadingStates: newLoadingStates, - )); - - try { - Project? project = HomeCubit.getInstance().project; - - final success = await SceneApi.updateAutomationStatus( - event.automationId, - event.automationStatusUpdate, - project?.uuid ?? ''); - if (success) { - automationList = await SceneApi.getAutomationByUnitId( - event.automationStatusUpdate.spaceUuid, - event.communityId, - project?.uuid ?? ''); - newLoadingStates[event.automationId] = false; - emit(AutomationSceneLoaded( - currentState.scenes, - automationList, - loadingStates: newLoadingStates, - )); - } else { - emit(const AutomationSceneError(message: 'Something went wrong')); - } - } catch (e) { - emit(const AutomationSceneError(message: 'Something went wrong')); - } - } - } -} diff --git a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart deleted file mode 100644 index b34eb5ed..00000000 --- a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_event.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart'; -import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; - -abstract class AutomationSceneTriggerEvent extends Equatable { - const AutomationSceneTriggerEvent(); - - @override - List get props => []; -} - -class AutomationSceneScenes extends AutomationSceneTriggerEvent { - final String unitId; - final bool showInDevice; - final SpaceModel unit; - - const AutomationSceneScenes(this.unitId, this.unit, {this.showInDevice = false}); - - @override - List get props => [unitId, showInDevice]; -} - -class AutomationSceneAutomation extends AutomationSceneTriggerEvent { - final String unitId; - final String communityId; - - - const AutomationSceneAutomation(this.unitId, this.communityId); - - @override - List get props => [unitId, communityId]; -} - -class SceneTrigger extends AutomationSceneTriggerEvent { - final String sceneId; - final String name; - - const SceneTrigger(this.sceneId, this.name); - - @override - List get props => [sceneId]; -} - -//updateAutomationStatus -class UpdateAutomationStatus extends AutomationSceneTriggerEvent { - final String automationId; - final AutomationStatusUpdate automationStatusUpdate; - final String communityId; - - const UpdateAutomationStatus({required this.automationStatusUpdate, required this.automationId, required this.communityId}); - - @override - List get props => [automationStatusUpdate]; -} diff --git a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart b/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart deleted file mode 100644 index 6753372f..00000000 --- a/lib/pages/routines/bloc/automation_scene_trigger_bloc/automation_scene_trigger_status.dart +++ /dev/null @@ -1,51 +0,0 @@ - - -import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/routines/models/routine_model.dart'; - -abstract class AutomationSceneTriggerStatus extends Equatable { - const AutomationSceneTriggerStatus(); - - @override - List get props => []; -} - -class AutomationSceneInitial extends AutomationSceneTriggerStatus {} - -class AutomationSceneLoading extends AutomationSceneTriggerStatus {} - -class AutomationSceneLoaded extends AutomationSceneTriggerStatus { - final List scenes; - final List automationList; - final String? loadingSceneId; - final Map loadingStates; - - const AutomationSceneLoaded(this.scenes, this.automationList, - {this.loadingSceneId, this.loadingStates = const {}}); - - @override - List get props => - [scenes, loadingSceneId, automationList, loadingStates]; -} - -class AutomationSceneError extends AutomationSceneTriggerStatus { - final String message; - - const AutomationSceneError({required this.message}); - - @override - List get props => [message]; -} - -class SceneTriggerSuccess extends AutomationSceneTriggerStatus { - final String sceneName; - - const SceneTriggerSuccess(this.sceneName); - - @override - List get props => [sceneName]; -} - -class UpdateAutomationStatusLoading extends AutomationSceneTriggerStatus { - const UpdateAutomationStatusLoading(); -} diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index 361810c0..d4f61bae 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -5,6 +5,7 @@ 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/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,6 +52,8 @@ class RoutineBloc extends Bloc { on(_triggerSwitchTabsEvent); on(_createNewRoutineViewEvent); on(_resetErrorMessage); + on(_onSceneTrigger); + on(_onUpdateAutomationStatus); } FutureOr _triggerSwitchTabsEvent( @@ -1269,4 +1272,79 @@ 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/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 de627a43..3c140a4e 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -60,7 +60,13 @@ class _RoutinesViewState extends State { height: 10, ), RoutineViewCard( - isFromScenes: false, + isLoading: false, + onChanged: (v) {}, + status: '', + spaceId: '', + automationId: '', + communityId: '', + sceneId: '', cardType: '', spaceName: '', onTap: () { 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 08f4318e..809c462c 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'; @@ -46,48 +46,68 @@ 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( - cardType: 'scenes', - spaceName: 'scenes', - 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), Text( @@ -99,46 +119,74 @@ class _FetchRoutineScenesState extends State ), const SizedBox(height: 10), 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) ? 190 : 200, 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( - cardType: 'automations', - spaceName: 'automations', - 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: + 'aff21a57-2f91-4e5c-b99b-0182c3ab65a9'), + ); + }, + 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 fe94cf83..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,4 @@ +import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -7,42 +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( @@ -62,27 +99,44 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - (isFromScenes ?? false) - ? InkWell( - onTap: () {}, - child: SvgPicture.asset( - Assets.scenesPlayIcon, - fit: BoxFit.contain, - ), - ) - : CupertinoSwitch( - activeColor: ColorsManager.primaryColor, - value: false, - onChanged: (value) {}, - ) - ], - ), + 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, + ), + ) + 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: onTap, + onTap: widget.onTap, child: Column( children: [ Center( @@ -97,11 +151,11 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { ), height: iconSize, width: iconSize, - child: (isFromScenes ?? false) - ? (iconInBytes != null && - iconInBytes?.isNotEmpty == true) + child: (widget.isFromScenes ?? false) + ? (widget.iconInBytes != null && + widget.iconInBytes?.isNotEmpty == true) ? Image.memory( - iconInBytes!, + widget.iconInBytes!, height: iconSize, width: iconSize, fit: BoxFit.contain, @@ -120,16 +174,18 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { width: iconSize, fit: BoxFit.contain, ) - : (icon is String && icon.endsWith('.svg')) + : (widget.icon is String && + widget.icon.endsWith('.svg')) ? SvgPicture.asset( - icon, + widget.icon, fit: BoxFit.contain, ) : Icon( - icon, + widget.icon, color: ColorsManager.dialogBlueTitle, - size: - isSmallScreenSize(context) ? 30 : 40, + size: widget.isSmallScreenSize(context) + ? 30 + : 40, ), ), ), @@ -139,16 +195,17 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { child: Column( children: [ Text( - textString, + widget.textString, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, maxLines: 2, style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.blackColor, - fontSize: isSmallScreenSize(context) ? 10 : 12, + fontSize: + widget.isSmallScreenSize(context) ? 10 : 12, ), ), - if (spaceName != '') + if (widget.spaceName != '') Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, @@ -158,7 +215,7 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { fit: BoxFit.contain, ), Text( - spaceName, + widget.spaceName, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, maxLines: 2, @@ -166,7 +223,9 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { context.textTheme.bodySmall?.copyWith( color: ColorsManager.blackColor, fontSize: - isSmallScreenSize(context) ? 10 : 12, + widget.isSmallScreenSize(context) + ? 10 + : 12, ), ), ], diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index cbef2bc3..eaa09e27 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -12,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( @@ -37,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) { @@ -69,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( @@ -155,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; }, @@ -167,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; }, @@ -191,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) { @@ -200,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 @@ -217,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 @@ -232,7 +241,7 @@ class SceneApi { } } - static Future updateAutomationStatus(String automationId, + static Future updateAutomationStatus(String automationId, AutomationStatusUpdate createAutomationEnable, String projectId) async { try { final response = await _httpService.patch( @@ -260,7 +269,8 @@ class SceneApi { rethrow; } } - static Future> getAutomationByUnitId( + + static Future> getAutomationByUnitId( String unitId, String communityId, String projectId, @@ -285,5 +295,4 @@ class SceneApi { rethrow; } } - } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 5d462f28..0e7b0bd2 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -407,6 +407,8 @@ class Assets { '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 spaceLocationIcon = 'assets/icons/spaceLocationIcon.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'; } From 7e06dd76cf9a4ef31b43bc1ea721001b19572958 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 24 Mar 2025 13:57:53 +0300 Subject: [PATCH 016/238] create routine dialog flow --- lib/main.dart | 5 +- lib/main_dev.dart | 5 +- .../device_managment_bloc.dart | 3 +- .../view/device_managment_page.dart | 15 +- .../create_routine_bloc.dart | 51 ++++ .../create_routine_event.dart | 43 +++ .../create_routine_state.dart | 46 +++ .../bloc/routine_bloc/routine_bloc.dart | 279 ++++++++++++------ .../create_new_routines/commu_dropdown.dart | 96 ++++++ .../create_new_routines.dart | 148 ++++++++++ .../create_new_routines/space_dropdown.dart | 100 +++++++ lib/pages/routines/view/routines_view.dart | 154 ++++------ .../fetch_routine_scenes_automation.dart | 27 +- .../all_spaces/model/space_model.dart | 4 + lib/services/devices_mang_api.dart | 30 +- lib/services/space_mana_api.dart | 20 ++ lib/utils/constants/api_const.dart | 76 +++-- 17 files changed, 863 insertions(+), 239 deletions(-) create mode 100644 lib/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart create mode 100644 lib/pages/routines/bloc/create_routine_bloc/create_routine_event.dart create mode 100644 lib/pages/routines/bloc/create_routine_bloc/create_routine_state.dart create mode 100644 lib/pages/routines/create_new_routines/commu_dropdown.dart create mode 100644 lib/pages/routines/create_new_routines/create_new_routines.dart create mode 100644 lib/pages/routines/create_new_routines/space_dropdown.dart 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'; } From 6045158432799abb59fa18798f2bb25a7b4db44d Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 24 Mar 2025 15:32:02 +0300 Subject: [PATCH 017/238] Reset Selected space --- .../view/device_managment_page.dart | 3 -- .../bloc/routine_bloc/routine_bloc.dart | 46 ++++++++++--------- lib/pages/routines/view/routines_view.dart | 6 +-- 3 files changed, 28 insertions(+), 27 deletions(-) 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 0ed0e7c2..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 @@ -25,9 +25,6 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { create: (context) => DeviceManagementBloc()..add(FetchDevices(context)), ), - BlocProvider( - create: (context) => CreateRoutineBloc(), - ), ], child: WebScaffold( appBarTitle: Text( diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index ef9ca848..2664d026 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -1,4 +1,3 @@ - import 'dart:async'; import 'package:bloc/bloc.dart'; @@ -16,14 +15,9 @@ 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'; @@ -227,6 +221,7 @@ class RoutineBloc extends Bloc { BuildContext context = NavigationService.navigatorKey.currentContext!; var createRoutineBloc = context.read(); try { + if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') { var spaceBloc = context.read(); @@ -342,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.selectedSpaceId, + spaceUuid: createRoutineBloc.selectedSpaceId, iconId: state.selectedIcon ?? '', showInDevice: true, sceneName: state.routineName ?? '', @@ -472,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.selectedSpaceId, + spaceUuid: createRoutineBloc.selectedSpaceId, automationName: state.routineName ?? '', decisionExpr: state.selectedAutomationOperator, effectiveTime: EffectiveTime( @@ -866,24 +861,33 @@ class RoutineBloc extends Bloc { isUpdate: false, createRoutineView: false)); } - - FutureOr _deleteScene( - DeleteScene event, Emitter emit) async { + FutureOr _deleteScene(DeleteScene event, Emitter emit) async { try { - emit(state.copyWith(isLoading: true)); final projectId = await ProjectManager.getProjectUUID() ?? ''; + + emit(state.copyWith(isLoading: true)); BuildContext context = NavigationService.navigatorKey.currentContext!; - var createRoutineBloc = context.read(); + var spaceBloc = context.read(); if (state.isTabToRun) { await SceneApi.deleteScene( - unitUuid: createRoutineBloc.selectedSpaceId, - sceneId: state.sceneId ?? ''); + unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? ''); } else { await SceneApi.deleteAutomation( - projectId: projectId, - unitUuid: createRoutineBloc.selectedSpaceId, - automationId: state.automationId ?? ''); + unitUuid: spaceBloc.state.selectedSpaces[0], + 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()); @@ -896,7 +900,7 @@ class RoutineBloc extends Bloc { )); } } - + // FutureOr _deleteAutomation(DeleteAutomation event, Emitter emit) { // try { // emit(state.copyWith(isLoading: true)); diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index f21a2ad7..962b3b89 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -31,10 +31,10 @@ class _RoutinesViewState extends State { final routineBloc = context.read(); _bloc.add(SaveCommunityIdAndSpaceIdEvent( communityID: communityId, spaceID: spaceId)); - await Future.delayed(const Duration(milliseconds: 500)); + await Future.delayed(const Duration(seconds: 1)); routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true)); - await Future.delayed(const Duration(milliseconds: 500)); - BlocProvider.of(context).add(const ResetSelectedEvent()); + await Future.delayed(const Duration(milliseconds:500)); + _bloc.add(const ResetSelectedEvent()); } @override From 77a9aa2f19e878f791db64c7bd8621b084eb6eeb Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Wed, 26 Mar 2025 04:29:33 +0300 Subject: [PATCH 018/238] Added include spaces to the communites api --- .../space_tree/bloc/space_tree_bloc.dart | 117 +++++++----------- lib/services/space_mana_api.dart | 44 +++---- 2 files changed, 63 insertions(+), 98 deletions(-) diff --git a/lib/pages/space_tree/bloc/space_tree_bloc.dart b/lib/pages/space_tree/bloc/space_tree_bloc.dart index 31adaeb1..6315ee0f 100644 --- a/lib/pages/space_tree/bloc/space_tree_bloc.dart +++ b/lib/pages/space_tree/bloc/space_tree_bloc.dart @@ -28,11 +28,10 @@ class SpaceTreeBloc extends Bloc { try { final updatedCommunity = event.updatedCommunity; - final updatedCommunities = - List.from(state.communityList); + final updatedCommunities = List.from(state.communityList); - final index = updatedCommunities - .indexWhere((community) => community.uuid == updatedCommunity.uuid); + final index = + updatedCommunities.indexWhere((community) => community.uuid == updatedCommunity.uuid); if (index != -1) { updatedCommunities[index] = updatedCommunity; @@ -51,47 +50,41 @@ class SpaceTreeBloc extends Bloc { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; List communities = - await CommunitySpaceManagementApi().fetchCommunities(projectUuid); + await CommunitySpaceManagementApi().fetchCommunities(projectUuid, includeSpaces: true); - List updatedCommunities = await Future.wait( - communities.map((community) async { - List spaces = await CommunitySpaceManagementApi() - .getSpaceHierarchy(community.uuid, projectUuid); + // List updatedCommunities = await Future.wait( + // communities.map((community) async { + // List spaces = + // await CommunitySpaceManagementApi().getSpaceHierarchy(community.uuid, projectUuid); - return CommunityModel( - uuid: community.uuid, - createdAt: community.createdAt, - updatedAt: community.updatedAt, - name: community.name, - description: community.description, - spaces: spaces, - region: community.region, - ); - }).toList(), - ); + // return CommunityModel( + // uuid: community.uuid, + // createdAt: community.createdAt, + // updatedAt: community.updatedAt, + // name: community.name, + // description: community.description, + // spaces: spaces, + // region: community.region, + // ); + // }).toList(), + // ); - emit(state.copyWith( - communitiesList: updatedCommunities, - expandedCommunity: [], - expandedSpaces: [])); + emit(state.copyWith(communitiesList: communities, expandedCommunity: [], expandedSpaces: [])); } catch (e) { emit(SpaceTreeErrorState('Error loading communities and spaces: $e')); } } - void _onCommunityAdded( - OnCommunityAdded event, Emitter emit) async { + void _onCommunityAdded(OnCommunityAdded event, Emitter emit) async { final updatedCommunities = List.from(state.communityList); updatedCommunities.add(event.newCommunity); emit(state.copyWith(communitiesList: updatedCommunities)); } - _onCommunityExpanded( - OnCommunityExpanded event, Emitter emit) async { + _onCommunityExpanded(OnCommunityExpanded event, Emitter emit) async { try { - List updatedExpandedCommunityList = - List.from(state.expandedCommunities); + List updatedExpandedCommunityList = List.from(state.expandedCommunities); if (updatedExpandedCommunityList.contains(event.communityId)) { updatedExpandedCommunityList.remove(event.communityId); @@ -123,19 +116,14 @@ class SpaceTreeBloc extends Bloc { } } - _onCommunitySelected( - OnCommunitySelected event, Emitter emit) async { + _onCommunitySelected(OnCommunitySelected event, Emitter emit) async { try { List updatedSelectedCommunities = List.from(state.selectedCommunities.toSet().toList()); - List updatedSelectedSpaces = - List.from(state.selectedSpaces.toSet().toList()); - List updatedSoldChecks = - List.from(state.soldCheck.toSet().toList()); - Map> communityAndSpaces = - Map.from(state.selectedCommunityAndSpaces); - List selectedSpacesInCommunity = - communityAndSpaces[event.communityId] ?? []; + List updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList()); + List updatedSoldChecks = List.from(state.soldCheck.toSet().toList()); + Map> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces); + List selectedSpacesInCommunity = communityAndSpaces[event.communityId] ?? []; List childrenIds = _getAllChildIds(event.children); @@ -168,15 +156,11 @@ class SpaceTreeBloc extends Bloc { try { List updatedSelectedCommunities = List.from(state.selectedCommunities.toSet().toList()); - List updatedSelectedSpaces = - List.from(state.selectedSpaces.toSet().toList()); - List updatedSoldChecks = - List.from(state.soldCheck.toSet().toList()); - Map> communityAndSpaces = - Map.from(state.selectedCommunityAndSpaces); + List updatedSelectedSpaces = List.from(state.selectedSpaces.toSet().toList()); + List updatedSoldChecks = List.from(state.soldCheck.toSet().toList()); + Map> communityAndSpaces = Map.from(state.selectedCommunityAndSpaces); - List selectedSpacesInCommunity = - communityAndSpaces[event.communityModel.uuid] ?? []; + List selectedSpacesInCommunity = communityAndSpaces[event.communityModel.uuid] ?? []; List childrenIds = _getAllChildIds(event.children); bool isChildSelected = false; @@ -199,11 +183,9 @@ class SpaceTreeBloc extends Bloc { selectedSpacesInCommunity.addAll(childrenIds); } - List spaces = - _getThePathToChild(event.communityModel.uuid, event.spaceId); + List spaces = _getThePathToChild(event.communityModel.uuid, event.spaceId); for (String space in spaces) { - if (!updatedSelectedSpaces.contains(space) && - !updatedSoldChecks.contains(space)) { + if (!updatedSelectedSpaces.contains(space) && !updatedSoldChecks.contains(space)) { updatedSoldChecks.add(space); } } @@ -226,9 +208,7 @@ class SpaceTreeBloc extends Bloc { updatedSoldChecks.remove(event.spaceId); List parents = - _getThePathToChild(event.communityModel.uuid, event.spaceId) - .toSet() - .toList(); + _getThePathToChild(event.communityModel.uuid, event.spaceId).toSet().toList(); if (updatedSelectedSpaces.isEmpty) { updatedSoldChecks.removeWhere(parents.contains); @@ -236,8 +216,7 @@ class SpaceTreeBloc extends Bloc { } else { // Check if any parent has selected children for (String space in parents) { - if (!_noChildrenSelected( - event.communityModel, space, updatedSelectedSpaces, parents)) { + if (!_noChildrenSelected(event.communityModel, space, updatedSelectedSpaces, parents)) { updatedSoldChecks.remove(space); } } @@ -262,8 +241,8 @@ class SpaceTreeBloc extends Bloc { } } - _noChildrenSelected(CommunityModel community, String spaceId, - List selectedSpaces, List parents) { + _noChildrenSelected( + CommunityModel community, String spaceId, List selectedSpaces, List parents) { if (selectedSpaces.contains(spaceId)) { return true; } @@ -290,11 +269,10 @@ class SpaceTreeBloc extends Bloc { // Filter communities and expand only those that match the query filteredCommunity = communities.where((community) { - final containsQueryInCommunity = community.name - .toLowerCase() - .contains(event.searchQuery.toLowerCase()); - final containsQueryInSpaces = community.spaces.any( - (space) => _containsQuery(space, event.searchQuery.toLowerCase())); + final containsQueryInCommunity = + community.name.toLowerCase().contains(event.searchQuery.toLowerCase()); + final containsQueryInSpaces = + community.spaces.any((space) => _containsQuery(space, event.searchQuery.toLowerCase())); return containsQueryInCommunity || containsQueryInSpaces; }).toList(); @@ -347,8 +325,8 @@ class SpaceTreeBloc extends Bloc { // Helper function to determine if any space or its children match the search query bool _containsQuery(SpaceModel space, String query) { final matchesSpace = space.name.toLowerCase().contains(query); - final matchesChildren = space.children.any((child) => - _containsQuery(child, query)); // Recursive check for children + final matchesChildren = + space.children.any((child) => _containsQuery(child, query)); // Recursive check for children return matchesSpace || matchesChildren; } @@ -371,8 +349,8 @@ class SpaceTreeBloc extends Bloc { return children; } - bool _anySpacesSelectedInCommunity(CommunityModel community, - List selectedSpaces, List partialCheckedList) { + bool _anySpacesSelectedInCommunity( + CommunityModel community, List selectedSpaces, List partialCheckedList) { bool result = false; List ids = _getAllChildIds(community.spaces); for (var id in ids) { @@ -401,8 +379,7 @@ class SpaceTreeBloc extends Bloc { return ids; } - List _getAllParentsIds( - SpaceModel child, String spaceId, List listIds) { + List _getAllParentsIds(SpaceModel child, String spaceId, List listIds) { List ids = listIds; ids.add(child.uuid ?? ''); diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index f18a8da8..629f775c 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -3,27 +3,24 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; class CommunitySpaceManagementApi { // Community Management APIs Future> fetchCommunities(String projectId, - {int page = 1}) async { + {int page = 1, bool includeSpaces = false}) async { try { List allCommunities = []; bool hasNext = true; while (hasNext) { await HTTPService().get( - path: ApiEndpoints.getCommunityList - .replaceAll('{projectId}', projectId), - queryParameters: {'page': page}, + path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId), + queryParameters: {'page': page, 'includeSpaces': includeSpaces}, expectedResponseModel: (json) { try { List jsonData = json['data'] ?? []; @@ -52,8 +49,7 @@ class CommunitySpaceManagementApi { Future getCommunityById(String communityId) async { try { final response = await HTTPService().get( - path: ApiEndpoints.getCommunityById - .replaceAll('{communityId}', communityId), + path: ApiEndpoints.getCommunityById.replaceAll('{communityId}', communityId), expectedResponseModel: (json) { return CommunityModel.fromJson(json['data']); }, @@ -65,8 +61,7 @@ class CommunitySpaceManagementApi { } } - Future createCommunity( - String name, String description, String projectId) async { + Future createCommunity(String name, String description, String projectId) async { try { final response = await HTTPService().post( path: ApiEndpoints.createCommunity.replaceAll('{projectId}', projectId), @@ -85,8 +80,7 @@ class CommunitySpaceManagementApi { } } - Future updateCommunity( - String communityId, String name, String projectId) async { + Future updateCommunity(String communityId, String name, String projectId) async { try { final response = await HTTPService().put( path: ApiEndpoints.updateCommunity @@ -123,8 +117,7 @@ class CommunitySpaceManagementApi { } } - Future fetchSpaces( - String communityId, String projectId) async { + Future fetchSpaces(String communityId, String projectId) async { try { final response = await HTTPService().get( path: ApiEndpoints.listSpaces @@ -150,8 +143,7 @@ class CommunitySpaceManagementApi { } } - Future getSpace( - String communityId, String spaceId, String projectId) async { + Future getSpace(String communityId, String spaceId, String projectId) async { try { final response = await HTTPService().get( path: ApiEndpoints.getSpace @@ -262,8 +254,7 @@ class CommunitySpaceManagementApi { } } - Future deleteSpace( - String communityId, String spaceId, String projectId) async { + Future deleteSpace(String communityId, String spaceId, String projectId) async { try { final response = await HTTPService().delete( path: ApiEndpoints.deleteSpace @@ -281,17 +272,15 @@ class CommunitySpaceManagementApi { } } - Future> getSpaceHierarchy( - String communityId, String projectId) async { + Future> getSpaceHierarchy(String communityId, String projectId) async { try { final response = await HTTPService().get( path: ApiEndpoints.getSpaceHierarchy .replaceAll('{communityId}', communityId) .replaceAll('{projectId}', projectId), expectedResponseModel: (json) { - final spaceModels = (json['data'] as List) - .map((spaceJson) => SpaceModel.fromJson(spaceJson)) - .toList(); + final spaceModels = + (json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList(); return spaceModels; }, @@ -302,17 +291,16 @@ class CommunitySpaceManagementApi { return []; } } - Future> getSpaceOnlyWithDevices( - {String? communityId, String? projectId}) async { + + 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(); + final spaceModels = + (json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList(); return spaceModels; }, ); From dad18b77de2a4238b0ac2e0b37969fd21a44fbf5 Mon Sep 17 00:00:00 2001 From: fkarmoush Date: Wed, 26 Mar 2025 12:35:04 +0300 Subject: [PATCH 019/238] Removed `community` filter field from `DeviceSearchFilters`. --- .../widgets/device_search_filters.dart | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index 650c0d21..0f565525 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -12,8 +12,8 @@ class DeviceSearchFilters extends StatefulWidget { State createState() => _DeviceSearchFiltersState(); } -class _DeviceSearchFiltersState extends State with HelperResponsiveLayout { - final TextEditingController communityController = TextEditingController(); +class _DeviceSearchFiltersState extends State + with HelperResponsiveLayout { final TextEditingController unitNameController = TextEditingController(); final TextEditingController productNameController = TextEditingController(); @@ -22,11 +22,17 @@ class _DeviceSearchFiltersState extends State with HelperRe return isExtraLargeScreenSize(context) ? Row( children: [ - _buildSearchField("Community", communityController, 200), + _buildSearchField( + "Space Name", + unitNameController, + 200, + ), const SizedBox(width: 20), - _buildSearchField("Space Name", unitNameController, 200), - const SizedBox(width: 20), - _buildSearchField("Device Name / Product Name", productNameController, 300), + _buildSearchField( + "Device Name / Product Name", + productNameController, + 300, + ), const SizedBox(width: 20), _buildSearchResetButtons(), ], @@ -36,11 +42,10 @@ class _DeviceSearchFiltersState extends State with HelperRe runSpacing: 10, children: [ _buildSearchField( - "Community", - communityController, + "Space Name", + unitNameController, 200, ), - _buildSearchField("Space Name", unitNameController, 200), _buildSearchField( "Device Name / Product Name", productNameController, @@ -51,22 +56,25 @@ class _DeviceSearchFiltersState extends State with HelperRe ); } - Widget _buildSearchField(String title, TextEditingController controller, double width) { - return Container( - child: StatefulTextField( - title: title, - width: width, - elevation: 2, - controller: controller, - onSubmitted: () { - context.read().add(SearchDevices( - productName: productNameController.text, - unitName: unitNameController.text, - community: communityController.text, - searchField: true)); - }, - onChanged: (p0) {}, - ), + Widget _buildSearchField( + String title, + TextEditingController controller, + double width, + ) { + return StatefulTextField( + title: title, + width: width, + elevation: 2, + controller: controller, + onSubmitted: () { + final searchDevicesEvent = SearchDevices( + productName: productNameController.text, + unitName: unitNameController.text, + searchField: true, + ); + context.read().add(searchDevicesEvent); + }, + onChanged: (p0) {}, ); } @@ -74,13 +82,11 @@ class _DeviceSearchFiltersState extends State with HelperRe return SearchResetButtons( onSearch: () { context.read().add(SearchDevices( - community: communityController.text, unitName: unitNameController.text, productName: productNameController.text, searchField: true)); }, onReset: () { - communityController.clear(); unitNameController.clear(); productNameController.clear(); context.read() From b65f172f9dec7495cb5ec4c9c52a2941a2d3bcc7 Mon Sep 17 00:00:00 2001 From: fkarmoush Date: Wed, 26 Mar 2025 12:36:26 +0300 Subject: [PATCH 020/238] Removed memory leak bug from `DeviceSearchFilters` widget. --- .../widgets/device_search_filters.dart | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index 0f565525..428f0b58 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -14,8 +14,22 @@ class DeviceSearchFilters extends StatefulWidget { class _DeviceSearchFiltersState extends State with HelperResponsiveLayout { - final TextEditingController unitNameController = TextEditingController(); - final TextEditingController productNameController = TextEditingController(); + late final TextEditingController unitNameController; + late final TextEditingController productNameController; + + @override + void initState() { + unitNameController = TextEditingController(); + productNameController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + unitNameController.dispose(); + productNameController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { From d3128a9c9ca0bedab81dfc8e3980e753240078ac Mon Sep 17 00:00:00 2001 From: fkarmoush Date: Wed, 26 Mar 2025 12:37:03 +0300 Subject: [PATCH 021/238] Refactor search button callback for improved readability --- .../all_devices/widgets/device_search_filters.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index 428f0b58..659a291a 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -94,12 +94,13 @@ class _DeviceSearchFiltersState extends State Widget _buildSearchResetButtons() { return SearchResetButtons( - onSearch: () { - context.read().add(SearchDevices( - unitName: unitNameController.text, - productName: productNameController.text, - searchField: true)); - }, + onSearch: () => context.read().add( + SearchDevices( + unitName: unitNameController.text, + productName: productNameController.text, + searchField: true, + ), + ), onReset: () { unitNameController.clear(); productNameController.clear(); From f670ae78aa87e7022f6b10a9910770c8faa24f25 Mon Sep 17 00:00:00 2001 From: fkarmoush Date: Wed, 26 Mar 2025 12:39:51 +0300 Subject: [PATCH 022/238] Refactor device search filters layout for improved code organization and to remove any code duplication. --- .../widgets/device_search_filters.dart | 61 ++++++++----------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index 659a291a..3910257f 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -31,43 +31,34 @@ class _DeviceSearchFiltersState extends State super.dispose(); } + List get _widgets => [ + _buildSearchField( + "Space Name", + unitNameController, + 200, + ), + _buildSearchField( + "Device Name / Product Name", + productNameController, + 300, + ), + _buildSearchResetButtons(), + ]; + @override Widget build(BuildContext context) { - return isExtraLargeScreenSize(context) - ? Row( - children: [ - _buildSearchField( - "Space Name", - unitNameController, - 200, - ), - const SizedBox(width: 20), - _buildSearchField( - "Device Name / Product Name", - productNameController, - 300, - ), - const SizedBox(width: 20), - _buildSearchResetButtons(), - ], - ) - : Wrap( - spacing: 20, - runSpacing: 10, - children: [ - _buildSearchField( - "Space Name", - unitNameController, - 200, - ), - _buildSearchField( - "Device Name / Product Name", - productNameController, - 300, - ), - _buildSearchResetButtons(), - ], - ); + if (isExtraLargeScreenSize(context)) { + return Row( + spacing: 20, + children: _widgets, + ); + } + + return Wrap( + spacing: 20, + runSpacing: 10, + children: _widgets, + ); } Widget _buildSearchField( From 835dfe8785850f89f1ebb29958c08c2c010aa623 Mon Sep 17 00:00:00 2001 From: fkarmoush Date: Wed, 26 Mar 2025 12:40:25 +0300 Subject: [PATCH 023/238] Rename controller variables in `DeviceSearchFilters` to be private to control access control. --- .../widgets/device_search_filters.dart | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index 3910257f..27bcb0de 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -14,32 +14,32 @@ class DeviceSearchFilters extends StatefulWidget { class _DeviceSearchFiltersState extends State with HelperResponsiveLayout { - late final TextEditingController unitNameController; - late final TextEditingController productNameController; + late final TextEditingController _unitNameController; + late final TextEditingController _productNameController; @override void initState() { - unitNameController = TextEditingController(); - productNameController = TextEditingController(); + _unitNameController = TextEditingController(); + _productNameController = TextEditingController(); super.initState(); } @override void dispose() { - unitNameController.dispose(); - productNameController.dispose(); + _unitNameController.dispose(); + _productNameController.dispose(); super.dispose(); } List get _widgets => [ _buildSearchField( "Space Name", - unitNameController, + _unitNameController, 200, ), _buildSearchField( "Device Name / Product Name", - productNameController, + _productNameController, 300, ), _buildSearchResetButtons(), @@ -73,8 +73,8 @@ class _DeviceSearchFiltersState extends State controller: controller, onSubmitted: () { final searchDevicesEvent = SearchDevices( - productName: productNameController.text, - unitName: unitNameController.text, + productName: _productNameController.text, + unitName: _unitNameController.text, searchField: true, ); context.read().add(searchDevicesEvent); @@ -87,14 +87,14 @@ class _DeviceSearchFiltersState extends State return SearchResetButtons( onSearch: () => context.read().add( SearchDevices( - unitName: unitNameController.text, - productName: productNameController.text, + unitName: _unitNameController.text, + productName: _productNameController.text, searchField: true, ), ), onReset: () { - unitNameController.clear(); - productNameController.clear(); + _unitNameController.clear(); + _productNameController.clear(); context.read() ..add(ResetFilters()) ..add(FetchDevices(context)); From f307aaff9d767f1757e53b47fd90f83499447974 Mon Sep 17 00:00:00 2001 From: fkarmoush Date: Thu, 27 Mar 2025 11:08:57 +0300 Subject: [PATCH 024/238] Removed the usage of spacing property because the GitHub action doesnt support the version that has it, so instead opted for using `Padding`. --- .../all_devices/widgets/device_search_filters.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index 27bcb0de..1928768b 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -49,8 +49,14 @@ class _DeviceSearchFiltersState extends State Widget build(BuildContext context) { if (isExtraLargeScreenSize(context)) { return Row( - spacing: 20, - children: _widgets, + children: _widgets.map( + (e) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: e, + ); + }, + ).toList(), ); } From 22935f7007bef58e7d682428cf4ec7677bb75059 Mon Sep 17 00:00:00 2001 From: fkarmoush Date: Thu, 27 Mar 2025 14:52:26 +0300 Subject: [PATCH 025/238] SP-1271-rework --- .../widgets/device_search_filters.dart | 41 ++++--------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index 1928768b..fbbe65ab 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -14,34 +14,12 @@ class DeviceSearchFilters extends StatefulWidget { class _DeviceSearchFiltersState extends State with HelperResponsiveLayout { - late final TextEditingController _unitNameController; - late final TextEditingController _productNameController; - - @override - void initState() { - _unitNameController = TextEditingController(); - _productNameController = TextEditingController(); - super.initState(); - } - - @override - void dispose() { - _unitNameController.dispose(); - _productNameController.dispose(); - super.dispose(); - } + final _unitNameController = TextEditingController(); + final _productNameController = TextEditingController(); List get _widgets => [ - _buildSearchField( - "Space Name", - _unitNameController, - 200, - ), - _buildSearchField( - "Device Name / Product Name", - _productNameController, - 300, - ), + _buildSearchField("Space Name", _unitNameController, 200), + _buildSearchField("Device Name / Product Name", _productNameController, 300), _buildSearchResetButtons(), ]; @@ -49,14 +27,9 @@ class _DeviceSearchFiltersState extends State Widget build(BuildContext context) { if (isExtraLargeScreenSize(context)) { return Row( - children: _widgets.map( - (e) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: e, - ); - }, - ).toList(), + children: _widgets + .map((e) => Padding(padding: const EdgeInsets.all(10), child: e)) + .toList(), ); } From 573e7b42edf4ff93fc8ea8e47bb2a7efa76c19bf Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 27 Mar 2025 15:04:33 +0300 Subject: [PATCH 026/238] Refactor routine creation UI for improved code organization --- .../create_new_routines/commu_dropdown.dart | 134 +++++++++++---- .../create_new_routines.dart | 50 +++--- .../create_new_routines/space_dropdown.dart | 155 ++++++++++++------ lib/pages/routines/view/routines_view.dart | 2 - 4 files changed, 229 insertions(+), 112 deletions(-) diff --git a/lib/pages/routines/create_new_routines/commu_dropdown.dart b/lib/pages/routines/create_new_routines/commu_dropdown.dart index 13e2a437..ec031160 100644 --- a/lib/pages/routines/create_new_routines/commu_dropdown.dart +++ b/lib/pages/routines/create_new_routines/commu_dropdown.dart @@ -1,16 +1,16 @@ +import 'package:dropdown_button2/dropdown_button2.dart'; 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; + final TextEditingController _searchController = TextEditingController(); - const CommunityDropdown({ + CommunityDropdown({ Key? key, required this.selectedValue, required this.onChanged, @@ -34,59 +34,125 @@ class CommunityDropdown extends StatelessWidget { const SizedBox(height: 8), BlocBuilder( builder: (context, state) { - List communities = state.isSearching - ? state.filteredCommunity - : state.communityList; - return SizedBox( - child: DropdownButtonFormField( - dropdownColor: ColorsManager.whiteColors, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + ), + child: DropdownButton2( + underline: SizedBox(), value: selectedValue, - items: communities.map((community) { + items: state.communityList.map((community) { return DropdownMenuItem( value: community.uuid, - child: Text(' ${community.name}'), + child: Text( + ' ${community.name}', + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), ); }).toList(), onChanged: onChanged, - icon: const SizedBox.shrink(), - borderRadius: const BorderRadius.all(Radius.circular(10)), + style: TextStyle(color: Colors.black), hint: Padding( padding: EdgeInsets.only(left: 10), child: Text( - "Please Select", + " 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), + customButton: Container( + height: 45, + decoration: BoxDecoration( + border: + Border.all(color: ColorsManager.textGray, width: 1.0), + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 5, + child: Text( + selectedValue != null + ? " ${state.communityList.firstWhere((element) => element.uuid == selectedValue).name}" + : ' Please Select', + style: + Theme.of(context).textTheme.bodySmall!.copyWith( + color: selectedValue != null + ? Colors.black + : ColorsManager.textGray, + ), + overflow: TextOverflow.ellipsis, + ), ), - border: Border.all( - color: ColorsManager.textGray, - width: 1.0, + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + ), + height: 45, + child: const Icon( + Icons.keyboard_arrow_down, + color: ColorsManager.textGray, + ), + ), ), - ), - child: const Center( - child: Icon( - Icons.keyboard_arrow_down, - color: ColorsManager.textGray, + ], + ), + ), + dropdownStyleData: DropdownStyleData( + maxHeight: MediaQuery.of(context).size.height * 0.4, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + ), + ), + dropdownSearchData: DropdownSearchData( + searchController: _searchController, + searchInnerWidgetHeight: 50, + searchInnerWidget: Container( + height: 50, + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + child: TextFormField( + style: const TextStyle(color: Colors.black), + controller: _searchController, + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 12, + ), + hintText: 'Search for community...', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), ), ), ), + searchMatchFn: (item, searchValue) { + final communityName = + (item.child as Text).data?.toLowerCase() ?? ''; + return communityName + .contains(searchValue.toLowerCase().trim()); + }, + ), + onMenuStateChange: (isOpen) { + if (!isOpen) { + _searchController.clear(); + } + }, + menuItemStyleData: const MenuItemStyleData( + height: 40, ), ), - ); + )); }, ), ], diff --git a/lib/pages/routines/create_new_routines/create_new_routines.dart b/lib/pages/routines/create_new_routines/create_new_routines.dart index 5d1021f2..baf10748 100644 --- a/lib/pages/routines/create_new_routines/create_new_routines.dart +++ b/lib/pages/routines/create_new_routines/create_new_routines.dart @@ -62,28 +62,34 @@ class _CreateNewRoutinesDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ const Divider(), - CommunityDropdown( - selectedValue: _selectedCommunity, - onChanged: (String? newValue) { - setState(() { - _selectedCommunity = newValue; - _selectedSpace = null; - }); - if (newValue != null) { - _fetchSpaces(newValue); - } - }, + Padding( + padding: const EdgeInsets.only(left: 15, right: 15), + child: 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 SizedBox(height: 5), + Padding( + padding: const EdgeInsets.only(left: 15, right: 15), + child: SpaceDropdown( + hintMessage: spaceHint, + spaces: spaces, + selectedValue: _selectedSpace, + onChanged: (String? newValue) { + setState(() { + _selectedSpace = newValue; + }); + }, + ), ), const Divider(), Row( @@ -96,7 +102,6 @@ class _CreateNewRoutinesDialogState extends State { ), child: TextButton( onPressed: () { - Navigator.of(context).pop(); }, child: Text( @@ -139,6 +144,7 @@ class _CreateNewRoutinesDialogState extends State { ), ], ), + SizedBox(height: 10), ], ), ); diff --git a/lib/pages/routines/create_new_routines/space_dropdown.dart b/lib/pages/routines/create_new_routines/space_dropdown.dart index f207c736..a26ff9f4 100644 --- a/lib/pages/routines/create_new_routines/space_dropdown.dart +++ b/lib/pages/routines/create_new_routines/space_dropdown.dart @@ -1,7 +1,8 @@ +import 'package:dropdown_button2/dropdown_button2.dart'; 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; @@ -33,62 +34,108 @@ class SpaceDropdown extends StatelessWidget { ), ), 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, - ), + SizedBox( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), ), - ), - 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: DropdownButton2( + underline: const SizedBox(), + 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, + style: TextStyle(color: Colors.black), + hint: Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + hintMessage, + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.textGray, + ), ), ), - child: const Icon( - Icons.keyboard_arrow_down, - color: ColorsManager.textGray, + customButton: Container( + height: 45, + decoration: BoxDecoration( + border: + Border.all(color: ColorsManager.textGray, width: 1.0), + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 5, + child: Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + selectedValue != null + ? spaces + .firstWhere((e) => e.uuid == selectedValue) + .name + : hintMessage, + style: + Theme.of(context).textTheme.bodySmall!.copyWith( + color: selectedValue != null + ? Colors.black + : ColorsManager.textGray, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ), + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + ), + height: 45, + child: const Icon( + Icons.keyboard_arrow_down, + color: ColorsManager.textGray, + ), + ), + ), + ], + ), + ), + dropdownStyleData: DropdownStyleData( + maxHeight: MediaQuery.of(context).size.height * 0.4, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + ), + ), + menuItemStyleData: const MenuItemStyleData( + height: 60, ), ), ), diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index 962b3b89..83ab82b9 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -33,8 +33,6 @@ class _RoutinesViewState extends State { 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 From a034202d760538bbc477c90fc1f7648b9a3699cc Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 3 Apr 2025 10:14:24 +0400 Subject: [PATCH 027/238] updated endpoints --- .../models/factory_reset_model.dart | 3 ++ .../bloc/visitor_password_bloc.dart | 3 +- lib/services/access_mang_api.dart | 44 +++++++++++++------ lib/services/devices_mang_api.dart | 19 ++++---- lib/services/space_mana_api.dart | 2 - lib/utils/constants/api_const.dart | 30 +++++-------- 6 files changed, 56 insertions(+), 45 deletions(-) diff --git a/lib/pages/device_managment/all_devices/models/factory_reset_model.dart b/lib/pages/device_managment/all_devices/models/factory_reset_model.dart index aec14d16..56c6c90b 100644 --- a/lib/pages/device_managment/all_devices/models/factory_reset_model.dart +++ b/lib/pages/device_managment/all_devices/models/factory_reset_model.dart @@ -2,14 +2,17 @@ import 'package:flutter/foundation.dart'; class FactoryResetModel { final List devicesUuid; + final String operationType; FactoryResetModel({ required this.devicesUuid, + this.operationType = "RESET", }); factory FactoryResetModel.fromJson(Map json) { return FactoryResetModel( devicesUuid: List.from(json['devicesUuid']), + operationType: "RESET", ); } diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index 45fb03fe..438b1abf 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -194,9 +194,10 @@ class VisitorPasswordBloc emit(DeviceLoaded()); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - data = await AccessMangApi().fetchDevices(projectUuid); + data = await AccessMangApi().fetchDoorLockDeviceList(projectUuid); emit(TableLoaded(data)); } catch (e) { + print("error: $e"); emit(FailedState(e.toString())); } } diff --git a/lib/services/access_mang_api.dart b/lib/services/access_mang_api.dart index 6acbc395..a780a12b 100644 --- a/lib/services/access_mang_api.dart +++ b/lib/services/access_mang_api.dart @@ -6,13 +6,23 @@ import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; class AccessMangApi { + AccessMangApi() { + _validateEndpoints(); + } + + void _validateEndpoints() { + if (!ApiEndpoints.getDevices.contains('{projectId}')) { + throw Exception("Endpoint 'getDevices' must contain '{projectId}' placeholder."); + } + } + Future> fetchVisitorPassword(String projectId) async { try { final response = await HTTPService().get( - path: ApiEndpoints.visitorPassword.replaceAll('{projectId}', projectId), + path: ApiEndpoints.visitorPassword, showServerMessage: true, expectedResponseModel: (json) { - List jsonData = json; + List jsonData = json['data'] ?? []; List passwordList = jsonData.map((jsonItem) { return PasswordModel.fromJson(jsonItem); }).toList(); @@ -25,17 +35,22 @@ class AccessMangApi { } } - Future fetchDevices(String projectId) async { + Future fetchDoorLockDeviceList(String projectId) async { try { + // The endpoint structure is already validated during initialization. + final response = await HTTPService().get( path: ApiEndpoints.getDevices.replaceAll('{projectId}', projectId), + queryParameters: { + 'deviceType': 'DOOR_LOCK', + }, showServerMessage: true, expectedResponseModel: (json) { - List jsonData = json; - List passwordList = jsonData.map((jsonItem) { + List jsonData = json['data'] ?? []; + List deviceList = jsonData.map((jsonItem) { return DeviceModel.fromJson(jsonItem); }).toList(); - return passwordList; + return deviceList; }, ); return response; @@ -52,14 +67,15 @@ class AccessMangApi { String? invalidTime, List? devicesUuid}) async { final response = await HTTPService().post( - path: ApiEndpoints.sendOnlineOneTime, + path: ApiEndpoints.visitorPassword, body: jsonEncode({ "email": email, "passwordName": passwordName, "password": password, "devicesUuid": devicesUuid, "effectiveTime": effectiveTime, - "invalidTime": invalidTime + "invalidTime": invalidTime, + "operationType": "ONLINE_ONE_TIME", }), showServerMessage: true, expectedResponseModel: (json) { @@ -84,13 +100,13 @@ class AccessMangApi { "password": password, "effectiveTime": effectiveTime, "invalidTime": invalidTime, + "operationType": "ONLINE_MULTIPLE_TIME", }; if (scheduleList != null) { - body["scheduleList"] = - scheduleList.map((schedule) => schedule.toJson()).toList(); + body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList(); } final response = await HTTPService().post( - path: ApiEndpoints.sendOnlineMultipleTime, + path: ApiEndpoints.visitorPassword, body: jsonEncode(body), showServerMessage: true, expectedResponseModel: (json) { @@ -105,8 +121,9 @@ class AccessMangApi { Future postOffLineOneTime( {String? email, String? passwordName, List? devicesUuid}) async { final response = await HTTPService().post( - path: ApiEndpoints.sendOffLineOneTime, + path: ApiEndpoints.visitorPassword, body: jsonEncode({ + "operationType": "OFFLINE_ONE_TIME", "email": email, "passwordName": passwordName, "devicesUuid": devicesUuid @@ -126,13 +143,14 @@ class AccessMangApi { String? invalidTime, List? devicesUuid}) async { final response = await HTTPService().post( - path: ApiEndpoints.sendOffLineMultipleTime, + path: ApiEndpoints.visitorPassword, body: jsonEncode({ "email": email, "devicesUuid": devicesUuid, "passwordName": passwordName, "effectiveTime": effectiveTime, "invalidTime": invalidTime, + "operationType": "OFFLINE_MULTIPLE_TIME", }), showServerMessage: true, expectedResponseModel: (json) { diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 25a0177f..0b16d826 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -24,7 +24,7 @@ class DevicesManagementApi { showServerMessage: true, expectedResponseModel: (json) { List jsonData = - communityId.isNotEmpty && spaceId.isNotEmpty ? json['data'] : json; + communityId.isNotEmpty && spaceId.isNotEmpty ? json['data'] : json['data']; List devicesList = jsonData.map((jsonItem) { return AllDevicesModel.fromJson(jsonItem); }).toList(); @@ -33,7 +33,7 @@ class DevicesManagementApi { ); return response; } catch (e) { - debugPrint('Error fetching $e'); + debugPrint('Error fetching device $e'); return []; } } @@ -44,7 +44,7 @@ class DevicesManagementApi { path: ApiEndpoints.getDeviceStatus.replaceAll('{uuid}', uuid), showServerMessage: true, expectedResponseModel: (json) { - return DeviceStatus.fromJson(json); + return DeviceStatus.fromJson(json['data']); }, ); return response; @@ -61,7 +61,7 @@ class DevicesManagementApi { Future getPowerClampInfo(String deviceId) async { try { final response = await HTTPService().get( - path: ApiEndpoints.powerClamp.replaceAll('{powerClampUuid}', deviceId), + path: ApiEndpoints.getDeviceStatus.replaceAll('{uuid}', deviceId), showServerMessage: true, expectedResponseModel: (json) { return json; @@ -98,6 +98,7 @@ class DevicesManagementApi { 'devicesUuid': uuids, 'code': code, 'value': value, + 'operationType': 'COMMAND', }; final response = await HTTPService().post( @@ -105,7 +106,7 @@ class DevicesManagementApi { body: body, showServerMessage: true, expectedResponseModel: (json) { - return (json['successResults'] as List).isNotEmpty; + return json['success'] ?? false; }, ); @@ -125,7 +126,7 @@ class DevicesManagementApi { if (json == null || json.isEmpty || json == []) { return devices; } - for (var device in json['devices']) { + for (var device in json['data']['devices']) { devices.add(DeviceModel.fromJson(device)); } return devices; @@ -153,7 +154,7 @@ class DevicesManagementApi { path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code), showServerMessage: false, expectedResponseModel: (json) { - return DeviceReport.fromJson(json); + return DeviceReport.fromJson(json['data']); }, ); return response; @@ -169,7 +170,7 @@ class DevicesManagementApi { .replaceAll('{endTime}', to ?? ''), showServerMessage: false, expectedResponseModel: (json) { - return DeviceReport.fromJson(json); + return DeviceReport.fromJson(json['data']); }, ); return response; @@ -185,7 +186,7 @@ class DevicesManagementApi { queryParameters: queryParameters, showServerMessage: true, expectedResponseModel: (json) { - return DeviceStatus.fromJson(json['status']); + return DeviceStatus.fromJson(json['data']['status']); }, ); return response; diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 4eced226..9b1b0df1 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -3,13 +3,11 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; -import 'package:syncrow_web/utils/constants/temp_const.dart'; class CommunitySpaceManagementApi { // Community Management APIs diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 71e0fc55..d0eefee7 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -9,17 +9,8 @@ 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 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 sendOffLineMultipleTime = - '/visitor-password/temporary-password/offline/multiple-time'; + static const String visitorPassword = '/visitor-passwords'; + static const String getDevices = '/projects/{projectId}/devices'; static const String getUser = '/user/{userUuid}'; @@ -28,15 +19,15 @@ abstract class ApiEndpoints { static const String getAllDevices = '/projects/{projectId}/devices'; static const String getSpaceDevices = '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/devices'; - static const String getDeviceStatus = '/device/{uuid}/functions/status'; - static const String getBatchStatus = '/device/status/batch'; + static const String getDeviceStatus = '/devices/{uuid}/functions/status'; + static const String getBatchStatus = '/devices/batch'; - static const String deviceControl = '/device/{uuid}/control'; - static const String deviceBatchControl = '/device/control/batch'; - static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices'; + static const String deviceControl = '/devices/{uuid}/command'; + static const String deviceBatchControl = '/devices/batch'; + static const String gatewayApi = '/devices/gateway/{gatewayUuid}/devices'; static const String openDoorLock = '/door-lock/open/{doorLockUuid}'; - static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; + static const String getDeviceLogs = '/devices/{uuid}/report-logs?code={code}'; // Space Module static const String createSpace = '/projects/{projectId}/communities/{communityId}/spaces'; @@ -57,14 +48,13 @@ abstract class ApiEndpoints { 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}'; + '/devices/{uuid}/report-logs?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 factoryReset = '/device/factory/reset/{deviceUuid}'; - static const String powerClamp = '/device/{powerClampUuid}/power-clamp/status'; + static const String factoryReset = '/devices/batch'; //product static const String listProducts = '/products'; From ab3f268f29ac8d5ecd3ca93be6dc4962addd7659 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Sun, 6 Apr 2025 01:14:16 +0300 Subject: [PATCH 028/238] Added pagination and search logic in space tree --- .../create_new_routines/commu_dropdown.dart | 5 +- .../space_tree/bloc/space_tree_bloc.dart | 103 +++++++--- .../space_tree/bloc/space_tree_event.dart | 21 +- .../space_tree/bloc/space_tree_state.dart | 19 +- .../space_tree/model/pagination_model.dart | 20 ++ .../space_tree/view/space_tree_view.dart | 179 ++++++++++-------- .../all_spaces/model/community_model.dart | 10 +- lib/services/devices_mang_api.dart | 27 +-- lib/services/space_mana_api.dart | 40 +++- 9 files changed, 284 insertions(+), 140 deletions(-) create mode 100644 lib/pages/space_tree/model/pagination_model.dart diff --git a/lib/pages/routines/create_new_routines/commu_dropdown.dart b/lib/pages/routines/create_new_routines/commu_dropdown.dart index 13e2a437..658b69a0 100644 --- a/lib/pages/routines/create_new_routines/commu_dropdown.dart +++ b/lib/pages/routines/create_new_routines/commu_dropdown.dart @@ -34,9 +34,8 @@ class CommunityDropdown extends StatelessWidget { const SizedBox(height: 8), BlocBuilder( builder: (context, state) { - List communities = state.isSearching - ? state.filteredCommunity - : state.communityList; + List communities = + state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList; return SizedBox( child: DropdownButtonFormField( diff --git a/lib/pages/space_tree/bloc/space_tree_bloc.dart b/lib/pages/space_tree/bloc/space_tree_bloc.dart index 6315ee0f..ab20b430 100644 --- a/lib/pages/space_tree/bloc/space_tree_bloc.dart +++ b/lib/pages/space_tree/bloc/space_tree_bloc.dart @@ -1,7 +1,10 @@ +import 'dart:async'; + import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart'; +import 'package:syncrow_web/pages/space_tree/model/pagination_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; @@ -18,7 +21,10 @@ class SpaceTreeBloc extends Bloc { on(_clearCachedData); on(_onCommunityAdded); on(_onCommunityUpdate); + on(_fetchPaginationSpaces); + on(_onDebouncedSearch); } + Timer _timer = Timer(const Duration(microseconds: 0), () {}); void _onCommunityUpdate( OnCommunityUpdated event, @@ -37,7 +43,7 @@ class SpaceTreeBloc extends Bloc { updatedCommunities[index] = updatedCommunity; emit(state.copyWith(communitiesList: updatedCommunities)); } else { - emit(SpaceTreeErrorState('Community not found in the list.')); + emit(const SpaceTreeErrorState('Community not found in the list.')); } } catch (e) { emit(SpaceTreeErrorState('Error updating community: $e')); @@ -49,8 +55,8 @@ class SpaceTreeBloc extends Bloc { try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - List communities = - await CommunitySpaceManagementApi().fetchCommunities(projectUuid, includeSpaces: true); + PaginationModel paginationModel = await CommunitySpaceManagementApi() + .fetchCommunitiesAndSpaces(projectId: projectUuid, page: 1); // List updatedCommunities = await Future.wait( // communities.map((community) async { @@ -69,12 +75,38 @@ class SpaceTreeBloc extends Bloc { // }).toList(), // ); - emit(state.copyWith(communitiesList: communities, expandedCommunity: [], expandedSpaces: [])); + emit(state.copyWith( + communitiesList: paginationModel.communities, + expandedCommunity: [], + expandedSpaces: [], + paginationModel: paginationModel)); } catch (e) { emit(SpaceTreeErrorState('Error loading communities and spaces: $e')); } } + _fetchPaginationSpaces(PaginationEvent event, Emitter emit) async { + emit(state.copyWith(paginationIsLoading: true)); + PaginationModel paginationModel = event.paginationModel; + List communities = List.from(event.communities); + try { + if (paginationModel.hasNext && state.searchQuery.isEmpty) { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + + paginationModel = await CommunitySpaceManagementApi().fetchCommunitiesAndSpaces( + projectId: projectUuid, page: paginationModel.pageNum, search: state.searchQuery); + + communities.addAll(paginationModel.communities); + } + } catch (e) { + emit(SpaceTreeErrorState('Error loading communities and spaces: $e')); + } + emit(state.copyWith( + communitiesList: communities, + paginationModel: paginationModel, + paginationIsLoading: false)); + } + void _onCommunityAdded(OnCommunityAdded event, Emitter emit) async { final updatedCommunities = List.from(state.communityList); updatedCommunities.add(event.newCommunity); @@ -264,28 +296,52 @@ class SpaceTreeBloc extends Bloc { _onSearch(SearchQueryEvent event, Emitter emit) async { try { - List communities = List.from(state.communityList); - List filteredCommunity = []; + const duration = Duration(seconds: 1); + if (_timer.isActive) { + _timer.cancel(); // clear timer + } + _timer = Timer(duration, () async => add(DebouncedSearchEvent(event.searchQuery))); + + // List communities = List.from(state.communityList); + // List filteredCommunity = []; // Filter communities and expand only those that match the query - filteredCommunity = communities.where((community) { - final containsQueryInCommunity = - community.name.toLowerCase().contains(event.searchQuery.toLowerCase()); - final containsQueryInSpaces = - community.spaces.any((space) => _containsQuery(space, event.searchQuery.toLowerCase())); + // filteredCommunity = communities.where((community) { + // final containsQueryInCommunity = + // community.name.toLowerCase().contains(event.searchQuery.toLowerCase()); + // final containsQueryInSpaces = + // community.spaces.any((space) => _containsQuery(space, event.searchQuery.toLowerCase())); - return containsQueryInCommunity || containsQueryInSpaces; - }).toList(); + // return containsQueryInCommunity || containsQueryInSpaces; + // }).toList(); - emit(state.copyWith( - filteredCommunity: filteredCommunity, - isSearching: event.searchQuery.isNotEmpty, - searchQuery: event.searchQuery)); + // emit(state.copyWith( + // filteredCommunity: filteredCommunity, + // isSearching: event.searchQuery.isNotEmpty, + // searchQuery: event.searchQuery)); } catch (e) { emit(const SpaceTreeErrorState('Something went wrong')); } } + _onDebouncedSearch(DebouncedSearchEvent event, Emitter emit) async { + emit(state.copyWith( + isSearching: true, + )); + PaginationModel paginationModel = const PaginationModel.emptyConstructor(); + try { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + + paginationModel = await CommunitySpaceManagementApi() + .fetchCommunitiesAndSpaces(projectId: projectUuid, page: 1, search: event.searchQuery); + } catch (_) {} + + emit(state.copyWith( + filteredCommunity: paginationModel.communities, + isSearching: false, + searchQuery: event.searchQuery)); + } + _clearAllData(ClearAllData event, Emitter emit) async { try { emit(state.copyWith( @@ -323,13 +379,13 @@ class SpaceTreeBloc extends Bloc { } // Helper function to determine if any space or its children match the search query - bool _containsQuery(SpaceModel space, String query) { - final matchesSpace = space.name.toLowerCase().contains(query); - final matchesChildren = - space.children.any((child) => _containsQuery(child, query)); // Recursive check for children + // bool _containsQuery(SpaceModel space, String query) { + // final matchesSpace = space.name.toLowerCase().contains(query); + // final matchesChildren = + // space.children.any((child) => _containsQuery(child, query)); // Recursive check for children - return matchesSpace || matchesChildren; - } + // return matchesSpace || matchesChildren; + // } List _getAllChildIds(List spaces) { List ids = []; @@ -403,6 +459,7 @@ class SpaceTreeBloc extends Bloc { @override Future close() async { + _timer.cancel(); super.close(); } } diff --git a/lib/pages/space_tree/bloc/space_tree_event.dart b/lib/pages/space_tree/bloc/space_tree_event.dart index fdf1240b..22254ce0 100644 --- a/lib/pages/space_tree/bloc/space_tree_event.dart +++ b/lib/pages/space_tree/bloc/space_tree_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/space_tree/model/pagination_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; @@ -11,6 +12,16 @@ class SpaceTreeEvent extends Equatable { class InitialEvent extends SpaceTreeEvent {} +class PaginationEvent extends SpaceTreeEvent { + final PaginationModel paginationModel; + final List communities; + + const PaginationEvent(this.paginationModel, this.communities); + + @override + List get props => [paginationModel, communities]; +} + class SearchForSpace extends SpaceTreeEvent { final String searchQuery; @@ -69,6 +80,15 @@ class SearchQueryEvent extends SpaceTreeEvent { List get props => [searchQuery]; } +class DebouncedSearchEvent extends SpaceTreeEvent { + final String searchQuery; + + const DebouncedSearchEvent(this.searchQuery); + + @override + List get props => [searchQuery]; +} + class OnCommunityAdded extends SpaceTreeEvent { final CommunityModel newCommunity; const OnCommunityAdded(this.newCommunity); @@ -85,7 +105,6 @@ class OnCommunityUpdated extends SpaceTreeEvent { List get props => [updatedCommunity]; } - class ClearAllData extends SpaceTreeEvent {} class ClearCachedData extends SpaceTreeEvent {} diff --git a/lib/pages/space_tree/bloc/space_tree_state.dart b/lib/pages/space_tree/bloc/space_tree_state.dart index 915197dc..595380fd 100644 --- a/lib/pages/space_tree/bloc/space_tree_state.dart +++ b/lib/pages/space_tree/bloc/space_tree_state.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/space_tree/model/pagination_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; class SpaceTreeState extends Equatable { @@ -12,6 +13,8 @@ class SpaceTreeState extends Equatable { final List soldCheck; final bool isSearching; final String searchQuery; + final PaginationModel paginationModel; + final bool paginationIsLoading; const SpaceTreeState( {this.communityList = const [], @@ -23,7 +26,9 @@ class SpaceTreeState extends Equatable { this.soldCheck = const [], this.isSearching = false, this.selectedCommunityAndSpaces = const {}, - this.searchQuery = ''}); + this.searchQuery = '', + this.paginationModel = const PaginationModel.emptyConstructor(), + this.paginationIsLoading = false}); SpaceTreeState copyWith( {List? communitiesList, @@ -35,7 +40,9 @@ class SpaceTreeState extends Equatable { List? soldCheck, bool? isSearching, Map>? selectedCommunityAndSpaces, - String? searchQuery}) { + String? searchQuery, + PaginationModel? paginationModel, + bool? paginationIsLoading}) { return SpaceTreeState( communityList: communitiesList ?? this.communityList, filteredCommunity: filteredCommunity ?? this.filteredCommunity, @@ -46,7 +53,9 @@ class SpaceTreeState extends Equatable { soldCheck: soldCheck ?? this.soldCheck, isSearching: isSearching ?? this.isSearching, selectedCommunityAndSpaces: selectedCommunityAndSpaces ?? this.selectedCommunityAndSpaces, - searchQuery: searchQuery ?? this.searchQuery); + searchQuery: searchQuery ?? this.searchQuery, + paginationModel: paginationModel ?? this.paginationModel, + paginationIsLoading: paginationIsLoading ?? this.paginationIsLoading); } @override @@ -60,7 +69,9 @@ class SpaceTreeState extends Equatable { soldCheck, isSearching, selectedCommunityAndSpaces, - searchQuery + searchQuery, + paginationModel, + paginationIsLoading ]; } diff --git a/lib/pages/space_tree/model/pagination_model.dart b/lib/pages/space_tree/model/pagination_model.dart new file mode 100644 index 00000000..c40df1e0 --- /dev/null +++ b/lib/pages/space_tree/model/pagination_model.dart @@ -0,0 +1,20 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; + +class PaginationModel { + final int pageNum; + final bool hasNext; + final int size; + final List communities; + + const PaginationModel( + {required this.pageNum, + required this.hasNext, + required this.size, + required this.communities}); + + const PaginationModel.emptyConstructor() + : pageNum = 1, + hasNext = true, + size = 10, + communities = const []; +} diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index 63926852..5b70da06 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -34,7 +34,8 @@ class _SpaceTreeViewState extends State { @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { - List list = state.isSearching ? state.filteredCommunity : state.communityList; + List list = + state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList; return Container( height: MediaQuery.sizeOf(context).height, decoration: widget.isSide == true @@ -61,7 +62,8 @@ class _SpaceTreeViewState extends State { borderRadius: const BorderRadius.all(Radius.circular(20)), border: Border.all(color: ColorsManager.grayBorder)), child: TextFormField( - style: const TextStyle(color: Colors.black), + style: context.textTheme.bodyMedium + ?.copyWith(color: ColorsManager.blackColor), onChanged: (value) { context.read().add(SearchQueryEvent(value)); }, @@ -94,88 +96,101 @@ class _SpaceTreeViewState extends State { ), const SizedBox(height: 16), Expanded( - child: ListView( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - children: [ - Container( - width: MediaQuery.sizeOf(context).width * 0.5, - padding: const EdgeInsets.all(8.0), - child: list.isEmpty - ? Center( - child: Text( - 'No results found', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.lightGrayColor, - fontWeight: FontWeight.w400, + child: state.isSearching + ? const Center(child: CircularProgressIndicator()) + : ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: [ + Container( + width: MediaQuery.sizeOf(context).width * 0.5, + padding: const EdgeInsets.all(8.0), + child: list.isEmpty + ? Center( + child: Text( + 'No results found', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.lightGrayColor, + fontWeight: FontWeight.w400, + ), ), - ), - ) - : Scrollbar( - scrollbarOrientation: ScrollbarOrientation.left, - thumbVisibility: true, - controller: _scrollController, - child: Padding( - padding: const EdgeInsets.only(left: 16), - child: ListView( - controller: _scrollController, - shrinkWrap: true, - children: list - .map( - (community) => CustomExpansionTileSpaceTree( - title: community.name, - isSelected: state.selectedCommunities - .contains(community.uuid), - isSoldCheck: state.selectedCommunities - .contains(community.uuid), - onExpansionChanged: () { - context - .read() - .add(OnCommunityExpanded(community.uuid)); - }, - isExpanded: state.expandedCommunities - .contains(community.uuid), - onItemSelected: () { - context.read().add( - OnCommunitySelected( - community.uuid, community.spaces)); - widget.onSelect(); - }, - children: community.spaces.map((space) { - return CustomExpansionTileSpaceTree( - title: space.name, - isExpanded: - state.expandedSpaces.contains(space.uuid), - onItemSelected: () { - context.read().add( - OnSpaceSelected(community, space.uuid ?? '', - space.children)); - widget.onSelect(); - }, - onExpansionChanged: () { - context.read().add( - OnSpaceExpanded( - community.uuid, space.uuid ?? '')); - }, - isSelected: - state.selectedSpaces.contains(space.uuid) || - state.soldCheck.contains(space.uuid), - isSoldCheck: state.soldCheck.contains(space.uuid), - children: _buildNestedSpaces( - context, state, space, community), - ); - }).toList(), - ), - ) - .toList(), - ), - ), - ), - ), - ], - ), + ) + : Scrollbar( + scrollbarOrientation: ScrollbarOrientation.left, + thumbVisibility: true, + controller: _scrollController, + child: NotificationListener( + onNotification: (notification) { + if (notification is ScrollEndNotification && + notification.metrics.extentAfter == 0) { + // If the user has reached the end of the list Load more data + context.read().add(PaginationEvent( + state.paginationModel, state.communityList)); + } + return false; + }, + child: Padding( + padding: const EdgeInsets.only(left: 16), + child: ListView.builder( + shrinkWrap: true, + itemCount: list.length, + controller: _scrollController, + itemBuilder: (context, index) { + return CustomExpansionTileSpaceTree( + title: list[index].name, + isSelected: state.selectedCommunities + .contains(list[index].uuid), + isSoldCheck: state.selectedCommunities + .contains(list[index].uuid), + onExpansionChanged: () { + context.read().add( + OnCommunityExpanded(list[index].uuid)); + }, + isExpanded: state.expandedCommunities + .contains(list[index].uuid), + onItemSelected: () { + context.read().add( + OnCommunitySelected(list[index].uuid, + list[index].spaces)); + widget.onSelect(); + }, + children: list[index].spaces.map((space) { + return CustomExpansionTileSpaceTree( + title: space.name, + isExpanded: state.expandedSpaces + .contains(space.uuid), + onItemSelected: () { + context.read().add( + OnSpaceSelected( + list[index], + space.uuid ?? '', + space.children)); + widget.onSelect(); + }, + onExpansionChanged: () { + context.read().add( + OnSpaceExpanded(list[index].uuid, + space.uuid ?? '')); + }, + isSelected: state.selectedSpaces + .contains(space.uuid) || + state.soldCheck.contains(space.uuid), + isSoldCheck: + state.soldCheck.contains(space.uuid), + children: _buildNestedSpaces( + context, state, space, list[index]), + ); + }).toList(), + ); + }), + ), + ), + ), + ), + ], + ), ), - + if (state.paginationIsLoading) const CircularProgressIndicator(), // Expanded( // child: Padding( // padding: const EdgeInsets.all(8.0), diff --git a/lib/pages/spaces_management/all_spaces/model/community_model.dart b/lib/pages/spaces_management/all_spaces/model/community_model.dart index 14d9d729..d1e27095 100644 --- a/lib/pages/spaces_management/all_spaces/model/community_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/community_model.dart @@ -22,11 +22,11 @@ class CommunityModel { factory CommunityModel.fromJson(Map json) { return CommunityModel( - uuid: json['uuid'], - createdAt: DateTime.parse(json['createdAt']), - updatedAt: DateTime.parse(json['updatedAt']), - name: json['name'], - description: json['description'], + uuid: json['uuid'] ?? '', + createdAt: DateTime.parse(json['createdAt'] ?? ''), + updatedAt: DateTime.parse(json['updatedAt'] ?? ''), + name: json['name'] ?? '', + description: json['description'] ?? '', region: json['region'] != null ? RegionModel.fromJson(json['region']) : null, spaces: json['spaces'] != null ? (json['spaces'] as List).map((space) => SpaceModel.fromJson(space)).toList() diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 604abaf4..9fe4a2f6 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -23,9 +23,7 @@ class DevicesManagementApi { : ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId), showServerMessage: true, expectedResponseModel: (json) { - List jsonData = communityId.isNotEmpty && spaceId.isNotEmpty - ? json['data'] - : json; + List jsonData = json['data']; List devicesList = jsonData.map((jsonItem) { return AllDevicesModel.fromJson(jsonItem); }).toList(); @@ -93,8 +91,7 @@ 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, @@ -118,8 +115,7 @@ 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, @@ -153,9 +149,7 @@ 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); @@ -228,8 +222,7 @@ 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), @@ -246,8 +239,7 @@ 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 @@ -270,9 +262,7 @@ 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 @@ -293,8 +283,7 @@ 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 629f775c..604c8cae 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/space_tree/model/pagination_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; @@ -11,8 +12,7 @@ import 'package:syncrow_web/utils/constants/api_const.dart'; class CommunitySpaceManagementApi { // Community Management APIs - Future> fetchCommunities(String projectId, - {int page = 1, bool includeSpaces = false}) async { + Future> fetchCommunities(String projectId, {int page = 1}) async { try { List allCommunities = []; bool hasNext = true; @@ -20,7 +20,9 @@ class CommunitySpaceManagementApi { while (hasNext) { await HTTPService().get( path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId), - queryParameters: {'page': page, 'includeSpaces': includeSpaces}, + queryParameters: { + 'page': page, + }, expectedResponseModel: (json) { try { List jsonData = json['data'] ?? []; @@ -46,6 +48,38 @@ class CommunitySpaceManagementApi { } } + Future fetchCommunitiesAndSpaces( + {required String projectId, int page = 1, String search = ''}) async { + PaginationModel paginationModel = const PaginationModel.emptyConstructor(); + + try { + bool hasNext = false; + await HTTPService().get( + path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId), + queryParameters: {'page': page, 'includeSpaces': true, 'size': 25, 'search': search}, + expectedResponseModel: (json) { + try { + List jsonData = json['data'] ?? []; + hasNext = json['hasNext'] ?? false; + int currentPage = json['page'] ?? 1; + List communityList = jsonData.map((jsonItem) { + return CommunityModel.fromJson(jsonItem); + }).toList(); + + page = currentPage + 1; + paginationModel = PaginationModel( + pageNum: page, hasNext: hasNext, size: 25, communities: communityList); + return paginationModel; + } catch (_) { + hasNext = false; + return []; + } + }, + ); + } catch (_) {} + return paginationModel; + } + Future getCommunityById(String communityId) async { try { final response = await HTTPService().get( From 4fc259491ec675d9072f8fc77638d34d43476e8c Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 6 Apr 2025 09:57:12 +0300 Subject: [PATCH 029/238] add WallPresenceSensor and add new icons for presence state and selected value --- assets/icons/Illuminance_icon.svg | 18 + assets/icons/Indicator_icon.svg | 12 + assets/icons/current_distance_icon.svg | 17 + assets/icons/far_detection_icon.svg | 16 + .../motion_detection_sensitivity_icon.svg | 21 + ...otion_detection_sensitivity_value_icon.svg | 5 + .../motionless_detection_sensitivity_icon.svg | 6 + assets/icons/presence_state.svg | 18 + assets/icons/presence_time_icon.svg | 29 + .../all_devices/models/devices_model.dart | 63 +- .../widgets/count_down_inching_view.dart | 71 +++ .../dialog_helper/device_dialog_helper.dart | 75 ++- .../routines/models/wps/wps_functions.dart | 290 +++++++++ .../models/wps/wps_operational_value.dart | 11 + lib/pages/routines/widgets/if_container.dart | 80 ++- .../routines/widgets/routine_devices.dart | 7 +- .../three_gang_switch_dialog.dart | 47 +- .../wall_sensor/time_wheel.dart | 150 +++++ .../wall_sensor/wall_presence_sensor.dart | 553 ++++++++++++++++++ .../routines/widgets/then_container.dart | 126 ++-- lib/utils/constants/assets.dart | 17 + 21 files changed, 1517 insertions(+), 115 deletions(-) create mode 100644 assets/icons/Illuminance_icon.svg create mode 100644 assets/icons/Indicator_icon.svg create mode 100644 assets/icons/current_distance_icon.svg create mode 100644 assets/icons/far_detection_icon.svg create mode 100644 assets/icons/motion_detection_sensitivity_icon.svg create mode 100644 assets/icons/motion_detection_sensitivity_value_icon.svg create mode 100644 assets/icons/motionless_detection_sensitivity_icon.svg create mode 100644 assets/icons/presence_state.svg create mode 100644 assets/icons/presence_time_icon.svg create mode 100644 lib/pages/routines/models/wps/wps_functions.dart create mode 100644 lib/pages/routines/models/wps/wps_operational_value.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart diff --git a/assets/icons/Illuminance_icon.svg b/assets/icons/Illuminance_icon.svg new file mode 100644 index 00000000..379c45f9 --- /dev/null +++ b/assets/icons/Illuminance_icon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/Indicator_icon.svg b/assets/icons/Indicator_icon.svg new file mode 100644 index 00000000..a542e9fa --- /dev/null +++ b/assets/icons/Indicator_icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/current_distance_icon.svg b/assets/icons/current_distance_icon.svg new file mode 100644 index 00000000..d4c87601 --- /dev/null +++ b/assets/icons/current_distance_icon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/far_detection_icon.svg b/assets/icons/far_detection_icon.svg new file mode 100644 index 00000000..b41b000a --- /dev/null +++ b/assets/icons/far_detection_icon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/motion_detection_sensitivity_icon.svg b/assets/icons/motion_detection_sensitivity_icon.svg new file mode 100644 index 00000000..cd527f13 --- /dev/null +++ b/assets/icons/motion_detection_sensitivity_icon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/motion_detection_sensitivity_value_icon.svg b/assets/icons/motion_detection_sensitivity_value_icon.svg new file mode 100644 index 00000000..21bebd7a --- /dev/null +++ b/assets/icons/motion_detection_sensitivity_value_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/motionless_detection_sensitivity_icon.svg b/assets/icons/motionless_detection_sensitivity_icon.svg new file mode 100644 index 00000000..a1dc2926 --- /dev/null +++ b/assets/icons/motionless_detection_sensitivity_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/presence_state.svg b/assets/icons/presence_state.svg new file mode 100644 index 00000000..0c5ad042 --- /dev/null +++ b/assets/icons/presence_state.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/presence_time_icon.svg b/assets/icons/presence_time_icon.svg new file mode 100644 index 00000000..d1c22e03 --- /dev/null +++ b/assets/icons/presence_time_icon.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index e71d5af8..0a3b346c 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart'; +import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/enum/device_types.dart'; @@ -148,7 +149,9 @@ class AllDevicesModel { productName = json['productName']?.toString(); if (json['spaces'] != null && json['spaces'] is List) { - spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList(); + spaces = (json['spaces'] as List) + .map((space) => DeviceSpaceModel.fromJson(space)) + .toList(); } } @@ -196,7 +199,8 @@ SOS String tempIcon = ''; if (type == DeviceType.LightBulb) { tempIcon = Assets.lightBulb; - } else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) { + } else if (type == DeviceType.CeilingSensor || + type == DeviceType.WallSensor) { tempIcon = Assets.sensors; } else if (type == DeviceType.AC) { tempIcon = Assets.ac; @@ -226,6 +230,8 @@ SOS // tempIcon = Assets.gang3touch; } else if (type == DeviceType.WaterLeak) { tempIcon = Assets.waterLeakNormal; + } else if (type == DeviceType.WaterLeak) { + tempIcon = Assets.waterLeakNormal; } else { tempIcon = Assets.logoHorizontal; } @@ -252,27 +258,62 @@ SOS case '1G': return [ OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''), - OneGangCountdownFunction(deviceId: uuid ?? '', deviceName: name ?? ''), + OneGangCountdownFunction( + deviceId: uuid ?? '', deviceName: name ?? ''), ]; case '2G': return [ TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''), TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''), - TwoGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''), - TwoGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangCountdown1Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangCountdown2Function( + deviceId: uuid ?? '', deviceName: name ?? ''), ]; case '3G': return [ - ThreeGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''), - ThreeGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''), - ThreeGangSwitch3Function(deviceId: uuid ?? '', deviceName: name ?? ''), - ThreeGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''), - ThreeGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''), - ThreeGangCountdown3Function(deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangSwitch1Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangSwitch2Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangSwitch3Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangCountdown1Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangCountdown2Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + ThreeGangCountdown3Function( + deviceId: uuid ?? '', deviceName: name ?? ''), ]; + case 'WPS': + return [ + //IF Functions + PresenceStateFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + CurrentDistanceFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + IlluminanceValueFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + PresenceTimeFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + //THEN Functions + FarDetectionFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + MotionSensitivityFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + MotionLessSensitivityFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + IndicatorFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + NoOneTimeFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + + // FarDetectionSliderFunction( + // deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN') + ]; default: return []; } diff --git a/lib/pages/device_managment/water_heater/widgets/count_down_inching_view.dart b/lib/pages/device_managment/water_heater/widgets/count_down_inching_view.dart index 53892c20..9c28d4d6 100644 --- a/lib/pages/device_managment/water_heater/widgets/count_down_inching_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/count_down_inching_view.dart @@ -95,6 +95,77 @@ class CountdownInchingView extends StatelessWidget { ); } + Row _hourMinutesSecondWheel( + BuildContext context, WaterHeaterDeviceStatusLoaded state) { + final isCountDown = + state.scheduleMode?.name == ScheduleModes.countdown.name; + late bool isActive; + if (isCountDown && + state.countdownRemaining != null && + state.isCountdownActive == true) { + isActive = true; + } else if (!isCountDown && + state.countdownRemaining != null && + state.isInchingActive == true) { + isActive = true; + } else { + isActive = false; + } + + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _buildPickerColumn( + context, + 'h', + isCountDown + ? (state.countdownHours ?? 0) + : (state.inchingHours ?? 0), + 24, (value) { + context.read().add(UpdateScheduleEvent( + scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, + hours: value, + minutes: isCountDown + ? (state.countdownMinutes ?? 0) + : (state.inchingMinutes ?? 0), + )); + }, isActive: isActive), + const SizedBox(width: 10), + _buildPickerColumn( + context, + 'm', + isCountDown + ? (state.countdownMinutes ?? 0) + : (state.inchingMinutes ?? 0), + 60, (value) { + context.read().add(UpdateScheduleEvent( + scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, + hours: isCountDown + ? (state.countdownHours ?? 0) + : (state.inchingHours ?? 0), + minutes: value, + )); + }, isActive: isActive), + const SizedBox(width: 10), + _buildPickerColumn( + context, + 'S', + isCountDown + ? (state.countdownMinutes ?? 0) + : (state.inchingMinutes ?? 0), + 60, (value) { + context.read().add(UpdateScheduleEvent( + scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, + hours: isCountDown + ? (state.countdownHours ?? 0) + : (state.inchingHours ?? 0), + minutes: value, + )); + }, isActive: isActive), + ], + ); + } + Widget _buildPickerColumn( BuildContext context, String label, diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index 933b35d7..48eaedb9 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -6,21 +6,24 @@ import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_swit import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart'; class DeviceDialogHelper { - static Future?> showDeviceDialog( - BuildContext context, - Map data, { + static Future?> showDeviceDialog({ + required BuildContext context, + required String dialogType, + required Map data, required bool removeComparetors, }) async { final functions = data['functions'] as List; try { final result = await _getDialogForDeviceType( - context, - data['productType'], - data, - functions, + dialogType: dialogType, + context: context, + productType: data['productType'], + data: data, + functions: functions, removeComparetors: removeComparetors, ); @@ -34,32 +37,66 @@ class DeviceDialogHelper { return null; } - static Future?> _getDialogForDeviceType(BuildContext context, - String productType, Map data, List functions, - {required bool removeComparetors}) async { + static Future?> _getDialogForDeviceType({ + required String dialogType, + required BuildContext context, + required String productType, + required Map data, + required List functions, + required bool removeComparetors, + }) async { final routineBloc = context.read(); final deviceSelectedFunctions = routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; - if (removeComparetors) { + if (removeComparetors && data['productType'] != 'WPS') { //remove the current temp function in the 'if container' functions.removeAt(3); } switch (productType) { case 'AC': - return ACHelper.showACFunctionsDialog(context, functions, data['device'], - deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors); + return ACHelper.showACFunctionsDialog( + context, + functions, + data['device'], + deviceSelectedFunctions, + data['uniqueCustomId'], + removeComparetors); case '1G': - return OneGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'], - deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors); + return OneGangSwitchHelper.showSwitchFunctionsDialog( + context, + functions, + data['device'], + deviceSelectedFunctions, + data['uniqueCustomId'], + removeComparetors); case '2G': - return TwoGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'], - deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors); + return TwoGangSwitchHelper.showSwitchFunctionsDialog( + context, + functions, + data['device'], + deviceSelectedFunctions, + data['uniqueCustomId'], + removeComparetors); case '3G': - return ThreeGangSwitchHelper.showSwitchFunctionsDialog(context, functions, data['device'], - deviceSelectedFunctions, data['uniqueCustomId'], removeComparetors); + return ThreeGangSwitchHelper.showSwitchFunctionsDialog( + context, + functions, + data['device'], + deviceSelectedFunctions, + data['uniqueCustomId'], + removeComparetors); + case 'WPS': + return WallPresenceSensor.showWPSFunctionsDialog( + dialogType: dialogType, + context: context, + functions: functions, + device: data['device'], + deviceSelectedFunctions: deviceSelectedFunctions, + uniqueCustomId: data['uniqueCustomId'], + removeComparetors: removeComparetors); default: return null; } diff --git a/lib/pages/routines/models/wps/wps_functions.dart b/lib/pages/routines/models/wps/wps_functions.dart new file mode 100644 index 00000000..d090686a --- /dev/null +++ b/lib/pages/routines/models/wps/wps_functions.dart @@ -0,0 +1,290 @@ + + +import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +abstract class WpsFunctions extends DeviceFunction { + final String type; + + WpsFunctions({ + required super.deviceId, + required super.deviceName, + required super.code, + required super.operationName, + required super.icon, + required this.type, + }); + + List getOperationalValues(); +} + +// For far_detection (75-600cm in 75cm steps) +class FarDetectionFunction extends WpsFunctions { + final int min; + final int max; + final int step; + final String unit; + + FarDetectionFunction( + {required super.deviceId, required super.deviceName, required type}) + : min = 75, + max = 600, + step = 75, + unit = 'cm', + super( + type: type, + code: 'far_detection', + operationName: 'Far Detection', + icon: Assets.farDetectionIcon, + ); + + @override + List getOperationalValues() { + final values = []; + for (var value = min; value <= max; value += step) { + values.add(WpsOperationalValue( + icon: Assets.currentDistanceIcon, + description: '$value $unit', + value: value, + )); + } + return values; + } +} + +// For presence_time (0-65535 minutes) +class PresenceTimeFunction extends WpsFunctions { + final int min; + final int max; + final int step; + final String unit; + + PresenceTimeFunction( + {required super.deviceId, required super.deviceName, required type}) + : min = 0, + max = 65535, + step = 1, + unit = 'Min', + super( + type: type, + code: 'presence_time', + operationName: 'Presence Time', + icon: Assets.presenceTimeIcon, + ); + + @override + List getOperationalValues() { + return [ + WpsOperationalValue( + icon: icon, + description: 'Custom $unit', + value: null, + ) + ]; + } +} + +// For motion_sensitivity_value (1-5 levels) +class MotionSensitivityFunction extends WpsFunctions { + final int min; + final int max; + final int step; + + MotionSensitivityFunction( + {required super.deviceId, required super.deviceName, required type}) + : min = 1, + max = 5, + step = 1, + super( + type: type, + code: 'motion_sensitivity_value', + operationName: 'Motion Detection Sensitivity', + icon: Assets.motionDetectionSensitivityIcon, + ); + + @override + List getOperationalValues() { + return List.generate( + (max - min) ~/ step + 1, + (index) => WpsOperationalValue( + icon: Assets.motionDetectionSensitivityValueIcon, + description: '${min + (index * step)}', + value: min + (index * step), + ), + ); + } +} + +class MotionLessSensitivityFunction extends WpsFunctions { + final int min; + final int max; + final int step; + + MotionLessSensitivityFunction( + {required super.deviceId, required super.deviceName, required type}) + : min = 1, + max = 5, + step = 1, + super( + type: type, + code: 'motionless_sensitivity', + operationName: 'Motionless Detection Sensitivity', + icon: Assets.motionlessDetectionSensitivityIcon, + ); + + @override + List getOperationalValues() { + return List.generate( + (max - min) ~/ step + 1, + (index) => WpsOperationalValue( + icon: Assets.currentDistanceIcon, + description: '${min + (index * step)}', + value: min + (index * step), + ), + ); + } +} + +class IndicatorFunction extends WpsFunctions { + IndicatorFunction( + {required super.deviceId, required super.deviceName, required type}) + : super( + type: type, + code: 'indicator', + operationName: 'Indicator', + icon: Assets.IndicatorIcon, + ); + + @override + List getOperationalValues() => [ + WpsOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + WpsOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +class NoOneTimeFunction extends WpsFunctions { + final int min; + final int max; + final String unit; + + NoOneTimeFunction( + {required super.deviceId, required super.deviceName, required type}) + : min = 10, + max = 10000, + unit = '秒', + super( + type: type, + code: 'no_one_time', + operationName: 'Nobody Time', + icon: Assets.nobodyTime, + ); + + @override + List getOperationalValues() { + return [ + WpsOperationalValue( + icon: icon, + description: 'Custom $unit', + value: null, + ) + ]; + } +} + +class PresenceStateFunction extends WpsFunctions { + PresenceStateFunction( + {required super.deviceId, required super.deviceName, required type}) + : super( + type: type, + code: 'presence_state', + operationName: 'Presence State', + icon: Assets.presenceStateIcon, + ); + + @override + List getOperationalValues() => [ + WpsOperationalValue( + icon: Assets.assetsAcPower, + description: "None", + value: true, + ), + WpsOperationalValue( + icon: Assets.presenceStateIcon, + description: "Presence", + value: false, + ), + ]; +} + +class CurrentDistanceFunction extends WpsFunctions { + final int min; + final int max; + final int step; + + CurrentDistanceFunction( + {required super.deviceId, required super.deviceName, required type}) + : min = 1, + max = 600, + step = 1, + super( + type: type, + code: 'current_distance', + operationName: 'Current Distance', + icon: Assets.currentDistanceIcon, + ); + + @override + List getOperationalValues() { + List values = []; + for (int temp = min; temp <= max; temp += step) { + values.add(WpsOperationalValue( + icon: Assets.assetsTempreture, + description: "${temp}CM", + value: temp, + )); + } + return values; + } +} + +class IlluminanceValueFunction extends WpsFunctions { + final int min; + final int max; + final int step; + + IlluminanceValueFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 0, + max = 10000, + step = 10, + super( + code: 'illuminance_value', + operationName: 'Illuminance Value', + icon: Assets.IlluminanceIcon, + ); + + @override + List getOperationalValues() { + List values = []; + for (int lux = min; lux <= max; lux += step) { + values.add(WpsOperationalValue( + icon: Assets.IlluminanceIcon, + description: "$lux Lux", + value: lux, + )); + } + return values; + } +} diff --git a/lib/pages/routines/models/wps/wps_operational_value.dart b/lib/pages/routines/models/wps/wps_operational_value.dart new file mode 100644 index 00000000..28b05d1f --- /dev/null +++ b/lib/pages/routines/models/wps/wps_operational_value.dart @@ -0,0 +1,11 @@ +class WpsOperationalValue { + final String icon; + final String description; + final dynamic value; + + WpsOperationalValue({ + required this.icon, + required this.description, + required this.value, + }); +} diff --git a/lib/pages/routines/widgets/if_container.dart b/lib/pages/routines/widgets/if_container.dart index d1736240..d6078143 100644 --- a/lib/pages/routines/widgets/if_container.dart +++ b/lib/pages/routines/widgets/if_container.dart @@ -26,7 +26,9 @@ class IfContainer extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text('IF', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const Text('IF', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), if (state.isAutomation && state.ifItems.isNotEmpty) AutomationOperatorSelector( selectedOperator: state.selectedAutomationOperator), @@ -53,34 +55,47 @@ class IfContainer extends StatelessWidget { (index) => GestureDetector( onTap: () async { if (!state.isTabToRun) { - final result = await DeviceDialogHelper.showDeviceDialog( - context, state.ifItems[index], - removeComparetors: false); + final result = await DeviceDialogHelper + .showDeviceDialog( + context: context, + data: state.ifItems[index], + removeComparetors: false, + dialogType: "IF"); if (result != null) { - context - .read() - .add(AddToIfContainer(state.ifItems[index], false)); - } else if (!['AC', '1G', '2G', '3G'] - .contains(state.ifItems[index]['productType'])) { - context - .read() - .add(AddToIfContainer(state.ifItems[index], false)); + context.read().add( + AddToIfContainer( + state.ifItems[index], false)); + } else if (![ + 'AC', + '1G', + '2G', + '3G', + 'WPS' + ].contains( + state.ifItems[index]['productType'])) { + context.read().add( + AddToIfContainer( + state.ifItems[index], false)); } } }, child: DraggableCard( - imagePath: state.ifItems[index]['imagePath'] ?? '', + imagePath: + state.ifItems[index]['imagePath'] ?? '', title: state.ifItems[index]['title'] ?? '', deviceData: state.ifItems[index], - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 8), isFromThen: false, isFromIf: true, onRemove: () { - context.read().add(RemoveDragCard( - index: index, - isFromThen: false, - key: state.ifItems[index]['uniqueCustomId'])); + context.read().add( + RemoveDragCard( + index: index, + isFromThen: false, + key: state.ifItems[index] + ['uniqueCustomId'])); }, ), )), @@ -90,6 +105,7 @@ class IfContainer extends StatelessWidget { ); }, onAcceptWithDetails: (data) async { + print('data.data=${data.data}'); final uniqueCustomId = const Uuid().v4(); final mutableData = Map.from(data.data); @@ -101,15 +117,25 @@ class IfContainer extends StatelessWidget { if (!state.isTabToRun) { if (mutableData['deviceId'] == 'tab_to_run') { - context.read().add(AddToIfContainer(mutableData, true)); + context + .read() + .add(AddToIfContainer(mutableData, true)); } else { - final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData, + final result = await DeviceDialogHelper.showDeviceDialog( + dialogType: 'IF', + context: context, + data: mutableData, removeComparetors: false); if (result != null) { - context.read().add(AddToIfContainer(mutableData, false)); - } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) { - context.read().add(AddToIfContainer(mutableData, false)); + context + .read() + .add(AddToIfContainer(mutableData, false)); + } else if (!['AC', '1G', '2G', '3G', 'WPS'] + .contains(mutableData['productType'])) { + context + .read() + .add(AddToIfContainer(mutableData, false)); } } } @@ -155,7 +181,9 @@ class AutomationOperatorSelector extends StatelessWidget { ), ), onPressed: () { - context.read().add(const ChangeAutomationOperator(operator: 'or')); + context + .read() + .add(const ChangeAutomationOperator(operator: 'or')); }, ), Container( @@ -181,7 +209,9 @@ class AutomationOperatorSelector extends StatelessWidget { ), ), onPressed: () { - context.read().add(const ChangeAutomationOperator(operator: 'and')); + context + .read() + .add(const ChangeAutomationOperator(operator: 'and')); }, ), ], diff --git a/lib/pages/routines/widgets/routine_devices.dart b/lib/pages/routines/widgets/routine_devices.dart index 21531ad3..2d3f7236 100644 --- a/lib/pages/routines/widgets/routine_devices.dart +++ b/lib/pages/routines/widgets/routine_devices.dart @@ -37,7 +37,8 @@ class _RoutineDevicesState extends State { device.productType == 'AC' || device.productType == '1G' || device.productType == '2G' || - device.productType == '3G') + device.productType == '3G' || + device.productType == 'WPS') .toList(); return Wrap( @@ -46,7 +47,9 @@ class _RoutineDevicesState extends State { children: deviceList.asMap().entries.map((entry) { final device = entry.value; if (state.searchText != null && state.searchText!.isNotEmpty) { - return device.name!.toLowerCase().contains(state.searchText!.toLowerCase()) + return device.name! + .toLowerCase() + .contains(state.searchText!.toLowerCase()) ? DraggableCard( imagePath: device.getDefaultIcon(device.productType), title: device.name ?? '', diff --git a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart index f565f07d..9a1511ea 100644 --- a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -22,21 +22,25 @@ class ThreeGangSwitchHelper { String uniqueCustomId, bool removeComparetors, ) async { - List switchFunctions = functions.whereType().toList(); + List switchFunctions = + functions.whereType().toList(); return showDialog?>( context: context, builder: (BuildContext context) { + print('functions: $functions'); + return BlocProvider( - create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + create: (_) => FunctionBloc() + ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), child: AlertDialog( contentPadding: EdgeInsets.zero, content: BlocBuilder( builder: (context, state) { final selectedFunction = state.selectedFunction; final selectedOperationName = state.selectedOperationName; - final selectedFunctionData = - state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction, + final selectedFunctionData = state.addedFunctions + .firstWhere((f) => f.functionCode == selectedFunction, orElse: () => DeviceFunctionData( entityId: '', functionCode: selectedFunction ?? '', @@ -83,9 +87,12 @@ class ThreeGangSwitchHelper { color: ColorsManager.textGray, ), onTap: () { - context.read().add(SelectFunction( + context + .read() + .add(SelectFunction( functionCode: function.code, - operationName: function.operationName, + operationName: + function.operationName, )); }, ); @@ -177,7 +184,8 @@ class ThreeGangSwitchHelper { ); } - final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = + switchFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -214,11 +222,11 @@ class ThreeGangSwitchHelper { selectedFunctionData, ), const SizedBox(height: 20), - _buildCountDownDisplay( - context, initialValue, device, operationName, selectedFunctionData, selectCode), + _buildCountDownDisplay(context, initialValue, device, operationName, + selectedFunctionData, selectCode), const SizedBox(height: 20), - _buildCountDownSlider( - context, initialValue, device, operationName, selectedFunctionData, selectCode), + _buildCountDownSlider(context, initialValue, device, operationName, + selectedFunctionData, selectCode), ], ); } @@ -259,7 +267,8 @@ class ThreeGangSwitchHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: + conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -307,7 +316,8 @@ class ThreeGangSwitchHelper { value: (initialValue ?? 0).toDouble(), min: operationalValues.minValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / + divisions: (((operationalValues.maxValue ?? 0) - + (operationalValues.minValue ?? 0)) / (operationalValues.stepValue ?? 1)) .round(), onChanged: (value) { @@ -359,9 +369,13 @@ class ThreeGangSwitchHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, size: 24, - color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, ), onTap: () { if (!isSelected) { @@ -373,7 +387,8 @@ class ThreeGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, + valueDescription: + selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart new file mode 100644 index 00000000..a5b24a22 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart @@ -0,0 +1,150 @@ + +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class TimeWheelPicker extends StatefulWidget { + final int initialHours; + final int initialMinutes; + final int initialSeconds; + final Function(int, int, int) onTimeChanged; + + const TimeWheelPicker({ + super.key, + required this.initialHours, + required this.initialMinutes, + required this.initialSeconds, + required this.onTimeChanged, + }); + + @override + State createState() => _TimeWheelPickerState(); +} + +class _TimeWheelPickerState extends State { + late FixedExtentScrollController _hoursController; + late FixedExtentScrollController _minutesController; + late FixedExtentScrollController _secondsController; + + @override + void initState() { + super.initState(); + _hoursController = FixedExtentScrollController(initialItem: widget.initialHours); + _minutesController = FixedExtentScrollController(initialItem: widget.initialMinutes); + _secondsController = FixedExtentScrollController(initialItem: widget.initialSeconds); + } + + @override + void didUpdateWidget(TimeWheelPicker oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.initialHours != widget.initialHours) { + _hoursController.jumpToItem(widget.initialHours); + } + if (oldWidget.initialMinutes != widget.initialMinutes) { + _minutesController.jumpToItem(widget.initialMinutes); + } + if (oldWidget.initialSeconds != widget.initialSeconds) { + _secondsController.jumpToItem(widget.initialSeconds); + } + } + + @override + void dispose() { + _hoursController.dispose(); + _minutesController.dispose(); + _secondsController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildPickerColumn( + label: 'h', + controller: _hoursController, + itemCount: 24, + onChanged: (value) => _handleTimeChange( + value, + _minutesController.selectedItem, + _secondsController.selectedItem, + ), + ), + const SizedBox(width: 5), + _buildPickerColumn( + label: 'm', + controller: _minutesController, + itemCount: 60, + onChanged: (value) => _handleTimeChange( + _hoursController.selectedItem, + value, + _secondsController.selectedItem, + ), + ), + const SizedBox(width: 5), + _buildPickerColumn( + label: 's', + controller: _secondsController, + itemCount: 60, + onChanged: (value) => _handleTimeChange( + _hoursController.selectedItem, + _minutesController.selectedItem, + value, + ), + ), + ], + ); + } + + void _handleTimeChange(int hours, int minutes, int seconds) { + widget.onTimeChanged(hours, minutes, seconds); + } + + Widget _buildPickerColumn({ + required String label, + required FixedExtentScrollController controller, + required int itemCount, + required Function(int) onChanged, + }) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 40, + width: 40, + padding: const EdgeInsets.symmetric(horizontal: 5), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(8), + ), + child: ListWheelScrollView.useDelegate( + controller: controller, + itemExtent: 40.0, + physics: const FixedExtentScrollPhysics(), + onSelectedItemChanged: onChanged, + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) => Center( + child: Text( + index.toString().padLeft(2), + style: const TextStyle( + fontSize: 18, + color: ColorsManager.blue1, + ), + ), + ), + childCount: itemCount, + ), + ), + ), + const SizedBox(width: 5), + Text( + label, + style: const TextStyle( + color: ColorsManager.blackColor, + fontSize: 18, + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart new file mode 100644 index 00000000..960b22de --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart @@ -0,0 +1,553 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; +import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; + + +class WallPresenceSensor extends StatefulWidget { + final List functions; + final AllDevicesModel? device; + final List? deviceSelectedFunctions; + final String? uniqueCustomId; + final String? dialogType; + final bool removeComparetors; + + const WallPresenceSensor({ + super.key, + required this.functions, + this.device, + this.deviceSelectedFunctions, + this.uniqueCustomId, + this.dialogType, + this.removeComparetors = false, + }); + + static Future?> showWPSFunctionsDialog({ + required BuildContext context, + required List functions, + AllDevicesModel? device, + List? deviceSelectedFunctions, + String? uniqueCustomId, + String? dialogType, + bool removeComparetors = false, + }) async { + return showDialog?>( + context: context, + builder: (context) => WallPresenceSensor( + functions: functions, + device: device, + deviceSelectedFunctions: deviceSelectedFunctions, + uniqueCustomId: uniqueCustomId, + removeComparetors: removeComparetors, + dialogType: dialogType, + ), + ); + } + + @override + State createState() => _WallPresenceSensorState(); +} + +class _WallPresenceSensorState extends State { + late final List _wpsFunctions; + + @override + void initState() { + super.initState(); + _wpsFunctions = + widget.functions.whereType().where((function) { + if (widget.dialogType == 'THEN') { + return function.type == 'THEN' || function.type == 'BOTH'; + } + return function.type == 'IF' || function.type == 'BOTH'; + }).toList(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => FunctionBloc() + ..add(InitializeFunctions(widget.deviceSelectedFunctions ?? [])), + child: _buildDialogContent(), + ); + } + + Widget _buildDialogContent() { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: BlocBuilder( + builder: (context, state) { + final selectedFunction = state.selectedFunction; + return Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('Presence Sensor Condition'), + Expanded(child: _buildMainContent(context, state)), + _buildDialogFooter(context, state), + ], + ), + ); + }, + ), + ); + } + + Widget _buildMainContent(BuildContext context, FunctionBlocState state) { + return Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildFunctionList(context), + if (state.selectedFunction != null) _buildValueSelector(context, state), + ], + ); + } + + Widget _buildFunctionList(BuildContext context) { + return SizedBox( + width: 360, + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: _wpsFunctions.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), + child: Divider(color: ColorsManager.dividerColor), + ), + itemBuilder: (context, index) { + final function = _wpsFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + width: 24, + height: 24, + placeholderBuilder: (context) => const SizedBox( + width: 24, + height: 24, + ), + ), + title: Text( + function.operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: () => context.read().add( + SelectFunction( + functionCode: function.code, + operationName: function.operationName, + ), + ), + ); + }, + ), + ); + } + + Widget _buildValueSelector(BuildContext context, FunctionBlocState state) { + final selectedFunction = state.selectedFunction!; + final functionData = state.addedFunctions.firstWhere( + (f) => f.functionCode == selectedFunction, + orElse: () => DeviceFunctionData( + entityId: '', + functionCode: selectedFunction, + operationName: '', + value: null, + ), + ); + + return Expanded( + child: _ValueSelector( + selectedFunction: selectedFunction, + functionData: functionData, + acFunctions: _wpsFunctions, + device: widget.device, + dialogType: widget.dialogType!, + removeComparators: widget.removeComparetors, + ), + ); + } + + Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) { + return DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: state.addedFunctions.isNotEmpty + ? () { + context.read().add( + AddFunctionToRoutine( + state.addedFunctions, + widget.uniqueCustomId!, + ), + ); + Navigator.pop( + context, + {'deviceId': widget.functions.first.deviceId}, + ); + } + : null, + isConfirmEnabled: state.selectedFunction != null, + ); + } +} + +class _ValueSelector extends StatelessWidget { + final String selectedFunction; + final DeviceFunctionData functionData; + final List acFunctions; + final AllDevicesModel? device; + final String dialogType; + final bool removeComparators; + + const _ValueSelector({ + required this.selectedFunction, + required this.functionData, + required this.acFunctions, + required this.device, + required this.dialogType, + required this.removeComparators, + }); + + @override + Widget build(BuildContext context) { + final selectedFn = + acFunctions.firstWhere((f) => f.code == selectedFunction); + final values = selectedFn.getOperationalValues(); + + if (_isSliderFunction(selectedFunction)) { + return _SliderValueSelector( + selectedFunction: selectedFunction, + functionData: functionData, + device: device, + dialogType: dialogType, + ); + } + + return _OperationalValuesList( + values: values, + selectedValue: functionData.value, + device: device, + operationName: selectedFn.operationName, + selectCode: selectedFunction, + ); + } + + bool _isSliderFunction(String function) => [ + 'current_distance', + 'presence_time', + 'illuminance_value' + ].contains(function); +} + +class _SliderValueSelector extends StatelessWidget { + final String selectedFunction; + final DeviceFunctionData functionData; + final AllDevicesModel? device; + final String dialogType; + + const _SliderValueSelector({ + required this.selectedFunction, + required this.functionData, + required this.device, + required this.dialogType, + }); + + @override + Widget build(BuildContext context) { + final initialValue = functionData.value ?? 250; + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 20), + _ConditionToggle( + currentCondition: functionData.condition, + selectCode: selectedFunction, + device: device, + operationName: functionData.operationName, + selectedValue: functionData.value, + ), + _ValueDisplay( + value: initialValue, + functionCode: selectedFunction, + ), + const SizedBox(height: 20), + _FunctionSlider( + initialValue: initialValue, + functionCode: selectedFunction, + functionData: functionData, + device: device, + ), + ], + ); + } +} + +class _ConditionToggle extends StatelessWidget { + final String? currentCondition; + final String selectCode; + final AllDevicesModel? device; + final String operationName; + final dynamic selectedValue; + + const _ConditionToggle({ + this.currentCondition, + required this.selectCode, + this.device, + required this.operationName, + this.selectedValue, + }); + + @override + Widget build(BuildContext context) { + const conditions = ["<", "==", ">"]; + return ToggleButtons( + onPressed: (index) => _updateCondition(context, conditions[index]), + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: + conditions.map((c) => c == (currentCondition ?? "==")).toList(), + children: conditions.map((c) => Text(c)).toList(), + ); + } + + void _updateCondition(BuildContext context, String condition) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + condition: condition, + value: selectedValue, + ), + ), + ); + } +} + +class _ValueDisplay extends StatelessWidget { + final dynamic value; + final String functionCode; + + const _ValueDisplay({ + required this.value, + required this.functionCode, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + _getDisplayText(), + style: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ); + } + + String _getDisplayText() { + final intValue = (value as num?)?.toInt() ?? 0; + switch (functionCode) { + case 'presence_time': + return '$intValue Min'; + case 'current_distance': + return '$intValue CM'; + case 'illuminance_value': + return '$intValue Lux'; + default: + return '$intValue'; + } + } +} + +class _FunctionSlider extends StatelessWidget { + final dynamic initialValue; + final String functionCode; + final DeviceFunctionData functionData; + final AllDevicesModel? device; + + const _FunctionSlider({ + required this.initialValue, + required this.functionCode, + required this.functionData, + required this.device, + }); + + @override + Widget build(BuildContext context) { + final (min, max) = _getSliderRange(); + return Slider( + value: initialValue is int ? initialValue.toDouble() : min, + min: min, + max: max, + divisions: (max - min).toInt(), + onChanged: (value) => _updateValue(context, value.toInt()), + ); + } + + (double, double) _getSliderRange() { + switch (functionCode) { + case 'presence_time': + return (0, 65535); + case 'current_distance': + return (1, 600); + case 'illuminance_value': + return (0, 10000); + default: + return (200, 300); + } + } + + void _updateValue(BuildContext context, int value) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: functionCode, + operationName: functionData.operationName, + value: value, + condition: functionData.condition, + ), + ), + ); + } +} + +class _OperationalValuesList extends StatelessWidget { + final List values; + final dynamic selectedValue; + final AllDevicesModel? device; + final String operationName; + final String selectCode; + + const _OperationalValuesList({ + required this.values, + required this.selectedValue, + required this.device, + required this.operationName, + required this.selectCode, + }); + + @override + Widget build(BuildContext context) { + return operationName == 'Nobody Time' + ? _buildTimeWheel(context) + : ListView.builder( + padding: const EdgeInsets.all(20), + itemCount: values.length, + itemBuilder: (context, index) => + _buildValueItem(context, values[index]), + ); + } + + Widget _buildTimeWheel(BuildContext context) { + final currentTotalSeconds = selectedValue as int? ?? 0; + return TimeWheelPicker( + initialHours: currentTotalSeconds ~/ 3600, + initialMinutes: (currentTotalSeconds % 3600) ~/ 60, + initialSeconds: currentTotalSeconds % 60, + onTimeChanged: (h, m, s) => _updateTotalSeconds(context, h, m, s), + ); + } + + Widget _buildValueItem(BuildContext context, WpsOperationalValue value) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildValueIcon(context, value), + Expanded(child: _buildValueDescription(value)), + _buildValueRadio(context, value), + ], + ), + ); + } + + Widget _buildValueIcon(context, WpsOperationalValue value) { + return Column( + children: [ + if (_shouldShowTextDescription) + Text(value.description.replaceAll("cm", '')), + SvgPicture.asset(value.icon, width: 25, height: 25), + ], + ); + } + + bool get _shouldShowTextDescription => + operationName == 'Far Detection' || + operationName == 'Motionless Detection Sensitivity'; + + Widget _buildValueDescription(WpsOperationalValue value) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text(value.description), + ); + } + + Widget _buildValueRadio(context, WpsOperationalValue value) { + return Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (_) => _selectValue(context, value.value), + ); + } + + void _selectValue(BuildContext context, dynamic value) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value, + ), + ), + ); + } + + void _updateTotalSeconds(BuildContext context, int h, int m, int s) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: h * 3600 + m * 60 + s, + ), + ), + ); + } +} diff --git a/lib/pages/routines/widgets/then_container.dart b/lib/pages/routines/widgets/then_container.dart index 2fcc62b8..5b26056a 100644 --- a/lib/pages/routines/widgets/then_container.dart +++ b/lib/pages/routines/widgets/then_container.dart @@ -26,7 +26,9 @@ class ThenContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('THEN', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const Text('THEN', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), state.isLoading && state.isUpdate == true ? const Center( @@ -39,12 +41,17 @@ class ThenContainer extends StatelessWidget { state.thenItems.length, (index) => GestureDetector( onTap: () async { - if (state.thenItems[index]['deviceId'] == 'delay') { - final result = await DelayHelper.showDelayPickerDialog( - context, state.thenItems[index]); + if (state.thenItems[index] + ['deviceId'] == + 'delay') { + final result = await DelayHelper + .showDelayPickerDialog(context, + state.thenItems[index]); if (result != null) { - context.read().add(AddToThenContainer({ + context + .read() + .add(AddToThenContainer({ ...state.thenItems[index], 'imagePath': Assets.delay, 'title': 'Delay', @@ -53,58 +60,86 @@ class ThenContainer extends StatelessWidget { return; } - if (state.thenItems[index]['type'] == 'automation') { + if (state.thenItems[index]['type'] == + 'automation') { final result = await showDialog( context: context, - builder: (BuildContext context) => AutomationDialog( + builder: (BuildContext context) => + AutomationDialog( automationName: - state.thenItems[index]['name'] ?? 'Automation', + state.thenItems[index] + ['name'] ?? + 'Automation', automationId: - state.thenItems[index]['deviceId'] ?? '', - uniqueCustomId: state.thenItems[index] - ['uniqueCustomId'], + state.thenItems[index] + ['deviceId'] ?? + '', + uniqueCustomId: + state.thenItems[index] + ['uniqueCustomId'], ), ); if (result != null) { - context.read().add(AddToThenContainer({ + context + .read() + .add(AddToThenContainer({ ...state.thenItems[index], - 'imagePath': Assets.automation, - 'title': state.thenItems[index]['name'] ?? - state.thenItems[index]['title'], + 'imagePath': + Assets.automation, + 'title': + state.thenItems[index] + ['name'] ?? + state.thenItems[index] + ['title'], })); } return; } - final result = await DeviceDialogHelper.showDeviceDialog( - context, state.thenItems[index], - removeComparetors: true); + final result = await DeviceDialogHelper + .showDeviceDialog( + context: context, + data: state.thenItems[index], + removeComparetors: true, + dialogType: "THEN"); if (result != null) { - context - .read() - .add(AddToThenContainer(state.thenItems[index])); - } else if (!['AC', '1G', '2G', '3G'] - .contains(state.thenItems[index]['productType'])) { - context - .read() - .add(AddToThenContainer(state.thenItems[index])); + context.read().add( + AddToThenContainer( + state.thenItems[index])); + } else if (![ + 'AC', + '1G', + '2G', + '3G', + 'WPS' + ].contains(state.thenItems[index] + ['productType'])) { + context.read().add( + AddToThenContainer( + state.thenItems[index])); } }, child: DraggableCard( - imagePath: state.thenItems[index]['imagePath'] ?? '', - title: state.thenItems[index]['title'] ?? '', + imagePath: state.thenItems[index] + ['imagePath'] ?? + '', + title: state.thenItems[index] + ['title'] ?? + '', deviceData: state.thenItems[index], - padding: - const EdgeInsets.symmetric(horizontal: 4, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 8), isFromThen: true, isFromIf: false, onRemove: () { - context.read().add(RemoveDragCard( - index: index, - isFromThen: true, - key: state.thenItems[index]['uniqueCustomId'])); + context.read().add( + RemoveDragCard( + index: index, + isFromThen: true, + key: state.thenItems[index] + ['uniqueCustomId'])); }, ), ))), @@ -114,6 +149,8 @@ class ThenContainer extends StatelessWidget { ); }, onAcceptWithDetails: (data) async { + print('data.data THEN=${data.data}'); + final uniqueCustomId = const Uuid().v4(); final mutableData = Map.from(data.data); mutableData['uniqueCustomId'] = uniqueCustomId; @@ -129,8 +166,8 @@ class ThenContainer extends StatelessWidget { } if (mutableData['type'] == 'automation') { - int index = - state.thenItems.indexWhere((item) => item['deviceId'] == mutableData['deviceId']); + int index = state.thenItems.indexWhere( + (item) => item['deviceId'] == mutableData['deviceId']); if (index != -1) { return; } @@ -155,8 +192,8 @@ class ThenContainer extends StatelessWidget { } if (mutableData['type'] == 'tap_to_run' && state.isAutomation) { - int index = - state.thenItems.indexWhere((item) => item['deviceId'] == mutableData['deviceId']); + int index = state.thenItems.indexWhere( + (item) => item['deviceId'] == mutableData['deviceId']); if (index != -1) { return; } @@ -174,7 +211,8 @@ class ThenContainer extends StatelessWidget { } if (mutableData['deviceId'] == 'delay') { - final result = await DelayHelper.showDelayPickerDialog(context, mutableData); + final result = + await DelayHelper.showDelayPickerDialog(context, mutableData); if (result != null) { context.read().add(AddToThenContainer({ @@ -186,11 +224,15 @@ class ThenContainer extends StatelessWidget { return; } - final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData, - removeComparetors: true); + final result = await DeviceDialogHelper.showDeviceDialog( + context: context, + data: mutableData, + removeComparetors: true, + dialogType: "THEN"); if (result != null) { context.read().add(AddToThenContainer(mutableData)); - } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) { + } else if (!['AC', '1G', '2G', '3G', 'WPS'] + .contains(mutableData['productType'])) { context.read().add(AddToThenContainer(mutableData)); } }, diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 0e7b0bd2..01359f57 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -411,4 +411,21 @@ class Assets { static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.svg'; static const String scenesPlayIconCheck = 'assets/icons/scenesPlayIconCheck.svg'; + static const String presenceStateIcon = 'assets/icons/presence_state.svg'; + static const String currentDistanceIcon = + 'assets/icons/current_distance_icon.svg'; + + static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg'; + static const String motionDetectionSensitivityIcon = + 'assets/icons/motion_detection_sensitivity_icon.svg'; + + static const String motionlessDetectionSensitivityIcon = + 'assets/icons/motionless_detection_sensitivity_icon.svg'; + + static const String IndicatorIcon = 'assets/icons/Indicator_icon.svg'; + static const String motionDetectionSensitivityValueIcon = 'assets/icons/motion_detection_sensitivity_value_icon.svg'; + static const String presenceTimeIcon = 'assets/icons/presence_time_icon.svg'; + static const String IlluminanceIcon = 'assets/icons/Illuminance_icon.svg'; + + //Illuminance_icon } From 49c743befb86f3ac896bc172e2b547a2c04d68cd Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 6 Apr 2025 10:04:36 +0300 Subject: [PATCH 030/238] remove debug print statements --- .../widgets/routine_dialogs/three_gang_switch_dialog.dart | 2 -- lib/pages/routines/widgets/then_container.dart | 2 -- 2 files changed, 4 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart index 9a1511ea..b4400b35 100644 --- a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -28,8 +28,6 @@ class ThreeGangSwitchHelper { return showDialog?>( context: context, builder: (BuildContext context) { - print('functions: $functions'); - return BlocProvider( create: (_) => FunctionBloc() ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), diff --git a/lib/pages/routines/widgets/then_container.dart b/lib/pages/routines/widgets/then_container.dart index 5b26056a..a4d0461d 100644 --- a/lib/pages/routines/widgets/then_container.dart +++ b/lib/pages/routines/widgets/then_container.dart @@ -149,8 +149,6 @@ class ThenContainer extends StatelessWidget { ); }, onAcceptWithDetails: (data) async { - print('data.data THEN=${data.data}'); - final uniqueCustomId = const Uuid().v4(); final mutableData = Map.from(data.data); mutableData['uniqueCustomId'] = uniqueCustomId; From ad922577dae8120c15802553800009dd014ee7e2 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 6 Apr 2025 11:28:59 +0300 Subject: [PATCH 031/238] Refactor SVG icons and update asset paths and fix RoutinesView issues --- assets/icons/scenesPlayIcon.png | Bin 0 -> 1703 bytes assets/icons/scenesPlayIcon.svg | 17 ---------- assets/icons/scenesPlayIconCheck.png | Bin 0 -> 1802 bytes assets/icons/scenesPlayIconCheck.svg | 30 ------------------ lib/pages/routines/view/routines_view.dart | 1 + .../fetch_routine_scenes_automation.dart | 6 ++-- .../main_routine_view/routine_view_card.dart | 6 ++-- lib/utils/constants/assets.dart | 4 +-- 8 files changed, 9 insertions(+), 55 deletions(-) create mode 100644 assets/icons/scenesPlayIcon.png delete mode 100644 assets/icons/scenesPlayIcon.svg create mode 100644 assets/icons/scenesPlayIconCheck.png delete mode 100644 assets/icons/scenesPlayIconCheck.svg diff --git a/assets/icons/scenesPlayIcon.png b/assets/icons/scenesPlayIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..923ad7d0bd74775326fde2d23b0fb6259c5901af GIT binary patch literal 1703 zcmV;Y23YxtP)9UV zJs=h#!37RT2*i#6l|@MGt5|G<_v!Z!Gsqzr8`tEQo@YGs=6~P&_x~|=KH`*XadEMV z2JTha+1Wg|D|v@tR=G93ReM?^$i0Y%{6 zfT5!~7-|Ke%*QWre!xg`N=iz&#>PhV*4CDAczCGY-`~Fk67XJQx7!JzN~6)xTo1=_ zE)1tirE(k`9N1!GV{HKHnwXey(|eJSaX|=;z{o05gQ?DFG)Ck8_UqTLzpkjL_;q}I zymoDEZF+Zi*KDy^L>fCgJLbj3#i7yB(c1d@`a*zA2JqYT?X_#y^!Q$od7hgj8+q{H zfriu&78Vwlk&*FDe}8}F-rnB60>Izc+}vF4>+7q^&(D8FJT8OM($WMkI}1oMoV*Jy zb{3`8YU6u)dj5hW0tRv#u+GlT_uyqBp0rWf5&VN9F!Ay6T3JO&B>rH5oE&3gV>M9Y zmjsG7EN}cHP~1r{t*x#6^z?Lu*=&xUnwt7ME-vm{7D8HCS!qj7PW~OXwE^caLnGt_ zqVoROz(`fy-QB9WxjDVV;kW|H6oncL5gg>Zfq{Yd022xO)V+A|g7>y3wS48cBVVN0 zVeIJW`0?JodpYca=4c%TUC@4sJ^ytw$ zcEJR-y-QC|zlGZ{n98w~BtRq`1TMkpbQ*GVbH9g+US}6fYPH%>T3Y%GZuL;3pgfo2 z0n&jApw6w%SI+v=et6zmuI&cuy z*4D;WS65GcPg2Ut%1U*)Tx7da2#{1ZJ3GrG?`VPeD;6SJSXhvNz>^rH?{nVc$B)z9 zZZ{=sl8{JV#vyC-h&uu@cLECmA)m4J^>wDx>Dc}I_Zec2&$-BPX)qfBNm`2BBq0$9 z$(DF{naS6OP^nMRp9F)X%LG9n&pi_i5#HYb7y;?!2oPLO2}&x`sjm;0mzUY_@Gyfn zGJjyehb1K?G2|}hk7!M#6BY8VgoA!aN>pUUBH#F2jU+TUILKgt?CRC4EFmGmCm2#w zAYc?S7z`=6EMb@>mBDsAKu{*(o@uQUNE#K)^@}AMQBTu zREFwKME-KwY_>Cvgm#~rn#!m;L^&dP5*aV_FM$bEP2L1&nxP64yxm1bMG_zpr74w{ zE-)YT$ACbkq@+mK?d@&0v9ZChp`gGJVPpRiAbjhlm?*^|)IV;h!hwpgD-b3G2DwPG zCq!Avo`L~GG&3{P&-1(;v5JHulDjhmB{vqaH8nMjo@GZTK|Oi$grOw$>)O5u4Z~Va zPEH5ZZG-I*pd$$(mw8a1*xTFN*HE0*o`)&fPOvmTKi>-5wLonS#H^zh$Ll@yF~&D< z-k9p@>N+7tBfCH%>6Dd~{ajU5HE1%K%qZHV%JH~jgmJrQ9tx&qv^XEw1=G;bP(^up z`BZp#_+e&drd#RNyd^~UYD7#uL>MzNGV&*k{Regd1Xa}`rv0JU>o;D$d}(cHXz(a? z^(RF>N)Q3=Z$N&DL0uj4A|J7<{o%uhzriav5QEHkdYh zR)6oWm8JJor)aRVFa%8LNB0ZG^a>Hy*40E=4SF=iU5%lBhqV$9ScWBwuXogKN#fqNI? x+y9ozr>MkZo}BlPPcoDvKF-@l0zP(;=|9Vj#95-|^6&rv002ovPDHLkV1oZxBV_;p literal 0 HcmV?d00001 diff --git a/assets/icons/scenesPlayIcon.svg b/assets/icons/scenesPlayIcon.svg deleted file mode 100644 index 6417b0e6..00000000 --- a/assets/icons/scenesPlayIcon.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/assets/icons/scenesPlayIconCheck.png b/assets/icons/scenesPlayIconCheck.png new file mode 100644 index 0000000000000000000000000000000000000000..6f7101f790c2a114da9f38f184a23e63afb31b17 GIT binary patch literal 1802 zcmV+l2le=gP)FolJMh2Zzb z(9qCLkWic(vGjBgmf8zY*5g-LKVYPLZr!@YK6>;>Jv}|mFD@=>=jZ1)KzLl&I2;ZF zsM2UObg#g&tQ*UvQmLE^3k&vb+qT&O)ZNw9<)P~$A!CCu8iA2rqJ~nv$z+Pa`Oed) zPbU`@6@A{>+4*36e7tvdcGhCGT18qjGc%Uq;o;Wy_Vx!A6%}~^nE>EB>DkSjHyiLg zFY63gq!`)1f4_#@pwsE1)6>)6Z)$2Po|~JS_X6;@rlzLG8XFr+u3o)*igav*Bqb&B zK5?eVGOWA{Ep`^J)oNp3zI^!uETI_4YQSo1Yi~lAnRL=dhF5cyG8MCvqFCs;^FzYO8wc2?5 z_U+GbYJeMg@BdOgKs!*O^lJn`Fz(v5D?^Dw6veJ*&z}8kv)Lw@ASFINKI`n+vsL~<{ZmS<^Z4=OUrKQ68N;PN^c?@8URa0ng6q_4Vq&5VEZ{FZKbDe`Qix3b2*1ab=s|(p3NSp{%Mu`@oVO^c$fryI zf?yP35uvWG&I3JmfV1EQuS-fw{)mi>%xr6G``w=odMDb63Vm0?K?I790TUF&4`VbM zDN~l8g&Rfr$inJW-$W(4&>2`P2?teb5wRnn`3Z=2YoJzYX=$M{Mx2R9pV9+N=#3jU z-pk3!`35nS=QX)h zDHDKzXZ=ZlU|A2;I z5uhatp_d8ZCl0(|9OUeQ5@j(lF-Mb=le3}Bzyv9SgM(FwT`R2Z1lw2|aeS`R9Ai3j z=8U<#yu21>G%@QaWSzpo!W`7B7PHxG0cn$%<1%AJaJ%U~5T;icaqcneEUm4rMR)Js z?Fk79Sno<}hj6{|F9>M-Gvkn4Rl_MFxL+zP7dGe&~;lqc5msh_< z@-M3e1$ZhL=CdwFBc=zR}$3JCNaTfFb#}9!68E8e=h8Dh#RpDe>%JsV$1AwY$6f-mzoHiopn8 zhlhvTjvP7S!uJAUC7@MB?uleQ?`ac9830u*`^uy*MV&qA{8Nqc0vfZlQbSuO6D!#^%+8 zW0VQ)NXW~}^I)e=Si%8N3n&i@c&QZ`W1$6JJ`ZAMp%EGPFJks`pjQ#maihNdZ>zkG s>L0$v`Y!oPhW8U6>l-7AKfB2CHz0EJyP5|2asU7T07*qoM6N<$g8AoEVgLXD literal 0 HcmV?d00001 diff --git a/assets/icons/scenesPlayIconCheck.svg b/assets/icons/scenesPlayIconCheck.svg deleted file mode 100644 index 9e81869a..00000000 --- a/assets/icons/scenesPlayIconCheck.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index 83ab82b9..c398243c 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -90,6 +90,7 @@ class _RoutinesViewState extends State { ], ), ), + const SizedBox(height: 50), ], ), ) 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 483500b1..99c131ee 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 @@ -117,7 +117,7 @@ class _FetchRoutineScenesState extends State fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 5), + const SizedBox(height: 3), if (state.automations.isEmpty) Expanded( child: Text( @@ -130,8 +130,8 @@ class _FetchRoutineScenesState extends State if (state.automations.isNotEmpty) ConstrainedBox( constraints: BoxConstraints( - maxHeight: isSmallScreenSize(context) ? 185 : 192, - maxWidth: MediaQuery.sizeOf(context).width * 0.7), + maxHeight: isSmallScreenSize(context) ? 190 : 195, + ), child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: state.automations.length, 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 345a86c8..5f471973 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 @@ -106,10 +106,10 @@ class _RoutineViewCardState extends State { if (widget.isFromScenes ?? false) InkWell( onTap: _handleSceneTap, - child: SvgPicture.asset( + child: Image.asset( _showTemporaryCheck - ? Assets.scenesPlayIconCheck - : Assets.scenesPlayIcon, + ? Assets.scenesPlayIcon + : Assets.scenesPlayIconCheck, fit: BoxFit.contain, ), ) diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 0e7b0bd2..dc4036ad 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -408,7 +408,7 @@ class Assets { static const String spaceLinkIcon = 'assets/icons/space_link_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 scenesPlayIcon = 'assets/icons/scenesPlayIcon.png'; static const String scenesPlayIconCheck = - 'assets/icons/scenesPlayIconCheck.svg'; + 'assets/icons/scenesPlayIconCheck.png'; } From 8dc4081a892bf6732df82d8f69afa2800f647dcb Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 6 Apr 2025 16:38:34 +0300 Subject: [PATCH 032/238] save presence state and edit selected value --- .../all_devices/models/devices_model.dart | 16 ++++++++-------- lib/pages/routines/models/wps/wps_functions.dart | 10 ++++------ .../wall_sensor/wall_presence_sensor.dart | 6 +++--- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index 0a3b346c..2663d931 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -291,25 +291,25 @@ SOS return [ //IF Functions PresenceStateFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), CurrentDistanceFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), IlluminanceValueFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), PresenceTimeFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), //THEN Functions FarDetectionFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), MotionSensitivityFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), MotionLessSensitivityFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), IndicatorFunction( deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), NoOneTimeFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), // FarDetectionSliderFunction( // deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN') diff --git a/lib/pages/routines/models/wps/wps_functions.dart b/lib/pages/routines/models/wps/wps_functions.dart index d090686a..4f48300e 100644 --- a/lib/pages/routines/models/wps/wps_functions.dart +++ b/lib/pages/routines/models/wps/wps_functions.dart @@ -1,5 +1,3 @@ - - import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart'; @@ -195,7 +193,7 @@ class NoOneTimeFunction extends WpsFunctions { WpsOperationalValue( icon: icon, description: 'Custom $unit', - value: null, + value: null, ) ]; } @@ -216,12 +214,12 @@ class PresenceStateFunction extends WpsFunctions { WpsOperationalValue( icon: Assets.assetsAcPower, description: "None", - value: true, + value: 'none', ), WpsOperationalValue( icon: Assets.presenceStateIcon, description: "Presence", - value: false, + value: 'presence', ), ]; } @@ -238,7 +236,7 @@ class CurrentDistanceFunction extends WpsFunctions { step = 1, super( type: type, - code: 'current_distance', + code: 'dis_current', operationName: 'Current Distance', icon: Assets.currentDistanceIcon, ); diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart index 960b22de..ee91b321 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart @@ -252,7 +252,7 @@ class _ValueSelector extends StatelessWidget { } bool _isSliderFunction(String function) => [ - 'current_distance', + 'dis_current', 'presence_time', 'illuminance_value' ].contains(function); @@ -382,7 +382,7 @@ class _ValueDisplay extends StatelessWidget { switch (functionCode) { case 'presence_time': return '$intValue Min'; - case 'current_distance': + case 'dis_current': return '$intValue CM'; case 'illuminance_value': return '$intValue Lux'; @@ -421,7 +421,7 @@ class _FunctionSlider extends StatelessWidget { switch (functionCode) { case 'presence_time': return (0, 65535); - case 'current_distance': + case 'dis_current': return (1, 600); case 'illuminance_value': return (0, 10000); From ca44f3bf55234adb0f0ed0b0acdf01961286972b Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 7 Apr 2025 12:53:12 +0300 Subject: [PATCH 033/238] fix routine popup --- .../create_routine_bloc.dart | 20 +- .../create_routine_event.dart | 8 + .../create_routine_state.dart | 7 + .../create_new_routines/commu_dropdown.dart | 224 +++++++-------- .../create_new_routines.dart | 265 ++++++++++-------- lib/pages/routines/widgets/if_container.dart | 2 - 6 files changed, 290 insertions(+), 236 deletions(-) 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 index 5a8e5590..b472d034 100644 --- a/lib/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart +++ b/lib/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart @@ -2,6 +2,7 @@ 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/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; @@ -10,11 +11,12 @@ class CreateRoutineBloc extends Bloc { on(_fetchSpaceOnlyWithDevices); on(saveSpaceIdCommunityId); on(resetSelected); + on(_fetchCommunity); } String selectedSpaceId = ''; String selectedCommunityId = ''; - + List communities = []; List spacesOnlyWithDevices = []; Future _fetchSpaceOnlyWithDevices( @@ -30,7 +32,7 @@ class CreateRoutineBloc extends Bloc { emit(SpaceWithDeviceLoadedState(spacesOnlyWithDevices)); } catch (e) { - emit(SpaceTreeErrorState('Error loading communities and spaces: $e')); + emit(SpaceTreeErrorState('Error loading spaces: $e')); } } @@ -48,4 +50,18 @@ class CreateRoutineBloc extends Bloc { selectedCommunityId = ''; emit(const ResetSelectedState()); } + + Future _fetchCommunity( + FetchCommunityEvent event, Emitter emit) async { + emit(const CommunitiesLoadingState()); + + try { + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + communities = + await CommunitySpaceManagementApi().fetchCommunities(projectUuid); + emit(const CommunityLoadedState()); + } catch (e) { + emit(SpaceTreeErrorState('Error loading communities $e')); + } + } } 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 index 24e620c0..ba901497 100644 --- a/lib/pages/routines/bloc/create_routine_bloc/create_routine_event.dart +++ b/lib/pages/routines/bloc/create_routine_bloc/create_routine_event.dart @@ -41,3 +41,11 @@ class ResetSelectedEvent extends CreateRoutineEvent { @override List get props => []; } + + +class FetchCommunityEvent extends CreateRoutineEvent { + const FetchCommunityEvent(); + + @override + List get props => []; +} \ No newline at end of file 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 index 4911304b..5ebc20f7 100644 --- a/lib/pages/routines/bloc/create_routine_bloc/create_routine_state.dart +++ b/lib/pages/routines/bloc/create_routine_bloc/create_routine_state.dart @@ -44,3 +44,10 @@ class ResetSelectedState extends CreateRoutineState { const ResetSelectedState(); } +class CommunityLoadedState extends CreateRoutineState { + const CommunityLoadedState(); +} + +class CommunitiesLoadingState extends CreateRoutineState { + const CommunitiesLoadingState(); +} \ No newline at end of file diff --git a/lib/pages/routines/create_new_routines/commu_dropdown.dart b/lib/pages/routines/create_new_routines/commu_dropdown.dart index 32ee7219..5b96e977 100644 --- a/lib/pages/routines/create_new_routines/commu_dropdown.dart +++ b/lib/pages/routines/create_new_routines/commu_dropdown.dart @@ -1,12 +1,11 @@ import 'package:dropdown_button2/dropdown_button2.dart'; 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'; class CommunityDropdown extends StatelessWidget { final String? selectedValue; + final List communities; final Function(String?) onChanged; final TextEditingController _searchController = TextEditingController(); @@ -14,6 +13,7 @@ class CommunityDropdown extends StatelessWidget { Key? key, required this.selectedValue, required this.onChanged, + required this.communities, }) : super(key: key); @override @@ -32,123 +32,123 @@ class CommunityDropdown extends StatelessWidget { ), ), const SizedBox(height: 8), - BlocBuilder( - builder: (context, state) { - return SizedBox( - child: Container( + SizedBox( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + ), + child: DropdownButton2( + underline: const SizedBox(), + value: selectedValue, + items: communities.map((community) { + return DropdownMenuItem( + value: community.uuid, + child: Text( + ' ${community.name}', + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ); + }).toList(), + onChanged: onChanged, + style: const TextStyle(color: Colors.black), + hint: Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + " Please Select", + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.textGray, + ), + ), + ), + customButton: Container( + height: 45, + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.textGray, width: 1.0), + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 5, + child: Text( + selectedValue != null + ? " ${communities.firstWhere((element) => element.uuid == selectedValue).name}" + : ' Please Select', + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: selectedValue != null + ? Colors.black + : ColorsManager.textGray, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Expanded( + child: Container( + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: const BorderRadius.only( + topRight: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + ), + height: 45, + child: const Icon( + Icons.keyboard_arrow_down, + color: ColorsManager.textGray, + ), + ), + ), + ], + ), + ), + dropdownStyleData: DropdownStyleData( + maxHeight: MediaQuery.of(context).size.height * 0.4, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), ), - child: DropdownButton2( - underline: SizedBox(), - value: selectedValue, - items: state.communityList.map((community) { - return DropdownMenuItem( - value: community.uuid, - child: Text( - ' ${community.name}', - overflow: TextOverflow.ellipsis, - maxLines: 1, + ), + dropdownSearchData: DropdownSearchData( + searchController: _searchController, + searchInnerWidgetHeight: 50, + searchInnerWidget: Container( + height: 50, + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: TextFormField( + style: const TextStyle(color: Colors.black), + controller: _searchController, + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 12, ), - ); - }).toList(), - onChanged: onChanged, - style: TextStyle(color: Colors.black), - hint: Padding( - padding: EdgeInsets.only(left: 10), - child: Text( - " Please Select", - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.textGray, - ), - ), - ), - customButton: Container( - height: 45, - decoration: BoxDecoration( - border: Border.all(color: ColorsManager.textGray, width: 1.0), - borderRadius: BorderRadius.circular(10), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - flex: 5, - child: Text( - selectedValue != null - ? " ${state.communityList.firstWhere((element) => element.uuid == selectedValue).name}" - : ' Please Select', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: - selectedValue != null ? Colors.black : ColorsManager.textGray, - ), - overflow: TextOverflow.ellipsis, - ), - ), - Expanded( - child: Container( - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: const BorderRadius.only( - topRight: Radius.circular(10), - bottomRight: Radius.circular(10), - ), - ), - height: 45, - child: const Icon( - Icons.keyboard_arrow_down, - color: ColorsManager.textGray, - ), - ), - ), - ], - ), - ), - dropdownStyleData: DropdownStyleData( - maxHeight: MediaQuery.of(context).size.height * 0.4, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - ), - ), - dropdownSearchData: DropdownSearchData( - searchController: _searchController, - searchInnerWidgetHeight: 50, - searchInnerWidget: Container( - height: 50, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: TextFormField( - style: const TextStyle(color: Colors.black), - controller: _searchController, - decoration: InputDecoration( - isDense: true, - contentPadding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 12, - ), - hintText: 'Search for community...', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - ), + hintText: 'Search for community...', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), ), ), - searchMatchFn: (item, searchValue) { - final communityName = (item.child as Text).data?.toLowerCase() ?? ''; - return communityName.contains(searchValue.toLowerCase().trim()); - }, - ), - onMenuStateChange: (isOpen) { - if (!isOpen) { - _searchController.clear(); - } - }, - menuItemStyleData: const MenuItemStyleData( - height: 40, ), ), - )); - }, - ), + searchMatchFn: (item, searchValue) { + final communityName = + (item.child as Text).data?.toLowerCase() ?? ''; + return communityName + .contains(searchValue.toLowerCase().trim()); + }, + ), + onMenuStateChange: (isOpen) { + if (!isOpen) { + _searchController.clear(); + } + }, + menuItemStyleData: const MenuItemStyleData( + height: 40, + ), + ), + )) ], ), ); diff --git a/lib/pages/routines/create_new_routines/create_new_routines.dart b/lib/pages/routines/create_new_routines/create_new_routines.dart index baf10748..4900af9b 100644 --- a/lib/pages/routines/create_new_routines/create_new_routines.dart +++ b/lib/pages/routines/create_new_routines/create_new_routines.dart @@ -5,11 +5,10 @@ import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routi 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); + const CreateNewRoutinesDialog({super.key}); @override State createState() => @@ -19,136 +18,162 @@ class CreateNewRoutinesDialog extends StatefulWidget { 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; + return BlocProvider( + create: (BuildContext context) => + CreateRoutineBloc()..add(const FetchCommunityEvent()), + child: BlocBuilder( + builder: (context, state) { + final _bloc = BlocProvider.of(context); + final spaces = _bloc.spacesOnlyWithDevices; + final isLoadingCommunities = state is CommunitiesLoadingState; + final isLoadingSpaces = state is SpaceWithDeviceLoadingState; + String spaceHint = 'Select a community first'; + if (_selectedCommunity != null) { + if (isLoadingSpaces) { + spaceHint = 'Loading spaces...'; + } else if (spaces.isEmpty) { + spaceHint = 'No spaces available'; + } else { + spaceHint = 'Select Space'; + } + } - 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(), - Padding( - padding: const EdgeInsets.only(left: 15, right: 15), - child: CommunityDropdown( - selectedValue: _selectedCommunity, - onChanged: (String? newValue) { - setState(() { - _selectedCommunity = newValue; - _selectedSpace = null; - }); - if (newValue != null) { - _fetchSpaces(newValue); - } - }, - ), + 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, + ), ), - const SizedBox(height: 5), - Padding( - padding: const EdgeInsets.only(left: 15, right: 15), - child: SpaceDropdown( - hintMessage: spaceHint, - spaces: spaces, - selectedValue: _selectedSpace, - onChanged: (String? newValue) { - setState(() { - _selectedSpace = newValue; - }); - }, - ), - ), - const Divider(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + content: Stack( 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, - ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Divider(), + Padding( + padding: const EdgeInsets.only(left: 15, right: 15), + child: CommunityDropdown( + communities: _bloc.communities, + selectedValue: _selectedCommunity, + onChanged: (String? newValue) { + setState(() { + _selectedCommunity = newValue; + _selectedSpace = null; + }); + if (newValue != null) { + _bloc.add(SpaceOnlyWithDevicesEvent(newValue)); + } + }, + ), ), - ), - ), - 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 && + const SizedBox(height: 5), + Padding( + padding: const EdgeInsets.only(left: 15, right: 15), + child: 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 - ? ColorsManager.blueColor - : Colors.blue.shade100, + ? () { + 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, + ), + ), ), + ), + ], + ), + const SizedBox(height: 10), + ], + ), + if (isLoadingCommunities) + const SizedBox( + height: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Center( + child: Center( + child: CircularProgressIndicator( + color: ColorsManager.primaryColor, + ), + ), + ), + ], ), ), - ), ], ), - SizedBox(height: 10), - ], - ), - ); - }, - ); + ); + }, + )); } } diff --git a/lib/pages/routines/widgets/if_container.dart b/lib/pages/routines/widgets/if_container.dart index d6078143..eebf3fb7 100644 --- a/lib/pages/routines/widgets/if_container.dart +++ b/lib/pages/routines/widgets/if_container.dart @@ -105,9 +105,7 @@ class IfContainer extends StatelessWidget { ); }, onAcceptWithDetails: (data) async { - print('data.data=${data.data}'); final uniqueCustomId = const Uuid().v4(); - final mutableData = Map.from(data.data); mutableData['uniqueCustomId'] = uniqueCustomId; From d264409d29ad786a0062d466ec255b138dbc057d Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 7 Apr 2025 14:27:36 +0300 Subject: [PATCH 034/238] - Refactor the WpsFunctions class in wps_functions.dart to use 'cm' instead of 'temp' for the description of operational values. - Update the WallPresenceSensor class in wall_presence_sensor.dart to use the selected operation name --- lib/pages/routines/models/wps/wps_functions.dart | 7 ++++--- .../wall_sensor/wall_presence_sensor.dart | 10 +++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/pages/routines/models/wps/wps_functions.dart b/lib/pages/routines/models/wps/wps_functions.dart index 4f48300e..8907927c 100644 --- a/lib/pages/routines/models/wps/wps_functions.dart +++ b/lib/pages/routines/models/wps/wps_functions.dart @@ -244,11 +244,12 @@ class CurrentDistanceFunction extends WpsFunctions { @override List getOperationalValues() { List values = []; - for (int temp = min; temp <= max; temp += step) { + for (int cm = min; cm <= max; cm += step) { values.add(WpsOperationalValue( icon: Assets.assetsTempreture, - description: "${temp}CM", - value: temp, + description: "${cm}CM", + + value: cm, )); } return values; diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart index ee91b321..e065080f 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart @@ -13,7 +13,6 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; - class WallPresenceSensor extends StatefulWidget { final List functions; final AllDevicesModel? device; @@ -171,7 +170,7 @@ class _WallPresenceSensorState extends State { orElse: () => DeviceFunctionData( entityId: '', functionCode: selectedFunction, - operationName: '', + operationName: state.selectedOperationName!, value: null, ), ); @@ -251,11 +250,8 @@ class _ValueSelector extends StatelessWidget { ); } - bool _isSliderFunction(String function) => [ - 'dis_current', - 'presence_time', - 'illuminance_value' - ].contains(function); + bool _isSliderFunction(String function) => + ['dis_current', 'presence_time', 'illuminance_value'].contains(function); } class _SliderValueSelector extends StatelessWidget { From 6bd9fb7e4e28f6a0ff697cec20d5751bd4094f10 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 7 Apr 2025 16:22:21 +0300 Subject: [PATCH 035/238] Refactor routine_view_card.dart to adjust the size o --- .../fetch_routine_scenes_automation.dart | 312 ++++++++++-------- .../main_routine_view/routine_view_card.dart | 2 + 2 files changed, 168 insertions(+), 146 deletions(-) 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 99c131ee..0a22208c 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 @@ -31,165 +31,185 @@ class _FetchRoutineScenesState extends State ? const Center( child: CircularProgressIndicator(), ) - : Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Scenes (Tab to Run)", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - if (state.scenes.isEmpty) - Expanded( - child: Text( + : SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Scenes (Tab to Run)", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + if (state.scenes.isEmpty) + Text( "No scenes found", style: context.textTheme.bodyMedium?.copyWith( color: ColorsManager.grayColor, ), ), - ), - if (state.scenes.isNotEmpty) - ConstrainedBox( - constraints: BoxConstraints( - 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) { - final scene = state.scenes[index]; - final isLoading = - state.loadingSceneId == scene.id; + if (state.scenes.isNotEmpty) + SizedBox( + height: 200, + child: ListView.builder( + shrinkWrap: 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), - ); - 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, - ), - ); - }), + return Padding( + padding: EdgeInsets.only( + right: + isSmallScreenSize(context) ? 4.0 : 8.0, + ), + child: Column( + children: [ + 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), + ); + 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: 10), + Text( + "Automations", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), ), - const SizedBox(height: 10), - Text( - "Automations", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 3), - if (state.automations.isEmpty) - Expanded( - child: Text( + const SizedBox(height: 3), + if (state.automations.isEmpty) + Text( "No automations found", style: context.textTheme.bodyMedium?.copyWith( color: ColorsManager.grayColor, ), ), - ), - if (state.automations.isNotEmpty) - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: isSmallScreenSize(context) ? 190 : 195, - ), - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: state.automations.length, - itemBuilder: (context, index) { - final isLoading = state.automations! - .contains(state.automations[index].id); + if (state.automations.isNotEmpty) + SizedBox( + height: 200, - 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), - ); - 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, - ), - ); - }), - ), - ], + child: ListView.builder( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: state.automations.length, + itemBuilder: (context, index) { + final isLoading = state.automations! + .contains(state.automations[index].id); + + return Column( + children: [ + Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) + ? 4.0 + : 8.0, + ), + child: RoutineViewCard( + isLoading: isLoading, + onChanged: (v) { + 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), + ); + 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 5f471973..841ffa6e 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 @@ -177,6 +177,8 @@ class _RoutineViewCardState extends State { : (widget.icon is String && widget.icon.endsWith('.svg')) ? SvgPicture.asset( + height: iconSize, + width: iconSize, widget.icon, fit: BoxFit.contain, ) From a56f4e488e8da0039e0359c9d853de5b7fccdb0e Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 7 Apr 2025 16:42:44 +0300 Subject: [PATCH 036/238] Refactor routine_view_card.dart to adjust the size of the CircularProgressIndicator Fix routine popup Update wall_presence_sensor.dart to handle null selectedOperationName --- .../routines/create_new_routines/create_new_routines.dart | 6 ++---- .../routine_dialogs/wall_sensor/wall_presence_sensor.dart | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/pages/routines/create_new_routines/create_new_routines.dart b/lib/pages/routines/create_new_routines/create_new_routines.dart index 4900af9b..0542f888 100644 --- a/lib/pages/routines/create_new_routines/create_new_routines.dart +++ b/lib/pages/routines/create_new_routines/create_new_routines.dart @@ -161,10 +161,8 @@ class _CreateNewRoutinesDialogState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ Center( - child: Center( - child: CircularProgressIndicator( - color: ColorsManager.primaryColor, - ), + child: CircularProgressIndicator( + color: ColorsManager.primaryColor, ), ), ], diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart index e065080f..b7733511 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart @@ -170,7 +170,7 @@ class _WallPresenceSensorState extends State { orElse: () => DeviceFunctionData( entityId: '', functionCode: selectedFunction, - operationName: state.selectedOperationName!, + operationName: state.selectedOperationName ?? '', value: null, ), ); From 7accf1d4c85770cb152eaaca114c4566330c900c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 13:07:53 +0300 Subject: [PATCH 037/238] SP-1366- Add Gateway Device Card to Devices Section. --- lib/pages/routines/widgets/routine_devices.dart | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/pages/routines/widgets/routine_devices.dart b/lib/pages/routines/widgets/routine_devices.dart index 2d3f7236..ba5756e0 100644 --- a/lib/pages/routines/widgets/routine_devices.dart +++ b/lib/pages/routines/widgets/routine_devices.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.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/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart'; @@ -32,14 +31,10 @@ class _RoutineDevicesState extends State { } }); - List deviceList = state.devices - .where((device) => - device.productType == 'AC' || - device.productType == '1G' || - device.productType == '2G' || - device.productType == '3G' || - device.productType == 'WPS') - .toList(); + final deviceList = state.devices.where((device) { + const allowedProductTypes = {'AC', '1G', '2G', '3G', 'WPS', 'GW'}; + return allowedProductTypes.contains(device.productType); + }).toList(); return Wrap( spacing: 10, @@ -63,7 +58,7 @@ class _RoutineDevicesState extends State { 'uniqueCustomId': '', }, ) - : Container(); + : const SizedBox.shrink(); } else { return DraggableCard( imagePath: device.getDefaultIcon(device.productType), From 46f318734ad7f137ff5a5e67efb5839184f510f3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 15:06:11 +0300 Subject: [PATCH 038/238] Add SVG icons for active bell and gear --- assets/icons/active_bell.svg | 21 +++++++++++++++++++++ assets/icons/gear.svg | 3 +++ lib/utils/constants/assets.dart | 3 +++ 3 files changed, 27 insertions(+) create mode 100644 assets/icons/active_bell.svg create mode 100644 assets/icons/gear.svg diff --git a/assets/icons/active_bell.svg b/assets/icons/active_bell.svg new file mode 100644 index 00000000..3887ead5 --- /dev/null +++ b/assets/icons/active_bell.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/gear.svg b/assets/icons/gear.svg new file mode 100644 index 00000000..02dbab04 --- /dev/null +++ b/assets/icons/gear.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index e81512ff..d9788ee8 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -426,5 +426,8 @@ class Assets { static const String motionDetectionSensitivityValueIcon = 'assets/icons/motion_detection_sensitivity_value_icon.svg'; static const String presenceTimeIcon = 'assets/icons/presence_time_icon.svg'; static const String IlluminanceIcon = 'assets/icons/Illuminance_icon.svg'; + static const String gear = 'assets/icons/gear.svg'; + static const String activeBell='assets/icons/active_bell.svg'; + } From 9d4395e204b3a1b43d0f46676ae85c44ef78fb39 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 15:08:01 +0300 Subject: [PATCH 039/238] Added gateway product type to `IfContainer`. --- lib/pages/routines/widgets/if_container.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pages/routines/widgets/if_container.dart b/lib/pages/routines/widgets/if_container.dart index eebf3fb7..b65e99c7 100644 --- a/lib/pages/routines/widgets/if_container.dart +++ b/lib/pages/routines/widgets/if_container.dart @@ -72,6 +72,7 @@ class IfContainer extends StatelessWidget { '2G', '3G', 'WPS' + 'GW', ].contains( state.ifItems[index]['productType'])) { context.read().add( @@ -129,7 +130,7 @@ class IfContainer extends StatelessWidget { context .read() .add(AddToIfContainer(mutableData, false)); - } else if (!['AC', '1G', '2G', '3G', 'WPS'] + } else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW'] .contains(mutableData['productType'])) { context .read() From 8a244dcd239ac31763b8265b54812227341ff79f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 15:08:15 +0300 Subject: [PATCH 040/238] Created `GatewayModel`. --- .../gateway/model/gateway_model.dart | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 lib/pages/device_managment/gateway/model/gateway_model.dart diff --git a/lib/pages/device_managment/gateway/model/gateway_model.dart b/lib/pages/device_managment/gateway/model/gateway_model.dart new file mode 100644 index 00000000..8e8d00f9 --- /dev/null +++ b/lib/pages/device_managment/gateway/model/gateway_model.dart @@ -0,0 +1,40 @@ +class GatewayModel { + final String uuid; + final bool switchAlarmSound; + final String masterState; + final bool factoryReset; + final String alarmActive; + + GatewayModel({ + required this.uuid, + required this.switchAlarmSound, + required this.masterState, + required this.factoryReset, + required this.alarmActive, + }); + + factory GatewayModel.fromJson(Map json) { + final status = json['status'] as List; + + final switchAlarmSound = status.firstWhere( + (item) => item['code'] == 'switch_alarm_sound', + )['value'] as bool; + final masterState = status.firstWhere( + (item) => item['code'] == 'master_state', + )['value'] as String; + final factoryReset = status.firstWhere( + (item) => item['code'] == 'factory_reset', + )['value'] as bool; + final alarmActive = status.firstWhere( + (item) => item['code'] == 'alarm_active', + )['value'] as String; + + return GatewayModel( + uuid: json['uuid'] as String, + switchAlarmSound: switchAlarmSound, + masterState: masterState, + factoryReset: factoryReset, + alarmActive: alarmActive, + ); + } +} From b0846b2fefc8ed6546e3b45864446fcaa4d3aca1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 15:08:33 +0300 Subject: [PATCH 041/238] Add Gateway operational value classes and implementations --- lib/pages/routines/models/gateway.dart | 108 +++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 lib/pages/routines/models/gateway.dart diff --git a/lib/pages/routines/models/gateway.dart b/lib/pages/routines/models/gateway.dart new file mode 100644 index 00000000..be257a8b --- /dev/null +++ b/lib/pages/routines/models/gateway.dart @@ -0,0 +1,108 @@ +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class GatewayOperationalValue { + final String icon; + final String description; + final dynamic value; + + GatewayOperationalValue({ + required this.icon, + required this.description, + required this.value, + }); +} + +abstract class GatewayFunctions extends DeviceFunction { + final String type; + + GatewayFunctions({ + required super.deviceId, + required super.deviceName, + required super.code, + required super.operationName, + required super.icon, + required this.type, + }); + + List getOperationalValues(); +} + +final class GatewaySwitchAlarmSound extends GatewayFunctions { + GatewaySwitchAlarmSound({ + required super.deviceId, + required super.deviceName, + required super.type, + super.code = '', + super.operationName = 'Switch Alarm Sound', + super.icon = Assets.activeBell, + }); + + @override + List getOperationalValues() => [ + GatewayOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + GatewayOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +final class GatewayMasterState extends GatewayFunctions { + GatewayMasterState({ + required super.deviceId, + required super.deviceName, + required super.type, + super.code = '', + super.operationName = 'Master State', + super.icon = Assets.gear, + }); + + @override + List getOperationalValues() { + return [ + GatewayOperationalValue( + icon: Assets.assetsAcPower, + description: "Normal", + value: true, + ), + GatewayOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "Alarm", + value: false, + ), + ]; + } +} + +final class GatewayFactoryReset extends GatewayFunctions { + GatewayFactoryReset({ + required super.deviceId, + required super.deviceName, + required super.type, + super.code = '', + super.operationName = 'Factory Reset', + super.icon = Assets.factoryReset, + }); + + @override + List getOperationalValues() { + return [ + GatewayOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + GatewayOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; + } +} From b6752683fdc29c531921748be319b6d1d03b1574 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 15:08:53 +0300 Subject: [PATCH 042/238] Add Gateway dialog and functions integration --- .../all_devices/models/devices_model.dart | 19 +++ .../dialog_helper/device_dialog_helper.dart | 25 ++- .../gateway/gateway_dialog.dart | 29 ++++ .../gateway/gateway_if_dialog.dart | 144 ++++++++++++++++++ 4 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index 2663d931..d6999a98 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart'; +import 'package:syncrow_web/pages/routines/models/gateway.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/enum/device_types.dart'; @@ -314,6 +315,24 @@ SOS // FarDetectionSliderFunction( // deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN') ]; + case 'GW': + return [ + GatewaySwitchAlarmSound( + deviceId: uuid ?? '', + deviceName: name ?? '', + type: 'IF', + ), + GatewayMasterState( + deviceId: uuid ?? '', + deviceName: name ?? '', + type: 'IF', + ), + GatewayFactoryReset( + deviceId: uuid ?? '', + deviceName: name ?? '', + type: 'IF', + ), + ]; default: return []; } diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index 48eaedb9..a94b6740 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ac_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart'; -import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart'; class DeviceDialogHelper { @@ -57,12 +58,13 @@ class DeviceDialogHelper { switch (productType) { case 'AC': return ACHelper.showACFunctionsDialog( - context, - functions, - data['device'], - deviceSelectedFunctions, - data['uniqueCustomId'], - removeComparetors); + context, + functions, + data['device'], + deviceSelectedFunctions, + data['uniqueCustomId'], + removeComparetors, + ); case '1G': return OneGangSwitchHelper.showSwitchFunctionsDialog( @@ -97,6 +99,15 @@ class DeviceDialogHelper { deviceSelectedFunctions: deviceSelectedFunctions, uniqueCustomId: data['uniqueCustomId'], removeComparetors: removeComparetors); + case 'GW': + return GatewayHelper.showGatewayFunctionsDialog( + dialogType: dialogType, + context: context, + functions: functions, + uniqueCustomId: data['uniqueCustomId'], + deviceSelectedFunctions: deviceSelectedFunctions, + ); + default: return null; } diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart new file mode 100644 index 00000000..fe7379fc --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart'; + +final class GatewayHelper { + static Future?> showGatewayFunctionsDialog({ + required String dialogType, + required BuildContext context, + required List functions, + required String? uniqueCustomId, + required List deviceSelectedFunctions, + }) async { + return showDialog( + context: context, + builder: (context) => BlocProvider( + create: (context) => FunctionBloc() + ..add( + InitializeFunctions(deviceSelectedFunctions), + ), + child: GatewayIfDialog( + uniqueCustomId: uniqueCustomId, + functions: functions, + deviceSelectedFunctions: deviceSelectedFunctions), + ), + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart new file mode 100644 index 00000000..ecaf5ba9 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/gateway.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class GatewayIfDialog extends StatefulWidget { + const GatewayIfDialog({ + required this.uniqueCustomId, + required this.functions, + required this.deviceSelectedFunctions, + super.key, + }); + + final String? uniqueCustomId; + final List functions; + final List deviceSelectedFunctions; + + @override + State createState() => _GatewayIfDialogState(); +} + +class _GatewayIfDialogState extends State { + late final List _gatewayFunctions; + + @override + void initState() { + super.initState(); + _gatewayFunctions = widget.functions + .whereType() + .where((function) => function.type == 'IF' || function.type == 'BOTH') + .toList(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: BlocBuilder( + builder: (context, state) { + final selectedFunction = state.selectedFunction; + return Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('Gateway Conditions'), + Expanded(child: _buildMainContent(context, state)), + _buildDialogFooter(context, state), + ], + ), + ); + }, + ), + ); + } + + Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) { + return DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: state.addedFunctions.isNotEmpty + ? () { + context.read().add( + AddFunctionToRoutine( + state.addedFunctions, + widget.uniqueCustomId ?? '-1', + ), + ); + Navigator.pop( + context, + {'deviceId': widget.functions.firstOrNull?.deviceId}, + ); + } + : null, + isConfirmEnabled: state.selectedFunction != null, + ); + } + + Widget _buildMainContent(BuildContext context, FunctionBlocState state) { + return Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildFunctionList(context), + // if (state.selectedFunction != null) _buildValueSelector(context, state), + ], + ); + } + + Widget _buildFunctionList(BuildContext context) { + return SizedBox( + width: 360, + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: _gatewayFunctions.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), + child: Divider(color: ColorsManager.dividerColor), + ), + itemBuilder: (context, index) { + final function = _gatewayFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + width: 24, + height: 24, + placeholderBuilder: (context) => const SizedBox( + width: 24, + height: 24, + ), + ), + title: Text( + function.operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: () => context.read().add( + SelectFunction( + functionCode: function.code, + operationName: function.operationName, + ), + ), + ); + }, + ), + ); + } +} From 006bd4c8aebea218f71a47b762b342c276e9edb7 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 16:20:22 +0300 Subject: [PATCH 043/238] =?UTF-8?q?SP-1364/=20Gateway=20Conditions=20Popup?= =?UTF-8?q?=20(for=20=E2=80=9CIf=E2=80=9D=20section)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../all_devices/models/devices_model.dart | 6 +- lib/pages/routines/models/gateway.dart | 10 +- .../gateway/gateway_if_dialog.dart | 111 +++++++++++++++++- 3 files changed, 113 insertions(+), 14 deletions(-) diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index d6999a98..a71de8ce 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -320,17 +320,17 @@ SOS GatewaySwitchAlarmSound( deviceId: uuid ?? '', deviceName: name ?? '', - type: 'IF', + type: 'BOTH', ), GatewayMasterState( deviceId: uuid ?? '', deviceName: name ?? '', - type: 'IF', + type: 'BOTH', ), GatewayFactoryReset( deviceId: uuid ?? '', deviceName: name ?? '', - type: 'IF', + type: 'BOTH', ), ]; default: diff --git a/lib/pages/routines/models/gateway.dart b/lib/pages/routines/models/gateway.dart index be257a8b..daeebd53 100644 --- a/lib/pages/routines/models/gateway.dart +++ b/lib/pages/routines/models/gateway.dart @@ -33,7 +33,7 @@ final class GatewaySwitchAlarmSound extends GatewayFunctions { required super.deviceId, required super.deviceName, required super.type, - super.code = '', + super.code = 'switch_alarm_sound', super.operationName = 'Switch Alarm Sound', super.icon = Assets.activeBell, }); @@ -58,7 +58,7 @@ final class GatewayMasterState extends GatewayFunctions { required super.deviceId, required super.deviceName, required super.type, - super.code = '', + super.code = 'master_state', super.operationName = 'Master State', super.icon = Assets.gear, }); @@ -69,12 +69,12 @@ final class GatewayMasterState extends GatewayFunctions { GatewayOperationalValue( icon: Assets.assetsAcPower, description: "Normal", - value: true, + value: 'Normal', ), GatewayOperationalValue( icon: Assets.assetsAcPowerOFF, description: "Alarm", - value: false, + value: 'Alarm', ), ]; } @@ -85,7 +85,7 @@ final class GatewayFactoryReset extends GatewayFunctions { required super.deviceId, required super.deviceName, required super.type, - super.code = '', + super.code = 'factory_reset', super.operationName = 'Factory Reset', super.icon = Assets.factoryReset, }); diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart index ecaf5ba9..457110b7 100644 --- a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; @@ -32,10 +33,7 @@ class _GatewayIfDialogState extends State { @override void initState() { super.initState(); - _gatewayFunctions = widget.functions - .whereType() - .where((function) => function.type == 'IF' || function.type == 'BOTH') - .toList(); + _gatewayFunctions = widget.functions.whereType().toList(); } @override @@ -89,11 +87,32 @@ class _GatewayIfDialogState extends State { } Widget _buildMainContent(BuildContext context, FunctionBlocState state) { + final selectedFunction = state.selectedFunction; + final selectedOperationName = state.selectedOperationName; + final selectedFunctionData = state.addedFunctions.firstWhere( + (f) => f.functionCode == selectedFunction, + orElse: () => DeviceFunctionData( + entityId: '', + functionCode: selectedFunction ?? '', + operationName: '', + value: null, + ), + ); + return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildFunctionList(context), - // if (state.selectedFunction != null) _buildValueSelector(context, state), + if (state.selectedFunction != null) + Expanded( + child: _buildValueSelector( + context: context, + selectedFunction: selectedFunction ?? '', + selectedFunctionData: selectedFunctionData, + acFunctions: _gatewayFunctions, + operationName: selectedOperationName ?? '', + ), + ), ], ); } @@ -103,7 +122,6 @@ class _GatewayIfDialogState extends State { width: 360, child: ListView.separated( shrinkWrap: false, - physics: const AlwaysScrollableScrollPhysics(), itemCount: _gatewayFunctions.length, separatorBuilder: (context, index) => const Padding( padding: EdgeInsets.symmetric(horizontal: 40.0), @@ -141,4 +159,85 @@ class _GatewayIfDialogState extends State { ), ); } + + static Widget _buildValueSelector({ + required BuildContext context, + required String selectedFunction, + required DeviceFunctionData? selectedFunctionData, + required List acFunctions, + AllDevicesModel? device, + required String operationName, + }) { + final selectedGatewayFunctions = acFunctions.firstWhere( + (f) => f.code == selectedFunction, + ); + final values = selectedGatewayFunctions.getOperationalValues(); + + return _buildOperationalValuesList( + context: context, + values: values, + selectedValue: selectedFunctionData?.value, + device: device, + operationName: operationName, + selectCode: selectedFunction, + selectedFunctionData: selectedFunctionData, + ); + } + + static Widget _buildOperationalValuesList({ + required BuildContext context, + required List values, + required dynamic selectedValue, + AllDevicesModel? device, + required String operationName, + required String selectCode, + DeviceFunctionData? selectedFunctionData, + }) { + return ListView.builder( + itemCount: values.length, + itemBuilder: (context, index) { + final value = values[index]; + final isSelected = selectedValue == value.value; + return ListTile( + leading: SvgPicture.asset( + value.icon, + width: 24, + height: 24, + placeholderBuilder: (context) => Container( + width: 24, + height: 24, + color: Colors.transparent, + ), + ), + title: Text( + value.description, + style: context.textTheme.bodyMedium, + ), + trailing: Icon( + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + size: 24, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, + ), + onTap: () { + if (!isSelected) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value.value, + condition: selectedFunctionData?.condition, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + } + }, + ); + }, + ); + } } From 970f7ed16f18356b5b95ededf039f0bd69d03103 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 16:41:34 +0300 Subject: [PATCH 044/238] SP-1365 --- .../helper/dialog_helper/device_dialog_helper.dart | 9 ++++----- .../widgets/routine_dialogs/gateway/gateway_dialog.dart | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index a94b6740..1fac903e 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -50,10 +50,10 @@ class DeviceDialogHelper { final deviceSelectedFunctions = routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; - if (removeComparetors && data['productType'] != 'WPS') { - //remove the current temp function in the 'if container' - functions.removeAt(3); - } + // if (removeComparetors && data['productType'] != 'WPS') { + // //remove the current temp function in the 'if container' + // functions.removeAt(3); + // } switch (productType) { case 'AC': @@ -101,7 +101,6 @@ class DeviceDialogHelper { removeComparetors: removeComparetors); case 'GW': return GatewayHelper.showGatewayFunctionsDialog( - dialogType: dialogType, context: context, functions: functions, uniqueCustomId: data['uniqueCustomId'], diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart index fe7379fc..b9638cd9 100644 --- a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart @@ -6,7 +6,6 @@ import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gatew final class GatewayHelper { static Future?> showGatewayFunctionsDialog({ - required String dialogType, required BuildContext context, required List functions, required String? uniqueCustomId, From 3a0c8edf8601e0a79c0ff90d7827d8e6bb9938fc Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 8 Apr 2025 16:42:31 +0300 Subject: [PATCH 045/238] =?UTF-8?q?SP-1365/=20Gateway=20Functions=20Popup?= =?UTF-8?q?=20(for=20=E2=80=9CThen=E2=80=9D=20section).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/routines/widgets/then_container.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pages/routines/widgets/then_container.dart b/lib/pages/routines/widgets/then_container.dart index a4d0461d..e0828721 100644 --- a/lib/pages/routines/widgets/then_container.dart +++ b/lib/pages/routines/widgets/then_container.dart @@ -113,7 +113,8 @@ class ThenContainer extends StatelessWidget { '1G', '2G', '3G', - 'WPS' + 'WPS', + "GW", ].contains(state.thenItems[index] ['productType'])) { context.read().add( @@ -229,7 +230,7 @@ class ThenContainer extends StatelessWidget { dialogType: "THEN"); if (result != null) { context.read().add(AddToThenContainer(mutableData)); - } else if (!['AC', '1G', '2G', '3G', 'WPS'] + } else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW'] .contains(mutableData['productType'])) { context.read().add(AddToThenContainer(mutableData)); } From a242377ea645b19eb9d9233f5568d9ee0f8268e9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 09:46:45 +0300 Subject: [PATCH 046/238] Fully refactored `CreateRoutine/Gateway` feature. --- .../dialog_helper/device_dialog_helper.dart | 3 +- .../routine_dialog_function_list_tile.dart | 42 +++ .../routine_dialog_selection_list_tile.dart | 47 ++++ .../gateway/gateway_dialog.dart | 143 +++++++++-- .../gateway_dialog_value_selector.dart | 58 +++++ .../gateway/gateway_functions_list.dart | 43 ++++ .../gateway/gateway_helper.dart | 34 +++ .../gateway/gateway_if_dialog.dart | 243 ------------------ 8 files changed, 350 insertions(+), 263 deletions(-) create mode 100644 lib/pages/routines/widgets/routine_dialog_function_list_tile.dart create mode 100644 lib/pages/routines/widgets/routine_dialog_selection_list_tile.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog_value_selector.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/gateway/gateway_functions_list.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart delete mode 100644 lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index 1fac903e..f029677e 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ac_dialog.dart'; -import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart'; @@ -105,6 +105,7 @@ class DeviceDialogHelper { functions: functions, uniqueCustomId: data['uniqueCustomId'], deviceSelectedFunctions: deviceSelectedFunctions, + device: data['device'], ); default: diff --git a/lib/pages/routines/widgets/routine_dialog_function_list_tile.dart b/lib/pages/routines/widgets/routine_dialog_function_list_tile.dart new file mode 100644 index 00000000..3e98a06e --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialog_function_list_tile.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class RoutineDialogFunctionListTile extends StatelessWidget { + const RoutineDialogFunctionListTile({ + super.key, + required this.iconPath, + required this.operationName, + required this.onTap, + }); + + final String iconPath; + final String operationName; + final void Function() onTap; + + @override + Widget build(BuildContext context) { + return ListTile( + leading: SvgPicture.asset( + iconPath, + width: 24, + height: 24, + placeholderBuilder: (context) => const SizedBox( + width: 24, + height: 24, + ), + ), + title: Text( + operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: onTap, + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialog_selection_list_tile.dart b/lib/pages/routines/widgets/routine_dialog_selection_list_tile.dart new file mode 100644 index 00000000..b661e591 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialog_selection_list_tile.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class RoutineDialogSelectionListTile extends StatelessWidget { + const RoutineDialogSelectionListTile({ + required this.iconPath, + required this.description, + required this.isSelected, + required this.onTap, + super.key, + }); + + final bool isSelected; + final String iconPath; + final String description; + final void Function() onTap; + + @override + Widget build(BuildContext context) { + return ListTile( + leading: SvgPicture.asset( + iconPath, + width: 24, + height: 24, + placeholderBuilder: (context) => Container( + width: 24, + height: 24, + color: Colors.transparent, + ), + ), + title: Text( + description, + style: context.textTheme.bodyMedium, + ), + trailing: Icon( + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + size: 24, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, + ), + onTap: onTap, + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart index b9638cd9..fc7189f2 100644 --- a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart @@ -1,28 +1,133 @@ import 'package:flutter/material.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/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; -import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart'; +import 'package:syncrow_web/pages/routines/models/gateway.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog_value_selector.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_functions_list.dart'; -final class GatewayHelper { - static Future?> showGatewayFunctionsDialog({ - required BuildContext context, - required List functions, - required String? uniqueCustomId, - required List deviceSelectedFunctions, - }) async { - return showDialog( - context: context, - builder: (context) => BlocProvider( - create: (context) => FunctionBloc() - ..add( - InitializeFunctions(deviceSelectedFunctions), - ), - child: GatewayIfDialog( - uniqueCustomId: uniqueCustomId, - functions: functions, - deviceSelectedFunctions: deviceSelectedFunctions), +class GatewayDialog extends StatefulWidget { + const GatewayDialog({ + required this.uniqueCustomId, + required this.functions, + required this.deviceSelectedFunctions, + required this.device, + super.key, + }); + + final String? uniqueCustomId; + final List functions; + final List deviceSelectedFunctions; + final AllDevicesModel? device; + + @override + State createState() => _GatewayDialogState(); +} + +class _GatewayDialogState extends State { + late final List _gatewayFunctions; + + @override + void initState() { + super.initState(); + _gatewayFunctions = widget.functions.whereType().toList(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: BlocBuilder( + builder: (context, state) { + final selectedFunction = state.selectedFunction; + return Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('Gateway Conditions'), + Expanded(child: _buildMainContent(context, state)), + _buildDialogFooter(context, state), + ], + ), + ); + }, ), ); } + + Widget _buildMainContent(BuildContext context, FunctionBlocState state) { + final selectedFunction = state.selectedFunction; + final selectedOperationName = state.selectedOperationName; + final selectedFunctionData = state.addedFunctions.firstWhere( + (f) => f.functionCode == selectedFunction, + orElse: () => DeviceFunctionData( + entityId: '', + functionCode: selectedFunction ?? '', + operationName: '', + value: null, + ), + ); + final selectedGatewayFunctions = _gatewayFunctions.firstWhere( + (f) => f.code == selectedFunction, + orElse: () => GatewaySwitchAlarmSound( + code: selectedFunction ?? '', + deviceId: '', + deviceName: '', + operationName: '', + icon: '', + type: '', + ), + ); + final operations = selectedGatewayFunctions.getOperationalValues(); + + return Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + GatewayFunctionsList(gatewayFunctions: _gatewayFunctions), + if (state.selectedFunction != null) + Expanded( + child: GatewayDialogValueSelector( + operations: operations, + selectedFunction: selectedFunction ?? '', + selectedFunctionData: selectedFunctionData, + gatewayFunctions: _gatewayFunctions, + operationName: selectedOperationName ?? '', + device: widget.device, + ), + ), + ], + ); + } + + Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) { + return DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: state.addedFunctions.isNotEmpty + ? () { + context.read().add( + AddFunctionToRoutine( + state.addedFunctions, + widget.uniqueCustomId ?? '-1', + ), + ); + Navigator.pop( + context, + {'deviceId': widget.functions.firstOrNull?.deviceId}, + ); + } + : null, + isConfirmEnabled: state.selectedFunction != null, + ); + } } diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog_value_selector.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog_value_selector.dart new file mode 100644 index 00000000..392c3012 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog_value_selector.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.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/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/gateway.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialog_selection_list_tile.dart'; + +class GatewayDialogValueSelector extends StatelessWidget { + const GatewayDialogValueSelector({ + required this.operations, + required this.selectedFunction, + required this.selectedFunctionData, + required this.gatewayFunctions, + required this.device, + required this.operationName, + super.key, + }); + + final List operations; + final String selectedFunction; + final DeviceFunctionData? selectedFunctionData; + final List gatewayFunctions; + final AllDevicesModel? device; + final String operationName; + + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: operations.length, + itemBuilder: (context, index) { + final operation = operations[index]; + final isSelected = selectedFunctionData?.value == operation.value; + return RoutineDialogSelectionListTile( + iconPath: operation.icon, + description: operation.description, + isSelected: isSelected, + onTap: () { + if (!isSelected) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: operationName, + value: operation.value, + condition: selectedFunctionData?.condition, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + } + }, + ); + }, + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_functions_list.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_functions_list.dart new file mode 100644 index 00000000..6d253dcb --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_functions_list.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/gateway.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class GatewayFunctionsList extends StatelessWidget { + const GatewayFunctionsList({ + required this.gatewayFunctions, + super.key, + }); + + final List gatewayFunctions; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 360, + child: ListView.separated( + shrinkWrap: false, + itemCount: gatewayFunctions.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), + child: Divider(color: ColorsManager.dividerColor), + ), + itemBuilder: (context, index) { + final function = gatewayFunctions[index]; + return RoutineDialogFunctionListTile( + iconPath: function.icon, + operationName: function.operationName, + onTap: () => context.read().add( + SelectFunction( + functionCode: function.code, + operationName: function.operationName, + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart new file mode 100644 index 00000000..9a9351ca --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.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/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart'; + +abstract final class GatewayHelper { + const GatewayHelper._(); + + static Future?> showGatewayFunctionsDialog({ + required BuildContext context, + required List functions, + required String? uniqueCustomId, + required List deviceSelectedFunctions, + required AllDevicesModel? device, + }) async { + return showDialog( + context: context, + builder: (context) => BlocProvider( + create: (context) => FunctionBloc() + ..add( + InitializeFunctions(deviceSelectedFunctions), + ), + child: GatewayDialog( + uniqueCustomId: uniqueCustomId, + functions: functions, + deviceSelectedFunctions: deviceSelectedFunctions, + device: device, + ), + ), + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart deleted file mode 100644 index 457110b7..00000000 --- a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_if_dialog.dart +++ /dev/null @@ -1,243 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; -import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routines/models/device_functions.dart'; -import 'package:syncrow_web/pages/routines/models/gateway.dart'; -import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; -import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; - -class GatewayIfDialog extends StatefulWidget { - const GatewayIfDialog({ - required this.uniqueCustomId, - required this.functions, - required this.deviceSelectedFunctions, - super.key, - }); - - final String? uniqueCustomId; - final List functions; - final List deviceSelectedFunctions; - - @override - State createState() => _GatewayIfDialogState(); -} - -class _GatewayIfDialogState extends State { - late final List _gatewayFunctions; - - @override - void initState() { - super.initState(); - _gatewayFunctions = widget.functions.whereType().toList(); - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - contentPadding: EdgeInsets.zero, - content: BlocBuilder( - builder: (context, state) { - final selectedFunction = state.selectedFunction; - return Container( - width: selectedFunction != null ? 600 : 360, - height: 450, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - padding: const EdgeInsets.only(top: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const DialogHeader('Gateway Conditions'), - Expanded(child: _buildMainContent(context, state)), - _buildDialogFooter(context, state), - ], - ), - ); - }, - ), - ); - } - - Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) { - return DialogFooter( - onCancel: () => Navigator.pop(context), - onConfirm: state.addedFunctions.isNotEmpty - ? () { - context.read().add( - AddFunctionToRoutine( - state.addedFunctions, - widget.uniqueCustomId ?? '-1', - ), - ); - Navigator.pop( - context, - {'deviceId': widget.functions.firstOrNull?.deviceId}, - ); - } - : null, - isConfirmEnabled: state.selectedFunction != null, - ); - } - - Widget _buildMainContent(BuildContext context, FunctionBlocState state) { - final selectedFunction = state.selectedFunction; - final selectedOperationName = state.selectedOperationName; - final selectedFunctionData = state.addedFunctions.firstWhere( - (f) => f.functionCode == selectedFunction, - orElse: () => DeviceFunctionData( - entityId: '', - functionCode: selectedFunction ?? '', - operationName: '', - value: null, - ), - ); - - return Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildFunctionList(context), - if (state.selectedFunction != null) - Expanded( - child: _buildValueSelector( - context: context, - selectedFunction: selectedFunction ?? '', - selectedFunctionData: selectedFunctionData, - acFunctions: _gatewayFunctions, - operationName: selectedOperationName ?? '', - ), - ), - ], - ); - } - - Widget _buildFunctionList(BuildContext context) { - return SizedBox( - width: 360, - child: ListView.separated( - shrinkWrap: false, - itemCount: _gatewayFunctions.length, - separatorBuilder: (context, index) => const Padding( - padding: EdgeInsets.symmetric(horizontal: 40.0), - child: Divider(color: ColorsManager.dividerColor), - ), - itemBuilder: (context, index) { - final function = _gatewayFunctions[index]; - return ListTile( - leading: SvgPicture.asset( - function.icon, - width: 24, - height: 24, - placeholderBuilder: (context) => const SizedBox( - width: 24, - height: 24, - ), - ), - title: Text( - function.operationName, - style: context.textTheme.bodyMedium, - ), - trailing: const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.textGray, - ), - onTap: () => context.read().add( - SelectFunction( - functionCode: function.code, - operationName: function.operationName, - ), - ), - ); - }, - ), - ); - } - - static Widget _buildValueSelector({ - required BuildContext context, - required String selectedFunction, - required DeviceFunctionData? selectedFunctionData, - required List acFunctions, - AllDevicesModel? device, - required String operationName, - }) { - final selectedGatewayFunctions = acFunctions.firstWhere( - (f) => f.code == selectedFunction, - ); - final values = selectedGatewayFunctions.getOperationalValues(); - - return _buildOperationalValuesList( - context: context, - values: values, - selectedValue: selectedFunctionData?.value, - device: device, - operationName: operationName, - selectCode: selectedFunction, - selectedFunctionData: selectedFunctionData, - ); - } - - static Widget _buildOperationalValuesList({ - required BuildContext context, - required List values, - required dynamic selectedValue, - AllDevicesModel? device, - required String operationName, - required String selectCode, - DeviceFunctionData? selectedFunctionData, - }) { - return ListView.builder( - itemCount: values.length, - itemBuilder: (context, index) { - final value = values[index]; - final isSelected = selectedValue == value.value; - return ListTile( - leading: SvgPicture.asset( - value.icon, - width: 24, - height: 24, - placeholderBuilder: (context) => Container( - width: 24, - height: 24, - color: Colors.transparent, - ), - ), - title: Text( - value.description, - style: context.textTheme.bodyMedium, - ), - trailing: Icon( - isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, - size: 24, - color: isSelected - ? ColorsManager.primaryColorWithOpacity - : ColorsManager.textGray, - ), - onTap: () { - if (!isSelected) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - value: value.value, - condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - } - }, - ); - }, - ); - } -} From 9b69ec31e9200df34cc3ce4aadad23219507894a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 09:57:18 +0300 Subject: [PATCH 047/238] Refactor RoutineDevices to use a class-level constant for allowed product types. --- lib/pages/routines/widgets/routine_devices.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/pages/routines/widgets/routine_devices.dart b/lib/pages/routines/widgets/routine_devices.dart index ba5756e0..f6ff0db9 100644 --- a/lib/pages/routines/widgets/routine_devices.dart +++ b/lib/pages/routines/widgets/routine_devices.dart @@ -17,6 +17,8 @@ class _RoutineDevicesState extends State { context.read().add(FetchDevicesInRoutine()); } + static const _allowedProductTypes = {'AC', '1G', '2G', '3G', 'WPS', 'GW'}; + @override Widget build(BuildContext context) { return BlocBuilder( @@ -31,10 +33,9 @@ class _RoutineDevicesState extends State { } }); - final deviceList = state.devices.where((device) { - const allowedProductTypes = {'AC', '1G', '2G', '3G', 'WPS', 'GW'}; - return allowedProductTypes.contains(device.productType); - }).toList(); + final deviceList = state.devices + .where((device) => _allowedProductTypes.contains(device.productType)) + .toList(); return Wrap( spacing: 10, From 774f21a55b1801d88f84efda14150bad2b2985fe Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 09:59:15 +0300 Subject: [PATCH 048/238] Refactor RoutineDevices to consolidate device data preparation in a single map to remove code duplication. --- .../routines/widgets/routine_devices.dart | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/lib/pages/routines/widgets/routine_devices.dart b/lib/pages/routines/widgets/routine_devices.dart index f6ff0db9..84fd44c0 100644 --- a/lib/pages/routines/widgets/routine_devices.dart +++ b/lib/pages/routines/widgets/routine_devices.dart @@ -42,37 +42,32 @@ class _RoutineDevicesState extends State { runSpacing: 10, children: deviceList.asMap().entries.map((entry) { final device = entry.value; + + final deviceData = { + 'device': device, + 'imagePath': device.getDefaultIcon(device.productType), + 'title': device.name ?? '', + 'deviceId': device.uuid, + 'productType': device.productType, + 'functions': device.functions, + 'uniqueCustomId': '', + }; + if (state.searchText != null && state.searchText!.isNotEmpty) { return device.name! .toLowerCase() .contains(state.searchText!.toLowerCase()) ? DraggableCard( - imagePath: device.getDefaultIcon(device.productType), - title: device.name ?? '', - deviceData: { - 'device': device, - 'imagePath': device.getDefaultIcon(device.productType), - 'title': device.name ?? '', - 'deviceId': device.uuid, - 'productType': device.productType, - 'functions': device.functions, - 'uniqueCustomId': '', - }, + imagePath: deviceData['imagePath'] as String, + title: deviceData['title'] as String, + deviceData: deviceData, ) : const SizedBox.shrink(); } else { return DraggableCard( - imagePath: device.getDefaultIcon(device.productType), - title: device.name ?? '', - deviceData: { - 'device': device, - 'imagePath': device.getDefaultIcon(device.productType), - 'title': device.name ?? '', - 'deviceId': device.uuid, - 'productType': device.productType, - 'functions': device.functions, - 'uniqueCustomId': '', - }, + imagePath: deviceData['imagePath'] as String, + title: deviceData['title'] as String, + deviceData: deviceData, ); } }).toList(), From d08ab8caac0a5cee5331e14c75bfa1c73431669e Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 9 Apr 2025 10:09:56 +0300 Subject: [PATCH 049/238] add dialogType to devices and add parameter in showSwitchFunctionsDialog --- .../all_devices/models/devices_model.dart | 32 +++++---- .../dialog_helper/device_dialog_helper.dart | 57 ++++++++-------- lib/pages/routines/models/ac/ac_function.dart | 27 ++++++-- .../three_gang_switch/three_gang_switch.dart | 12 ++-- .../widgets/routine_dialogs/ac_dialog.dart | 56 ++++++++++------ .../one_gang_switch_dialog.dart | 62 +++++++++++------- .../three_gang_switch_dialog.dart | 17 ++--- .../two_gang_switch_dialog.dart | 65 ++++++++++++------- 8 files changed, 198 insertions(+), 130 deletions(-) diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index 2663d931..deac5b2a 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -247,12 +247,18 @@ SOS switch (productType) { case 'AC': return [ - SwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''), - ModeFunction(deviceId: uuid ?? '', deviceName: name ?? ''), - TempSetFunction(deviceId: uuid ?? '', deviceName: name ?? ''), - CurrentTempFunction(deviceId: uuid ?? '', deviceName: name ?? ''), - LevelFunction(deviceId: uuid ?? '', deviceName: name ?? ''), - ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? ''), + SwitchFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ModeFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + TempSetFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + CurrentTempFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + LevelFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ChildLockFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ]; case '1G': @@ -275,17 +281,17 @@ SOS case '3G': return [ ThreeGangSwitch1Function( - deviceId: uuid ?? '', deviceName: name ?? ''), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ThreeGangSwitch2Function( - deviceId: uuid ?? '', deviceName: name ?? ''), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ThreeGangSwitch3Function( - deviceId: uuid ?? '', deviceName: name ?? ''), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ThreeGangCountdown1Function( - deviceId: uuid ?? '', deviceName: name ?? ''), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ThreeGangCountdown2Function( - deviceId: uuid ?? '', deviceName: name ?? ''), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ThreeGangCountdown3Function( - deviceId: uuid ?? '', deviceName: name ?? ''), + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ]; case 'WPS': return [ @@ -311,8 +317,6 @@ SOS NoOneTimeFunction( deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), - // FarDetectionSliderFunction( - // deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN') ]; default: return []; diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index 48eaedb9..96919364 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -49,45 +49,46 @@ class DeviceDialogHelper { final deviceSelectedFunctions = routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; - if (removeComparetors && data['productType'] != 'WPS') { - //remove the current temp function in the 'if container' - functions.removeAt(3); - } switch (productType) { case 'AC': return ACHelper.showACFunctionsDialog( - context, - functions, - data['device'], - deviceSelectedFunctions, - data['uniqueCustomId'], - removeComparetors); + context: context, + functions: functions, + device: data['device'], + deviceSelectedFunctions: deviceSelectedFunctions, + uniqueCustomId: data['uniqueCustomId'], + removeComparetors: removeComparetors, + dialogType: dialogType, + ); case '1G': return OneGangSwitchHelper.showSwitchFunctionsDialog( - context, - functions, - data['device'], - deviceSelectedFunctions, - data['uniqueCustomId'], - removeComparetors); + dialogType: dialogType, + context: context, + functions: functions, + device: data['device'], + deviceSelectedFunctions: deviceSelectedFunctions, + uniqueCustomId: data['uniqueCustomId'], + removeComparetors: removeComparetors); case '2G': return TwoGangSwitchHelper.showSwitchFunctionsDialog( - context, - functions, - data['device'], - deviceSelectedFunctions, - data['uniqueCustomId'], - removeComparetors); + dialogType: dialogType, + context: context, + functions: functions, + device: data['device'], + deviceSelectedFunctions: deviceSelectedFunctions, + uniqueCustomId: data['uniqueCustomId'], + removeComparetors: removeComparetors); case '3G': return ThreeGangSwitchHelper.showSwitchFunctionsDialog( - context, - functions, - data['device'], - deviceSelectedFunctions, - data['uniqueCustomId'], - removeComparetors); + dialogType: dialogType, + context: context, + functions: functions, + device: data['device'], + deviceSelectedFunctions: deviceSelectedFunctions, + uniqueCustomId: data['uniqueCustomId'], + removeComparetors: removeComparetors); case 'WPS': return WallPresenceSensor.showWPSFunctionsDialog( dialogType: dialogType, diff --git a/lib/pages/routines/models/ac/ac_function.dart b/lib/pages/routines/models/ac/ac_function.dart index 0af1ff69..0b534e88 100644 --- a/lib/pages/routines/models/ac/ac_function.dart +++ b/lib/pages/routines/models/ac/ac_function.dart @@ -5,23 +5,28 @@ import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; abstract class ACFunction extends DeviceFunction { + final String type; + ACFunction({ required super.deviceId, required super.deviceName, required super.code, required super.operationName, required super.icon, + required this.type, }); List getOperationalValues(); } class SwitchFunction extends ACFunction { - SwitchFunction({required super.deviceId, required super.deviceName}) + SwitchFunction( + {required super.deviceId, required super.deviceName, required type}) : super( code: 'switch', operationName: 'Power', icon: Assets.assetsAcPower, + type: type, ); @override @@ -40,11 +45,13 @@ class SwitchFunction extends ACFunction { } class ModeFunction extends ACFunction { - ModeFunction({required super.deviceId, required super.deviceName}) + ModeFunction( + {required super.deviceId, required super.deviceName, required type}) : super( code: 'mode', operationName: 'Mode', icon: Assets.assetsFreezing, + type: type, ); @override @@ -72,7 +79,8 @@ class TempSetFunction extends ACFunction { final int max; final int step; - TempSetFunction({required super.deviceId, required super.deviceName}) + TempSetFunction( + {required super.deviceId, required super.deviceName, required type}) : min = 160, max = 300, step = 1, @@ -80,6 +88,7 @@ class TempSetFunction extends ACFunction { code: 'temp_set', operationName: 'Set Temperature', icon: Assets.assetsTempreture, + type: type, ); @override @@ -97,8 +106,10 @@ class TempSetFunction extends ACFunction { } class LevelFunction extends ACFunction { - LevelFunction({required super.deviceId, required super.deviceName}) + LevelFunction( + {required super.deviceId, required super.deviceName, required type}) : super( + type: type, code: 'level', operationName: 'Fan Speed', icon: Assets.assetsFanSpeed, @@ -130,8 +141,10 @@ class LevelFunction extends ACFunction { } class ChildLockFunction extends ACFunction { - ChildLockFunction({required super.deviceId, required super.deviceName}) + ChildLockFunction( + {required super.deviceId, required super.deviceName, required type}) : super( + type: type, code: 'child_lock', operationName: 'Child Lock', icon: Assets.assetsChildLock, @@ -157,11 +170,13 @@ class CurrentTempFunction extends ACFunction { final int max; final int step; - CurrentTempFunction({required super.deviceId, required super.deviceName}) + CurrentTempFunction( + {required super.deviceId, required super.deviceName, required type}) : min = -100, max = 990, step = 1, super( + type: type, code: 'temp_current', operationName: 'Current Temperature', icon: Assets.currentTemp, diff --git a/lib/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart b/lib/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart index ca0ed497..9bdd30b4 100644 --- a/lib/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart +++ b/lib/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart @@ -3,7 +3,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operation import 'package:syncrow_web/utils/constants/assets.dart'; class ThreeGangSwitch1Function extends BaseSwitchFunction { - ThreeGangSwitch1Function({required super.deviceId, required super.deviceName}) + ThreeGangSwitch1Function({required super.deviceId, required super.deviceName ,required type}) : super( code: 'switch_1', operationName: 'Light 1 Switch', @@ -26,7 +26,7 @@ class ThreeGangSwitch1Function extends BaseSwitchFunction { } class ThreeGangCountdown1Function extends BaseSwitchFunction { - ThreeGangCountdown1Function({required super.deviceId, required super.deviceName}) + ThreeGangCountdown1Function({required super.deviceId, required super.deviceName ,required type}) : super( code: 'countdown_1', operationName: 'Light 1 Countdown', @@ -47,7 +47,7 @@ class ThreeGangCountdown1Function extends BaseSwitchFunction { } class ThreeGangSwitch2Function extends BaseSwitchFunction { - ThreeGangSwitch2Function({required super.deviceId, required super.deviceName}) + ThreeGangSwitch2Function({required super.deviceId, required super.deviceName, required type}) : super( code: 'switch_2', operationName: 'Light 2 Switch', @@ -70,7 +70,7 @@ class ThreeGangSwitch2Function extends BaseSwitchFunction { } class ThreeGangCountdown2Function extends BaseSwitchFunction { - ThreeGangCountdown2Function({required super.deviceId, required super.deviceName}) + ThreeGangCountdown2Function({required super.deviceId, required super.deviceName ,required type}) : super( code: 'countdown_2', operationName: 'Light 2 Countdown', @@ -91,7 +91,7 @@ class ThreeGangCountdown2Function extends BaseSwitchFunction { } class ThreeGangSwitch3Function extends BaseSwitchFunction { - ThreeGangSwitch3Function({required super.deviceId, required super.deviceName}) + ThreeGangSwitch3Function({required super.deviceId, required super.deviceName ,required type}) : super( code: 'switch_3', operationName: 'Light 3 Switch', @@ -114,7 +114,7 @@ class ThreeGangSwitch3Function extends BaseSwitchFunction { } class ThreeGangCountdown3Function extends BaseSwitchFunction { - ThreeGangCountdown3Function({required super.deviceId, required super.deviceName}) + ThreeGangCountdown3Function({required super.deviceId, required super.deviceName ,required type}) : super( code: 'countdown_3', operationName: 'Light 3 Countdown', diff --git a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart index 90c56ede..bbaa645a 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart @@ -13,29 +13,38 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; class ACHelper { - static Future?> showACFunctionsDialog( - BuildContext context, - List functions, - AllDevicesModel? device, - List? deviceSelectedFunctions, - String uniqueCustomId, - bool? removeComparetors, - ) async { - List acFunctions = functions.whereType().toList(); + static Future?> showACFunctionsDialog({ + required BuildContext context, + required List functions, + required AllDevicesModel? device, + required List? deviceSelectedFunctions, + required String uniqueCustomId, + required bool? removeComparetors, + required String dialogType, + }) async { + List acFunctions = + functions.whereType().where((function) { + if (dialogType == 'THEN') { + return function.type == 'THEN' || function.type == 'BOTH'; + } + return function.type == 'IF' || function.type == 'BOTH'; + }).toList(); + // List acFunctions = functions.whereType().toList(); return showDialog?>( context: context, builder: (BuildContext context) { return BlocProvider( - create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + create: (_) => FunctionBloc() + ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), child: AlertDialog( contentPadding: EdgeInsets.zero, content: BlocBuilder( builder: (context, state) { final selectedFunction = state.selectedFunction; final selectedOperationName = state.selectedOperationName; - final selectedFunctionData = - state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction, + final selectedFunctionData = state.addedFunctions + .firstWhere((f) => f.functionCode == selectedFunction, orElse: () => DeviceFunctionData( entityId: '', functionCode: selectedFunction ?? '', @@ -65,8 +74,10 @@ class ACHelper { child: _buildFunctionsList( context: context, acFunctions: acFunctions, - onFunctionSelected: (functionCode, operationName) => - context.read().add(SelectFunction( + onFunctionSelected: + (functionCode, operationName) => context + .read() + .add(SelectFunction( functionCode: functionCode, operationName: operationName, )), @@ -194,7 +205,8 @@ class ACHelper { ); } - final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = + acFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -290,7 +302,8 @@ class ACHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: + conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -384,9 +397,13 @@ class ACHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, size: 24, - color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, ), onTap: () { if (!isSelected) { @@ -398,7 +415,8 @@ class ACHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, + valueDescription: + selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart index adccb33d..4fe87da3 100644 --- a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -14,29 +14,32 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class OneGangSwitchHelper { - static Future?> showSwitchFunctionsDialog( - BuildContext context, - List functions, - AllDevicesModel? device, - List? deviceSelectedFunctions, - String uniqueCustomId, - bool removeComparetors, - ) async { - List acFunctions = functions.whereType().toList(); + static Future?> showSwitchFunctionsDialog({ + required String dialogType, + required BuildContext context, + required List functions, + required AllDevicesModel? device, + required List? deviceSelectedFunctions, + required String uniqueCustomId, + required bool removeComparetors, + }) async { + List acFunctions = + functions.whereType().toList(); return showDialog?>( context: context, builder: (BuildContext context) { return BlocProvider( - create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + create: (_) => FunctionBloc() + ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), child: AlertDialog( contentPadding: EdgeInsets.zero, content: BlocBuilder( builder: (context, state) { final selectedFunction = state.selectedFunction; final selectedOperationName = state.selectedOperationName; - final selectedFunctionData = - state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction, + final selectedFunctionData = state.addedFunctions + .firstWhere((f) => f.functionCode == selectedFunction, orElse: () => DeviceFunctionData( entityId: '', functionCode: selectedFunction ?? '', @@ -83,9 +86,12 @@ class OneGangSwitchHelper { color: ColorsManager.textGray, ), onTap: () { - context.read().add(SelectFunction( + context + .read() + .add(SelectFunction( functionCode: function.code, - operationName: function.operationName, + operationName: + function.operationName, )); }, ); @@ -175,7 +181,8 @@ class OneGangSwitchHelper { ); } - final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = + acFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -212,11 +219,11 @@ class OneGangSwitchHelper { selectedFunctionData, ), const SizedBox(height: 20), - _buildCountDownDisplay( - context, initialValue, device, operationName, selectedFunctionData, selectCode), + _buildCountDownDisplay(context, initialValue, device, operationName, + selectedFunctionData, selectCode), const SizedBox(height: 20), - _buildCountDownSlider( - context, initialValue, device, operationName, selectedFunctionData, selectCode), + _buildCountDownSlider(context, initialValue, device, operationName, + selectedFunctionData, selectCode), ], ); } @@ -257,7 +264,8 @@ class OneGangSwitchHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: + conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -305,7 +313,8 @@ class OneGangSwitchHelper { value: (initialValue ?? 0).toDouble(), min: operationalValues.minValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / + divisions: (((operationalValues.maxValue ?? 0) - + (operationalValues.minValue ?? 0)) / (operationalValues.stepValue ?? 1)) .round(), onChanged: (value) { @@ -357,9 +366,13 @@ class OneGangSwitchHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, size: 24, - color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, ), onTap: () { if (!isSelected) { @@ -371,7 +384,8 @@ class OneGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, + valueDescription: + selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart index b4400b35..f1cbd6aa 100644 --- a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -14,14 +14,15 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class ThreeGangSwitchHelper { - static Future?> showSwitchFunctionsDialog( - BuildContext context, - List functions, - AllDevicesModel? device, - List? deviceSelectedFunctions, - String uniqueCustomId, - bool removeComparetors, - ) async { + static Future?> showSwitchFunctionsDialog({ + required BuildContext context, + required List functions, + required AllDevicesModel? device, + required List? deviceSelectedFunctions, + required String uniqueCustomId, + required String dialogType, + required bool removeComparetors, + }) async { List switchFunctions = functions.whereType().toList(); diff --git a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart index c894f65c..d4c47445 100644 --- a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart @@ -14,29 +14,32 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class TwoGangSwitchHelper { - static Future?> showSwitchFunctionsDialog( - BuildContext context, - List functions, - AllDevicesModel? device, - List? deviceSelectedFunctions, - String uniqueCustomId, - bool removeComparetors, - ) async { - List switchFunctions = functions.whereType().toList(); + static Future?> showSwitchFunctionsDialog({ + required BuildContext context, + required List functions, + required AllDevicesModel? device, + required List? deviceSelectedFunctions, + required String uniqueCustomId, + required bool removeComparetors, + required String dialogType, + }) async { + List switchFunctions = + functions.whereType().toList(); return showDialog?>( context: context, builder: (BuildContext context) { return BlocProvider( - create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + create: (_) => FunctionBloc() + ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), child: AlertDialog( contentPadding: EdgeInsets.zero, content: BlocBuilder( builder: (context, state) { final selectedFunction = state.selectedFunction; final selectedOperationName = state.selectedOperationName; - final selectedFunctionData = - state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction, + final selectedFunctionData = state.addedFunctions + .firstWhere((f) => f.functionCode == selectedFunction, orElse: () => DeviceFunctionData( entityId: '', functionCode: selectedFunction ?? '', @@ -83,9 +86,12 @@ class TwoGangSwitchHelper { color: ColorsManager.textGray, ), onTap: () { - context.read().add(SelectFunction( + context + .read() + .add(SelectFunction( functionCode: function.code, - operationName: function.operationName, + operationName: + function.operationName, )); }, ); @@ -161,7 +167,8 @@ class TwoGangSwitchHelper { required String operationName, required bool removeComparetors, }) { - if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') { + if (selectedFunction == 'countdown_1' || + selectedFunction == 'countdown_2') { final initialValue = selectedFunctionData?.value ?? 200; return _buildTemperatureSelector( context: context, @@ -175,7 +182,8 @@ class TwoGangSwitchHelper { ); } - final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = + switchFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -212,11 +220,11 @@ class TwoGangSwitchHelper { selectedFunctionData, ), const SizedBox(height: 20), - _buildCountDownDisplay( - context, initialValue, device, operationName, selectedFunctionData, selectCode), + _buildCountDownDisplay(context, initialValue, device, operationName, + selectedFunctionData, selectCode), const SizedBox(height: 20), - _buildCountDownSlider( - context, initialValue, device, operationName, selectedFunctionData, selectCode), + _buildCountDownSlider(context, initialValue, device, operationName, + selectedFunctionData, selectCode), ], ); } @@ -257,7 +265,8 @@ class TwoGangSwitchHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: + conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -305,7 +314,8 @@ class TwoGangSwitchHelper { value: (initialValue ?? 0).toDouble(), min: operationalValues.minValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / + divisions: (((operationalValues.maxValue ?? 0) - + (operationalValues.minValue ?? 0)) / (operationalValues.stepValue ?? 1)) .round(), onChanged: (value) { @@ -357,9 +367,13 @@ class TwoGangSwitchHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + isSelected + ? Icons.radio_button_checked + : Icons.radio_button_unchecked, size: 24, - color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, ), onTap: () { if (!isSelected) { @@ -371,7 +385,8 @@ class TwoGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, + valueDescription: + selectedFunctionData?.valueDescription, ), ), ); From 92aa574944021ce019a0293bece855c0da5da5cb Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 10:21:49 +0300 Subject: [PATCH 050/238] update `GatewayMasterState.getOperationalValues` to match what the api expects. --- lib/pages/routines/models/gateway.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/routines/models/gateway.dart b/lib/pages/routines/models/gateway.dart index daeebd53..dcb29148 100644 --- a/lib/pages/routines/models/gateway.dart +++ b/lib/pages/routines/models/gateway.dart @@ -69,12 +69,12 @@ final class GatewayMasterState extends GatewayFunctions { GatewayOperationalValue( icon: Assets.assetsAcPower, description: "Normal", - value: 'Normal', + value: 'normal', ), GatewayOperationalValue( icon: Assets.assetsAcPowerOFF, description: "Alarm", - value: 'Alarm', + value: 'alarm', ), ]; } From fda96025e9a88cf32f285aabede9bbb449010b6f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 10:41:38 +0300 Subject: [PATCH 051/238] uncommented code. --- .../helper/dialog_helper/device_dialog_helper.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index f029677e..d1146df9 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -50,10 +50,10 @@ class DeviceDialogHelper { final deviceSelectedFunctions = routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; - // if (removeComparetors && data['productType'] != 'WPS') { - // //remove the current temp function in the 'if container' - // functions.removeAt(3); - // } + if (removeComparetors && data['productType'] != 'WPS') { + //remove the current temp function in the 'if container' + functions.removeAt(3); + } switch (productType) { case 'AC': From 9c97e2879aa8facf645ba931bd33014ada56af5c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 10:57:10 +0300 Subject: [PATCH 052/238] Refactor constructor syntax for Gateway functions to use initializer list --- lib/pages/routines/models/gateway.dart | 27 ++++++++++--------- .../gateway/gateway_dialog.dart | 3 --- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/pages/routines/models/gateway.dart b/lib/pages/routines/models/gateway.dart index dcb29148..b1a70d2e 100644 --- a/lib/pages/routines/models/gateway.dart +++ b/lib/pages/routines/models/gateway.dart @@ -33,10 +33,11 @@ final class GatewaySwitchAlarmSound extends GatewayFunctions { required super.deviceId, required super.deviceName, required super.type, - super.code = 'switch_alarm_sound', - super.operationName = 'Switch Alarm Sound', - super.icon = Assets.activeBell, - }); + }) : super( + code: 'switch_alarm_sound', + operationName: 'Switch Alarm Sound', + icon: Assets.activeBell, + ); @override List getOperationalValues() => [ @@ -58,10 +59,11 @@ final class GatewayMasterState extends GatewayFunctions { required super.deviceId, required super.deviceName, required super.type, - super.code = 'master_state', - super.operationName = 'Master State', - super.icon = Assets.gear, - }); + }) : super( + code: 'master_state', + operationName: 'Master State', + icon: Assets.gear, + ); @override List getOperationalValues() { @@ -85,10 +87,11 @@ final class GatewayFactoryReset extends GatewayFunctions { required super.deviceId, required super.deviceName, required super.type, - super.code = 'factory_reset', - super.operationName = 'Factory Reset', - super.icon = Assets.factoryReset, - }); + }) : super( + code: 'factory_reset', + operationName: 'Factory Reset', + icon: Assets.factoryReset, + ); @override List getOperationalValues() { diff --git a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart index fc7189f2..364854ce 100644 --- a/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/gateway/gateway_dialog.dart @@ -81,11 +81,8 @@ class _GatewayDialogState extends State { final selectedGatewayFunctions = _gatewayFunctions.firstWhere( (f) => f.code == selectedFunction, orElse: () => GatewaySwitchAlarmSound( - code: selectedFunction ?? '', deviceId: '', deviceName: '', - operationName: '', - icon: '', type: '', ), ); From fd6b737556bb134bf0795319cd2458e6150da3c3 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 9 Apr 2025 12:17:26 +0300 Subject: [PATCH 053/238] Refactor one_gang_switch_dialog.dart to use one_gang_switch instead of ac in function names and variables --- .../one_gang_switch_dialog.dart | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart index 4fe87da3..d0fcde28 100644 --- a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -5,8 +5,10 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart'; +import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; +import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; @@ -23,7 +25,7 @@ class OneGangSwitchHelper { required String uniqueCustomId, required bool removeComparetors, }) async { - List acFunctions = + List oneGangFunctions = functions.whereType().toList(); return showDialog?>( @@ -64,12 +66,12 @@ class OneGangSwitchHelper { // Left side: Function list Expanded( child: ListView.separated( - itemCount: acFunctions.length, + itemCount: oneGangFunctions.length, separatorBuilder: (_, __) => const Divider( color: ColorsManager.dividerColor, ), itemBuilder: (context, index) { - final function = acFunctions[index]; + final function = oneGangFunctions[index]; return ListTile( leading: SvgPicture.asset( function.icon, @@ -105,7 +107,7 @@ class OneGangSwitchHelper { context: context, selectedFunction: selectedFunction, selectedFunctionData: selectedFunctionData, - acFunctions: acFunctions, + acFunctions: oneGangFunctions, device: device, operationName: selectedOperationName ?? '', removeComparetors: removeComparetors, @@ -180,9 +182,14 @@ class OneGangSwitchHelper { removeComparetors: removeComparetors, ); } + final selectedFn = acFunctions.firstWhere( + (f) => f.code == selectedFunction, + orElse: () => OneGangSwitchFunction( + deviceId: '', + deviceName: '', + ), + ); - final selectedFn = - acFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( From c90b9a1912cd812db16648794878322285e79554 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 12:24:59 +0300 Subject: [PATCH 054/238] bugfix/ range index errorof automations in routines. --- .../main_routine_view/fetch_routine_scenes_automation.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0a22208c..92a837b6 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 @@ -183,7 +183,7 @@ class _FetchRoutineScenesState extends State state.automations[index].id, cardType: 'automations', spaceName: - state.scenes[index].spaceName, + state.automations[index].spaceName, onTap: () { BlocProvider.of(context) .add( From 694a5a7dda87dd619eee4358055d28acf5c3ae04 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 12:32:44 +0300 Subject: [PATCH 055/238] Refactor GatewayModel.fromJson to use a loop for status parsing instead of firstWhere. --- .../gateway/model/gateway_model.dart | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/lib/pages/device_managment/gateway/model/gateway_model.dart b/lib/pages/device_managment/gateway/model/gateway_model.dart index 8e8d00f9..b9600fd7 100644 --- a/lib/pages/device_managment/gateway/model/gateway_model.dart +++ b/lib/pages/device_managment/gateway/model/gateway_model.dart @@ -14,27 +14,36 @@ class GatewayModel { }); factory GatewayModel.fromJson(Map json) { - final status = json['status'] as List; + final status = json['status'] as List? ?? []; - final switchAlarmSound = status.firstWhere( - (item) => item['code'] == 'switch_alarm_sound', - )['value'] as bool; - final masterState = status.firstWhere( - (item) => item['code'] == 'master_state', - )['value'] as String; - final factoryReset = status.firstWhere( - (item) => item['code'] == 'factory_reset', - )['value'] as bool; - final alarmActive = status.firstWhere( - (item) => item['code'] == 'alarm_active', - )['value'] as String; + bool? switchAlarmSound; + String? masterState; + bool? factoryReset; + String? alarmActive; + + for (final item in status) { + switch (item['code']) { + case 'switch_alarm_sound': + switchAlarmSound = item['value'] as bool; + break; + case 'master_state': + masterState = item['value'] as String; + break; + case 'factory_reset': + factoryReset = item['value'] as bool; + break; + case 'alarm_active': + alarmActive = item['value'] as String; + break; + } + } return GatewayModel( uuid: json['uuid'] as String, - switchAlarmSound: switchAlarmSound, - masterState: masterState, - factoryReset: factoryReset, - alarmActive: alarmActive, + switchAlarmSound: switchAlarmSound ?? false, + masterState: masterState ?? '', + factoryReset: factoryReset ?? false, + alarmActive: alarmActive ?? '', ); } } From 387ef99728382cf082f5f4a96859b15f420f9c98 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 12:33:20 +0300 Subject: [PATCH 056/238] fix: handle nullable uuid in GatewayModel.fromJson --- lib/pages/device_managment/gateway/model/gateway_model.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/device_managment/gateway/model/gateway_model.dart b/lib/pages/device_managment/gateway/model/gateway_model.dart index b9600fd7..a522aa00 100644 --- a/lib/pages/device_managment/gateway/model/gateway_model.dart +++ b/lib/pages/device_managment/gateway/model/gateway_model.dart @@ -39,7 +39,7 @@ class GatewayModel { } return GatewayModel( - uuid: json['uuid'] as String, + uuid: json['uuid'] as String? ?? '', switchAlarmSound: switchAlarmSound ?? false, masterState: masterState ?? '', factoryReset: factoryReset ?? false, From 6c691d4b3ceb71c468ee2802a298c5f1c47c07eb Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 12:46:43 +0300 Subject: [PATCH 057/238] displays ceiling sensor in routine --- .../helper/dialog_helper/device_dialog_helper.dart | 2 +- lib/pages/routines/widgets/if_container.dart | 5 +++-- lib/pages/routines/widgets/routine_devices.dart | 3 ++- lib/pages/routines/widgets/then_container.dart | 9 +++++---- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index 96919364..caf92489 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ac_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart'; -import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart'; class DeviceDialogHelper { diff --git a/lib/pages/routines/widgets/if_container.dart b/lib/pages/routines/widgets/if_container.dart index eebf3fb7..5ca549fb 100644 --- a/lib/pages/routines/widgets/if_container.dart +++ b/lib/pages/routines/widgets/if_container.dart @@ -71,7 +71,8 @@ class IfContainer extends StatelessWidget { '1G', '2G', '3G', - 'WPS' + 'WPS', + 'CPS', ].contains( state.ifItems[index]['productType'])) { context.read().add( @@ -129,7 +130,7 @@ class IfContainer extends StatelessWidget { context .read() .add(AddToIfContainer(mutableData, false)); - } else if (!['AC', '1G', '2G', '3G', 'WPS'] + } else if (!['AC', '1G', '2G', '3G', 'WPS', 'CPS'] .contains(mutableData['productType'])) { context .read() diff --git a/lib/pages/routines/widgets/routine_devices.dart b/lib/pages/routines/widgets/routine_devices.dart index 2d3f7236..5f642bac 100644 --- a/lib/pages/routines/widgets/routine_devices.dart +++ b/lib/pages/routines/widgets/routine_devices.dart @@ -38,7 +38,8 @@ class _RoutineDevicesState extends State { device.productType == '1G' || device.productType == '2G' || device.productType == '3G' || - device.productType == 'WPS') + device.productType == 'WPS' || + device.productType == 'CPS') .toList(); return Wrap( diff --git a/lib/pages/routines/widgets/then_container.dart b/lib/pages/routines/widgets/then_container.dart index a4d0461d..d1856879 100644 --- a/lib/pages/routines/widgets/then_container.dart +++ b/lib/pages/routines/widgets/then_container.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; -import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/automation_dialog.dart'; -import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/delay_dialog.dart'; import 'package:syncrow_web/pages/routines/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/dragable_card.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/automation_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/delay_dialog.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:uuid/uuid.dart'; @@ -113,7 +113,8 @@ class ThenContainer extends StatelessWidget { '1G', '2G', '3G', - 'WPS' + 'WPS', + 'CPS', ].contains(state.thenItems[index] ['productType'])) { context.read().add( @@ -229,7 +230,7 @@ class ThenContainer extends StatelessWidget { dialogType: "THEN"); if (result != null) { context.read().add(AddToThenContainer(mutableData)); - } else if (!['AC', '1G', '2G', '3G', 'WPS'] + } else if (!['AC', '1G', '2G', '3G', 'WPS', 'CPS'] .contains(mutableData['productType'])) { context.read().add(AddToThenContainer(mutableData)); } From 4ea91b93330f06dc92b4b4bcccaadc5f3691a918 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 13:16:36 +0300 Subject: [PATCH 058/238] SP-1276/ Show Configuration Pop-up on Drop. --- .../dialog_helper/device_dialog_helper.dart | 12 ++- .../ceiling_sensor/ceiling_sensor_dialog.dart | 77 +++++++++++++++++++ .../ceiling_sensor/ceiling_sensor_helper.dart | 36 +++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index caf92489..df440004 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ac_dialog.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart'; @@ -49,7 +50,6 @@ class DeviceDialogHelper { final deviceSelectedFunctions = routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; - switch (productType) { case 'AC': return ACHelper.showACFunctionsDialog( @@ -98,6 +98,16 @@ class DeviceDialogHelper { deviceSelectedFunctions: deviceSelectedFunctions, uniqueCustomId: data['uniqueCustomId'], removeComparetors: removeComparetors); + case 'CPS': + return CeilingSensorHelper.showCeilingSensorDialog( + context: context, + functions: functions, + device: data['device'], + deviceSelectedFunctions: deviceSelectedFunctions, + uniqueCustomId: data['uniqueCustomId'], + dialogType: dialogType, + ); + default: return null; } diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart new file mode 100644 index 00000000..8820d305 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.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/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; + +class CeilingSensorDialog extends StatelessWidget { + const CeilingSensorDialog({ + required this.uniqueCustomId, + required this.functions, + required this.deviceSelectedFunctions, + required this.device, + required this.dialogType, + super.key, + }); + final String? uniqueCustomId; + final List functions; + final List deviceSelectedFunctions; + final AllDevicesModel? device; + final String dialogType; + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: BlocBuilder( + builder: (context, state) { + final selectedFunction = state.selectedFunction; + final selectedOperationName = state.selectedOperationName; + final selectedFunctionData = state.addedFunctions + .firstWhere((f) => f.functionCode == selectedFunction, + orElse: () => DeviceFunctionData( + entityId: '', + functionCode: selectedFunction ?? '', + operationName: '', + value: null, + )); + + return Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('Presence Sensor Condition'), + DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: state.addedFunctions.isNotEmpty + ? () { + context.read().add( + AddFunctionToRoutine( + state.addedFunctions, '{uniqueCustomId}'), + ); + + Navigator.pop(context, { + // 'deviceId': functions.first.deviceId, + }); + } + : null, + isConfirmEnabled: selectedFunction != null, + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart new file mode 100644 index 00000000..310ca5c4 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.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/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart'; + +abstract final class CeilingSensorHelper { + const CeilingSensorHelper._(); + + static Future?> showCeilingSensorDialog({ + required BuildContext context, + required String? uniqueCustomId, + required List functions, + required List deviceSelectedFunctions, + required AllDevicesModel? device, + required String dialogType, + }) { + return showDialog( + context: context, + builder: (context) => BlocProvider( + create: (context) => FunctionBloc() + ..add( + InitializeFunctions(deviceSelectedFunctions), + ), + child: CeilingSensorDialog( + uniqueCustomId: uniqueCustomId, + functions: functions, + deviceSelectedFunctions: deviceSelectedFunctions, + device: device, + dialogType: dialogType, + ), + ), + ); + } +} From c0662bb19ecd31b35a786ae3316f95421c461c6e Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 15:23:56 +0300 Subject: [PATCH 059/238] Added assets. --- assets/icons/boundary.svg | 28 +++++++++++++ assets/icons/close_to_motion.svg | 10 +++++ assets/icons/cps_custom_mode.svg | 15 +++++++ assets/icons/cps_mode1.svg | 6 +++ assets/icons/cps_mode2.svg | 7 ++++ assets/icons/cps_mode3.svg | 6 +++ assets/icons/cps_mode4.svg | 6 +++ assets/icons/far_away_motion.svg | 11 +++++ assets/icons/motion_meter.svg | 10 +++++ assets/icons/moving_speed.svg | 9 ++++ assets/icons/presence_judgement_threshold.svg | 15 +++++++ assets/icons/radar_fault.svg | 14 +++++++ assets/icons/self_testing_failure.svg | 23 +++++++++++ assets/icons/self_testing_success.svg | 23 +++++++++++ assets/icons/self_testing_timeout.svg | 41 +++++++++++++++++++ assets/icons/space_type.svg | 10 +++++ assets/icons/spatial_motion_value.svg | 18 ++++++++ assets/icons/spatial_static_value.svg | 6 +++ assets/icons/sports_para.svg | 6 +++ lib/utils/constants/assets.dart | 22 +++++++++- 20 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 assets/icons/boundary.svg create mode 100644 assets/icons/close_to_motion.svg create mode 100644 assets/icons/cps_custom_mode.svg create mode 100644 assets/icons/cps_mode1.svg create mode 100644 assets/icons/cps_mode2.svg create mode 100644 assets/icons/cps_mode3.svg create mode 100644 assets/icons/cps_mode4.svg create mode 100644 assets/icons/far_away_motion.svg create mode 100644 assets/icons/motion_meter.svg create mode 100644 assets/icons/moving_speed.svg create mode 100644 assets/icons/presence_judgement_threshold.svg create mode 100644 assets/icons/radar_fault.svg create mode 100644 assets/icons/self_testing_failure.svg create mode 100644 assets/icons/self_testing_success.svg create mode 100644 assets/icons/self_testing_timeout.svg create mode 100644 assets/icons/space_type.svg create mode 100644 assets/icons/spatial_motion_value.svg create mode 100644 assets/icons/spatial_static_value.svg create mode 100644 assets/icons/sports_para.svg diff --git a/assets/icons/boundary.svg b/assets/icons/boundary.svg new file mode 100644 index 00000000..73b4ab24 --- /dev/null +++ b/assets/icons/boundary.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/close_to_motion.svg b/assets/icons/close_to_motion.svg new file mode 100644 index 00000000..8ba6c8c6 --- /dev/null +++ b/assets/icons/close_to_motion.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/cps_custom_mode.svg b/assets/icons/cps_custom_mode.svg new file mode 100644 index 00000000..4176c939 --- /dev/null +++ b/assets/icons/cps_custom_mode.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/icons/cps_mode1.svg b/assets/icons/cps_mode1.svg new file mode 100644 index 00000000..407105df --- /dev/null +++ b/assets/icons/cps_mode1.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/cps_mode2.svg b/assets/icons/cps_mode2.svg new file mode 100644 index 00000000..9464a7ea --- /dev/null +++ b/assets/icons/cps_mode2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/cps_mode3.svg b/assets/icons/cps_mode3.svg new file mode 100644 index 00000000..998329fd --- /dev/null +++ b/assets/icons/cps_mode3.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/cps_mode4.svg b/assets/icons/cps_mode4.svg new file mode 100644 index 00000000..3136a806 --- /dev/null +++ b/assets/icons/cps_mode4.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/far_away_motion.svg b/assets/icons/far_away_motion.svg new file mode 100644 index 00000000..9458eb0d --- /dev/null +++ b/assets/icons/far_away_motion.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/motion_meter.svg b/assets/icons/motion_meter.svg new file mode 100644 index 00000000..85469973 --- /dev/null +++ b/assets/icons/motion_meter.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/moving_speed.svg b/assets/icons/moving_speed.svg new file mode 100644 index 00000000..6db52050 --- /dev/null +++ b/assets/icons/moving_speed.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/presence_judgement_threshold.svg b/assets/icons/presence_judgement_threshold.svg new file mode 100644 index 00000000..d5537da7 --- /dev/null +++ b/assets/icons/presence_judgement_threshold.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/assets/icons/radar_fault.svg b/assets/icons/radar_fault.svg new file mode 100644 index 00000000..b2295d1a --- /dev/null +++ b/assets/icons/radar_fault.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/self_testing_failure.svg b/assets/icons/self_testing_failure.svg new file mode 100644 index 00000000..c86c9ec2 --- /dev/null +++ b/assets/icons/self_testing_failure.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/self_testing_success.svg b/assets/icons/self_testing_success.svg new file mode 100644 index 00000000..1f8976b0 --- /dev/null +++ b/assets/icons/self_testing_success.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/self_testing_timeout.svg b/assets/icons/self_testing_timeout.svg new file mode 100644 index 00000000..55c1e632 --- /dev/null +++ b/assets/icons/self_testing_timeout.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/space_type.svg b/assets/icons/space_type.svg new file mode 100644 index 00000000..af5f6845 --- /dev/null +++ b/assets/icons/space_type.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/spatial_motion_value.svg b/assets/icons/spatial_motion_value.svg new file mode 100644 index 00000000..018da674 --- /dev/null +++ b/assets/icons/spatial_motion_value.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/spatial_static_value.svg b/assets/icons/spatial_static_value.svg new file mode 100644 index 00000000..95ca6112 --- /dev/null +++ b/assets/icons/spatial_static_value.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/sports_para.svg b/assets/icons/sports_para.svg new file mode 100644 index 00000000..6f9d5ece --- /dev/null +++ b/assets/icons/sports_para.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index d9788ee8..7bcd1484 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -428,6 +428,24 @@ class Assets { static const String IlluminanceIcon = 'assets/icons/Illuminance_icon.svg'; static const String gear = 'assets/icons/gear.svg'; static const String activeBell='assets/icons/active_bell.svg'; - - + static const String cpsCustomMode = 'assets/icons/cps_custom_mode.svg'; + static const String cpsMode1 = 'assets/icons/cps_mode1.svg'; + static const String cpsMode2 = 'assets/icons/cps_mode2.svg'; + static const String cpsMode3 = 'assets/icons/cps_mode3.svg'; + static const String cpsMode4 = 'assets/icons/cps_mode4.svg'; + static const String closeToMotion = 'assets/icons/close_to_motion.svg'; + static const String farAwayMotion = 'assets/icons/far_away_motion.svg'; + static const String communicationFault = 'assets/icons/communication_fault.svg'; + static const String radarFault = 'assets/icons/radar_fault.svg'; + static const String selfTestingSuccess = 'assets/icons/self_testing_success.svg'; + static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg'; + static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg'; + static const String movingSpeed = 'assets/icons/moving_speed.svg'; + static const String boundary = 'assets/icons/boundary.svg'; + static const String motionMeter = 'assets/icons/motion_meter.svg'; + static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg'; + static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg'; + static const String presenceJudgementThrshold = 'assets/icons/presence_judgement_threshold.svg'; + static const String spaceType = 'assets/icons/space_type.svg'; + static const String sportsPara = 'assets/icons/sports_para.svg'; } From cd9ed5861a8ebacd71fbea6bb75b8d3ca3d6361c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 15:40:44 +0300 Subject: [PATCH 060/238] Implemented some operations for the cps feature. --- .../ceiling_presence_sensor_functions.dart | 635 ++++++++++++++++++ 1 file changed, 635 insertions(+) create mode 100644 lib/pages/routines/models/ceiling_presence_sensor_functions.dart diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart new file mode 100644 index 00000000..5bd15e66 --- /dev/null +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -0,0 +1,635 @@ +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class CpsOperationalValue { + final String icon; + final String description; + final dynamic value; + + CpsOperationalValue({ + required this.icon, + required this.description, + required this.value, + }); +} + +abstract class CpsFunctions extends DeviceFunction { + CpsFunctions({ + required super.deviceId, + required super.deviceName, + required super.code, + required super.operationName, + required super.icon, + required this.type, + }); + + final String type; + + List getOperationalValues(); +} + +final class CpsRadarSwitchFunction extends CpsFunctions { + CpsRadarSwitchFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'radar_switch', + operationName: 'Radar Switch', + icon: Assets.acPower, + ); + + @override + List getOperationalValues() => [ + CpsOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + CpsOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +final class CpsSpatialParameterSwitchFunction extends CpsFunctions { + CpsSpatialParameterSwitchFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'space_para_switch', + operationName: 'Spatial Parameter Switch', + icon: Assets.acPower, + ); + + @override + List getOperationalValues() => [ + CpsOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + CpsOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), + ]; +} + +final class CpsSensitivityFunction extends CpsFunctions { + CpsSensitivityFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 1, + max = 10, + step = 1, + scale = 0, + super( + code: 'far_detection', + operationName: 'Far Detection', + icon: Assets.sensitivity, + ); + + final int min; + final int max; + final int step; + final int scale; + + @override + List getOperationalValues() { + final values = []; + for (var value = min; value <= max; value += step) { + values.add( + CpsOperationalValue( + icon: Assets.sensitivity, + description: '$value', + value: value, + ), + ); + } + return values; + } +} + +final class CpsMovingSpeedFunction extends CpsFunctions { + CpsMovingSpeedFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.speedoMeter, + ); + @override + List getOperationalValues() { + return []; + } +} + +final class CpsSpatialStaticValueFunction extends CpsFunctions { + CpsSpatialStaticValueFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.spatialStaticValue, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + + +final class CpsSpatialMotionValueFunction extends CpsFunctions { + CpsSpatialMotionValueFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.spatialMotionValue, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + + +final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions { + CpsMaxDistanceOfDetectionFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.currentDistanceIcon, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + + +final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions { + CpsMaxDistanceOfStaticDetectionFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.currentDistanceIcon, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + + +final class CpsDetectionRangeFunction extends CpsFunctions { + CpsDetectionRangeFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.farDetection, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + + +final class CpsDistanceOfMovingObjectsFunction extends CpsFunctions { + CpsDistanceOfMovingObjectsFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.currentDistanceIcon, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + + +final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions { + CpsPresenceJudgementThrsholdFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.presenceJudgementThrshold, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + +final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions { + CpsMotionAmplitudeTriggerThresholdFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.presenceJudgementThrshold, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + + +final class CpsPerpetualBoundaryFunction extends CpsFunctions { + CpsPerpetualBoundaryFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.boundary, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + + + + +final class CpsMotionTriggerBoundaryFunction extends CpsFunctions { + CpsMotionTriggerBoundaryFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.motionMeter, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + + +final class CpsMotionTriggerTimeFunction extends CpsFunctions { + CpsMotionTriggerTimeFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.motionMeter, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + +final class CpsMotionToStaticTimeFunction extends CpsFunctions { + CpsMotionToStaticTimeFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.motionMeter, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + +final class CpsEnteringNoBodyStateTimeFunction extends CpsFunctions { + CpsEnteringNoBodyStateTimeFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.motionMeter, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} + + + +final class CpsSelfTestResultFunctions extends CpsFunctions { + CpsSelfTestResultFunctions({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'self_test_result', + operationName: 'Self-Test Result', + icon: Assets.selfTestResult, + ); + @override + List getOperationalValues() { + return [ + CpsOperationalValue( + description: 'Self Testing', + icon: Assets.selfTestResult, + value: 'self_testing', + ), + CpsOperationalValue( + description: 'Self Testing Success', + icon: Assets.selfTestingSuccess, + value: 'self_testing_success', + ), + CpsOperationalValue( + description: 'Self Testing Failure', + icon: Assets.selfTestingFailure, + value: 'self_testing_failure', + ), + CpsOperationalValue( + description: 'Self Testing Timeout', + icon: Assets.selfTestingTimeout, + value: 'self_testing_timeout', + ), + CpsOperationalValue( + description: 'Communication Fault', + icon: Assets.communicationFault, + value: 'communication_fault', + ), + ]; + } +} + +final class CpsNobodyTimeFunction extends CpsFunctions { + CpsNobodyTimeFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'nobody_time', + operationName: 'Entering Nobody Time', + icon: Assets.assetsNobodyTime, + ); + + @override + List getOperationalValues() { + return [ + CpsOperationalValue( + icon: Assets.assetsNobodyTime, + description: 'None', + value: 'none', + ), + CpsOperationalValue( + icon: Assets.assetsNobodyTime, + description: '10sec', + value: '10s', + ), + CpsOperationalValue( + icon: Assets.assetsNobodyTime, + description: '30sec', + value: '30s', + ), + CpsOperationalValue( + icon: Assets.assetsNobodyTime, + description: '1min', + value: '1min', + ), + CpsOperationalValue( + icon: Assets.assetsNobodyTime, + description: '2min', + value: '2min', + ), + CpsOperationalValue( + icon: Assets.assetsNobodyTime, + description: '5min', + value: '5min', + ), + CpsOperationalValue( + icon: Assets.assetsNobodyTime, + description: '10min', + value: '10min', + ), + CpsOperationalValue( + icon: Assets.assetsNobodyTime, + description: '30min', + value: '30min', + ), + CpsOperationalValue( + icon: Assets.assetsNobodyTime, + description: '1hour', + value: '1hr', + ), + ]; + } +} + +final class CpsMovementFunctions extends CpsFunctions { + CpsMovementFunctions({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'movement', + operationName: 'Movement', + icon: Assets.motion, + ); + @override + List getOperationalValues() { + return [ + CpsOperationalValue( + description: 'None', + icon: Assets.nobodyTime, + value: 'none', + ), + CpsOperationalValue( + description: 'Close To', + icon: Assets.closeToMotion, + value: 'parlour', + ), + CpsOperationalValue( + description: 'Far Away', + icon: Assets.farAwayMotion, + value: 'far_away', + ), + ]; + } +} + + +final class CpsCustomModeFunction extends CpsFunctions { + CpsCustomModeFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'custom_mode', + operationName: 'Custom Mode', + icon: Assets.cpsCustomMode, + ); + + @override + List getOperationalValues() { + return [ + CpsOperationalValue( + icon: Assets.cpsMode1, + description: 'Mode 1', + value: 'mode1', + ), + CpsOperationalValue( + icon: Assets.cpsMode2, + description: 'Mode 2', + value: 'mode2', + ), + CpsOperationalValue( + icon: Assets.cpsMode3, + description: 'Mode 3', + value: 'mode3', + ), + CpsOperationalValue( + icon: Assets.cpsMode4, + description: 'Mode 4', + value: 'mode4', + ), + ]; + } +} + +final class CpsSpaceTypeFunctions extends CpsFunctions { + CpsSpaceTypeFunctions({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'space_type', + operationName: 'Space Type', + icon: Assets.spaceType, + ); + @override + List getOperationalValues() { + return [ + CpsOperationalValue( + description: 'Office', + icon: Assets.office, + value: 'office', + ), + CpsOperationalValue( + description: 'Parlour', + icon: Assets.parlour, + value: 'parlour', + ), + CpsOperationalValue( + description: 'Bathroom', + icon: Assets.bathroom, + value: 'bathroom', + ), + CpsOperationalValue( + description: 'Bedroom', + icon: Assets.bedroom, + value: 'bedroom', + ), + CpsOperationalValue( + description: 'DIY', + icon: Assets.dyi, + value: 'dyi', + ), + ]; + } +} + +class CpsPresenceStatusFunctions extends CpsFunctions { + CpsPresenceStatusFunctions({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: 'presence_state', + operationName: 'Presence Status', + icon: Assets.presenceSensor, + ); + + @override + List getOperationalValues() { + return [ + CpsOperationalValue( + icon: Assets.nobodyTime, + description: 'None', + value: 'none', + ), + CpsOperationalValue( + icon: Assets.presenceState, + description: 'Presence', + value: 'presence', + ), + CpsOperationalValue( + icon: Assets.motion, + description: 'Motion', + value: 'motion', + ), + ]; + } +} + +final class CpsSportsParaFunction extends CpsFunctions { + CpsSportsParaFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : super( + code: '', + operationName: '', + icon: Assets.sportsPara, + ); + @override + List getOperationalValues() { + // TODO: implement getOperationalValues + throw UnimplementedError(); + } +} \ No newline at end of file From c1d9846899212b768011b828b2ec73427f48d7ae Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 15:41:21 +0300 Subject: [PATCH 061/238] Enhance CeilingSensorHelper: Add deviceName and deviceId parameters to sensor functions --- .../ceiling_sensor/ceiling_sensor_helper.dart | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart index 310ca5c4..67083504 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.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/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart'; @@ -33,4 +34,132 @@ abstract final class CeilingSensorHelper { ), ); } + + static List> getCeilingSensorFunctions({ + required String uuid, + required String name, + }) { + return [ + CpsRadarSwitchFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsSpatialParameterSwitchFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsSensitivityFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsMovingSpeedFunction( + deviceName: name, + deviceId: uuid, + type: 'IF', + ), + CpsSpatialStaticValueFunction( + deviceName: name, + deviceId: uuid, + type: 'IF', + ), + CpsSpatialMotionValueFunction( + deviceName: name, + deviceId: uuid, + type: 'IF', + ), + CpsMaxDistanceOfDetectionFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsMaxDistanceOfStaticDetectionFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsDetectionRangeFunction( + deviceName: name, + deviceId: uuid, + type: 'IF', + ), + CpsDistanceOfMovingObjectsFunction( + deviceName: name, + deviceId: uuid, + type: 'IF', + ), + CpsPresenceJudgementThrsholdFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsMotionAmplitudeTriggerThresholdFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsPerpetualBoundaryFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsMotionTriggerBoundaryFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsMotionTriggerTimeFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsMotionToStaticTimeFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsEnteringNoBodyStateTimeFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsSelfTestResultFunctions( + deviceName: name, + deviceId: uuid, + type: 'IF', + ), + CpsNobodyTimeFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsMovementFunctions( + deviceName: name, + deviceId: uuid, + type: 'IF', + ), + CpsCustomModeFunction( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsSpaceTypeFunctions( + deviceName: name, + deviceId: uuid, + type: 'BOTH', + ), + CpsPresenceStatusFunctions( + deviceName: name, + deviceId: uuid, + type: 'IF', + ), + CpsSportsParaFunction( + deviceName: name, + deviceId: uuid, + type: 'IF', + ), + ]; + } } From 120ed85d105c8b1a2c86d16fd7b5fac1d0964f84 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 15:41:28 +0300 Subject: [PATCH 062/238] Add CPS case to device function switch for ceiling sensor integration --- .../device_managment/all_devices/models/devices_model.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index 2ea085c5..9069f8a3 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switc import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gateway.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/enum/device_types.dart'; @@ -337,6 +338,11 @@ SOS type: 'BOTH', ), ]; + case 'CPS': + return CeilingSensorHelper.getCeilingSensorFunctions( + uuid: uuid ?? '', + name: name ?? '', + ); default: return []; } From 3f565788d5c6fa5051c61d54902040de5496d8a4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 15:42:22 +0300 Subject: [PATCH 063/238] Refactor CeilingSensorDialog to be a StatefulWidget. --- .../ceiling_sensor/ceiling_sensor_dialog.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index 8820d305..62594d38 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -7,7 +7,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; -class CeilingSensorDialog extends StatelessWidget { +class CeilingSensorDialog extends StatefulWidget { const CeilingSensorDialog({ required this.uniqueCustomId, required this.functions, @@ -22,6 +22,11 @@ class CeilingSensorDialog extends StatelessWidget { final AllDevicesModel? device; final String dialogType; + @override + State createState() => _CeilingSensorDialogState(); +} + +class _CeilingSensorDialogState extends State { @override Widget build(BuildContext context) { return AlertDialog( From 6d612398ed96f09d02e5ca79aea864163122f0bd Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 16:41:03 +0300 Subject: [PATCH 064/238] Update operation names in CpsFunctions and add toggle codes to CeilingSensorHelper --- assets/icons/communication_fault.svg | 22 +++ .../ceiling_presence_sensor_functions.dart | 55 +++---- .../ceiling_sensor/ceiling_sensor_dialog.dart | 151 +++++++++++++++++- .../ceiling_sensor/ceiling_sensor_helper.dart | 11 ++ 4 files changed, 205 insertions(+), 34 deletions(-) create mode 100644 assets/icons/communication_fault.svg diff --git a/assets/icons/communication_fault.svg b/assets/icons/communication_fault.svg new file mode 100644 index 00000000..e2ab1b40 --- /dev/null +++ b/assets/icons/communication_fault.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart index 5bd15e66..4aa2e650 100644 --- a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -90,8 +90,8 @@ final class CpsSensitivityFunction extends CpsFunctions { step = 1, scale = 0, super( - code: 'far_detection', - operationName: 'Far Detection', + code: 'sensitivity', + operationName: 'Sensitivity', icon: Assets.sensitivity, ); @@ -123,7 +123,7 @@ final class CpsMovingSpeedFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Moving Speed', icon: Assets.speedoMeter, ); @override @@ -139,7 +139,7 @@ final class CpsSpatialStaticValueFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Spacial Static Value', icon: Assets.spatialStaticValue, ); @override @@ -149,7 +149,6 @@ final class CpsSpatialStaticValueFunction extends CpsFunctions { } } - final class CpsSpatialMotionValueFunction extends CpsFunctions { CpsSpatialMotionValueFunction({ required super.deviceId, @@ -157,7 +156,7 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Spatial Motion Value', icon: Assets.spatialMotionValue, ); @override @@ -167,7 +166,6 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions { } } - final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions { CpsMaxDistanceOfDetectionFunction({ required super.deviceId, @@ -175,7 +173,7 @@ final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Maximum Distance Of Detection', icon: Assets.currentDistanceIcon, ); @override @@ -185,7 +183,6 @@ final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions { } } - final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions { CpsMaxDistanceOfStaticDetectionFunction({ required super.deviceId, @@ -193,7 +190,7 @@ final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Maximum Distance Of Static Detection', icon: Assets.currentDistanceIcon, ); @override @@ -203,7 +200,6 @@ final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions { } } - final class CpsDetectionRangeFunction extends CpsFunctions { CpsDetectionRangeFunction({ required super.deviceId, @@ -211,7 +207,7 @@ final class CpsDetectionRangeFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Detection Range', icon: Assets.farDetection, ); @override @@ -221,7 +217,6 @@ final class CpsDetectionRangeFunction extends CpsFunctions { } } - final class CpsDistanceOfMovingObjectsFunction extends CpsFunctions { CpsDistanceOfMovingObjectsFunction({ required super.deviceId, @@ -229,7 +224,7 @@ final class CpsDistanceOfMovingObjectsFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Distance Of Moving Objects', icon: Assets.currentDistanceIcon, ); @override @@ -239,7 +234,6 @@ final class CpsDistanceOfMovingObjectsFunction extends CpsFunctions { } } - final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions { CpsPresenceJudgementThrsholdFunction({ required super.deviceId, @@ -247,7 +241,7 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Presence Judgement Threshold', icon: Assets.presenceJudgementThrshold, ); @override @@ -264,7 +258,7 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Motion Amplitude Trigger Threshold', icon: Assets.presenceJudgementThrshold, ); @override @@ -274,7 +268,6 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions { } } - final class CpsPerpetualBoundaryFunction extends CpsFunctions { CpsPerpetualBoundaryFunction({ required super.deviceId, @@ -282,7 +275,7 @@ final class CpsPerpetualBoundaryFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Perpetual Boundary', icon: Assets.boundary, ); @override @@ -292,9 +285,6 @@ final class CpsPerpetualBoundaryFunction extends CpsFunctions { } } - - - final class CpsMotionTriggerBoundaryFunction extends CpsFunctions { CpsMotionTriggerBoundaryFunction({ required super.deviceId, @@ -302,7 +292,7 @@ final class CpsMotionTriggerBoundaryFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Motion Trigger Boundary', icon: Assets.motionMeter, ); @override @@ -312,7 +302,6 @@ final class CpsMotionTriggerBoundaryFunction extends CpsFunctions { } } - final class CpsMotionTriggerTimeFunction extends CpsFunctions { CpsMotionTriggerTimeFunction({ required super.deviceId, @@ -320,7 +309,7 @@ final class CpsMotionTriggerTimeFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Motion Trigger Time', icon: Assets.motionMeter, ); @override @@ -337,7 +326,7 @@ final class CpsMotionToStaticTimeFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Motion To Static Time', icon: Assets.motionMeter, ); @override @@ -354,7 +343,7 @@ final class CpsEnteringNoBodyStateTimeFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Entering Nobody State Time', icon: Assets.motionMeter, ); @override @@ -364,8 +353,6 @@ final class CpsEnteringNoBodyStateTimeFunction extends CpsFunctions { } } - - final class CpsSelfTestResultFunctions extends CpsFunctions { CpsSelfTestResultFunctions({ required super.deviceId, @@ -404,6 +391,11 @@ final class CpsSelfTestResultFunctions extends CpsFunctions { icon: Assets.communicationFault, value: 'communication_fault', ), + CpsOperationalValue( + description: 'Radar Fault', + icon: Assets.radarFault, + value: 'radar_fault', + ), ]; } } @@ -503,7 +495,6 @@ final class CpsMovementFunctions extends CpsFunctions { } } - final class CpsCustomModeFunction extends CpsFunctions { CpsCustomModeFunction({ required super.deviceId, @@ -624,7 +615,7 @@ final class CpsSportsParaFunction extends CpsFunctions { required super.type, }) : super( code: '', - operationName: '', + operationName: 'Sports Para', icon: Assets.sportsPara, ); @override @@ -632,4 +623,4 @@ final class CpsSportsParaFunction extends CpsFunctions { // TODO: implement getOperationalValues throw UnimplementedError(); } -} \ No newline at end of file +} diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index 62594d38..0f19f204 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -3,9 +3,13 @@ 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/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialog_selection_list_tile.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; class CeilingSensorDialog extends StatefulWidget { const CeilingSensorDialog({ @@ -27,6 +31,20 @@ class CeilingSensorDialog extends StatefulWidget { } class _CeilingSensorDialogState extends State { + late final List _cpsFunctions; + + @override + void initState() { + super.initState(); + + _cpsFunctions = widget.functions.whereType().where((function) { + if (widget.dialogType == 'THEN') { + return function.type == 'THEN' || function.type == 'BOTH'; + } + return function.type == 'IF' || function.type == 'BOTH'; + }).toList(); + } + @override Widget build(BuildContext context) { return AlertDialog( @@ -56,17 +74,20 @@ class _CeilingSensorDialogState extends State { mainAxisSize: MainAxisSize.min, children: [ const DialogHeader('Presence Sensor Condition'), + Expanded(child: _buildMainContent(context, state)), DialogFooter( onCancel: () => Navigator.pop(context), onConfirm: state.addedFunctions.isNotEmpty ? () { context.read().add( AddFunctionToRoutine( - state.addedFunctions, '{uniqueCustomId}'), + state.addedFunctions, + '${widget.uniqueCustomId}', + ), ); Navigator.pop(context, { - // 'deviceId': functions.first.deviceId, + 'deviceId': widget.functions.first.deviceId, }); } : null, @@ -79,4 +100,130 @@ class _CeilingSensorDialogState extends State { ), ); } + + Widget _buildMainContent(BuildContext context, FunctionBlocState state) { + final selectedFunction = state.selectedFunction; + final selectedOperationName = state.selectedOperationName; + final selectedFunctionData = state.addedFunctions.firstWhere( + (f) => f.functionCode == selectedFunction, + orElse: () => DeviceFunctionData( + entityId: '', + functionCode: selectedFunction ?? '', + operationName: '', + value: null, + ), + ); + final selectedCpsFunctions = _cpsFunctions.firstWhere( + (f) => f.code == selectedFunction, + orElse: () => CpsMovementFunctions( + deviceId: '', + deviceName: '', + type: '', + ), + ); + final operations = selectedCpsFunctions.getOperationalValues(); + + return Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + CpsFunctionsList(cpsFunctions: _cpsFunctions), + if (state.selectedFunction != null) + Expanded( + child: CpsDialogValueSelector( + operations: operations, + selectedFunction: selectedFunction ?? '', + selectedFunctionData: selectedFunctionData, + cpsFunctions: _cpsFunctions, + operationName: selectedOperationName ?? '', + device: widget.device, + ), + ), + ], + ); + } +} + +class CpsFunctionsList extends StatelessWidget { + const CpsFunctionsList({required this.cpsFunctions, super.key}); + + final List cpsFunctions; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 360, + child: ListView.separated( + shrinkWrap: false, + itemCount: cpsFunctions.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), + child: Divider(color: ColorsManager.dividerColor), + ), + itemBuilder: (context, index) { + final function = cpsFunctions[index]; + return RoutineDialogFunctionListTile( + iconPath: function.icon, + operationName: function.operationName, + onTap: () => context.read().add( + SelectFunction( + functionCode: function.code, + operationName: function.operationName, + ), + ), + ); + }, + ), + ); + } +} + +class CpsDialogValueSelector extends StatelessWidget { + const CpsDialogValueSelector({ + required this.operations, + required this.selectedFunction, + required this.selectedFunctionData, + required this.cpsFunctions, + required this.device, + required this.operationName, + super.key, + }); + + final List operations; + final String selectedFunction; + final DeviceFunctionData? selectedFunctionData; + final List cpsFunctions; + final AllDevicesModel? device; + final String operationName; + + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: operations.length, + itemBuilder: (context, index) { + final operation = operations[index]; + final isSelected = selectedFunctionData?.value == operation.value; + return RoutineDialogSelectionListTile( + iconPath: operation.icon, + description: operation.description, + isSelected: isSelected, + onTap: () { + if (!isSelected) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: operationName, + value: operation.value, + condition: selectedFunctionData?.condition, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + } + }, + ); + }, + ); + } } diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart index 67083504..291c2f81 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart @@ -162,4 +162,15 @@ abstract final class CeilingSensorHelper { ), ]; } + + static const toggleCodes = { + 'radar_switch', + 'space_para_switch', + 'self_test_result', + 'nobody_time', + 'movement', + 'custom_mode', + 'space_type', + 'presence_state', + }; } From 627ff4e9294d2a1daf82d56a4bde421c03031368 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 9 Apr 2025 17:00:21 +0300 Subject: [PATCH 065/238] Update operation codes and values for various CPS functions. --- .../ceiling_presence_sensor_functions.dart | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart index 4aa2e650..5408ccd0 100644 --- a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -122,7 +122,7 @@ final class CpsMovingSpeedFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'moving_speed', operationName: 'Moving Speed', icon: Assets.speedoMeter, ); @@ -138,7 +138,7 @@ final class CpsSpatialStaticValueFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'spatial_static_value', operationName: 'Spacial Static Value', icon: Assets.spatialStaticValue, ); @@ -155,7 +155,7 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'spatial_motion_value', operationName: 'Spatial Motion Value', icon: Assets.spatialMotionValue, ); @@ -172,7 +172,7 @@ final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'maximum_distance_of_detection', operationName: 'Maximum Distance Of Detection', icon: Assets.currentDistanceIcon, ); @@ -189,7 +189,7 @@ final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'maximum_distance_of_static_detection', operationName: 'Maximum Distance Of Static Detection', icon: Assets.currentDistanceIcon, ); @@ -206,7 +206,7 @@ final class CpsDetectionRangeFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'detection_range', operationName: 'Detection Range', icon: Assets.farDetection, ); @@ -223,7 +223,7 @@ final class CpsDistanceOfMovingObjectsFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'distance_of_moving_objects', operationName: 'Distance Of Moving Objects', icon: Assets.currentDistanceIcon, ); @@ -240,7 +240,7 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'presence_judgement_threshold', operationName: 'Presence Judgement Threshold', icon: Assets.presenceJudgementThrshold, ); @@ -257,7 +257,7 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'motion_amplitude_trigger_threshold', operationName: 'Motion Amplitude Trigger Threshold', icon: Assets.presenceJudgementThrshold, ); @@ -274,7 +274,7 @@ final class CpsPerpetualBoundaryFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'perpetual_boundary', operationName: 'Perpetual Boundary', icon: Assets.boundary, ); @@ -291,7 +291,7 @@ final class CpsMotionTriggerBoundaryFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'motion_trigger_boundary', operationName: 'Motion Trigger Boundary', icon: Assets.motionMeter, ); @@ -308,7 +308,7 @@ final class CpsMotionTriggerTimeFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'motion_trigger_time', operationName: 'Motion Trigger Time', icon: Assets.motionMeter, ); @@ -325,7 +325,7 @@ final class CpsMotionToStaticTimeFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'motion_to_static_time', operationName: 'Motion To Static Time', icon: Assets.motionMeter, ); @@ -342,7 +342,7 @@ final class CpsEnteringNoBodyStateTimeFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'entering_nobody_state_time', operationName: 'Entering Nobody State Time', icon: Assets.motionMeter, ); @@ -369,22 +369,22 @@ final class CpsSelfTestResultFunctions extends CpsFunctions { CpsOperationalValue( description: 'Self Testing', icon: Assets.selfTestResult, - value: 'self_testing', + value: 'check', ), CpsOperationalValue( description: 'Self Testing Success', icon: Assets.selfTestingSuccess, - value: 'self_testing_success', + value: 'check_success', ), CpsOperationalValue( description: 'Self Testing Failure', icon: Assets.selfTestingFailure, - value: 'self_testing_failure', + value: 'check_failure', ), CpsOperationalValue( description: 'Self Testing Timeout', icon: Assets.selfTestingTimeout, - value: 'self_testing_timeout', + value: 'check_timeout', ), CpsOperationalValue( description: 'Communication Fault', @@ -614,7 +614,7 @@ final class CpsSportsParaFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: '', + code: 'sports_para', operationName: 'Sports Para', icon: Assets.sportsPara, ); From 729080ec4e7c2416d5de0c8fca708912163a4bdd Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 10:11:12 +0300 Subject: [PATCH 066/238] Refactor CPS function codes to use helper methods for consistency and clarity --- .../ceiling_presence_sensor_functions.dart | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart index 5408ccd0..0a03a803 100644 --- a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -1,6 +1,11 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +extension _Helper on String { + String get correct => this; + String get wrong => this; +} + class CpsOperationalValue { final String icon; final String description; @@ -34,7 +39,7 @@ final class CpsRadarSwitchFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'radar_switch', + code: 'radar_switch'.correct, operationName: 'Radar Switch', icon: Assets.acPower, ); @@ -60,7 +65,7 @@ final class CpsSpatialParameterSwitchFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'space_para_switch', + code: 'space_para_switch'.correct, operationName: 'Spatial Parameter Switch', icon: Assets.acPower, ); @@ -90,7 +95,7 @@ final class CpsSensitivityFunction extends CpsFunctions { step = 1, scale = 0, super( - code: 'sensitivity', + code: 'sensitivity'.correct, operationName: 'Sensitivity', icon: Assets.sensitivity, ); @@ -138,7 +143,7 @@ final class CpsSpatialStaticValueFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'spatial_static_value', + code: 'space_static_val'.correct, operationName: 'Spacial Static Value', icon: Assets.spatialStaticValue, ); @@ -155,7 +160,7 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'spatial_motion_value', + code: 'space_move_val'.correct, operationName: 'Spatial Motion Value', icon: Assets.spatialMotionValue, ); @@ -172,7 +177,7 @@ final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'maximum_distance_of_detection', + code: 'moving_max_dis'.correct, operationName: 'Maximum Distance Of Detection', icon: Assets.currentDistanceIcon, ); @@ -189,7 +194,7 @@ final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'maximum_distance_of_static_detection', + code: 'static_max_dis'.correct, operationName: 'Maximum Distance Of Static Detection', icon: Assets.currentDistanceIcon, ); @@ -206,7 +211,7 @@ final class CpsDetectionRangeFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'detection_range', + code: 'moving_range'.correct, operationName: 'Detection Range', icon: Assets.farDetection, ); @@ -223,7 +228,7 @@ final class CpsDistanceOfMovingObjectsFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'distance_of_moving_objects', + code: 'presence_range'.correct, operationName: 'Distance Of Moving Objects', icon: Assets.currentDistanceIcon, ); @@ -240,7 +245,7 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'presence_judgement_threshold', + code: 'presence_judgement_threshold'.wrong, operationName: 'Presence Judgement Threshold', icon: Assets.presenceJudgementThrshold, ); @@ -257,7 +262,7 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'motion_amplitude_trigger_threshold', + code: 'motion_amplitude_trigger_threshold'.wrong, operationName: 'Motion Amplitude Trigger Threshold', icon: Assets.presenceJudgementThrshold, ); @@ -274,7 +279,7 @@ final class CpsPerpetualBoundaryFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'perpetual_boundary', + code: 'perceptual_boundary'.correct, operationName: 'Perpetual Boundary', icon: Assets.boundary, ); @@ -291,7 +296,7 @@ final class CpsMotionTriggerBoundaryFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'motion_trigger_boundary', + code: 'moving_boundary'.correct, operationName: 'Motion Trigger Boundary', icon: Assets.motionMeter, ); @@ -308,7 +313,7 @@ final class CpsMotionTriggerTimeFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'motion_trigger_time', + code: 'moving_rigger_time'.correct, operationName: 'Motion Trigger Time', icon: Assets.motionMeter, ); @@ -325,7 +330,7 @@ final class CpsMotionToStaticTimeFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'motion_to_static_time', + code: 'moving_static_time'.correct, operationName: 'Motion To Static Time', icon: Assets.motionMeter, ); @@ -342,7 +347,7 @@ final class CpsEnteringNoBodyStateTimeFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'entering_nobody_state_time', + code: 'none_body_time', operationName: 'Entering Nobody State Time', icon: Assets.motionMeter, ); @@ -359,7 +364,7 @@ final class CpsSelfTestResultFunctions extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'self_test_result', + code: 'checking_result'.correct, operationName: 'Self-Test Result', icon: Assets.selfTestResult, ); @@ -406,7 +411,7 @@ final class CpsNobodyTimeFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'nobody_time', + code: 'nobody_time'.correct, operationName: 'Entering Nobody Time', icon: Assets.assetsNobodyTime, ); @@ -469,7 +474,7 @@ final class CpsMovementFunctions extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'movement', + code: 'body_movement'.correct, operationName: 'Movement', icon: Assets.motion, ); @@ -539,7 +544,7 @@ final class CpsSpaceTypeFunctions extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'space_type', + code: 'scene'.correct, operationName: 'Space Type', icon: Assets.spaceType, ); @@ -581,7 +586,7 @@ class CpsPresenceStatusFunctions extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'presence_state', + code: 'presence_state'.correct, operationName: 'Presence Status', icon: Assets.presenceSensor, ); @@ -614,7 +619,7 @@ final class CpsSportsParaFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'sports_para', + code: 'sports_para'.correct, operationName: 'Sports Para', icon: Assets.sportsPara, ); From 7c68b90aef070aeb01fb43d2d15704dc312d700e Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 10:59:43 +0300 Subject: [PATCH 067/238] Refactor CPS functions to implement operational value generation with min, max, and step parameters. --- .../ceiling_presence_sensor_functions.dart | 347 +++++++++++++++--- 1 file changed, 298 insertions(+), 49 deletions(-) diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart index 0a03a803..f10b0ece 100644 --- a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -1,7 +1,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -extension _Helper on String { +extension _Helper on String { String get correct => this; String get wrong => this; } @@ -93,7 +93,6 @@ final class CpsSensitivityFunction extends CpsFunctions { }) : min = 1, max = 10, step = 1, - scale = 0, super( code: 'sensitivity'.correct, operationName: 'Sensitivity', @@ -103,7 +102,6 @@ final class CpsSensitivityFunction extends CpsFunctions { final int min; final int max; final int step; - final int scale; @override List getOperationalValues() { @@ -126,14 +124,29 @@ final class CpsMovingSpeedFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0, + max = 32, + step = 1, + super( code: 'moving_speed', operationName: 'Moving Speed', icon: Assets.speedoMeter, ); + + final int min; + final int max; + final int step; + @override List getOperationalValues() { - return []; + return List.generate( + (max - min) ~/ step + 1, + (index) => CpsOperationalValue( + icon: Assets.speedoMeter, + description: '${min + (index * step)}', + value: min + (index * step), + ), + ); } } @@ -142,15 +155,29 @@ final class CpsSpatialStaticValueFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0, + max = 255, + step = 1, + super( code: 'space_static_val'.correct, operationName: 'Spacial Static Value', icon: Assets.spatialStaticValue, ); + + final int min; + final int max; + final int step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + return List.generate( + (max - min) ~/ step + 1, + (index) => CpsOperationalValue( + icon: Assets.spatialStaticValue, + description: '${min + (index * step)}', + value: min + (index * step), + ), + ); } } @@ -159,15 +186,29 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0, + max = 255, + step = 1, + super( code: 'space_move_val'.correct, operationName: 'Spatial Motion Value', icon: Assets.spatialMotionValue, ); + + final int min; + final int max; + final int step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + return List.generate( + (max - min) ~/ step + 1, + (index) => CpsOperationalValue( + icon: Assets.spatialMotionValue, + description: '${min + (index * step)}', + value: min + (index * step), + ), + ); } } @@ -176,15 +217,33 @@ final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( - code: 'moving_max_dis'.correct, + }) : min = 0.0, + max = 10.0, + step = 0.5, + super( + code: 'moving_max_dis'.correct, operationName: 'Maximum Distance Of Detection', icon: Assets.currentDistanceIcon, ); + + final double min; + final double max; + final double step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + final count = ((max - min) / step).round() + 1; + return List.generate( + count, + (index) { + final value = (min + (index * step)); + return CpsOperationalValue( + icon: Assets.currentDistanceIcon, + description: '${value.toStringAsFixed(1)}M', + value: value, + ); + }, + ); } } @@ -193,15 +252,33 @@ final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0.0, + max = 10.0, + step = 0.5, + super( code: 'static_max_dis'.correct, operationName: 'Maximum Distance Of Static Detection', icon: Assets.currentDistanceIcon, ); + + final double min; + final double max; + final double step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + final count = ((max - min) / step).round() + 1; + return List.generate( + count, + (index) { + final value = (min + (index * step)); + return CpsOperationalValue( + icon: Assets.currentDistanceIcon, + description: '${value.toStringAsFixed(1)}M', + value: value, + ); + }, + ); } } @@ -210,15 +287,33 @@ final class CpsDetectionRangeFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0.0, + max = 25.5, + step = 0.1, + super( code: 'moving_range'.correct, operationName: 'Detection Range', icon: Assets.farDetection, ); + + final double min; + final double max; + final double step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + final count = ((max - min) / step).round() + 1; + return List.generate( + count, + (index) { + final value = (min + (index * step)); + return CpsOperationalValue( + icon: Assets.farDetection, + description: '${value.toStringAsFixed(1)}M', + value: value, + ); + }, + ); } } @@ -227,15 +322,33 @@ final class CpsDistanceOfMovingObjectsFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0.0, + max = 25.5, + step = 0.1, + super( code: 'presence_range'.correct, operationName: 'Distance Of Moving Objects', icon: Assets.currentDistanceIcon, ); + + final double min; + final double max; + final double step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + final count = ((max - min) / step).round() + 1; + return List.generate( + count, + (index) { + final value = (min + (index * step)); + return CpsOperationalValue( + icon: Assets.currentDistanceIcon, + description: '${value.toStringAsFixed(1)}M', + value: value, + ); + }, + ); } } @@ -244,15 +357,29 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0, + max = 255, + step = 5, + super( code: 'presence_judgement_threshold'.wrong, operationName: 'Presence Judgement Threshold', icon: Assets.presenceJudgementThrshold, ); + + final int min; + final int max; + final int step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + return List.generate( + (max - min) ~/ step + 1, + (index) => CpsOperationalValue( + icon: Assets.presenceJudgementThrshold, + description: '${min + (index * step)}', + value: min + (index * step), + ), + ); } } @@ -261,15 +388,29 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0, + max = 255, + step = 5, + super( code: 'motion_amplitude_trigger_threshold'.wrong, operationName: 'Motion Amplitude Trigger Threshold', icon: Assets.presenceJudgementThrshold, ); + + final int min; + final int max; + final int step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + return List.generate( + (max - min) ~/ step + 1, + (index) => CpsOperationalValue( + icon: Assets.presenceJudgementThrshold, + description: '${min + (index * step)}', + value: min + (index * step), + ), + ); } } @@ -278,15 +419,33 @@ final class CpsPerpetualBoundaryFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0.00, + max = 5.00, + step = 0.50, + super( code: 'perceptual_boundary'.correct, operationName: 'Perpetual Boundary', icon: Assets.boundary, ); + + final double min; + final double max; + final double step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + final count = ((max - min) / step).round() + 1; + return List.generate( + count, + (index) { + final value = (min + (index * step)); + return CpsOperationalValue( + icon: Assets.boundary, + description: '${value.toStringAsFixed(1)}M', + value: value, + ); + }, + ); } } @@ -295,15 +454,33 @@ final class CpsMotionTriggerBoundaryFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0.0, + max = 5.0, + step = 0.5, + super( code: 'moving_boundary'.correct, operationName: 'Motion Trigger Boundary', icon: Assets.motionMeter, ); + + final double min; + final double max; + final double step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + final count = ((max - min) / step).round() + 1; + return List.generate( + count, + (index) { + final value = (min + (index * step)); + return CpsOperationalValue( + icon: Assets.motionMeter, + description: '${value.toStringAsFixed(1)}M', + value: value, + ); + }, + ); } } @@ -312,15 +489,33 @@ final class CpsMotionTriggerTimeFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0.0, + max = 2.0, + step = 0.1, + super( code: 'moving_rigger_time'.correct, operationName: 'Motion Trigger Time', icon: Assets.motionMeter, ); + + final double min; + final double max; + final double step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + final count = ((max - min) / step).round() + 1; + return List.generate( + count, + (index) { + final value = (min + (index * step)); + return CpsOperationalValue( + icon: Assets.motionMeter, + description: '${value.toStringAsFixed(3)}sec', + value: value, + ); + }, + ); } } @@ -329,15 +524,33 @@ final class CpsMotionToStaticTimeFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0.0, + max = 50.0, + step = 1.0, + super( code: 'moving_static_time'.correct, operationName: 'Motion To Static Time', icon: Assets.motionMeter, ); + + final double min; + final double max; + final double step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + final count = ((max - min) / step).round() + 1; + return List.generate( + count, + (index) { + final value = (min + (index * step)); + return CpsOperationalValue( + icon: Assets.motionMeter, + description: '${value.toStringAsFixed(0)}s', + value: value, + ); + }, + ); } } @@ -346,15 +559,33 @@ final class CpsEnteringNoBodyStateTimeFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 0.0, + max = 300.0, + step = 5.0, + super( code: 'none_body_time', operationName: 'Entering Nobody State Time', icon: Assets.motionMeter, ); + + final double min; + final double max; + final double step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + final count = ((max - min) / step).round() + 1; + return List.generate( + count, + (index) { + final value = (min + (index * step)); + return CpsOperationalValue( + icon: Assets.motionMeter, + description: '${value.toStringAsFixed(0)}sec', + value: value, + ); + }, + ); } } @@ -489,7 +720,7 @@ final class CpsMovementFunctions extends CpsFunctions { CpsOperationalValue( description: 'Close To', icon: Assets.closeToMotion, - value: 'parlour', + value: 'close_to', ), CpsOperationalValue( description: 'Far Away', @@ -618,14 +849,32 @@ final class CpsSportsParaFunction extends CpsFunctions { required super.deviceId, required super.deviceName, required super.type, - }) : super( + }) : min = 1, + max = 100, + step = 1, + super( code: 'sports_para'.correct, operationName: 'Sports Para', icon: Assets.sportsPara, ); + + final double min; + final double max; + final double step; + @override List getOperationalValues() { - // TODO: implement getOperationalValues - throw UnimplementedError(); + final count = ((max - min) / step).round() + 1; + return List.generate( + count, + (index) { + final value = (min + (index * step)); + return CpsOperationalValue( + icon: Assets.motionMeter, + description: value.toStringAsFixed(0), + value: value, + ); + }, + ); } } From 75b2f964285d15be548d648929b58e5450490b86 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 11:27:02 +0300 Subject: [PATCH 068/238] added spaces in operational value descriptions to match figma design. --- .../ceiling_presence_sensor_functions.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart index f10b0ece..670dd4d4 100644 --- a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -239,7 +239,7 @@ final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions { final value = (min + (index * step)); return CpsOperationalValue( icon: Assets.currentDistanceIcon, - description: '${value.toStringAsFixed(1)}M', + description: '${value.toStringAsFixed(1)} M', value: value, ); }, @@ -274,7 +274,7 @@ final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions { final value = (min + (index * step)); return CpsOperationalValue( icon: Assets.currentDistanceIcon, - description: '${value.toStringAsFixed(1)}M', + description: '${value.toStringAsFixed(1)} M', value: value, ); }, @@ -309,7 +309,7 @@ final class CpsDetectionRangeFunction extends CpsFunctions { final value = (min + (index * step)); return CpsOperationalValue( icon: Assets.farDetection, - description: '${value.toStringAsFixed(1)}M', + description: '${value.toStringAsFixed(1)} M', value: value, ); }, @@ -344,7 +344,7 @@ final class CpsDistanceOfMovingObjectsFunction extends CpsFunctions { final value = (min + (index * step)); return CpsOperationalValue( icon: Assets.currentDistanceIcon, - description: '${value.toStringAsFixed(1)}M', + description: '${value.toStringAsFixed(1)} M', value: value, ); }, @@ -476,7 +476,7 @@ final class CpsMotionTriggerBoundaryFunction extends CpsFunctions { final value = (min + (index * step)); return CpsOperationalValue( icon: Assets.motionMeter, - description: '${value.toStringAsFixed(1)}M', + description: '${value.toStringAsFixed(1)} M', value: value, ); }, @@ -511,7 +511,7 @@ final class CpsMotionTriggerTimeFunction extends CpsFunctions { final value = (min + (index * step)); return CpsOperationalValue( icon: Assets.motionMeter, - description: '${value.toStringAsFixed(3)}sec', + description: '${value.toStringAsFixed(3)} sec', value: value, ); }, @@ -546,7 +546,7 @@ final class CpsMotionToStaticTimeFunction extends CpsFunctions { final value = (min + (index * step)); return CpsOperationalValue( icon: Assets.motionMeter, - description: '${value.toStringAsFixed(0)}s', + description: '${value.toStringAsFixed(0)} sec', value: value, ); }, @@ -581,7 +581,7 @@ final class CpsEnteringNoBodyStateTimeFunction extends CpsFunctions { final value = (min + (index * step)); return CpsOperationalValue( icon: Assets.motionMeter, - description: '${value.toStringAsFixed(0)}sec', + description: '${value.toStringAsFixed(0)} sec', value: value, ); }, From 796409600eae71e84a390c1dc1b1eff576002444 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 14:03:08 +0300 Subject: [PATCH 069/238] Update operation codes for CpsPresenceJudgementThresholdFunction and CpsMotionAmplitudeTriggerThresholdFunction. --- .../routines/models/ceiling_presence_sensor_functions.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart index 670dd4d4..afa5ba5e 100644 --- a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -361,7 +361,7 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions { max = 255, step = 5, super( - code: 'presence_judgement_threshold'.wrong, + code: 'presence_reference', operationName: 'Presence Judgement Threshold', icon: Assets.presenceJudgementThrshold, ); @@ -392,7 +392,7 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions { max = 255, step = 5, super( - code: 'motion_amplitude_trigger_threshold'.wrong, + code: 'moving_reference', operationName: 'Motion Amplitude Trigger Threshold', icon: Assets.presenceJudgementThrshold, ); From 9ca6fb8640d353a17c48730bcfa0fe5d0252b89c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 14:03:44 +0300 Subject: [PATCH 070/238] Remove unnecessary extension methods for operational value codes in CPS functions --- .../ceiling_presence_sensor_functions.dart | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart index afa5ba5e..6e181407 100644 --- a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -1,11 +1,6 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -extension _Helper on String { - String get correct => this; - String get wrong => this; -} - class CpsOperationalValue { final String icon; final String description; @@ -39,7 +34,7 @@ final class CpsRadarSwitchFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'radar_switch'.correct, + code: 'radar_switch', operationName: 'Radar Switch', icon: Assets.acPower, ); @@ -65,7 +60,7 @@ final class CpsSpatialParameterSwitchFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'space_para_switch'.correct, + code: 'space_para_switch', operationName: 'Spatial Parameter Switch', icon: Assets.acPower, ); @@ -94,7 +89,7 @@ final class CpsSensitivityFunction extends CpsFunctions { max = 10, step = 1, super( - code: 'sensitivity'.correct, + code: 'sensitivity', operationName: 'Sensitivity', icon: Assets.sensitivity, ); @@ -159,7 +154,7 @@ final class CpsSpatialStaticValueFunction extends CpsFunctions { max = 255, step = 1, super( - code: 'space_static_val'.correct, + code: 'space_static_val', operationName: 'Spacial Static Value', icon: Assets.spatialStaticValue, ); @@ -190,7 +185,7 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions { max = 255, step = 1, super( - code: 'space_move_val'.correct, + code: 'space_move_val', operationName: 'Spatial Motion Value', icon: Assets.spatialMotionValue, ); @@ -221,7 +216,7 @@ final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions { max = 10.0, step = 0.5, super( - code: 'moving_max_dis'.correct, + code: 'moving_max_dis', operationName: 'Maximum Distance Of Detection', icon: Assets.currentDistanceIcon, ); @@ -256,7 +251,7 @@ final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions { max = 10.0, step = 0.5, super( - code: 'static_max_dis'.correct, + code: 'static_max_dis', operationName: 'Maximum Distance Of Static Detection', icon: Assets.currentDistanceIcon, ); @@ -291,7 +286,7 @@ final class CpsDetectionRangeFunction extends CpsFunctions { max = 25.5, step = 0.1, super( - code: 'moving_range'.correct, + code: 'moving_range', operationName: 'Detection Range', icon: Assets.farDetection, ); @@ -326,7 +321,7 @@ final class CpsDistanceOfMovingObjectsFunction extends CpsFunctions { max = 25.5, step = 0.1, super( - code: 'presence_range'.correct, + code: 'presence_range', operationName: 'Distance Of Moving Objects', icon: Assets.currentDistanceIcon, ); @@ -423,7 +418,7 @@ final class CpsPerpetualBoundaryFunction extends CpsFunctions { max = 5.00, step = 0.50, super( - code: 'perceptual_boundary'.correct, + code: 'perceptual_boundary', operationName: 'Perpetual Boundary', icon: Assets.boundary, ); @@ -458,7 +453,7 @@ final class CpsMotionTriggerBoundaryFunction extends CpsFunctions { max = 5.0, step = 0.5, super( - code: 'moving_boundary'.correct, + code: 'moving_boundary', operationName: 'Motion Trigger Boundary', icon: Assets.motionMeter, ); @@ -493,7 +488,7 @@ final class CpsMotionTriggerTimeFunction extends CpsFunctions { max = 2.0, step = 0.1, super( - code: 'moving_rigger_time'.correct, + code: 'moving_rigger_time', operationName: 'Motion Trigger Time', icon: Assets.motionMeter, ); @@ -528,7 +523,7 @@ final class CpsMotionToStaticTimeFunction extends CpsFunctions { max = 50.0, step = 1.0, super( - code: 'moving_static_time'.correct, + code: 'moving_static_time', operationName: 'Motion To Static Time', icon: Assets.motionMeter, ); @@ -595,7 +590,7 @@ final class CpsSelfTestResultFunctions extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'checking_result'.correct, + code: 'checking_result', operationName: 'Self-Test Result', icon: Assets.selfTestResult, ); @@ -642,7 +637,7 @@ final class CpsNobodyTimeFunction extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'nobody_time'.correct, + code: 'nobody_time', operationName: 'Entering Nobody Time', icon: Assets.assetsNobodyTime, ); @@ -705,7 +700,7 @@ final class CpsMovementFunctions extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'body_movement'.correct, + code: 'body_movement', operationName: 'Movement', icon: Assets.motion, ); @@ -775,7 +770,7 @@ final class CpsSpaceTypeFunctions extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'scene'.correct, + code: 'scene', operationName: 'Space Type', icon: Assets.spaceType, ); @@ -817,7 +812,7 @@ class CpsPresenceStatusFunctions extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'presence_state'.correct, + code: 'presence_state', operationName: 'Presence Status', icon: Assets.presenceSensor, ); @@ -853,7 +848,7 @@ final class CpsSportsParaFunction extends CpsFunctions { max = 100, step = 1, super( - code: 'sports_para'.correct, + code: 'sports_para', operationName: 'Sports Para', icon: Assets.sportsPara, ); From 9d3b58deebe3d4a8c6f12e9a0ec831d7f21a8fc8 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 14:32:09 +0300 Subject: [PATCH 071/238] Refactor wall presence sensor components and add new widgets for improved functionality and clarity to ensure reusability in the future for other devices. --- .../routines/widgets/condition_toggle.dart | 33 ++ .../routines/widgets/function_slider.dart | 26 ++ .../wall_sensor/wall_presence_sensor.dart | 353 +----------------- .../wps_operational_values_list.dart | 114 ++++++ .../wps_value_selector_widget.dart | 95 +++++ .../widgets/slider_value_selector.dart | 67 ++++ lib/pages/routines/widgets/value_display.dart | 31 ++ 7 files changed, 372 insertions(+), 347 deletions(-) create mode 100644 lib/pages/routines/widgets/condition_toggle.dart create mode 100644 lib/pages/routines/widgets/function_slider.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_operational_values_list.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart create mode 100644 lib/pages/routines/widgets/slider_value_selector.dart create mode 100644 lib/pages/routines/widgets/value_display.dart diff --git a/lib/pages/routines/widgets/condition_toggle.dart b/lib/pages/routines/widgets/condition_toggle.dart new file mode 100644 index 00000000..99ea2f04 --- /dev/null +++ b/lib/pages/routines/widgets/condition_toggle.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ConditionToggle extends StatelessWidget { + final String? currentCondition; + final void Function(String condition) onChanged; + + const ConditionToggle({ + required this.onChanged, + this.currentCondition, + super.key, + }); + + static const _conditions = ["<", "==", ">"]; + + @override + Widget build(BuildContext context) { + return ToggleButtons( + onPressed: (index) => onChanged(_conditions[index]), + borderRadius: const BorderRadius.all(Radius.circular(8)), + selectedBorderColor: ColorsManager.primaryColorWithOpacity, + selectedColor: Colors.white, + fillColor: ColorsManager.primaryColorWithOpacity, + color: ColorsManager.primaryColorWithOpacity, + constraints: const BoxConstraints( + minHeight: 40.0, + minWidth: 40.0, + ), + isSelected: _conditions.map((c) => c == (currentCondition ?? "==")).toList(), + children: _conditions.map((c) => Text(c)).toList(), + ); + } +} diff --git a/lib/pages/routines/widgets/function_slider.dart b/lib/pages/routines/widgets/function_slider.dart new file mode 100644 index 00000000..b7fb0d53 --- /dev/null +++ b/lib/pages/routines/widgets/function_slider.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +class FunctionSlider extends StatelessWidget { + final dynamic initialValue; + + final (double min, double max) range; + final void Function(double value) onChanged; + + const FunctionSlider({ + required this.onChanged, + required this.initialValue, + required this.range, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Slider( + value: initialValue is int ? initialValue.toDouble() : range.$1, + min: range.$1, + max: range.$2, + divisions: (range.$2 - range.$1).toInt(), + onChanged: onChanged, + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart index b7733511..91abb34e 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart @@ -1,17 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; -import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; -import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; class WallPresenceSensor extends StatefulWidget { final List functions; @@ -63,8 +62,7 @@ class _WallPresenceSensorState extends State { @override void initState() { super.initState(); - _wpsFunctions = - widget.functions.whereType().where((function) { + _wpsFunctions = widget.functions.whereType().where((function) { if (widget.dialogType == 'THEN') { return function.type == 'THEN' || function.type == 'BOTH'; } @@ -176,10 +174,10 @@ class _WallPresenceSensorState extends State { ); return Expanded( - child: _ValueSelector( + child: WpsValueSelectorWidget( selectedFunction: selectedFunction, functionData: functionData, - acFunctions: _wpsFunctions, + wpsFunctions: _wpsFunctions, device: widget.device, dialogType: widget.dialogType!, removeComparators: widget.removeComparetors, @@ -208,342 +206,3 @@ class _WallPresenceSensorState extends State { ); } } - -class _ValueSelector extends StatelessWidget { - final String selectedFunction; - final DeviceFunctionData functionData; - final List acFunctions; - final AllDevicesModel? device; - final String dialogType; - final bool removeComparators; - - const _ValueSelector({ - required this.selectedFunction, - required this.functionData, - required this.acFunctions, - required this.device, - required this.dialogType, - required this.removeComparators, - }); - - @override - Widget build(BuildContext context) { - final selectedFn = - acFunctions.firstWhere((f) => f.code == selectedFunction); - final values = selectedFn.getOperationalValues(); - - if (_isSliderFunction(selectedFunction)) { - return _SliderValueSelector( - selectedFunction: selectedFunction, - functionData: functionData, - device: device, - dialogType: dialogType, - ); - } - - return _OperationalValuesList( - values: values, - selectedValue: functionData.value, - device: device, - operationName: selectedFn.operationName, - selectCode: selectedFunction, - ); - } - - bool _isSliderFunction(String function) => - ['dis_current', 'presence_time', 'illuminance_value'].contains(function); -} - -class _SliderValueSelector extends StatelessWidget { - final String selectedFunction; - final DeviceFunctionData functionData; - final AllDevicesModel? device; - final String dialogType; - - const _SliderValueSelector({ - required this.selectedFunction, - required this.functionData, - required this.device, - required this.dialogType, - }); - - @override - Widget build(BuildContext context) { - final initialValue = functionData.value ?? 250; - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox(height: 20), - _ConditionToggle( - currentCondition: functionData.condition, - selectCode: selectedFunction, - device: device, - operationName: functionData.operationName, - selectedValue: functionData.value, - ), - _ValueDisplay( - value: initialValue, - functionCode: selectedFunction, - ), - const SizedBox(height: 20), - _FunctionSlider( - initialValue: initialValue, - functionCode: selectedFunction, - functionData: functionData, - device: device, - ), - ], - ); - } -} - -class _ConditionToggle extends StatelessWidget { - final String? currentCondition; - final String selectCode; - final AllDevicesModel? device; - final String operationName; - final dynamic selectedValue; - - const _ConditionToggle({ - this.currentCondition, - required this.selectCode, - this.device, - required this.operationName, - this.selectedValue, - }); - - @override - Widget build(BuildContext context) { - const conditions = ["<", "==", ">"]; - return ToggleButtons( - onPressed: (index) => _updateCondition(context, conditions[index]), - borderRadius: const BorderRadius.all(Radius.circular(8)), - selectedBorderColor: ColorsManager.primaryColorWithOpacity, - selectedColor: Colors.white, - fillColor: ColorsManager.primaryColorWithOpacity, - color: ColorsManager.primaryColorWithOpacity, - constraints: const BoxConstraints( - minHeight: 40.0, - minWidth: 40.0, - ), - isSelected: - conditions.map((c) => c == (currentCondition ?? "==")).toList(), - children: conditions.map((c) => Text(c)).toList(), - ); - } - - void _updateCondition(BuildContext context, String condition) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - condition: condition, - value: selectedValue, - ), - ), - ); - } -} - -class _ValueDisplay extends StatelessWidget { - final dynamic value; - final String functionCode; - - const _ValueDisplay({ - required this.value, - required this.functionCode, - }); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Text( - _getDisplayText(), - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ); - } - - String _getDisplayText() { - final intValue = (value as num?)?.toInt() ?? 0; - switch (functionCode) { - case 'presence_time': - return '$intValue Min'; - case 'dis_current': - return '$intValue CM'; - case 'illuminance_value': - return '$intValue Lux'; - default: - return '$intValue'; - } - } -} - -class _FunctionSlider extends StatelessWidget { - final dynamic initialValue; - final String functionCode; - final DeviceFunctionData functionData; - final AllDevicesModel? device; - - const _FunctionSlider({ - required this.initialValue, - required this.functionCode, - required this.functionData, - required this.device, - }); - - @override - Widget build(BuildContext context) { - final (min, max) = _getSliderRange(); - return Slider( - value: initialValue is int ? initialValue.toDouble() : min, - min: min, - max: max, - divisions: (max - min).toInt(), - onChanged: (value) => _updateValue(context, value.toInt()), - ); - } - - (double, double) _getSliderRange() { - switch (functionCode) { - case 'presence_time': - return (0, 65535); - case 'dis_current': - return (1, 600); - case 'illuminance_value': - return (0, 10000); - default: - return (200, 300); - } - } - - void _updateValue(BuildContext context, int value) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: functionCode, - operationName: functionData.operationName, - value: value, - condition: functionData.condition, - ), - ), - ); - } -} - -class _OperationalValuesList extends StatelessWidget { - final List values; - final dynamic selectedValue; - final AllDevicesModel? device; - final String operationName; - final String selectCode; - - const _OperationalValuesList({ - required this.values, - required this.selectedValue, - required this.device, - required this.operationName, - required this.selectCode, - }); - - @override - Widget build(BuildContext context) { - return operationName == 'Nobody Time' - ? _buildTimeWheel(context) - : ListView.builder( - padding: const EdgeInsets.all(20), - itemCount: values.length, - itemBuilder: (context, index) => - _buildValueItem(context, values[index]), - ); - } - - Widget _buildTimeWheel(BuildContext context) { - final currentTotalSeconds = selectedValue as int? ?? 0; - return TimeWheelPicker( - initialHours: currentTotalSeconds ~/ 3600, - initialMinutes: (currentTotalSeconds % 3600) ~/ 60, - initialSeconds: currentTotalSeconds % 60, - onTimeChanged: (h, m, s) => _updateTotalSeconds(context, h, m, s), - ); - } - - Widget _buildValueItem(BuildContext context, WpsOperationalValue value) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildValueIcon(context, value), - Expanded(child: _buildValueDescription(value)), - _buildValueRadio(context, value), - ], - ), - ); - } - - Widget _buildValueIcon(context, WpsOperationalValue value) { - return Column( - children: [ - if (_shouldShowTextDescription) - Text(value.description.replaceAll("cm", '')), - SvgPicture.asset(value.icon, width: 25, height: 25), - ], - ); - } - - bool get _shouldShowTextDescription => - operationName == 'Far Detection' || - operationName == 'Motionless Detection Sensitivity'; - - Widget _buildValueDescription(WpsOperationalValue value) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Text(value.description), - ); - } - - Widget _buildValueRadio(context, WpsOperationalValue value) { - return Radio( - value: value.value, - groupValue: selectedValue, - onChanged: (_) => _selectValue(context, value.value), - ); - } - - void _selectValue(BuildContext context, dynamic value) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - value: value, - ), - ), - ); - } - - void _updateTotalSeconds(BuildContext context, int h, int m, int s) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectCode, - operationName: operationName, - value: h * 3600 + m * 60 + s, - ), - ), - ); - } -} diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_operational_values_list.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_operational_values_list.dart new file mode 100644 index 00000000..6c149cd3 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_operational_values_list.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/wps/wps_operational_value.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart'; + +class WpsOperationalValuesList extends StatelessWidget { + final List values; + final dynamic selectedValue; + final AllDevicesModel? device; + final String operationName; + final String selectCode; + + const WpsOperationalValuesList({ + required this.values, + required this.selectedValue, + required this.device, + required this.operationName, + required this.selectCode, + super.key, + }); + + @override + Widget build(BuildContext context) { + return operationName == 'Nobody Time' + ? _buildTimeWheel(context) + : ListView.builder( + padding: const EdgeInsets.all(20), + itemCount: values.length, + itemBuilder: (context, index) => _buildValueItem(context, values[index]), + ); + } + + Widget _buildTimeWheel(BuildContext context) { + final currentTotalSeconds = selectedValue as int? ?? 0; + return TimeWheelPicker( + initialHours: currentTotalSeconds ~/ 3600, + initialMinutes: (currentTotalSeconds % 3600) ~/ 60, + initialSeconds: currentTotalSeconds % 60, + onTimeChanged: (h, m, s) => _updateTotalSeconds(context, h, m, s), + ); + } + + Widget _buildValueItem(BuildContext context, WpsOperationalValue value) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildValueIcon(context, value), + Expanded(child: _buildValueDescription(value)), + _buildValueRadio(context, value), + ], + ), + ); + } + + Widget _buildValueIcon(context, WpsOperationalValue value) { + return Column( + children: [ + if (_shouldShowTextDescription) Text(value.description.replaceAll("cm", '')), + SvgPicture.asset(value.icon, width: 25, height: 25), + ], + ); + } + + bool get _shouldShowTextDescription => + operationName == 'Far Detection' || + operationName == 'Motionless Detection Sensitivity'; + + Widget _buildValueDescription(WpsOperationalValue value) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text(value.description), + ); + } + + Widget _buildValueRadio(context, WpsOperationalValue value) { + return Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (_) => _selectValue(context, value.value), + ); + } + + void _selectValue(BuildContext context, dynamic value) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: value, + ), + ), + ); + } + + void _updateTotalSeconds(BuildContext context, int h, int m, int s) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectCode, + operationName: operationName, + value: h * 3600 + m * 60 + s, + ), + ), + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart new file mode 100644 index 00000000..5f38b240 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.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/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_operational_values_list.dart'; +import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; + +class WpsValueSelectorWidget extends StatelessWidget { + final String selectedFunction; + final DeviceFunctionData functionData; + final List wpsFunctions; + final AllDevicesModel? device; + final String dialogType; + final bool removeComparators; + + const WpsValueSelectorWidget({ + required this.selectedFunction, + required this.functionData, + required this.wpsFunctions, + required this.device, + required this.dialogType, + required this.removeComparators, + super.key, + }); + + @override + Widget build(BuildContext context) { + final selectedFn = wpsFunctions.firstWhere((f) => f.code == selectedFunction); + final values = selectedFn.getOperationalValues(); + + if (_isSliderFunction(selectedFunction)) { + return SliderValueSelector( + selectedFunction: selectedFunction, + functionData: functionData, + device: device, + dialogType: dialogType, + sliderRange: sliderRange, + displayedValue: getDisplayText, + initialValue: functionData.value ?? 250, + onConditionChanged: (condition) => context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: functionData.operationName, + condition: condition, + value: functionData.value, + ), + ), + ), + onSliderChanged: (value) => context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: functionData.operationName, + value: value.toInt(), + condition: functionData.condition, + ), + ), + ), + ); + } + + return WpsOperationalValuesList( + values: values, + selectedValue: functionData.value, + device: device, + operationName: selectedFn.operationName, + selectCode: selectedFunction, + ); + } + + bool _isSliderFunction(String function) => + ['dis_current', 'presence_time', 'illuminance_value'].contains(function); + + (double, double) get sliderRange => switch (functionData.functionCode) { + 'presence_time' => (0, 65535), + 'dis_current' => (0, 600), + 'illuminance_value' => (0, 10000), + _ => (200, 300), + }; + + String get getDisplayText { + final intValue = int.tryParse('${functionData.value ?? ''}'); + return switch (functionData.functionCode) { + 'presence_time' => '${intValue ?? '0'} Min', + 'dis_current' => '${intValue ?? '250'} CM', + 'illuminance_value' => '${intValue ?? '0'} Lux', + _ => '$intValue', + }; + } +} diff --git a/lib/pages/routines/widgets/slider_value_selector.dart b/lib/pages/routines/widgets/slider_value_selector.dart new file mode 100644 index 00000000..6b408008 --- /dev/null +++ b/lib/pages/routines/widgets/slider_value_selector.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/condition_toggle.dart'; +import 'package:syncrow_web/pages/routines/widgets/function_slider.dart'; +import 'package:syncrow_web/pages/routines/widgets/value_display.dart'; + +class SliderValueSelector extends StatelessWidget { + final String selectedFunction; + final DeviceFunctionData functionData; + final AllDevicesModel? device; + final String dialogType; + final (double, double) sliderRange; + final String displayedValue; + final Object? initialValue; + final void Function(String condition) onConditionChanged; + final void Function(double value) onSliderChanged; + + const SliderValueSelector({ + required this.selectedFunction, + required this.functionData, + required this.device, + required this.dialogType, + required this.sliderRange, + required this.displayedValue, + required this.initialValue, + required this.onConditionChanged, + required this.onSliderChanged, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 20), + ConditionToggle( + currentCondition: functionData.condition, + onChanged: onConditionChanged, + ), + ValueDisplay( + value: initialValue, + label: displayedValue, + ), + const SizedBox(height: 20), + FunctionSlider( + initialValue: initialValue, + range: sliderRange, onChanged: onSliderChanged, + // void _updateValue(BuildContext context, int value) { + // context.read().add( + // AddFunction( + // functionData: DeviceFunctionData( + // entityId: device?.uuid ?? '', + // functionCode: functionCode, + // operationName: functionData.operationName, + // value: value, + // condition: functionData.condition, + // ), + // ), + // ); + // } + ), + ], + ); + } +} diff --git a/lib/pages/routines/widgets/value_display.dart b/lib/pages/routines/widgets/value_display.dart new file mode 100644 index 00000000..e9b76325 --- /dev/null +++ b/lib/pages/routines/widgets/value_display.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class ValueDisplay extends StatelessWidget { + final dynamic value; + final String label; + + const ValueDisplay({ + required this.value, + required this.label, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + label, + style: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ); + } +} From 551779c33ae1abbe22db0094ca0dffa9b2c7a476 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 14:41:58 +0300 Subject: [PATCH 072/238] moved widgets to their own files. --- .../ceiling_sensor/ceiling_sensor_dialog.dart | 91 +------------------ .../cps_dialog_value_selector.dart | 58 ++++++++++++ .../ceiling_sensor/cps_functions_list.dart | 40 ++++++++ 3 files changed, 101 insertions(+), 88 deletions(-) create mode 100644 lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_value_selector.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index 0f19f204..50756c69 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -7,9 +7,8 @@ import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functi import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; -import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart'; -import 'package:syncrow_web/pages/routines/widgets/routine_dialog_selection_list_tile.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_value_selector.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart'; class CeilingSensorDialog extends StatefulWidget { const CeilingSensorDialog({ @@ -20,6 +19,7 @@ class CeilingSensorDialog extends StatefulWidget { required this.dialogType, super.key, }); + final String? uniqueCustomId; final List functions; final List deviceSelectedFunctions; @@ -142,88 +142,3 @@ class _CeilingSensorDialogState extends State { ); } } - -class CpsFunctionsList extends StatelessWidget { - const CpsFunctionsList({required this.cpsFunctions, super.key}); - - final List cpsFunctions; - - @override - Widget build(BuildContext context) { - return SizedBox( - width: 360, - child: ListView.separated( - shrinkWrap: false, - itemCount: cpsFunctions.length, - separatorBuilder: (context, index) => const Padding( - padding: EdgeInsets.symmetric(horizontal: 40.0), - child: Divider(color: ColorsManager.dividerColor), - ), - itemBuilder: (context, index) { - final function = cpsFunctions[index]; - return RoutineDialogFunctionListTile( - iconPath: function.icon, - operationName: function.operationName, - onTap: () => context.read().add( - SelectFunction( - functionCode: function.code, - operationName: function.operationName, - ), - ), - ); - }, - ), - ); - } -} - -class CpsDialogValueSelector extends StatelessWidget { - const CpsDialogValueSelector({ - required this.operations, - required this.selectedFunction, - required this.selectedFunctionData, - required this.cpsFunctions, - required this.device, - required this.operationName, - super.key, - }); - - final List operations; - final String selectedFunction; - final DeviceFunctionData? selectedFunctionData; - final List cpsFunctions; - final AllDevicesModel? device; - final String operationName; - - @override - Widget build(BuildContext context) { - return ListView.builder( - itemCount: operations.length, - itemBuilder: (context, index) { - final operation = operations[index]; - final isSelected = selectedFunctionData?.value == operation.value; - return RoutineDialogSelectionListTile( - iconPath: operation.icon, - description: operation.description, - isSelected: isSelected, - onTap: () { - if (!isSelected) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: selectedFunction, - operationName: operationName, - value: operation.value, - condition: selectedFunctionData?.condition, - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - } - }, - ); - }, - ); - } -} diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_value_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_value_selector.dart new file mode 100644 index 00000000..504017a2 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_value_selector.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.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/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialog_selection_list_tile.dart'; + +class CpsDialogValueSelector extends StatelessWidget { + const CpsDialogValueSelector({ + required this.operations, + required this.selectedFunction, + required this.selectedFunctionData, + required this.cpsFunctions, + required this.device, + required this.operationName, + super.key, + }); + + final List operations; + final String selectedFunction; + final DeviceFunctionData? selectedFunctionData; + final List cpsFunctions; + final AllDevicesModel? device; + final String operationName; + + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: operations.length, + itemBuilder: (context, index) { + final operation = operations[index]; + final isSelected = selectedFunctionData?.value == operation.value; + return RoutineDialogSelectionListTile( + iconPath: operation.icon, + description: operation.description, + isSelected: isSelected, + onTap: () { + if (!isSelected) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: operationName, + value: operation.value, + condition: selectedFunctionData?.condition, + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + } + }, + ); + }, + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart new file mode 100644 index 00000000..2a35428a --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CpsFunctionsList extends StatelessWidget { + const CpsFunctionsList({required this.cpsFunctions, super.key}); + + final List cpsFunctions; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 360, + child: ListView.separated( + shrinkWrap: false, + itemCount: cpsFunctions.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), + child: Divider(color: ColorsManager.dividerColor), + ), + itemBuilder: (context, index) { + final function = cpsFunctions[index]; + return RoutineDialogFunctionListTile( + iconPath: function.icon, + operationName: function.operationName, + onTap: () => context.read().add( + SelectFunction( + functionCode: function.code, + operationName: function.operationName, + ), + ), + ); + }, + ), + ); + } +} From cf103d5a7c1e67912688afab5f2f81906a600deb Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 15:15:20 +0300 Subject: [PATCH 073/238] Add CpsDialogSliderSelector for enhanced slider functionality and integrate with CeilingSensorDialog --- .../ceiling_sensor/ceiling_sensor_dialog.dart | 31 ++++-- .../cps_dialog_slider_selector.dart | 105 ++++++++++++++++++ .../widgets/slider_value_selector.dart | 16 +-- 3 files changed, 129 insertions(+), 23 deletions(-) create mode 100644 lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index 50756c69..7b51d064 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -7,6 +7,8 @@ import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functi import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_value_selector.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart'; @@ -122,21 +124,32 @@ class _CeilingSensorDialogState extends State { ), ); final operations = selectedCpsFunctions.getOperationalValues(); - + final isToggleFunction = + CeilingSensorHelper.toggleCodes.contains(selectedFunction); return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ CpsFunctionsList(cpsFunctions: _cpsFunctions), if (state.selectedFunction != null) Expanded( - child: CpsDialogValueSelector( - operations: operations, - selectedFunction: selectedFunction ?? '', - selectedFunctionData: selectedFunctionData, - cpsFunctions: _cpsFunctions, - operationName: selectedOperationName ?? '', - device: widget.device, - ), + child: isToggleFunction + ? CpsDialogValueSelector( + operations: operations, + selectedFunction: selectedFunction ?? '', + selectedFunctionData: selectedFunctionData, + cpsFunctions: _cpsFunctions, + operationName: selectedOperationName ?? '', + device: widget.device, + ) + : CpsDialogSliderSelector( + operations: operations, + selectedFunction: selectedFunction ?? '', + selectedFunctionData: selectedFunctionData, + cpsFunctions: _cpsFunctions, + operationName: selectedOperationName ?? '', + device: widget.device, + dialogType: widget.dialogType, + ), ), ], ); diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart new file mode 100644 index 00000000..d826afb5 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.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/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; + +class CpsDialogSliderSelector extends StatelessWidget { + const CpsDialogSliderSelector({ + required this.operations, + required this.selectedFunction, + required this.selectedFunctionData, + required this.cpsFunctions, + required this.device, + required this.operationName, + required this.dialogType, + super.key, + }); + + final List operations; + final String selectedFunction; + final DeviceFunctionData selectedFunctionData; + final List cpsFunctions; + final AllDevicesModel? device; + final String operationName; + final String dialogType; + + @override + Widget build(BuildContext context) { + return SliderValueSelector( + selectedFunction: selectedFunction, + functionData: selectedFunctionData, + device: device, + dialogType: dialogType, + sliderRange: _sliderRange, + displayedValue: _displayText, + initialValue: selectedFunctionData.value ?? 0, + onConditionChanged: (condition) => context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: selectedFunctionData.operationName, + condition: condition, + value: selectedFunctionData.value, + ), + ), + ), + onSliderChanged: (value) => context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: selectedFunctionData.operationName, + value: value.toInt(), + condition: selectedFunctionData.condition, + ), + ), + ), + ); + } + + double get sliderStepper { + return 1; + } + + (double, double) get _sliderRange => switch (selectedFunctionData.functionCode) { + 'moving_speed' => (0, 32), + 'space_static_val' => (0, 255), + 'space_move_val' => (0, 255), + 'moving_max_dis' => (0, 10), + 'static_max_dis' => (0, 10), + 'moving_range' => (0, 25.5), + 'presence_range' => (0, 25.5), + 'presence_judgement_threshold' => (0, 255), + 'motion_amplitude_trigger_threshold' => (0, 255), + 'perceptual_boundary' => (0, 5), + 'moving_boundary' => (0, 5), + 'moving_rigger_time' => (0, 2), + 'moving_static_time' => (0, 50), + 'none_body_time' => (0, 300), + _ => (0, 300), + }; + + String get _displayText { + final value = selectedFunctionData.value; + final parsedValue = double.tryParse('$value') ?? value; + + return switch (selectedFunctionData.functionCode) { + 'moving_max_dis' || + 'static_max_dis' || + 'moving_range' || + 'presence_range' || + 'perceptual_boundary' || + 'moving_boundary' => + '${parsedValue?.toStringAsFixed(1) ?? '0'} M', + 'moving_rigger_time' => '${parsedValue?.toStringAsFixed(3) ?? '0'} s', + 'moving_static_time' || + 'none_body_time' => + '${parsedValue?.toStringAsFixed(0) ?? '0'} s', + _ => '${parsedValue ?? 0}', + }; + } +} diff --git a/lib/pages/routines/widgets/slider_value_selector.dart b/lib/pages/routines/widgets/slider_value_selector.dart index 6b408008..82c59533 100644 --- a/lib/pages/routines/widgets/slider_value_selector.dart +++ b/lib/pages/routines/widgets/slider_value_selector.dart @@ -46,20 +46,8 @@ class SliderValueSelector extends StatelessWidget { const SizedBox(height: 20), FunctionSlider( initialValue: initialValue, - range: sliderRange, onChanged: onSliderChanged, - // void _updateValue(BuildContext context, int value) { - // context.read().add( - // AddFunction( - // functionData: DeviceFunctionData( - // entityId: device?.uuid ?? '', - // functionCode: functionCode, - // operationName: functionData.operationName, - // value: value, - // condition: functionData.condition, - // ), - // ), - // ); - // } + range: sliderRange, + onChanged: onSliderChanged, ), ], ); From fadb23d631a6c9ce3bb5febbcb941e12f171db24 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 15:26:36 +0300 Subject: [PATCH 074/238] Refactor SliderValueSelector layout by removing unnecessary SizedBox and adjusting spacing for improved UI consistency. --- lib/pages/routines/widgets/slider_value_selector.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pages/routines/widgets/slider_value_selector.dart b/lib/pages/routines/widgets/slider_value_selector.dart index 82c59533..dd26caf1 100644 --- a/lib/pages/routines/widgets/slider_value_selector.dart +++ b/lib/pages/routines/widgets/slider_value_selector.dart @@ -32,9 +32,9 @@ class SliderValueSelector extends StatelessWidget { @override Widget build(BuildContext context) { return Column( + spacing: 16, mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox(height: 20), ConditionToggle( currentCondition: functionData.condition, onChanged: onConditionChanged, @@ -43,7 +43,6 @@ class SliderValueSelector extends StatelessWidget { value: initialValue, label: displayedValue, ), - const SizedBox(height: 20), FunctionSlider( initialValue: initialValue, range: sliderRange, From 74046c5aede8d97ed0497daf2f69ce143087fd2b Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 16:22:02 +0300 Subject: [PATCH 075/238] Refactor SliderValueSelector and ValueDisplay to include unit handling and improve UI consistency across sensor dialogs. --- .../cps_dialog_slider_selector.dart | 30 ++-- .../wps_value_selector_widget.dart | 18 ++- .../widgets/slider_value_selector.dart | 132 +++++++++++++++--- lib/pages/routines/widgets/value_display.dart | 4 +- 4 files changed, 145 insertions(+), 39 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index d826afb5..3c792eca 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -29,13 +29,12 @@ class CpsDialogSliderSelector extends StatelessWidget { @override Widget build(BuildContext context) { return SliderValueSelector( - selectedFunction: selectedFunction, - functionData: selectedFunctionData, - device: device, + currentCondition: selectedFunctionData.condition, dialogType: dialogType, sliderRange: _sliderRange, displayedValue: _displayText, initialValue: selectedFunctionData.value ?? 0, + unit: _unit, onConditionChanged: (condition) => context.read().add( AddFunction( functionData: DeviceFunctionData( @@ -58,13 +57,10 @@ class CpsDialogSliderSelector extends StatelessWidget { ), ), ), + ); } - double get sliderStepper { - return 1; - } - (double, double) get _sliderRange => switch (selectedFunctionData.functionCode) { 'moving_speed' => (0, 32), 'space_static_val' => (0, 255), @@ -94,12 +90,26 @@ class CpsDialogSliderSelector extends StatelessWidget { 'presence_range' || 'perceptual_boundary' || 'moving_boundary' => - '${parsedValue?.toStringAsFixed(1) ?? '0'} M', - 'moving_rigger_time' => '${parsedValue?.toStringAsFixed(3) ?? '0'} s', + '${parsedValue?.toStringAsFixed(1) ?? '0'}', + 'moving_rigger_time' => '${parsedValue?.toStringAsFixed(3) ?? '0'}', 'moving_static_time' || 'none_body_time' => - '${parsedValue?.toStringAsFixed(0) ?? '0'} s', + '${parsedValue?.toStringAsFixed(0) ?? '0'}', _ => '${parsedValue ?? 0}', }; } + + String get _unit { + return switch (selectedFunctionData.functionCode) { + 'moving_max_dis' || + 'static_max_dis' || + 'moving_range' || + 'presence_range' || + 'perceptual_boundary' || + 'moving_boundary' => + 'M', + 'moving_rigger_time' || 'moving_static_time' || 'none_body_time' => 'sec', + _ => '', + }; + } } diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart index 5f38b240..ccd82e23 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart @@ -32,9 +32,7 @@ class WpsValueSelectorWidget extends StatelessWidget { if (_isSliderFunction(selectedFunction)) { return SliderValueSelector( - selectedFunction: selectedFunction, - functionData: functionData, - device: device, + currentCondition: functionData.condition, dialogType: dialogType, sliderRange: sliderRange, displayedValue: getDisplayText, @@ -61,6 +59,7 @@ class WpsValueSelectorWidget extends StatelessWidget { ), ), ), + unit: _unit, ); } @@ -86,10 +85,17 @@ class WpsValueSelectorWidget extends StatelessWidget { String get getDisplayText { final intValue = int.tryParse('${functionData.value ?? ''}'); return switch (functionData.functionCode) { - 'presence_time' => '${intValue ?? '0'} Min', - 'dis_current' => '${intValue ?? '250'} CM', - 'illuminance_value' => '${intValue ?? '0'} Lux', + 'presence_time' => '${intValue ?? '0'}', + 'dis_current' => '${intValue ?? '250'}', + 'illuminance_value' => '${intValue ?? '0'}', _ => '$intValue', }; } + + String get _unit => switch (functionData.functionCode) { + 'presence_time' => 'Min', + 'dis_current' => 'CM', + 'illuminance_value' => 'Lux', + _ => '', + }; } diff --git a/lib/pages/routines/widgets/slider_value_selector.dart b/lib/pages/routines/widgets/slider_value_selector.dart index dd26caf1..dfcfeac4 100644 --- a/lib/pages/routines/widgets/slider_value_selector.dart +++ b/lib/pages/routines/widgets/slider_value_selector.dart @@ -1,54 +1,142 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:flutter/services.dart'; import 'package:syncrow_web/pages/routines/widgets/condition_toggle.dart'; import 'package:syncrow_web/pages/routines/widgets/function_slider.dart'; import 'package:syncrow_web/pages/routines/widgets/value_display.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class SliderValueSelector extends StatelessWidget { - final String selectedFunction; - final DeviceFunctionData functionData; - final AllDevicesModel? device; + final String? currentCondition; final String dialogType; final (double, double) sliderRange; final String displayedValue; final Object? initialValue; final void Function(String condition) onConditionChanged; final void Function(double value) onSliderChanged; + final String unit; const SliderValueSelector({ - required this.selectedFunction, - required this.functionData, - required this.device, required this.dialogType, required this.sliderRange, required this.displayedValue, required this.initialValue, required this.onConditionChanged, required this.onSliderChanged, + required this.currentCondition, + required this.unit, super.key, }); @override Widget build(BuildContext context) { - return Column( - spacing: 16, + if (dialogType == 'IF') { + return Column( + spacing: 16, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ConditionToggle( + currentCondition: currentCondition, + onChanged: onConditionChanged, + ), + ValueDisplay( + value: initialValue, + label: displayedValue, + unit: unit, + ), + FunctionSlider( + initialValue: initialValue, + range: sliderRange, + onChanged: onSliderChanged, + ), + ], + ); + } + + return Row( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ - ConditionToggle( - currentCondition: functionData.condition, - onChanged: onConditionChanged, - ), - ValueDisplay( - value: initialValue, - label: displayedValue, - ), - FunctionSlider( - initialValue: initialValue, - range: sliderRange, - onChanged: onSliderChanged, + const Spacer(), + Expanded( + flex: 2, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + onChanged: (value) => onSliderChanged(double.tryParse(value) ?? 0), + expands: false, + onTapOutside: (_) => FocusScope.of(context).unfocus(), + initialValue: displayedValue, + style: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + inputFormatters: [ + RangeInputFormatter(min: sliderRange.$1, max: sliderRange.$2), + ], + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(8), + ), + filled: true, + fillColor: ColorsManager.textFieldGreyColor.withOpacity(0.5), + suffixText: unit, + hintStyle: context.textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + ), + ), + ), + const SizedBox(height: 8), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Min: ${sliderRange.$1}', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.lightGrayColor, + ), + ), + const Spacer(), + Text( + 'Max: ${sliderRange.$2}', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.lightGrayColor, + ), + ), + ], + ), + ], + ), ), + const Spacer(), ], ); } } + +class RangeInputFormatter extends TextInputFormatter { + const RangeInputFormatter({required this.min, required this.max}); + + final double min; + final double max; + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + final text = newValue.text; + if (text.isEmpty) { + return newValue; + } + + final value = double.tryParse(text); + if (value == null || value < min || value > max) { + return oldValue; + } + + return newValue; + } +} diff --git a/lib/pages/routines/widgets/value_display.dart b/lib/pages/routines/widgets/value_display.dart index e9b76325..a47fb5b4 100644 --- a/lib/pages/routines/widgets/value_display.dart +++ b/lib/pages/routines/widgets/value_display.dart @@ -5,11 +5,13 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; class ValueDisplay extends StatelessWidget { final dynamic value; final String label; + final String unit; const ValueDisplay({ required this.value, required this.label, super.key, + required this.unit, }); @override @@ -21,7 +23,7 @@ class ValueDisplay extends StatelessWidget { borderRadius: BorderRadius.circular(10), ), child: Text( - label, + '$label$unit', style: context.textTheme.headlineMedium!.copyWith( color: ColorsManager.primaryColorWithOpacity, ), From a5d26d04eb5d26f8949dea3b53a19f81f11303a0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 16:35:19 +0300 Subject: [PATCH 076/238] Remove unused variables from CeilingSensorDialog to streamline state management --- .../ceiling_sensor/ceiling_sensor_dialog.dart | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index 7b51d064..9190bf37 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -54,15 +54,6 @@ class _CeilingSensorDialogState extends State { content: BlocBuilder( builder: (context, state) { final selectedFunction = state.selectedFunction; - final selectedOperationName = state.selectedOperationName; - final selectedFunctionData = state.addedFunctions - .firstWhere((f) => f.functionCode == selectedFunction, - orElse: () => DeviceFunctionData( - entityId: '', - functionCode: selectedFunction ?? '', - operationName: '', - value: null, - )); return Container( width: selectedFunction != null ? 600 : 360, From 49439816d5c8ddca8f8e8f57cb1b19f62e59ce22 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 10 Apr 2025 16:43:33 +0300 Subject: [PATCH 077/238] Update SliderValueSelector to improve text styling for unit and range labels --- lib/pages/routines/widgets/slider_value_selector.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/routines/widgets/slider_value_selector.dart b/lib/pages/routines/widgets/slider_value_selector.dart index dfcfeac4..db5c4675 100644 --- a/lib/pages/routines/widgets/slider_value_selector.dart +++ b/lib/pages/routines/widgets/slider_value_selector.dart @@ -83,7 +83,7 @@ class SliderValueSelector extends StatelessWidget { filled: true, fillColor: ColorsManager.textFieldGreyColor.withOpacity(0.5), suffixText: unit, - hintStyle: context.textTheme.headlineMedium!.copyWith( + suffixStyle: context.textTheme.headlineMedium!.copyWith( color: ColorsManager.primaryColorWithOpacity, ), ), @@ -94,14 +94,14 @@ class SliderValueSelector extends StatelessWidget { children: [ Text( 'Min: ${sliderRange.$1}', - style: context.textTheme.bodySmall!.copyWith( + style: context.textTheme.labelSmall!.copyWith( color: ColorsManager.lightGrayColor, ), ), const Spacer(), Text( 'Max: ${sliderRange.$2}', - style: context.textTheme.bodySmall!.copyWith( + style: context.textTheme.labelSmall!.copyWith( color: ColorsManager.lightGrayColor, ), ), From ebbecb9738854f8251b78f184dd860cd9fd108b0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 09:18:09 +0300 Subject: [PATCH 078/238] Update toggleCodes in CeilingSensorHelper to replace 'self_test_result' and 'movement' with 'checking_result' and 'body_movement' for improved clarity. --- .../ceiling_sensor/ceiling_sensor_helper.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart index 291c2f81..940daf23 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart @@ -166,11 +166,11 @@ abstract final class CeilingSensorHelper { static const toggleCodes = { 'radar_switch', 'space_para_switch', - 'self_test_result', + 'checking_result', 'nobody_time', - 'movement', + 'body_movement', 'custom_mode', - 'space_type', + 'scene', 'presence_state', }; } From 828db5d5e4105e5ddccf0e0633dcfce447de3813 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 09:57:45 +0300 Subject: [PATCH 079/238] Refactor CpsDialogSliderSelector to improve value parsing and formatting in _displayText method --- .../ceiling_sensor/cps_dialog_slider_selector.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index 3c792eca..53f66b67 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -57,7 +57,6 @@ class CpsDialogSliderSelector extends StatelessWidget { ), ), ), - ); } @@ -81,7 +80,7 @@ class CpsDialogSliderSelector extends StatelessWidget { String get _displayText { final value = selectedFunctionData.value; - final parsedValue = double.tryParse('$value') ?? value; + final parsedValue = double.tryParse('$value'); return switch (selectedFunctionData.functionCode) { 'moving_max_dis' || @@ -90,12 +89,12 @@ class CpsDialogSliderSelector extends StatelessWidget { 'presence_range' || 'perceptual_boundary' || 'moving_boundary' => - '${parsedValue?.toStringAsFixed(1) ?? '0'}', - 'moving_rigger_time' => '${parsedValue?.toStringAsFixed(3) ?? '0'}', + parsedValue?.toStringAsFixed(1) ?? '0', + 'moving_rigger_time' => parsedValue?.toStringAsFixed(2) ?? '0', 'moving_static_time' || 'none_body_time' => - '${parsedValue?.toStringAsFixed(0) ?? '0'}', - _ => '${parsedValue ?? 0}', + parsedValue?.toStringAsFixed(0) ?? '0', + _ => '${parsedValue?.toStringAsFixed(0) ?? 0}', }; } From 60a1a9ad6f2c1853f0d92a5c335a1298bfe84862 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 10:09:39 +0300 Subject: [PATCH 080/238] Add dividendOfRange parameter to FunctionSlider and related components for improved range handling --- .../routines/widgets/function_slider.dart | 21 ++++++++++++----- .../cps_dialog_slider_selector.dart | 23 ++++++++++++++++++- .../wps_value_selector_widget.dart | 1 + .../widgets/slider_value_selector.dart | 3 +++ 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/lib/pages/routines/widgets/function_slider.dart b/lib/pages/routines/widgets/function_slider.dart index b7fb0d53..50167a7b 100644 --- a/lib/pages/routines/widgets/function_slider.dart +++ b/lib/pages/routines/widgets/function_slider.dart @@ -2,25 +2,34 @@ import 'package:flutter/material.dart'; class FunctionSlider extends StatelessWidget { final dynamic initialValue; - final (double min, double max) range; final void Function(double value) onChanged; + final double dividendOfRange; const FunctionSlider({ required this.onChanged, required this.initialValue, required this.range, + required this.dividendOfRange, super.key, }); @override Widget build(BuildContext context) { + final (min, max) = range; + final bool isValidRange = max > min; + final double value = initialValue is int + ? (initialValue as int).toDouble() + : (initialValue as double); + + final int? divisions = isValidRange ? ((max - min) / dividendOfRange).round() : null; + return Slider( - value: initialValue is int ? initialValue.toDouble() : range.$1, - min: range.$1, - max: range.$2, - divisions: (range.$2 - range.$1).toInt(), - onChanged: onChanged, + value: value.clamp(min, max), + min: min, + max: max, + divisions: divisions, + onChanged: isValidRange ? onChanged : null, ); } } diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index 53f66b67..2e399888 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -52,11 +52,12 @@ class CpsDialogSliderSelector extends StatelessWidget { entityId: device?.uuid ?? '', functionCode: selectedFunction, operationName: selectedFunctionData.operationName, - value: value.toInt(), + value: value, condition: selectedFunctionData.condition, ), ), ), + dividendOfRange: _dividendOfRange, ); } @@ -111,4 +112,24 @@ class CpsDialogSliderSelector extends StatelessWidget { _ => '', }; } + + double get _dividendOfRange => switch (selectedFunctionData.functionCode) { + 'sensitivity' => 1, + 'moving_speed' => 1, + 'space_static_val' => 1, + 'space_move_val' => 1, + 'presence_reference' => 5, + 'moving_reference' => 5, + 'moving_max_dis' => 0.5, + 'static_max_dis' => 0.5, + 'moving_range' => 0.1, + 'presence_range' => 0.1, + 'perceptual_boundary' => 0.5, + 'moving_boundary' => 0.5, + 'moving_rigger_time' => 0.1, + 'moving_static_time' => 1.0, + 'none_body_time' => 5.0, + 'sports_para' => 1.0, + _ => 1, + }; } diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart index ccd82e23..fcdc991a 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart @@ -60,6 +60,7 @@ class WpsValueSelectorWidget extends StatelessWidget { ), ), unit: _unit, + dividendOfRange: 1, ); } diff --git a/lib/pages/routines/widgets/slider_value_selector.dart b/lib/pages/routines/widgets/slider_value_selector.dart index db5c4675..bc35e674 100644 --- a/lib/pages/routines/widgets/slider_value_selector.dart +++ b/lib/pages/routines/widgets/slider_value_selector.dart @@ -15,6 +15,7 @@ class SliderValueSelector extends StatelessWidget { final void Function(String condition) onConditionChanged; final void Function(double value) onSliderChanged; final String unit; + final double dividendOfRange; const SliderValueSelector({ required this.dialogType, @@ -25,6 +26,7 @@ class SliderValueSelector extends StatelessWidget { required this.onSliderChanged, required this.currentCondition, required this.unit, + required this.dividendOfRange, super.key, }); @@ -48,6 +50,7 @@ class SliderValueSelector extends StatelessWidget { initialValue: initialValue, range: sliderRange, onChanged: onSliderChanged, + dividendOfRange: dividendOfRange, ), ], ); From c62e8b3f1541fa38c2de8ba7456acf011391b08a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 10:14:35 +0300 Subject: [PATCH 081/238] Update _displayText method to format values with two decimal places for improved precision --- .../ceiling_sensor/cps_dialog_slider_selector.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index 2e399888..d7f4b688 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -94,7 +94,7 @@ class CpsDialogSliderSelector extends StatelessWidget { 'moving_rigger_time' => parsedValue?.toStringAsFixed(2) ?? '0', 'moving_static_time' || 'none_body_time' => - parsedValue?.toStringAsFixed(0) ?? '0', + parsedValue?.toStringAsFixed(2) ?? '0', _ => '${parsedValue?.toStringAsFixed(0) ?? 0}', }; } From dcbca6481414c02c5c3c8481ede2a2db3f9f2276 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 10:16:20 +0300 Subject: [PATCH 082/238] Fix spacing in ValueDisplay text output for improved readability --- lib/pages/routines/widgets/value_display.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/routines/widgets/value_display.dart b/lib/pages/routines/widgets/value_display.dart index a47fb5b4..6a8bd949 100644 --- a/lib/pages/routines/widgets/value_display.dart +++ b/lib/pages/routines/widgets/value_display.dart @@ -23,7 +23,7 @@ class ValueDisplay extends StatelessWidget { borderRadius: BorderRadius.circular(10), ), child: Text( - '$label$unit', + '$label $unit ', style: context.textTheme.headlineMedium!.copyWith( color: ColorsManager.primaryColorWithOpacity, ), From fa8f29ff71dedd706e667a8415e1c0d9cb598ed4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 10:41:23 +0300 Subject: [PATCH 083/238] Add sensitivity feature. --- assets/icons/sensitivity_feature_1.svg | 5 +++++ assets/icons/sensitivity_feature_2.svg | 5 +++++ assets/icons/sensitivity_feature_3.svg | 5 +++++ assets/icons/sensitivity_feature_4.svg | 5 +++++ assets/icons/sensitivity_feature_5.svg | 5 +++++ assets/icons/sensitivity_feature_6.svg | 5 +++++ assets/icons/sensitivity_feature_7.svg | 5 +++++ assets/icons/sensitivity_feature_8.svg | 5 +++++ assets/icons/sensitivity_feature_9.svg | 5 +++++ lib/utils/constants/assets.dart | 9 +++++++++ 10 files changed, 54 insertions(+) create mode 100644 assets/icons/sensitivity_feature_1.svg create mode 100644 assets/icons/sensitivity_feature_2.svg create mode 100644 assets/icons/sensitivity_feature_3.svg create mode 100644 assets/icons/sensitivity_feature_4.svg create mode 100644 assets/icons/sensitivity_feature_5.svg create mode 100644 assets/icons/sensitivity_feature_6.svg create mode 100644 assets/icons/sensitivity_feature_7.svg create mode 100644 assets/icons/sensitivity_feature_8.svg create mode 100644 assets/icons/sensitivity_feature_9.svg diff --git a/assets/icons/sensitivity_feature_1.svg b/assets/icons/sensitivity_feature_1.svg new file mode 100644 index 00000000..21bebd7a --- /dev/null +++ b/assets/icons/sensitivity_feature_1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/sensitivity_feature_2.svg b/assets/icons/sensitivity_feature_2.svg new file mode 100644 index 00000000..7370ab6f --- /dev/null +++ b/assets/icons/sensitivity_feature_2.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/sensitivity_feature_3.svg b/assets/icons/sensitivity_feature_3.svg new file mode 100644 index 00000000..23b92c43 --- /dev/null +++ b/assets/icons/sensitivity_feature_3.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/sensitivity_feature_4.svg b/assets/icons/sensitivity_feature_4.svg new file mode 100644 index 00000000..7a92045f --- /dev/null +++ b/assets/icons/sensitivity_feature_4.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/sensitivity_feature_5.svg b/assets/icons/sensitivity_feature_5.svg new file mode 100644 index 00000000..5f056602 --- /dev/null +++ b/assets/icons/sensitivity_feature_5.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/sensitivity_feature_6.svg b/assets/icons/sensitivity_feature_6.svg new file mode 100644 index 00000000..288b172e --- /dev/null +++ b/assets/icons/sensitivity_feature_6.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/sensitivity_feature_7.svg b/assets/icons/sensitivity_feature_7.svg new file mode 100644 index 00000000..5779dfd2 --- /dev/null +++ b/assets/icons/sensitivity_feature_7.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/sensitivity_feature_8.svg b/assets/icons/sensitivity_feature_8.svg new file mode 100644 index 00000000..4816b1ba --- /dev/null +++ b/assets/icons/sensitivity_feature_8.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/sensitivity_feature_9.svg b/assets/icons/sensitivity_feature_9.svg new file mode 100644 index 00000000..978145b4 --- /dev/null +++ b/assets/icons/sensitivity_feature_9.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 7bcd1484..6973492e 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -448,4 +448,13 @@ class Assets { static const String presenceJudgementThrshold = 'assets/icons/presence_judgement_threshold.svg'; static const String spaceType = 'assets/icons/space_type.svg'; static const String sportsPara = 'assets/icons/sports_para.svg'; + static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg'; + static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg'; + static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg'; + static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.svg'; + static const String sensitivityFeature5 = 'assets/icons/sensitivity_feature_5.svg'; + static const String sensitivityFeature6 = 'assets/icons/sensitivity_feature_6.svg'; + static const String sensitivityFeature7 = 'assets/icons/sensitivity_feature_7.svg'; + static const String sensitivityFeature8 = 'assets/icons/sensitivity_feature_8.svg'; + static const String sensitivityFeature9 = 'assets/icons/sensitivity_feature_9.svg'; } From c54fd780b7d9e5bf27ae50641e6e532e5e7bef90 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 10:41:40 +0300 Subject: [PATCH 084/238] Update sensitivity function to adjust max value and add image assets; modify dialog logic for sensitivity handling --- .../ceiling_presence_sensor_functions.dart | 16 +++++++++++++++- .../ceiling_sensor/ceiling_sensor_dialog.dart | 7 +++++-- .../cps_dialog_slider_selector.dart | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart index 6e181407..8f3eeb5e 100644 --- a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -98,13 +98,27 @@ final class CpsSensitivityFunction extends CpsFunctions { final int max; final int step; + static const _images = [ + Assets.sensitivityFeature1, + Assets.sensitivityFeature1, + Assets.sensitivityFeature2, + Assets.sensitivityFeature3, + Assets.sensitivityFeature4, + Assets.sensitivityFeature5, + Assets.sensitivityFeature6, + Assets.sensitivityFeature7, + Assets.sensitivityFeature8, + Assets.sensitivityFeature9, + Assets.sensitivityFeature9, + ]; + @override List getOperationalValues() { final values = []; for (var value = min; value <= max; value += step) { values.add( CpsOperationalValue( - icon: Assets.sensitivity, + icon: _images[value], description: '$value', value: value, ), diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index 9190bf37..dc71a595 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -115,8 +115,11 @@ class _CeilingSensorDialogState extends State { ), ); final operations = selectedCpsFunctions.getOperationalValues(); - final isToggleFunction = - CeilingSensorHelper.toggleCodes.contains(selectedFunction); + final isSensitivityFunction = selectedFunction == 'sensitivity'; + final isToggleFunction = isSensitivityFunction + ? widget.dialogType == 'THEN' + : CeilingSensorHelper.toggleCodes.contains(selectedFunction); + return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index d7f4b688..59f48fbd 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -63,6 +63,7 @@ class CpsDialogSliderSelector extends StatelessWidget { (double, double) get _sliderRange => switch (selectedFunctionData.functionCode) { 'moving_speed' => (0, 32), + 'sensitivity' => (0, 10), 'space_static_val' => (0, 255), 'space_move_val' => (0, 255), 'moving_max_dis' => (0, 10), From 36e88d033be2a5270c556f2125e1dded88edc213 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 10:56:19 +0300 Subject: [PATCH 085/238] Refactor SliderValueSelector layout for improved readability and maintainability --- .../widgets/slider_value_selector.dart | 94 +++---------------- 1 file changed, 15 insertions(+), 79 deletions(-) diff --git a/lib/pages/routines/widgets/slider_value_selector.dart b/lib/pages/routines/widgets/slider_value_selector.dart index bc35e674..526e6fa0 100644 --- a/lib/pages/routines/widgets/slider_value_selector.dart +++ b/lib/pages/routines/widgets/slider_value_selector.dart @@ -3,8 +3,6 @@ import 'package:flutter/services.dart'; import 'package:syncrow_web/pages/routines/widgets/condition_toggle.dart'; import 'package:syncrow_web/pages/routines/widgets/function_slider.dart'; import 'package:syncrow_web/pages/routines/widgets/value_display.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; class SliderValueSelector extends StatelessWidget { final String? currentCondition; @@ -32,88 +30,26 @@ class SliderValueSelector extends StatelessWidget { @override Widget build(BuildContext context) { - if (dialogType == 'IF') { - return Column( - spacing: 16, - mainAxisAlignment: MainAxisAlignment.center, - children: [ + return Column( + spacing: 16, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (dialogType == 'IF') ConditionToggle( currentCondition: currentCondition, onChanged: onConditionChanged, ), - ValueDisplay( - value: initialValue, - label: displayedValue, - unit: unit, - ), - FunctionSlider( - initialValue: initialValue, - range: sliderRange, - onChanged: onSliderChanged, - dividendOfRange: dividendOfRange, - ), - ], - ); - } - - return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - const Spacer(), - Expanded( - flex: 2, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextFormField( - onChanged: (value) => onSliderChanged(double.tryParse(value) ?? 0), - expands: false, - onTapOutside: (_) => FocusScope.of(context).unfocus(), - initialValue: displayedValue, - style: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - inputFormatters: [ - RangeInputFormatter(min: sliderRange.$1, max: sliderRange.$2), - ], - decoration: InputDecoration( - border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(8), - ), - filled: true, - fillColor: ColorsManager.textFieldGreyColor.withOpacity(0.5), - suffixText: unit, - suffixStyle: context.textTheme.headlineMedium!.copyWith( - color: ColorsManager.primaryColorWithOpacity, - ), - ), - ), - const SizedBox(height: 8), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Min: ${sliderRange.$1}', - style: context.textTheme.labelSmall!.copyWith( - color: ColorsManager.lightGrayColor, - ), - ), - const Spacer(), - Text( - 'Max: ${sliderRange.$2}', - style: context.textTheme.labelSmall!.copyWith( - color: ColorsManager.lightGrayColor, - ), - ), - ], - ), - ], - ), + ValueDisplay( + value: initialValue, + label: displayedValue, + unit: unit, + ), + FunctionSlider( + initialValue: initialValue, + range: sliderRange, + onChanged: onSliderChanged, + dividendOfRange: dividendOfRange, ), - const Spacer(), ], ); } From 11feaf7c877227eeeda24010384c82b805499581 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 11:10:51 +0300 Subject: [PATCH 086/238] passed correct operation name to `onSliderChanged` and onConditionChanged`. in `CpsDialogSliderSelector`. --- .../ceiling_sensor/cps_dialog_slider_selector.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index 59f48fbd..49c3ac2e 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -40,7 +40,7 @@ class CpsDialogSliderSelector extends StatelessWidget { functionData: DeviceFunctionData( entityId: device?.uuid ?? '', functionCode: selectedFunction, - operationName: selectedFunctionData.operationName, + operationName: operationName, condition: condition, value: selectedFunctionData.value, ), @@ -51,7 +51,7 @@ class CpsDialogSliderSelector extends StatelessWidget { functionData: DeviceFunctionData( entityId: device?.uuid ?? '', functionCode: selectedFunction, - operationName: selectedFunctionData.operationName, + operationName: operationName, value: value, condition: selectedFunctionData.condition, ), From 97eb1c152b5846abf4a4a4a29899a6e676f683ea Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 13 Apr 2025 12:18:56 +0300 Subject: [PATCH 087/238] Implement a countdown timer for the AC and fix bugs in the 'Forgot Password' --- .../auth/view/forget_password_web_page.dart | 19 +- .../device_managment/ac/bloc/ac_bloc.dart | 193 ++++++++++++++++-- .../device_managment/ac/bloc/ac_event.dart | 30 +++ .../device_managment/ac/bloc/ac_state.dart | 38 +++- .../device_managment/ac/model/ac_model.dart | 9 + .../ac/view/ac_device_control.dart | 132 ++++++++---- 6 files changed, 344 insertions(+), 77 deletions(-) diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index f389f44f..7686ea8f 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -201,20 +201,17 @@ class ForgetPasswordWebPage extends StatelessWidget { !state.isButtonEnabled && state.remainingTime != 1 ? null - : () { + : + () { if (forgetBloc - .forgetEmailKey.currentState! - .validate() || - forgetBloc - .forgetRegionKey.currentState! - .validate()) { - if (forgetBloc - .forgetRegionKey.currentState! - .validate()) { - forgetBloc.add(StartTimerEvent()); - } + .forgetEmailKey + .currentState! + .validate()) { + forgetBloc.add( + StartTimerEvent()); } }, + child: Text( 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', style: TextStyle( diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart index 7c35034e..076e9050 100644 --- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -13,6 +13,7 @@ class AcBloc extends Bloc { late AcStatusModel deviceStatus; final String deviceId; Timer? _timer; + Timer? _countdownTimer; AcBloc({required this.deviceId}) : super(AcsInitialState()) { on(_onFetchAcStatus); @@ -21,7 +22,16 @@ class AcBloc extends Bloc { on(_onAcBatchControl); on(_onFactoryReset); on(_onAcStatusUpdated); + on(_onClose); + on(_handleIncreaseTime); + on(_handleDecreaseTime); + on(_handleUpdateTimer); + on(_handleToggleTimer); + on(_handleApiCountdownValue); } + bool timerActive = false; + int scheduledHours = 0; + int scheduledMinutes = 0; FutureOr _onFetchAcStatus( AcFetchDeviceStatusEvent event, Emitter emit) async { @@ -30,8 +40,23 @@ class AcBloc extends Bloc { final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); + if (deviceStatus.countdown1 != 0) { + // Convert API value to minutes + final totalMinutes = deviceStatus.countdown1 * 6; + scheduledHours = totalMinutes ~/ 60; + scheduledMinutes = totalMinutes % 60; + timerActive = true; + _startCountdownTimer(emit); + } + + emit(ACStatusLoaded( + status: deviceStatus, + scheduledHours: scheduledHours, + scheduledMinutes: scheduledMinutes, + isTimerActive: timerActive, + )); + _listenToChanges(event.deviceId); - emit(ACStatusLoaded(deviceStatus)); } catch (e) { emit(AcsFailedState(error: e.toString())); } @@ -70,31 +95,16 @@ class AcBloc extends Bloc { void _onAcStatusUpdated(AcStatusUpdated event, Emitter emit) { deviceStatus = event.deviceStatus; - emit(ACStatusLoaded(deviceStatus)); + emit(ACStatusLoaded(status: deviceStatus)); } - // Future testFirebaseConnection() async { - // // Reference to a test node in your database - // final testRef = FirebaseDatabase.instance.ref("test"); - - // // Write a test value - // await testRef.set("Hello, Firebase!"); - - // // Listen for changes on the test node - // testRef.onValue.listen((DatabaseEvent event) { - // final data = event.snapshot.value; - // print("Data from Firebase: $data"); - // // If you see "Hello, Firebase!" printed in your console, it means the connection works. - // }); - // } - FutureOr _onAcControl( AcControlEvent event, Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value, emit); - emit(ACStatusLoaded(deviceStatus)); + emit(ACStatusLoaded(status: deviceStatus)); await _runDebounce( isBatch: false, @@ -151,7 +161,7 @@ class AcBloc extends Bloc { void _revertValueAndEmit( String deviceId, String code, dynamic oldValue, Emitter emit) { _updateLocalValue(code, oldValue, emit); - emit(ACStatusLoaded(deviceStatus)); + emit(ACStatusLoaded(status: deviceStatus)); } void _updateLocalValue(String code, dynamic value, Emitter emit) { @@ -184,11 +194,16 @@ class AcBloc extends Bloc { if (value is bool) { deviceStatus = deviceStatus.copyWith(childLock: value); } + + case 'countdown_time': + if (value is int) { + deviceStatus = deviceStatus.copyWith(countdown1: value); + } break; default: break; } - emit(ACStatusLoaded(deviceStatus)); + emit(ACStatusLoaded(status: deviceStatus)); } dynamic _getValueByCode(String code) { @@ -203,6 +218,8 @@ class AcBloc extends Bloc { return deviceStatus.fanSpeedsString; case 'child_lock': return deviceStatus.childLock; + case 'countdown_time': + return deviceStatus.countdown1; default: return null; } @@ -216,7 +233,7 @@ class AcBloc extends Bloc { await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status); - emit(ACStatusLoaded(deviceStatus)); + emit(ACStatusLoaded(status: deviceStatus)); } catch (e) { emit(AcsFailedState(error: e.toString())); } @@ -228,7 +245,7 @@ class AcBloc extends Bloc { _updateLocalValue(event.code, event.value, emit); - emit(ACStatusLoaded(deviceStatus)); + emit(ACStatusLoaded(status: deviceStatus)); await _runDebounce( isBatch: true, @@ -257,4 +274,136 @@ class AcBloc extends Bloc { emit(AcsFailedState(error: e.toString())); } } + + void _onClose(OnClose event, Emitter emit) { + _countdownTimer?.cancel(); + _timer?.cancel(); + } + + void _handleIncreaseTime(IncreaseTimeEvent event, Emitter emit) { + if (state is! ACStatusLoaded) return; + final currentState = state as ACStatusLoaded; + int newHours = scheduledHours; + int newMinutes = scheduledMinutes + 30; + newHours += newMinutes ~/ 60; + newMinutes = newMinutes % 60; + if (newHours > 23) { + newHours = 23; + newMinutes = 59; + } + scheduledHours = newHours; + scheduledMinutes = newMinutes; + + emit(currentState.copyWith( + scheduledHours: scheduledHours, + scheduledMinutes: scheduledMinutes, + )); + } + + void _handleDecreaseTime(DecreaseTimeEvent event, Emitter emit) { + if (state is! ACStatusLoaded) return; + final currentState = state as ACStatusLoaded; + int totalMinutes = (scheduledHours * 60) + scheduledMinutes; + totalMinutes = (totalMinutes - 30).clamp(0, 1440); + scheduledHours = totalMinutes ~/ 60; + scheduledMinutes = totalMinutes % 60; + + emit(currentState.copyWith( + scheduledHours: scheduledHours, + scheduledMinutes: scheduledMinutes, + )); + } + + Future _handleToggleTimer( + ToggleScheduleEvent event, Emitter emit) async { + if (state is! ACStatusLoaded) return; + final currentState = state as ACStatusLoaded; + + timerActive = !timerActive; + + if (timerActive) { + final totalMinutes = scheduledHours * 60 + scheduledMinutes; + if (totalMinutes <= 0) { + timerActive = false; + emit(currentState.copyWith(isTimerActive: timerActive)); + return; + } + + try { + final scaledValue = totalMinutes ~/ 6; + await DevicesManagementApi().deviceControl( + deviceId, + Status(code: 'countdown_time', value: scaledValue), + ); + _startCountdownTimer(emit); + emit(currentState.copyWith(isTimerActive: timerActive)); + } catch (e) { + timerActive = false; + emit(AcsFailedState(error: e.toString())); + } + } else { + await DevicesManagementApi().deviceControl( + deviceId, + Status(code: 'countdown_time', value: 0), + ); + _countdownTimer?.cancel(); + scheduledHours = 0; + scheduledMinutes = 0; + emit(currentState.copyWith( + isTimerActive: timerActive, + scheduledHours: 0, + scheduledMinutes: 0, + )); + } + } + + void _startCountdownTimer(Emitter emit) { + _countdownTimer?.cancel(); + int totalSeconds = (scheduledHours * 3600) + (scheduledMinutes * 60); + + _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (totalSeconds > 0) { + totalSeconds--; + scheduledHours = totalSeconds ~/ 3600; + scheduledMinutes = (totalSeconds % 3600) ~/ 60; + add(UpdateTimerEvent()); + } else { + _countdownTimer?.cancel(); + timerActive = false; + scheduledHours = 0; + scheduledMinutes = 0; + add(TimerCompletedEvent()); + } + }); + } + + void _handleUpdateTimer(UpdateTimerEvent event, Emitter emit) { + if (state is ACStatusLoaded) { + final currentState = state as ACStatusLoaded; + emit(currentState.copyWith( + scheduledHours: scheduledHours, + scheduledMinutes: scheduledMinutes, + isTimerActive: timerActive, + )); + } + } + + void _handleApiCountdownValue( + ApiCountdownValueEvent event, Emitter emit) { + if (state is ACStatusLoaded) { + final totalMinutes = event.apiValue * 6; + final scheduledHours = totalMinutes ~/ 60; + scheduledMinutes = totalMinutes % 60; + _startCountdownTimer( + emit, + ); + add(UpdateTimerEvent()); + } + } + + @override + Future close() { + add(OnClose()); + return super.close(); + } } diff --git a/lib/pages/device_managment/ac/bloc/ac_event.dart b/lib/pages/device_managment/ac/bloc/ac_event.dart index 5492e198..9764f3ed 100644 --- a/lib/pages/device_managment/ac/bloc/ac_event.dart +++ b/lib/pages/device_managment/ac/bloc/ac_event.dart @@ -8,6 +8,7 @@ sealed class AcsEvent extends Equatable { @override List get props => []; } + class AcUpdated extends AcsEvent {} class AcFetchDeviceStatusEvent extends AcsEvent { @@ -18,10 +19,12 @@ class AcFetchDeviceStatusEvent extends AcsEvent { @override List get props => [deviceId]; } + class AcStatusUpdated extends AcsEvent { final AcStatusModel deviceStatus; AcStatusUpdated(this.deviceStatus); } + class AcFetchBatchStatusEvent extends AcsEvent { final List devicesIds; @@ -73,3 +76,30 @@ class AcFactoryResetEvent extends AcsEvent { @override List get props => [deviceId, factoryResetModel]; } + + + +class OnClose extends AcsEvent {} + +class IncreaseTimeEvent extends AcsEvent { + @override + List get props => []; +} + +class DecreaseTimeEvent extends AcsEvent { + @override + List get props => []; +} + +class ToggleScheduleEvent extends AcsEvent {} + +class TimerCompletedEvent extends AcsEvent {} + +class UpdateTimerEvent extends AcsEvent { +} + +class ApiCountdownValueEvent extends AcsEvent { + final int apiValue; + + const ApiCountdownValueEvent(this.apiValue); +} diff --git a/lib/pages/device_managment/ac/bloc/ac_state.dart b/lib/pages/device_managment/ac/bloc/ac_state.dart index dfd12e6d..3e1e2c68 100644 --- a/lib/pages/device_managment/ac/bloc/ac_state.dart +++ b/lib/pages/device_managment/ac/bloc/ac_state.dart @@ -2,8 +2,9 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; abstract class AcsState extends Equatable { - const AcsState(); + final bool isTimerActive; + const AcsState({this.isTimerActive = false}); @override List get props => []; } @@ -15,8 +16,30 @@ class AcsLoadingState extends AcsState {} class ACStatusLoaded extends AcsState { final AcStatusModel status; final DateTime timestamp; + final int scheduledHours; + final int scheduledMinutes; + final bool isTimerActive; - ACStatusLoaded(this.status) : timestamp = DateTime.now(); + ACStatusLoaded({ + required this.status, + this.scheduledHours = 0, + this.scheduledMinutes = 0, + this.isTimerActive = false, + }) : timestamp = DateTime.now(); + ACStatusLoaded copyWith({ + AcStatusModel? status, + int? scheduledHours, + int? scheduledMinutes, + bool? isTimerActive, + int? remainingTime, + }) { + return ACStatusLoaded( + status: status ?? this.status, + scheduledHours: scheduledHours ?? this.scheduledHours, + scheduledMinutes: scheduledMinutes ?? this.scheduledMinutes, + isTimerActive: isTimerActive ?? this.isTimerActive, + ); + } @override List get props => [status, timestamp]; @@ -40,3 +63,14 @@ class AcsFailedState extends AcsState { @override List get props => [error]; } + +class TimerRunInProgress extends AcsState { + final int remainingTime; + + const TimerRunInProgress(this.remainingTime); + + @override + List get props => [remainingTime]; +} + + diff --git a/lib/pages/device_managment/ac/model/ac_model.dart b/lib/pages/device_managment/ac/model/ac_model.dart index 1eb2145f..c67006b2 100644 --- a/lib/pages/device_managment/ac/model/ac_model.dart +++ b/lib/pages/device_managment/ac/model/ac_model.dart @@ -11,6 +11,7 @@ class AcStatusModel { final bool childLock; final TempModes acMode; final FanSpeeds acFanSpeed; + late final int countdown1; AcStatusModel({ required this.uuid, @@ -18,6 +19,7 @@ class AcStatusModel { required this.modeString, required this.tempSet, required this.currentTemp, + required this.countdown1, required this.fanSpeedsString, required this.childLock, }) : acMode = getACMode(modeString), @@ -30,6 +32,7 @@ class AcStatusModel { late int currentTemp; late String fanSpeeds; late bool childLock; + late int _countdown1 = 0; for (var status in jsonList) { switch (status.code) { @@ -51,6 +54,9 @@ class AcStatusModel { case 'child_lock': childLock = status.value ?? false; break; + case 'countdown_time': + _countdown1 = status.value ?? 0; + break; } } @@ -62,6 +68,7 @@ class AcStatusModel { currentTemp: currentTemp, fanSpeedsString: fanSpeeds, childLock: childLock, + countdown1: _countdown1, ); } @@ -73,6 +80,7 @@ class AcStatusModel { int? currentTemp, String? fanSpeedsString, bool? childLock, + int? countdown1, }) { return AcStatusModel( uuid: uuid ?? this.uuid, @@ -82,6 +90,7 @@ class AcStatusModel { currentTemp: currentTemp ?? this.currentTemp, fanSpeedsString: fanSpeedsString ?? this.fanSpeedsString, childLock: childLock ?? this.childLock, + countdown1: countdown1 ?? this.countdown1, ); } diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index 071344d7..e5c9bbbd 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -10,11 +10,10 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.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'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { - const AcDeviceControlsView({super.key, required this.device}); + AcDeviceControlsView({super.key, required this.device}); final AllDevicesModel device; @@ -23,11 +22,15 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); + return BlocProvider( create: (context) => AcBloc(deviceId: device.uuid!) ..add(AcFetchDeviceStatusEvent(device.uuid!)), child: BlocBuilder( builder: (context, state) { + final acBloc = BlocProvider.of(context); + + print(state); if (state is ACStatusLoaded) { return GridView( padding: const EdgeInsets.symmetric(horizontal: 50), @@ -78,56 +81,101 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { ), ToggleWidget( label: '', - labelWidget: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + labelWidget: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ - IconButton( - padding: const EdgeInsets.all(0), - onPressed: () {}, - icon: const Icon( - Icons.remove, - size: 28, - color: ColorsManager.greyColor, + Container( + width: MediaQuery.of(context).size.width, + decoration: const ShapeDecoration( + color: ColorsManager.primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(30)), + ), ), ), - Text( - '06', - style: context.textTheme.titleLarge!.copyWith( - color: ColorsManager.dialogBlueTitle, - fontWeight: FontWeight.bold, - ), - ), - Text( - 'h', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.blackColor), - ), - Text( - '30', - style: context.textTheme.titleLarge!.copyWith( - color: ColorsManager.dialogBlueTitle, - fontWeight: FontWeight.bold, - ), - ), - Text('m', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.blackColor)), - IconButton( - padding: const EdgeInsets.all(0), - onPressed: () {}, - icon: const Icon( - Icons.add, - size: 28, - color: ColorsManager.greyColor, + Center( + child: SizedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + onPressed: () { + if (acBloc.timerActive == false) { + context + .read() + .add(DecreaseTimeEvent()); + } + }, + icon: const Icon(Icons.remove, + color: ColorsManager.greyColor), + ), + Text( + acBloc.scheduledHours + .toString() + .padLeft(2, '0'), + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'h', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: ColorsManager.blackColor, + ), + ), + Text( + acBloc.scheduledMinutes + .toString() + .padLeft(2, '0'), + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'm', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: ColorsManager.blackColor, + ), + ), + IconButton( + onPressed: () { + if (acBloc.timerActive == false) { + context + .read() + .add(IncreaseTimeEvent()); + } + }, + icon: const Icon(Icons.add, + color: ColorsManager.greyColor), + ), + ], + ), ), ), ], ), - value: false, + value: acBloc.timerActive, code: 'ac_schedule', deviceId: device.uuid!, icon: Assets.acSchedule, - onChange: (value) {}, + onChange: (value) { + context.read().add(ToggleScheduleEvent()); + }, ), ToggleWidget( deviceId: device.uuid!, From cb6d50d3671e7a1b122add70f8d6e1b4dcf0fbbc Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 13 Apr 2025 12:21:35 +0300 Subject: [PATCH 088/238] remove unnecessary print statement --- lib/pages/device_managment/ac/view/ac_device_control.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index e5c9bbbd..4e8f896c 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -29,8 +29,6 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { child: BlocBuilder( builder: (context, state) { final acBloc = BlocProvider.of(context); - - print(state); if (state is ACStatusLoaded) { return GridView( padding: const EdgeInsets.symmetric(horizontal: 50), From bc32fe7941ebbab221c0b429d14b11a9c06b38d2 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:25:03 +0300 Subject: [PATCH 089/238] removed unused method and its use. --- .../all_spaces/widgets/sidebar_widget.dart | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 17566da7..e4a5d14d 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -36,7 +36,8 @@ class _SidebarWidgetState extends State { @override void initState() { super.initState(); - _selectedId = widget.selectedSpaceUuid; // Initialize with the passed selected space UUID + _selectedId = + widget.selectedSpaceUuid; // Initialize with the passed selected space UUID } @override @@ -61,8 +62,8 @@ class _SidebarWidgetState extends State { return widget.communities.where((community) { final containsQueryInCommunity = community.name.toLowerCase().contains(_searchQuery.toLowerCase()); - final containsQueryInSpaces = - community.spaces.any((space) => _containsQuery(space, _searchQuery.toLowerCase())); + final containsQueryInSpaces = community.spaces + .any((space) => _containsQuery(space, _searchQuery.toLowerCase())); return containsQueryInCommunity || containsQueryInSpaces; }).toList(); @@ -71,8 +72,8 @@ class _SidebarWidgetState extends State { // Helper function to determine if any space or its children match the search query bool _containsQuery(SpaceModel space, String query) { final matchesSpace = space.name.toLowerCase().contains(query); - final matchesChildren = - space.children.any((child) => _containsQuery(child, query)); // Recursive check for children + final matchesChildren = space.children.any( + (child) => _containsQuery(child, query)); // Recursive check for children // If the space or any of its children match the query, expand this space if (matchesSpace || matchesChildren) { @@ -106,7 +107,8 @@ class _SidebarWidgetState extends State { width: 300, decoration: subSectionContainerDecoration, child: Column( - mainAxisSize: MainAxisSize.min, // Ensures the Column only takes necessary height + mainAxisSize: + MainAxisSize.min, // Ensures the Column only takes necessary height crossAxisAlignment: CrossAxisAlignment.start, children: [ // Communities title with the add button @@ -153,9 +155,9 @@ class _SidebarWidgetState extends State { // Community list Expanded( child: ListView( - children: filteredCommunities.map((community) { - return _buildCommunityTile(context, community); - }).toList(), + children: filteredCommunities + .map((community) => _buildCommunityTile(context, community)) + .toList(), ), ), ], @@ -192,9 +194,7 @@ class _SidebarWidgetState extends State { SelectCommunityEvent(selectedCommunity: community), ); }, - onExpansionChanged: (String title, bool expanded) { - _handleExpansionChange(community.uuid, expanded); - }, + onExpansionChanged: (title, expanded) {}, children: hasChildren ? community.spaces .where((space) => (space.status != SpaceStatus.deleted || @@ -205,7 +205,8 @@ class _SidebarWidgetState extends State { ); } - Widget _buildSpaceTile(SpaceModel space, CommunityModel community, {int depth = 1}) { + Widget _buildSpaceTile(SpaceModel space, CommunityModel community, + {int depth = 1}) { bool isExpandedSpace = _isSpaceOrChildSelected(space); return Padding( padding: EdgeInsets.only(left: depth * 16.0), @@ -214,9 +215,7 @@ class _SidebarWidgetState extends State { key: ValueKey(space.uuid), isSelected: _selectedId == space.uuid, initiallyExpanded: isExpandedSpace, - onExpansionChanged: (bool expanded) { - _handleExpansionChange(space.uuid ?? '', expanded); - }, + onExpansionChanged: (expanded) {}, onItemSelected: () { setState(() { _selectedId = space.uuid; @@ -224,14 +223,15 @@ class _SidebarWidgetState extends State { }); context.read().add( - SelectSpaceEvent(selectedCommunity: community, selectedSpace: space), + SelectSpaceEvent( + selectedCommunity: community, selectedSpace: space), ); }, children: space.children.isNotEmpty - ? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList() + ? space.children + .map((childSpace) => _buildSpaceTile(childSpace, community)) + .toList() : [], // Recursively render child spaces if available )); } - - void _handleExpansionChange(String uuid, bool expanded) {} } From 978934399e96766eb9b4064cdffca696be9b6670 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:25:28 +0300 Subject: [PATCH 090/238] fixed invalid use of a private type in a public API in `SidebarWidget`. --- .../spaces_management/all_spaces/widgets/sidebar_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index e4a5d14d..81cec22f 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -25,7 +25,7 @@ class SidebarWidget extends StatefulWidget { }); @override - _SidebarWidgetState createState() => _SidebarWidgetState(); + State createState() => _SidebarWidgetState(); } class _SidebarWidgetState extends State { From 35a99ccda7c7ddc3d0654f562235ff993a02b492 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:27:04 +0300 Subject: [PATCH 091/238] removed unnecessary comments from `SidebarWidget`. --- .../all_spaces/widgets/sidebar_widget.dart | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 81cec22f..2e486cc6 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -29,15 +29,14 @@ class SidebarWidget extends StatefulWidget { } class _SidebarWidgetState extends State { - String _searchQuery = ''; // Track search query + String _searchQuery = ''; String? _selectedSpaceUuid; String? _selectedId; @override void initState() { super.initState(); - _selectedId = - widget.selectedSpaceUuid; // Initialize with the passed selected space UUID + _selectedId = widget.selectedSpaceUuid; } @override @@ -50,15 +49,12 @@ class _SidebarWidgetState extends State { } } - // Function to filter communities based on the search query List _filterCommunities() { if (_searchQuery.isEmpty) { - // Reset the selected community and space UUIDs if there's no query _selectedSpaceUuid = null; return widget.communities; } - // Filter communities and expand only those that match the query return widget.communities.where((community) { final containsQueryInCommunity = community.name.toLowerCase().contains(_searchQuery.toLowerCase()); @@ -69,13 +65,12 @@ class _SidebarWidgetState extends State { }).toList(); } - // Helper function to determine if any space or its children match the search query bool _containsQuery(SpaceModel space, String query) { final matchesSpace = space.name.toLowerCase().contains(query); final matchesChildren = space.children.any( - (child) => _containsQuery(child, query)); // Recursive check for children + (child) => _containsQuery(child, query), + ); - // If the space or any of its children match the query, expand this space if (matchesSpace || matchesChildren) { _selectedSpaceUuid = space.uuid; } @@ -84,12 +79,10 @@ class _SidebarWidgetState extends State { } bool _isSpaceOrChildSelected(SpaceModel space) { - // Return true if the current space or any of its child spaces is selected if (_selectedSpaceUuid == space.uuid) { return true; } - // Recursively check if any child spaces match the query for (var child in space.children) { if (_isSpaceOrChildSelected(child)) { return true; @@ -107,11 +100,9 @@ class _SidebarWidgetState extends State { width: 300, decoration: subSectionContainerDecoration, child: Column( - mainAxisSize: - MainAxisSize.min, // Ensures the Column only takes necessary height + mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Communities title with the add button Container( decoration: subSectionContainerDecoration, padding: const EdgeInsets.all(16.0), @@ -143,7 +134,6 @@ class _SidebarWidgetState extends State { ], ), ), - // Search bar CustomSearchBar( onSearchChanged: (query) { setState(() { @@ -152,7 +142,6 @@ class _SidebarWidgetState extends State { }, ), const SizedBox(height: 16), - // Community list Expanded( child: ListView( children: filteredCommunities @@ -185,7 +174,7 @@ class _SidebarWidgetState extends State { onItemSelected: () { setState(() { _selectedId = community.uuid; - _selectedSpaceUuid = null; // Update the selected community + _selectedSpaceUuid = null; }); context.read().add(CommunitySelectedEvent()); @@ -231,7 +220,7 @@ class _SidebarWidgetState extends State { ? space.children .map((childSpace) => _buildSpaceTile(childSpace, community)) .toList() - : [], // Recursively render child spaces if available + : [], )); } } From bfd3d4542e2a6829bc2871d65b6a00a49e35397a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:28:50 +0300 Subject: [PATCH 092/238] refactor: simplify onSearchChanged callback to use expressions. --- .../all_spaces/widgets/sidebar_widget.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 2e486cc6..7c362dd7 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -135,11 +135,7 @@ class _SidebarWidgetState extends State { ), ), CustomSearchBar( - onSearchChanged: (query) { - setState(() { - _searchQuery = query; - }); - }, + onSearchChanged: (query) => setState(() => _searchQuery = query), ), const SizedBox(height: 16), Expanded( From cd9821679effb740aaa61b4716689c53272a007d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:29:38 +0300 Subject: [PATCH 093/238] added trailing commas. --- .../all_spaces/widgets/sidebar_widget.dart | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 7c362dd7..482bb447 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -109,10 +109,12 @@ class _SidebarWidgetState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Communities', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: ColorsManager.blackColor, - )), + Text( + 'Communities', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: ColorsManager.blackColor, + ), + ), GestureDetector( onTap: () => _navigateToBlank(context), child: Container( @@ -194,29 +196,29 @@ class _SidebarWidgetState extends State { {int depth = 1}) { bool isExpandedSpace = _isSpaceOrChildSelected(space); return Padding( - padding: EdgeInsets.only(left: depth * 16.0), - child: SpaceTile( - title: space.name, - key: ValueKey(space.uuid), - isSelected: _selectedId == space.uuid, - initiallyExpanded: isExpandedSpace, - onExpansionChanged: (expanded) {}, - onItemSelected: () { - setState(() { - _selectedId = space.uuid; - _selectedSpaceUuid = space.uuid; - }); + padding: EdgeInsets.only(left: depth * 16.0), + child: SpaceTile( + title: space.name, + key: ValueKey(space.uuid), + isSelected: _selectedId == space.uuid, + initiallyExpanded: isExpandedSpace, + onExpansionChanged: (expanded) {}, + onItemSelected: () { + setState(() { + _selectedId = space.uuid; + _selectedSpaceUuid = space.uuid; + }); - context.read().add( - SelectSpaceEvent( - selectedCommunity: community, selectedSpace: space), - ); - }, - children: space.children.isNotEmpty - ? space.children - .map((childSpace) => _buildSpaceTile(childSpace, community)) - .toList() - : [], - )); + context.read().add( + SelectSpaceEvent(selectedCommunity: community, selectedSpace: space), + ); + }, + children: space.children.isNotEmpty + ? space.children + .map((childSpace) => _buildSpaceTile(childSpace, community)) + .toList() + : [], + ), + ); } } From c2f5a8df10ba6d9f4b44da229b018c697fb286e4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:31:17 +0300 Subject: [PATCH 094/238] refactor: streamline context usage and improve readability in SidebarWidget --- .../all_spaces/widgets/sidebar_widget.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 482bb447..d241a9c1 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -12,6 +12,7 @@ import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/cent import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.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'; import 'package:syncrow_web/utils/style.dart'; class SidebarWidget extends StatefulWidget { @@ -111,7 +112,7 @@ class _SidebarWidgetState extends State { children: [ Text( 'Communities', - style: Theme.of(context).textTheme.titleMedium?.copyWith( + style: context.textTheme.titleMedium?.copyWith( color: ColorsManager.blackColor, ), ), @@ -153,9 +154,7 @@ class _SidebarWidgetState extends State { } void _navigateToBlank(BuildContext context) { - setState(() { - _selectedId = ''; - }); + setState(() => _selectedId = ''); context.read().add( NewCommunityEvent(communities: widget.communities), ); @@ -194,14 +193,14 @@ class _SidebarWidgetState extends State { Widget _buildSpaceTile(SpaceModel space, CommunityModel community, {int depth = 1}) { - bool isExpandedSpace = _isSpaceOrChildSelected(space); + bool spaceIsExpanded = _isSpaceOrChildSelected(space); return Padding( padding: EdgeInsets.only(left: depth * 16.0), child: SpaceTile( title: space.name, key: ValueKey(space.uuid), isSelected: _selectedId == space.uuid, - initiallyExpanded: isExpandedSpace, + initiallyExpanded: spaceIsExpanded, onExpansionChanged: (expanded) {}, onItemSelected: () { setState(() { From 55695ca5db761e532469b0251a171f43a324516f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:33:50 +0300 Subject: [PATCH 095/238] refactor: improve readability and structure in SidebarWidget's space tile handling --- .../all_spaces/widgets/sidebar_widget.dart | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index d241a9c1..1e6b2bf5 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -113,8 +113,8 @@ class _SidebarWidgetState extends State { Text( 'Communities', style: context.textTheme.titleMedium?.copyWith( - color: ColorsManager.blackColor, - ), + color: ColorsManager.blackColor, + ), ), GestureDetector( onTap: () => _navigateToBlank(context), @@ -183,23 +183,30 @@ class _SidebarWidgetState extends State { onExpansionChanged: (title, expanded) {}, children: hasChildren ? community.spaces - .where((space) => (space.status != SpaceStatus.deleted || - space.status != SpaceStatus.parentDeleted)) - .map((space) => _buildSpaceTile(space, community)) + .where((space) { + final isDeleted = space.status != SpaceStatus.deleted; + final isParentDeleted = space.status != SpaceStatus.parentDeleted; + return (isDeleted || isParentDeleted); + }) + .map((space) => _buildSpaceTile(space: space, community: community)) .toList() : null, ); } - Widget _buildSpaceTile(SpaceModel space, CommunityModel community, - {int depth = 1}) { + Widget _buildSpaceTile({ + required SpaceModel space, + required CommunityModel community, + int depth = 1, + }) { bool spaceIsExpanded = _isSpaceOrChildSelected(space); + final isSelected = _selectedId == space.uuid; return Padding( padding: EdgeInsets.only(left: depth * 16.0), child: SpaceTile( title: space.name, key: ValueKey(space.uuid), - isSelected: _selectedId == space.uuid, + isSelected: isSelected, initiallyExpanded: spaceIsExpanded, onExpansionChanged: (expanded) {}, onItemSelected: () { @@ -212,11 +219,14 @@ class _SidebarWidgetState extends State { SelectSpaceEvent(selectedCommunity: community, selectedSpace: space), ); }, - children: space.children.isNotEmpty - ? space.children - .map((childSpace) => _buildSpaceTile(childSpace, community)) - .toList() - : [], + children: space.children + .map( + (childSpace) => _buildSpaceTile( + space: childSpace, + community: community, + ), + ) + .toList(), ), ); } From 62ee9a72d6143e47f6946421ecf60f22e347a330 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:47:00 +0300 Subject: [PATCH 096/238] moved `SidebarHeader` and `SidebarAddCommunityButton` to their own files. --- .../widgets/sidebar_add_community_button.dart | 32 ++++++++ .../all_spaces/widgets/sidebar_header.dart | 29 +++++++ .../all_spaces/widgets/sidebar_widget.dart | 78 +++++-------------- 3 files changed, 79 insertions(+), 60 deletions(-) create mode 100644 lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart create mode 100644 lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart new file mode 100644 index 00000000..2f434350 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class SidebarAddCommunityButton extends StatelessWidget { + const SidebarAddCommunityButton({super.key}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => _navigateToBlank(context), + child: Container( + width: 30, + height: 30, + decoration: const BoxDecoration( + color: ColorsManager.whiteColors, + shape: BoxShape.circle, + ), + child: Center( + child: SvgPicture.asset( + Assets.roundedAddIcon, + width: 24, + height: 24, + ), + ), + ), + ); + } + + void _navigateToBlank(BuildContext context) {} +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart new file mode 100644 index 00000000..1e386d4f --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class SidebarHeader extends StatelessWidget { + const SidebarHeader({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: subSectionContainerDecoration, + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Communities', + style: context.textTheme.titleMedium?.copyWith( + color: ColorsManager.blackColor, + ), + ), + const SidebarAddCommunityButton(), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 1e6b2bf5..a98dead8 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -1,18 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_header.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.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'; import 'package:syncrow_web/utils/style.dart'; class SidebarWidget extends StatefulWidget { @@ -20,9 +17,9 @@ class SidebarWidget extends StatefulWidget { final String? selectedSpaceUuid; const SidebarWidget({ - super.key, required this.communities, this.selectedSpaceUuid, + super.key, }); @override @@ -36,21 +33,19 @@ class _SidebarWidgetState extends State { @override void initState() { - super.initState(); _selectedId = widget.selectedSpaceUuid; + super.initState(); } @override void didUpdateWidget(covariant SidebarWidget oldWidget) { - super.didUpdateWidget(oldWidget); if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) { - setState(() { - _selectedId = widget.selectedSpaceUuid; - }); + setState(() => _selectedId = widget.selectedSpaceUuid); } + super.didUpdateWidget(oldWidget); } - List _filterCommunities() { + List _filteredCommunities() { if (_searchQuery.isEmpty) { _selectedSpaceUuid = null; return widget.communities; @@ -59,17 +54,20 @@ class _SidebarWidgetState extends State { return widget.communities.where((community) { final containsQueryInCommunity = community.name.toLowerCase().contains(_searchQuery.toLowerCase()); - final containsQueryInSpaces = community.spaces - .any((space) => _containsQuery(space, _searchQuery.toLowerCase())); + final containsQueryInSpaces = community.spaces.any((space) => + _containsQuery(space: space, query: _searchQuery.toLowerCase())); return containsQueryInCommunity || containsQueryInSpaces; }).toList(); } - bool _containsQuery(SpaceModel space, String query) { + bool _containsQuery({ + required SpaceModel space, + required String query, + }) { final matchesSpace = space.name.toLowerCase().contains(query); final matchesChildren = space.children.any( - (child) => _containsQuery(child, query), + (child) => _containsQuery(space: child, query: query), ); if (matchesSpace || matchesChildren) { @@ -95,7 +93,7 @@ class _SidebarWidgetState extends State { @override Widget build(BuildContext context) { - final filteredCommunities = _filterCommunities(); + final filteredCommunities = _filteredCommunities(); return Container( width: 300, @@ -104,39 +102,7 @@ class _SidebarWidgetState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - decoration: subSectionContainerDecoration, - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Communities', - style: context.textTheme.titleMedium?.copyWith( - color: ColorsManager.blackColor, - ), - ), - GestureDetector( - onTap: () => _navigateToBlank(context), - child: Container( - width: 30, - height: 30, - decoration: const BoxDecoration( - color: ColorsManager.whiteColors, - shape: BoxShape.circle, - ), - child: Center( - child: SvgPicture.asset( - Assets.roundedAddIcon, - width: 24, - height: 24, - ), - ), - ), - ), - ], - ), - ), + const SidebarHeader(), CustomSearchBar( onSearchChanged: (query) => setState(() => _searchQuery = query), ), @@ -153,15 +119,8 @@ class _SidebarWidgetState extends State { ); } - void _navigateToBlank(BuildContext context) { - setState(() => _selectedId = ''); - context.read().add( - NewCommunityEvent(communities: widget.communities), - ); - } - Widget _buildCommunityTile(BuildContext context, CommunityModel community) { - bool hasChildren = community.spaces.isNotEmpty; + final hasChildren = community.spaces.isNotEmpty; return CommunityTile( title: community.name, @@ -197,12 +156,11 @@ class _SidebarWidgetState extends State { Widget _buildSpaceTile({ required SpaceModel space, required CommunityModel community, - int depth = 1, }) { - bool spaceIsExpanded = _isSpaceOrChildSelected(space); + final spaceIsExpanded = _isSpaceOrChildSelected(space); final isSelected = _selectedId == space.uuid; return Padding( - padding: EdgeInsets.only(left: depth * 16.0), + padding: const EdgeInsetsDirectional.only(start: 16.0), child: SpaceTile( title: space.name, key: ValueKey(space.uuid), From c7c88987631551721db1dde86065c7bce5cfeb77 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 12:52:38 +0300 Subject: [PATCH 097/238] removed redundant code. --- .../all_spaces/widgets/sidebar_widget.dart | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index a98dead8..b7eacc0b 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -120,8 +120,6 @@ class _SidebarWidgetState extends State { } Widget _buildCommunityTile(BuildContext context, CommunityModel community) { - final hasChildren = community.spaces.isNotEmpty; - return CommunityTile( title: community.name, key: ValueKey(community.uuid), @@ -140,16 +138,14 @@ class _SidebarWidgetState extends State { ); }, onExpansionChanged: (title, expanded) {}, - children: hasChildren - ? community.spaces - .where((space) { - final isDeleted = space.status != SpaceStatus.deleted; - final isParentDeleted = space.status != SpaceStatus.parentDeleted; - return (isDeleted || isParentDeleted); - }) - .map((space) => _buildSpaceTile(space: space, community: community)) - .toList() - : null, + children: community.spaces + .where((space) { + final isDeleted = space.status != SpaceStatus.deleted; + final isParentDeleted = space.status != SpaceStatus.parentDeleted; + return (isDeleted || isParentDeleted); + }) + .map((space) => _buildSpaceTile(space: space, community: community)) + .toList(), ); } From 6493f02bccc7c3de144b5375c4807c04389aa79a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 13:08:23 +0300 Subject: [PATCH 098/238] simplify if statements readabaility. --- .../all_spaces/widgets/sidebar_widget.dart | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index b7eacc0b..92e72326 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -78,14 +78,11 @@ class _SidebarWidgetState extends State { } bool _isSpaceOrChildSelected(SpaceModel space) { - if (_selectedSpaceUuid == space.uuid) { - return true; - } + if (_selectedSpaceUuid == space.uuid) return true; + for (var child in space.children) { - if (_isSpaceOrChildSelected(child)) { - return true; - } + if (_isSpaceOrChildSelected(child)) return true; } return false; From 79f5ef7871df02a53f511ac9df114eba14ec82c3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 13:12:48 +0300 Subject: [PATCH 099/238] simplify space selection logic. --- .../all_spaces/widgets/sidebar_widget.dart | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 92e72326..b103184e 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -78,14 +78,9 @@ class _SidebarWidgetState extends State { } bool _isSpaceOrChildSelected(SpaceModel space) { - if (_selectedSpaceUuid == space.uuid) return true; - - - for (var child in space.children) { - if (_isSpaceOrChildSelected(child)) return true; - } - - return false; + final isSpaceSelected = _selectedSpaceUuid == space.uuid; + final anySubSpaceIsSelected = space.children.any(_isSpaceOrChildSelected); + return isSpaceSelected || anySubSpaceIsSelected; } @override From acad0e8c9c79431f6a5158bc7df90a8f4468ac52 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 13 Apr 2025 14:50:07 +0300 Subject: [PATCH 100/238] SP-1189-FE-Add-Button-Not-clickable-Opening-Pop-up-in-Community-Screen --- .../widgets/sidebar_add_community_button.dart | 51 +++++++++++++------ .../all_spaces/widgets/sidebar_header.dart | 8 +-- .../all_spaces/widgets/sidebar_widget.dart | 5 +- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart index 2f434350..5c769d48 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart @@ -1,32 +1,51 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; +import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class SidebarAddCommunityButton extends StatelessWidget { - const SidebarAddCommunityButton({super.key}); + const SidebarAddCommunityButton({ + required this.existingCommunityNames, + super.key, + }); + + final List existingCommunityNames; @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () => _navigateToBlank(context), - child: Container( - width: 30, - height: 30, - decoration: const BoxDecoration( - color: ColorsManager.whiteColors, - shape: BoxShape.circle, - ), - child: Center( - child: SvgPicture.asset( - Assets.roundedAddIcon, - width: 24, - height: 24, + return SizedBox.square( + dimension: 30, + child: IconButton( + style: IconButton.styleFrom( + iconSize: 20, + backgroundColor: ColorsManager.circleImageBackground, + shape: const CircleBorder( + side: BorderSide( + color: ColorsManager.lightGrayBorderColor, + width: 3, + ), ), ), + onPressed: () => _showCreateCommunityDialog(context), + icon: SvgPicture.asset(Assets.addIcon), ), ); } - void _navigateToBlank(BuildContext context) {} + void _showCreateCommunityDialog(BuildContext context) => showDialog( + context: context, + builder: (context) => CreateCommunityDialog( + isEditMode: false, + existingCommunityNames: existingCommunityNames, + onCreateCommunity: (name, description) { + context.read().add( + CreateCommunityEvent(name, description, context), + ); + }, + ), + ); } diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart index 1e386d4f..135be109 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart @@ -5,13 +5,15 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class SidebarHeader extends StatelessWidget { - const SidebarHeader({super.key}); + const SidebarHeader({required this.existingCommunityNames, super.key}); + + final List existingCommunityNames; @override Widget build(BuildContext context) { return Container( decoration: subSectionContainerDecoration, - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -21,7 +23,7 @@ class SidebarHeader extends StatelessWidget { color: ColorsManager.blackColor, ), ), - const SidebarAddCommunityButton(), + SidebarAddCommunityButton(existingCommunityNames: existingCommunityNames), ], ), ); diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index b103184e..35bb8ad2 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -94,7 +94,10 @@ class _SidebarWidgetState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SidebarHeader(), + SidebarHeader( + existingCommunityNames: + widget.communities.map((community) => community.name).toList(), + ), CustomSearchBar( onSearchChanged: (query) => setState(() => _searchQuery = query), ), From 9bf37243a6dd4f79f9d5cf3c7a2db0d1420f6fa4 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 13 Apr 2025 14:55:55 +0300 Subject: [PATCH 101/238] remove unused code and make a limitation for the nobody time picker --- .../device_managment/ac/model/ac_model.dart | 2 +- .../ac/view/ac_device_control.dart | 2 +- .../wall_sensor/time_wheel.dart | 65 ++++++++++++------- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/lib/pages/device_managment/ac/model/ac_model.dart b/lib/pages/device_managment/ac/model/ac_model.dart index c67006b2..6afc778d 100644 --- a/lib/pages/device_managment/ac/model/ac_model.dart +++ b/lib/pages/device_managment/ac/model/ac_model.dart @@ -11,7 +11,7 @@ class AcStatusModel { final bool childLock; final TempModes acMode; final FanSpeeds acFanSpeed; - late final int countdown1; + final int countdown1; AcStatusModel({ required this.uuid, diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index 4e8f896c..8c33c853 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -13,7 +13,7 @@ import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { - AcDeviceControlsView({super.key, required this.device}); + const AcDeviceControlsView({super.key, required this.device}); final AllDevicesModel device; diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart index a5b24a22..56f74054 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -28,9 +27,12 @@ class _TimeWheelPickerState extends State { @override void initState() { super.initState(); - _hoursController = FixedExtentScrollController(initialItem: widget.initialHours); - _minutesController = FixedExtentScrollController(initialItem: widget.initialMinutes); - _secondsController = FixedExtentScrollController(initialItem: widget.initialSeconds); + _hoursController = + FixedExtentScrollController(initialItem: widget.initialHours); + _minutesController = + FixedExtentScrollController(initialItem: widget.initialMinutes); + _secondsController = + FixedExtentScrollController(initialItem: widget.initialSeconds); } @override @@ -47,6 +49,8 @@ class _TimeWheelPickerState extends State { } } + + @override void dispose() { _hoursController.dispose(); @@ -61,26 +65,28 @@ class _TimeWheelPickerState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ _buildPickerColumn( - label: 'h', - controller: _hoursController, - itemCount: 24, - onChanged: (value) => _handleTimeChange( - value, - _minutesController.selectedItem, - _secondsController.selectedItem, - ), - ), + label: 'h', + controller: _hoursController, + itemCount: 3, + onChanged: (value) { + _handleTimeChange( + value, + _minutesController.selectedItem, + _secondsController.selectedItem, + ); + }), const SizedBox(width: 5), _buildPickerColumn( - label: 'm', - controller: _minutesController, - itemCount: 60, - onChanged: (value) => _handleTimeChange( - _hoursController.selectedItem, - value, - _secondsController.selectedItem, - ), - ), + label: 'm', + controller: _minutesController, + itemCount: 60, + onChanged: (value) { + _handleTimeChange( + _hoursController.selectedItem, + value, + _secondsController.selectedItem, + ); + }), const SizedBox(width: 5), _buildPickerColumn( label: 's', @@ -97,6 +103,19 @@ class _TimeWheelPickerState extends State { } void _handleTimeChange(int hours, int minutes, int seconds) { + int total = hours * 3600 + minutes * 60 + seconds; + if (total > 10000) { + hours = 2; + minutes = 46; + seconds = 40; + total = 10000; + WidgetsBinding.instance.addPostFrameCallback((_) { + _hoursController.jumpToItem(hours); + _minutesController.jumpToItem(minutes); + _secondsController.jumpToItem(seconds); + }); + } + widget.onTimeChanged(hours, minutes, seconds); } @@ -147,4 +166,4 @@ class _TimeWheelPickerState extends State { ], ); } -} \ No newline at end of file +} From 4fae2d6be0c0ba395194a0db67493f8a4ba82617 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 13 Apr 2025 16:19:16 +0300 Subject: [PATCH 102/238] Refactor SosDeviceControlsView --- .../sos/view/sos_device_control_view.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/pages/device_managment/sos/view/sos_device_control_view.dart b/lib/pages/device_managment/sos/view/sos_device_control_view.dart index dff67c55..dde4512b 100644 --- a/lib/pages/device_managment/sos/view/sos_device_control_view.dart +++ b/lib/pages/device_managment/sos/view/sos_device_control_view.dart @@ -13,7 +13,8 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la import '../models/sos_status_model.dart'; -class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { +class SosDeviceControlsView extends StatelessWidget + with HelperResponsiveLayout { const SosDeviceControlsView({ super.key, required this.device, @@ -24,7 +25,8 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => SosDeviceBloc()..add(GetDeviceStatus(device.uuid!)), + create: (context) => + SosDeviceBloc()..add(GetDeviceStatus(device.uuid!)), child: BlocBuilder( builder: (context, state) { if (state is SosDeviceLoadingState) { @@ -63,7 +65,8 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout )); } - Widget _buildStatusControls(BuildContext context, SosStatusModel deviceStatus) { + Widget _buildStatusControls( + BuildContext context, SosStatusModel deviceStatus) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -85,7 +88,7 @@ class SosDeviceControlsView extends StatelessWidget with HelperResponsiveLayout IconNameStatusContainer( isFullIcon: false, name: deviceStatus.sosStatus == 'sos' ? 'SOS' : 'Normal', - icon: deviceStatus.sosStatus == 'sos' ? Assets.sos : Assets.sosNormal, + icon: deviceStatus.sosStatus == 'sos' ? Assets.sosNormal : Assets.sos, onTap: () {}, status: false, textColor: ColorsManager.blackColor, From 140f4ff5e20e4e4b760d1ee355c8437db691bc8e Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 14 Apr 2025 09:57:25 +0300 Subject: [PATCH 103/238] Refactor AC device controls and toggle widget --- .../device_managment/ac/bloc/ac_bloc.dart | 21 ++++++++++++------- .../shared/toggle_widget.dart | 3 --- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart index 076e9050..3da6e848 100644 --- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -331,10 +331,13 @@ class AcBloc extends Bloc { try { final scaledValue = totalMinutes ~/ 6; - await DevicesManagementApi().deviceControl( - deviceId, - Status(code: 'countdown_time', value: scaledValue), - ); + Future.delayed(const Duration(seconds: 1), () async { + await DevicesManagementApi().deviceControl( + deviceId, + Status(code: 'countdown_time', value: scaledValue), + ); + }); + _startCountdownTimer(emit); emit(currentState.copyWith(isTimerActive: timerActive)); } catch (e) { @@ -342,10 +345,12 @@ class AcBloc extends Bloc { emit(AcsFailedState(error: e.toString())); } } else { - await DevicesManagementApi().deviceControl( - deviceId, - Status(code: 'countdown_time', value: 0), - ); + Future.delayed(const Duration(seconds: 1), () async { + await DevicesManagementApi().deviceControl( + deviceId, + Status(code: 'countdown_time', value: 0), + ); + }); _countdownTimer?.cancel(); scheduledHours = 0; scheduledMinutes = 0; diff --git a/lib/pages/device_managment/shared/toggle_widget.dart b/lib/pages/device_managment/shared/toggle_widget.dart index ad0ba8ad..4888572f 100644 --- a/lib/pages/device_managment/shared/toggle_widget.dart +++ b/lib/pages/device_managment/shared/toggle_widget.dart @@ -62,9 +62,6 @@ class ToggleWidget extends StatelessWidget { )), if (showToggle) Container( - height: 20, - width: 35, - padding: const EdgeInsets.only(right: 16, top: 10), child: CupertinoSwitch( value: value, activeColor: ColorsManager.dialogBlueTitle, From 9a2687d4c58e3659ca6f06d9d0821734ed5c467b Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Apr 2025 11:13:27 +0300 Subject: [PATCH 104/238] added some comments for clarity about how we should send the data to BE. --- .../ceiling_presence_sensor_functions.dart | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart index 8f3eeb5e..bdec76d3 100644 --- a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -1,6 +1,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +// TODO: functions in conditions / status in actions class CpsOperationalValue { final String icon; final String description; @@ -222,6 +223,7 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions { } final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions { + // confirm with BE CpsMaxDistanceOfDetectionFunction({ required super.deviceId, required super.deviceName, @@ -265,7 +267,7 @@ final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions { max = 10.0, step = 0.5, super( - code: 'static_max_dis', + code: 'static_max_dis', // 0 / 500 operationName: 'Maximum Distance Of Static Detection', icon: Assets.currentDistanceIcon, ); @@ -300,7 +302,7 @@ final class CpsDetectionRangeFunction extends CpsFunctions { max = 25.5, step = 0.1, super( - code: 'moving_range', + code: 'moving_range', // just then operationName: 'Detection Range', icon: Assets.farDetection, ); @@ -370,7 +372,7 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions { max = 255, step = 5, super( - code: 'presence_reference', + code: 'presence_reference', // max 255 // change widget operationName: 'Presence Judgement Threshold', icon: Assets.presenceJudgementThrshold, ); @@ -401,7 +403,7 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions { max = 255, step = 5, super( - code: 'moving_reference', + code: 'moving_reference', // max 255 // change widget operationName: 'Motion Amplitude Trigger Threshold', icon: Assets.presenceJudgementThrshold, ); @@ -432,7 +434,7 @@ final class CpsPerpetualBoundaryFunction extends CpsFunctions { max = 5.00, step = 0.50, super( - code: 'perceptual_boundary', + code: 'perceptual_boundary', // 0 / 500 operationName: 'Perpetual Boundary', icon: Assets.boundary, ); @@ -451,7 +453,7 @@ final class CpsPerpetualBoundaryFunction extends CpsFunctions { return CpsOperationalValue( icon: Assets.boundary, description: '${value.toStringAsFixed(1)}M', - value: value, + value: value + 1200, ); }, ); @@ -467,7 +469,7 @@ final class CpsMotionTriggerBoundaryFunction extends CpsFunctions { max = 5.0, step = 0.5, super( - code: 'moving_boundary', + code: 'moving_boundary', // 0 / 500 / step 50 operationName: 'Motion Trigger Boundary', icon: Assets.motionMeter, ); @@ -502,7 +504,7 @@ final class CpsMotionTriggerTimeFunction extends CpsFunctions { max = 2.0, step = 0.1, super( - code: 'moving_rigger_time', + code: 'moving_rigger_time', // 0 / 2000 steps 10 operationName: 'Motion Trigger Time', icon: Assets.motionMeter, ); @@ -537,7 +539,7 @@ final class CpsMotionToStaticTimeFunction extends CpsFunctions { max = 50.0, step = 1.0, super( - code: 'moving_static_time', + code: 'moving_static_time', // 0 / 6000 steps 100 operationName: 'Motion To Static Time', icon: Assets.motionMeter, ); @@ -572,7 +574,7 @@ final class CpsEnteringNoBodyStateTimeFunction extends CpsFunctions { max = 300.0, step = 5.0, super( - code: 'none_body_time', + code: 'none_body_time', // 0 / 300000 / steps 500 operationName: 'Entering Nobody State Time', icon: Assets.motionMeter, ); @@ -604,7 +606,7 @@ final class CpsSelfTestResultFunctions extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'checking_result', + code: 'checking_result', // just in action operationName: 'Self-Test Result', icon: Assets.selfTestResult, ); @@ -826,7 +828,7 @@ class CpsPresenceStatusFunctions extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'presence_state', + code: 'presence_state', // just in action operationName: 'Presence Status', icon: Assets.presenceSensor, ); @@ -862,7 +864,7 @@ final class CpsSportsParaFunction extends CpsFunctions { max = 100, step = 1, super( - code: 'sports_para', + code: 'sports_para', // just in action operationName: 'Sports Para', icon: Assets.sportsPara, ); From 1023170788c73c2f747eb9193fc6482a22e86f00 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Apr 2025 11:25:36 +0300 Subject: [PATCH 105/238] Sort communities in create new routine dropdown. --- .../routines/create_new_routines/create_new_routines.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pages/routines/create_new_routines/create_new_routines.dart b/lib/pages/routines/create_new_routines/create_new_routines.dart index 0542f888..8f28208f 100644 --- a/lib/pages/routines/create_new_routines/create_new_routines.dart +++ b/lib/pages/routines/create_new_routines/create_new_routines.dart @@ -63,7 +63,11 @@ class _CreateNewRoutinesDialogState extends State { Padding( padding: const EdgeInsets.only(left: 15, right: 15), child: CommunityDropdown( - communities: _bloc.communities, + communities: _bloc.communities..sort( + (a, b) => a.name.toLowerCase().compareTo( + b.name.toLowerCase(), + ), + ), selectedValue: _selectedCommunity, onChanged: (String? newValue) { setState(() { From 9e3a78f6b734cf9f2919593f17d81c990832c617 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Apr 2025 12:10:42 +0300 Subject: [PATCH 106/238] Update dialog header to reflect sensor condition type dynamically --- .../routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index dc71a595..21aa7c51 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -66,7 +66,9 @@ class _CeilingSensorDialogState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const DialogHeader('Presence Sensor Condition'), + DialogHeader( + 'Presence Sensor ${widget.dialogType == "IF" ? 'Conditions' : 'Functions'}', + ), Expanded(child: _buildMainContent(context, state)), DialogFooter( onCancel: () => Navigator.pop(context), From 06383018b9e275edbce98cdc69e8c8c0e309e151 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Apr 2025 14:32:22 +0300 Subject: [PATCH 107/238] refactored helpers into a helper class to release some complexity out of the widget. --- .../cps_dialog_slider_selector.dart | 121 ++++++++++++------ 1 file changed, 79 insertions(+), 42 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index 49c3ac2e..42b5a29e 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -31,10 +31,13 @@ class CpsDialogSliderSelector extends StatelessWidget { return SliderValueSelector( currentCondition: selectedFunctionData.condition, dialogType: dialogType, - sliderRange: _sliderRange, - displayedValue: _displayText, + sliderRange: CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode), + displayedValue: CpsSliderHelpers.displayText( + value: selectedFunctionData.value, + functionCode: selectedFunctionData.functionCode, + ), initialValue: selectedFunctionData.value ?? 0, - unit: _unit, + unit: CpsSliderHelpers.unit(selectedFunctionData.functionCode), onConditionChanged: (condition) => context.read().add( AddFunction( functionData: DeviceFunctionData( @@ -57,11 +60,50 @@ class CpsDialogSliderSelector extends StatelessWidget { ), ), ), - dividendOfRange: _dividendOfRange, + dividendOfRange: CpsSliderHelpers.dividendOfRange( + selectedFunctionData.functionCode, + ), ); } - (double, double) get _sliderRange => switch (selectedFunctionData.functionCode) { + double reverseMappedValue(double value) { + final (inputMin, inputMax) = + CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode); + final (outputMin, outputMax, outputStep) = CpsSliderHelpers.mappedRange( + selectedFunctionData.functionCode, + ); + + final clampedValue = value.clamp(outputMin, outputMax); + + final stepsFromMin = ((clampedValue - outputMin) / outputStep).round(); + + final mappedValue = inputMin + + (stepsFromMin * + CpsSliderHelpers.dividendOfRange(selectedFunctionData.functionCode)); + + return mappedValue.clamp(inputMin, inputMax); + } +} + +abstract final class CpsSliderHelpers { + static (double min, double max, double step) mappedRange(String functionCode) { + final (defaultMin, defaultMax) = sliderRange(functionCode); + final defaultDivdidend = dividendOfRange(functionCode); + return switch (functionCode) { + 'static_max_dis' => (0, 500, 50), + 'presence_reference' => (0, 255, 50), + 'moving_reference' => (0, 255, 5), + 'perceptual_boundary' => (0, 500, 50), + 'moving_boundary' => (0, 500, 50), + 'moving_rigger_time' => (0, 2000, 100), + 'moving_static_time' => (0, 6000, 100), + 'none_body_time' => (0, 300000, 500), + _ => (defaultMin, defaultMax, defaultDivdidend), + }; + } + + static (double min, double max) sliderRange(String functionCode) => + switch (functionCode) { 'moving_speed' => (0, 32), 'sensitivity' => (0, 10), 'space_static_val' => (0, 255), @@ -80,11 +122,40 @@ class CpsDialogSliderSelector extends StatelessWidget { _ => (0, 300), }; - String get _displayText { - final value = selectedFunctionData.value; + static double dividendOfRange(String functionCode) => switch (functionCode) { + 'presence_reference' => 5, + 'moving_reference' => 5, + 'moving_max_dis' => 0.5, + 'static_max_dis' => 0.5, + 'moving_range' => 0.1, + 'presence_range' => 0.1, + 'perceptual_boundary' => 0.5, + 'moving_boundary' => 0.5, + 'moving_rigger_time' => 0.1, + 'moving_static_time' => 1.0, + 'none_body_time' => 5.0, + _ => 1, + }; + + static String unit(String functionCode) => switch (functionCode) { + 'moving_max_dis' || + 'static_max_dis' || + 'moving_range' || + 'presence_range' || + 'perceptual_boundary' || + 'moving_boundary' => + 'M', + 'moving_rigger_time' || 'moving_static_time' || 'none_body_time' => 'sec', + _ => '', + }; + + static String displayText({ + required dynamic value, + required String functionCode, + }) { final parsedValue = double.tryParse('$value'); - return switch (selectedFunctionData.functionCode) { + return switch (functionCode) { 'moving_max_dis' || 'static_max_dis' || 'moving_range' || @@ -99,38 +170,4 @@ class CpsDialogSliderSelector extends StatelessWidget { _ => '${parsedValue?.toStringAsFixed(0) ?? 0}', }; } - - String get _unit { - return switch (selectedFunctionData.functionCode) { - 'moving_max_dis' || - 'static_max_dis' || - 'moving_range' || - 'presence_range' || - 'perceptual_boundary' || - 'moving_boundary' => - 'M', - 'moving_rigger_time' || 'moving_static_time' || 'none_body_time' => 'sec', - _ => '', - }; - } - - double get _dividendOfRange => switch (selectedFunctionData.functionCode) { - 'sensitivity' => 1, - 'moving_speed' => 1, - 'space_static_val' => 1, - 'space_move_val' => 1, - 'presence_reference' => 5, - 'moving_reference' => 5, - 'moving_max_dis' => 0.5, - 'static_max_dis' => 0.5, - 'moving_range' => 0.1, - 'presence_range' => 0.1, - 'perceptual_boundary' => 0.5, - 'moving_boundary' => 0.5, - 'moving_rigger_time' => 0.1, - 'moving_static_time' => 1.0, - 'none_body_time' => 5.0, - 'sports_para' => 1.0, - _ => 1, - }; } From f19cc616be9cff1155f867c99a022b8e74e7372e Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Apr 2025 14:32:49 +0300 Subject: [PATCH 108/238] moved `CpsSliderHelpers` to its own file. --- .../cps_dialog_slider_selector.dart | 88 +------------------ .../ceiling_sensor/cps_slider_helpers.dart | 86 ++++++++++++++++++ 2 files changed, 87 insertions(+), 87 deletions(-) create mode 100644 lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index 42b5a29e..2bdd1135 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart'; import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; class CpsDialogSliderSelector extends StatelessWidget { @@ -84,90 +85,3 @@ class CpsDialogSliderSelector extends StatelessWidget { return mappedValue.clamp(inputMin, inputMax); } } - -abstract final class CpsSliderHelpers { - static (double min, double max, double step) mappedRange(String functionCode) { - final (defaultMin, defaultMax) = sliderRange(functionCode); - final defaultDivdidend = dividendOfRange(functionCode); - return switch (functionCode) { - 'static_max_dis' => (0, 500, 50), - 'presence_reference' => (0, 255, 50), - 'moving_reference' => (0, 255, 5), - 'perceptual_boundary' => (0, 500, 50), - 'moving_boundary' => (0, 500, 50), - 'moving_rigger_time' => (0, 2000, 100), - 'moving_static_time' => (0, 6000, 100), - 'none_body_time' => (0, 300000, 500), - _ => (defaultMin, defaultMax, defaultDivdidend), - }; - } - - static (double min, double max) sliderRange(String functionCode) => - switch (functionCode) { - 'moving_speed' => (0, 32), - 'sensitivity' => (0, 10), - 'space_static_val' => (0, 255), - 'space_move_val' => (0, 255), - 'moving_max_dis' => (0, 10), - 'static_max_dis' => (0, 10), - 'moving_range' => (0, 25.5), - 'presence_range' => (0, 25.5), - 'presence_judgement_threshold' => (0, 255), - 'motion_amplitude_trigger_threshold' => (0, 255), - 'perceptual_boundary' => (0, 5), - 'moving_boundary' => (0, 5), - 'moving_rigger_time' => (0, 2), - 'moving_static_time' => (0, 50), - 'none_body_time' => (0, 300), - _ => (0, 300), - }; - - static double dividendOfRange(String functionCode) => switch (functionCode) { - 'presence_reference' => 5, - 'moving_reference' => 5, - 'moving_max_dis' => 0.5, - 'static_max_dis' => 0.5, - 'moving_range' => 0.1, - 'presence_range' => 0.1, - 'perceptual_boundary' => 0.5, - 'moving_boundary' => 0.5, - 'moving_rigger_time' => 0.1, - 'moving_static_time' => 1.0, - 'none_body_time' => 5.0, - _ => 1, - }; - - static String unit(String functionCode) => switch (functionCode) { - 'moving_max_dis' || - 'static_max_dis' || - 'moving_range' || - 'presence_range' || - 'perceptual_boundary' || - 'moving_boundary' => - 'M', - 'moving_rigger_time' || 'moving_static_time' || 'none_body_time' => 'sec', - _ => '', - }; - - static String displayText({ - required dynamic value, - required String functionCode, - }) { - final parsedValue = double.tryParse('$value'); - - return switch (functionCode) { - 'moving_max_dis' || - 'static_max_dis' || - 'moving_range' || - 'presence_range' || - 'perceptual_boundary' || - 'moving_boundary' => - parsedValue?.toStringAsFixed(1) ?? '0', - 'moving_rigger_time' => parsedValue?.toStringAsFixed(2) ?? '0', - 'moving_static_time' || - 'none_body_time' => - parsedValue?.toStringAsFixed(2) ?? '0', - _ => '${parsedValue?.toStringAsFixed(0) ?? 0}', - }; - } -} diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart new file mode 100644 index 00000000..80192056 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart @@ -0,0 +1,86 @@ +abstract final class CpsSliderHelpers { + static (double min, double max, double step) mappedRange(String functionCode) { + final (defaultMin, defaultMax) = sliderRange(functionCode); + final defaultDivdidend = dividendOfRange(functionCode); + return switch (functionCode) { + 'static_max_dis' => (0, 500, 50), + 'presence_reference' => (0, 255, 50), + 'moving_reference' => (0, 255, 5), + 'perceptual_boundary' => (0, 500, 50), + 'moving_boundary' => (0, 500, 50), + 'moving_rigger_time' => (0, 2000, 100), + 'moving_static_time' => (0, 6000, 100), + 'none_body_time' => (0, 300000, 500), + _ => (defaultMin, defaultMax, defaultDivdidend), + }; + } + + static (double min, double max) sliderRange(String functionCode) => + switch (functionCode) { + 'moving_speed' => (0, 32), + 'sensitivity' => (0, 10), + 'space_static_val' => (0, 255), + 'space_move_val' => (0, 255), + 'moving_max_dis' => (0, 10), + 'static_max_dis' => (0, 10), + 'moving_range' => (0, 25.5), + 'presence_range' => (0, 25.5), + 'presence_judgement_threshold' => (0, 255), + 'motion_amplitude_trigger_threshold' => (0, 255), + 'perceptual_boundary' => (0, 5), + 'moving_boundary' => (0, 5), + 'moving_rigger_time' => (0, 2), + 'moving_static_time' => (0, 50), + 'none_body_time' => (0, 300), + _ => (0, 300), + }; + + static double dividendOfRange(String functionCode) => switch (functionCode) { + 'presence_reference' => 5, + 'moving_reference' => 5, + 'moving_max_dis' => 0.5, + 'static_max_dis' => 0.5, + 'moving_range' => 0.1, + 'presence_range' => 0.1, + 'perceptual_boundary' => 0.5, + 'moving_boundary' => 0.5, + 'moving_rigger_time' => 0.1, + 'moving_static_time' => 1.0, + 'none_body_time' => 5.0, + _ => 1, + }; + + static String unit(String functionCode) => switch (functionCode) { + 'moving_max_dis' || + 'static_max_dis' || + 'moving_range' || + 'presence_range' || + 'perceptual_boundary' || + 'moving_boundary' => + 'M', + 'moving_rigger_time' || 'moving_static_time' || 'none_body_time' => 'sec', + _ => '', + }; + + static String displayText({ + required dynamic value, + required String functionCode, + }) { + final parsedValue = double.tryParse('$value'); + + return switch (functionCode) { + 'moving_max_dis' || + 'static_max_dis' || + 'moving_range' || + 'presence_range' || + 'perceptual_boundary' || + 'moving_boundary' => + parsedValue?.toStringAsFixed(1) ?? '0', + 'moving_rigger_time' => parsedValue?.toStringAsFixed(2) ?? '0', + 'moving_static_time' || + 'none_body_time' => + parsedValue?.toStringAsFixed(2) ?? '0', + _ => '${parsedValue?.toStringAsFixed(0) ?? 0}', + }; + } +} From 1493e35f6ad5175f2ac9827770a38ec1768437c4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Apr 2025 14:35:24 +0300 Subject: [PATCH 109/238] removed unused method. --- .../ceiling_sensor/ceiling_sensor_dialog.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index 21aa7c51..1403b2ca 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -150,4 +150,15 @@ class _CeilingSensorDialogState extends State { ], ); } +List updateValuesForAddedFunctions(List addedFunctions) { + return addedFunctions.map((function) { + if (function.functionCode == 'sensitivity') { + return function.copyWith( + value: function.value, + condition: function.condition, + ); + } + return function; + }).toList(); + } } From db84a9aa5ef3cf0b7d2ba0e8d44d35b37c0071a0 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 14 Apr 2025 16:03:34 +0300 Subject: [PATCH 110/238] fix a logic --- .../device_managment/ac/bloc/ac_bloc.dart | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart index 3da6e848..501d29d8 100644 --- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -331,13 +331,14 @@ class AcBloc extends Bloc { try { final scaledValue = totalMinutes ~/ 6; - Future.delayed(const Duration(seconds: 1), () async { - await DevicesManagementApi().deviceControl( - deviceId, - Status(code: 'countdown_time', value: scaledValue), - ); - }); - + await _runDebounce( + isBatch: false, + deviceId: deviceId, + code: 'countdown_time', + value: scaledValue, + oldValue: scaledValue, + emit: emit, + ); _startCountdownTimer(emit); emit(currentState.copyWith(isTimerActive: timerActive)); } catch (e) { @@ -345,12 +346,14 @@ class AcBloc extends Bloc { emit(AcsFailedState(error: e.toString())); } } else { - Future.delayed(const Duration(seconds: 1), () async { - await DevicesManagementApi().deviceControl( - deviceId, - Status(code: 'countdown_time', value: 0), - ); - }); + await _runDebounce( + isBatch: false, + deviceId: deviceId, + code: 'countdown_time', + value: 0, + oldValue: 0, + emit: emit, + ); _countdownTimer?.cancel(); scheduledHours = 0; scheduledMinutes = 0; From ebcd89d2a5a85a64057913ada4bb8084872ddd84 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 14 Apr 2025 16:24:50 +0300 Subject: [PATCH 111/238] Refactor ceiling sensor functions and update slider helper mappings for improved value handling --- .../ceiling_presence_sensor_functions.dart | 26 +++++---- .../ceiling_sensor/ceiling_sensor_dialog.dart | 53 +++++++++++++++++-- .../cps_dialog_slider_selector.dart | 18 ------- .../ceiling_sensor/cps_slider_helpers.dart | 10 ++-- 4 files changed, 68 insertions(+), 39 deletions(-) diff --git a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart index bdec76d3..6dbe5cf6 100644 --- a/lib/pages/routines/models/ceiling_presence_sensor_functions.dart +++ b/lib/pages/routines/models/ceiling_presence_sensor_functions.dart @@ -1,7 +1,6 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -// TODO: functions in conditions / status in actions class CpsOperationalValue { final String icon; final String description; @@ -223,7 +222,6 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions { } final class CpsMaxDistanceOfDetectionFunction extends CpsFunctions { - // confirm with BE CpsMaxDistanceOfDetectionFunction({ required super.deviceId, required super.deviceName, @@ -267,7 +265,7 @@ final class CpsMaxDistanceOfStaticDetectionFunction extends CpsFunctions { max = 10.0, step = 0.5, super( - code: 'static_max_dis', // 0 / 500 + code: 'static_max_dis', operationName: 'Maximum Distance Of Static Detection', icon: Assets.currentDistanceIcon, ); @@ -302,7 +300,7 @@ final class CpsDetectionRangeFunction extends CpsFunctions { max = 25.5, step = 0.1, super( - code: 'moving_range', // just then + code: 'moving_range', operationName: 'Detection Range', icon: Assets.farDetection, ); @@ -372,7 +370,7 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions { max = 255, step = 5, super( - code: 'presence_reference', // max 255 // change widget + code: 'presence_reference', operationName: 'Presence Judgement Threshold', icon: Assets.presenceJudgementThrshold, ); @@ -403,7 +401,7 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions { max = 255, step = 5, super( - code: 'moving_reference', // max 255 // change widget + code: 'moving_reference', operationName: 'Motion Amplitude Trigger Threshold', icon: Assets.presenceJudgementThrshold, ); @@ -434,7 +432,7 @@ final class CpsPerpetualBoundaryFunction extends CpsFunctions { max = 5.00, step = 0.50, super( - code: 'perceptual_boundary', // 0 / 500 + code: 'perceptual_boundary', operationName: 'Perpetual Boundary', icon: Assets.boundary, ); @@ -469,7 +467,7 @@ final class CpsMotionTriggerBoundaryFunction extends CpsFunctions { max = 5.0, step = 0.5, super( - code: 'moving_boundary', // 0 / 500 / step 50 + code: 'moving_boundary', operationName: 'Motion Trigger Boundary', icon: Assets.motionMeter, ); @@ -504,7 +502,7 @@ final class CpsMotionTriggerTimeFunction extends CpsFunctions { max = 2.0, step = 0.1, super( - code: 'moving_rigger_time', // 0 / 2000 steps 10 + code: 'moving_rigger_time', operationName: 'Motion Trigger Time', icon: Assets.motionMeter, ); @@ -539,7 +537,7 @@ final class CpsMotionToStaticTimeFunction extends CpsFunctions { max = 50.0, step = 1.0, super( - code: 'moving_static_time', // 0 / 6000 steps 100 + code: 'moving_static_time', operationName: 'Motion To Static Time', icon: Assets.motionMeter, ); @@ -574,7 +572,7 @@ final class CpsEnteringNoBodyStateTimeFunction extends CpsFunctions { max = 300.0, step = 5.0, super( - code: 'none_body_time', // 0 / 300000 / steps 500 + code: 'none_body_time', operationName: 'Entering Nobody State Time', icon: Assets.motionMeter, ); @@ -606,7 +604,7 @@ final class CpsSelfTestResultFunctions extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'checking_result', // just in action + code: 'checking_result', operationName: 'Self-Test Result', icon: Assets.selfTestResult, ); @@ -828,7 +826,7 @@ class CpsPresenceStatusFunctions extends CpsFunctions { required super.deviceName, required super.type, }) : super( - code: 'presence_state', // just in action + code: 'presence_state', operationName: 'Presence Status', icon: Assets.presenceSensor, ); @@ -864,7 +862,7 @@ final class CpsSportsParaFunction extends CpsFunctions { max = 100, step = 1, super( - code: 'sports_para', // just in action + code: 'sports_para', operationName: 'Sports Para', icon: Assets.sportsPara, ); diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index 1403b2ca..52665dfa 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_senso import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_value_selector.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart'; class CeilingSensorDialog extends StatefulWidget { const CeilingSensorDialog({ @@ -76,7 +77,8 @@ class _CeilingSensorDialogState extends State { ? () { context.read().add( AddFunctionToRoutine( - state.addedFunctions, + _updateValuesForAddedFunctions( + state.addedFunctions), '${widget.uniqueCustomId}', ), ); @@ -150,15 +152,58 @@ class _CeilingSensorDialogState extends State { ], ); } -List updateValuesForAddedFunctions(List addedFunctions) { + + List _updateValuesForAddedFunctions( + List addedFunctions, + ) { + const mappableSteppedFunctions = { + 'static_max_dis', + 'presence_reference', + 'moving_reference', + 'perceptual_boundary', + 'moving_boundary', + 'moving_rigger_time', + 'moving_static_time', + 'none_body_time', + 'moving_max_dis', + }; return addedFunctions.map((function) { - if (function.functionCode == 'sensitivity') { - return function.copyWith( + if (mappableSteppedFunctions.contains(function.functionCode)) { + final mappedValue = mapSteppedValue( value: function.value, + inputStep: CpsSliderHelpers.dividendOfRange(function.functionCode), + inputRange: CpsSliderHelpers.sliderRange(function.functionCode), + outputRange: CpsSliderHelpers.mappedRange(function.functionCode), + ); + return DeviceFunctionData( + value: mappedValue, + entityId: function.entityId, + functionCode: function.functionCode, + operationName: function.operationName, condition: function.condition, + actionExecutor: function.actionExecutor, + valueDescription: function.valueDescription, ); } return function; }).toList(); } + + int mapSteppedValue({ + required (double min, double max) inputRange, + required double inputStep, + required (double min, double max, double dividend) outputRange, + required double value, + }) { + final (inputMin, inputMax) = inputRange; + final (outputMin, outputMax, outputStep) = outputRange; + + final clampedValue = value.clamp(inputMin, inputMax); + + final stepsFromMin = ((clampedValue - inputMin) / inputStep).round(); + + final mappedValue = outputMin + (stepsFromMin * outputStep); + + return mappedValue.clamp(outputMin, outputMax).round(); + } } diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index 2bdd1135..1c549d9f 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -66,22 +66,4 @@ class CpsDialogSliderSelector extends StatelessWidget { ), ); } - - double reverseMappedValue(double value) { - final (inputMin, inputMax) = - CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode); - final (outputMin, outputMax, outputStep) = CpsSliderHelpers.mappedRange( - selectedFunctionData.functionCode, - ); - - final clampedValue = value.clamp(outputMin, outputMax); - - final stepsFromMin = ((clampedValue - outputMin) / outputStep).round(); - - final mappedValue = inputMin + - (stepsFromMin * - CpsSliderHelpers.dividendOfRange(selectedFunctionData.functionCode)); - - return mappedValue.clamp(inputMin, inputMax); - } } diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart index 80192056..03b7b5af 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart @@ -4,13 +4,14 @@ abstract final class CpsSliderHelpers { final defaultDivdidend = dividendOfRange(functionCode); return switch (functionCode) { 'static_max_dis' => (0, 500, 50), - 'presence_reference' => (0, 255, 50), + 'presence_reference' => (0, 255, 5), 'moving_reference' => (0, 255, 5), 'perceptual_boundary' => (0, 500, 50), 'moving_boundary' => (0, 500, 50), 'moving_rigger_time' => (0, 2000, 100), - 'moving_static_time' => (0, 6000, 100), - 'none_body_time' => (0, 300000, 500), + 'moving_static_time' => (0, 60000, 1000), + 'none_body_time' => (0, 300000, 5000), + 'moving_max_dis' => (0, 500, 50), _ => (defaultMin, defaultMax, defaultDivdidend), }; } @@ -83,4 +84,7 @@ abstract final class CpsSliderHelpers { _ => '${parsedValue?.toStringAsFixed(0) ?? 0}', }; } + // TODO: Sports Para causes 400 in IF and THEN / sports_para + // TODO: Detection range causes 400 in IF / moving_range + // TODO: Distance of moving objects causes 400 in IF / presence_range } From 4849bb41ba1c4112d09b65493c2ff83b82841658 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 15 Apr 2025 02:13:00 +0300 Subject: [PATCH 112/238] Added function to single card, included tag and location to cards --- assets/icons/device_tag_ic.svg | 4 + .../all_devices/models/device_sub_space.dart | 18 + .../all_devices/models/device_tag_model.dart | 20 + .../all_devices/models/devices_model.dart | 154 +++--- .../functions_bloc/functions_bloc_bloc.dart | 1 + .../bloc/routine_bloc/routine_bloc.dart | 468 ++++++++---------- lib/pages/routines/widgets/dragable_card.dart | 85 +++- .../routines/widgets/routine_devices.dart | 6 +- .../one_gang_switch_dialog.dart | 43 +- lib/utils/constants/assets.dart | 176 +++---- 10 files changed, 469 insertions(+), 506 deletions(-) create mode 100644 assets/icons/device_tag_ic.svg create mode 100644 lib/pages/device_managment/all_devices/models/device_sub_space.dart create mode 100644 lib/pages/device_managment/all_devices/models/device_tag_model.dart diff --git a/assets/icons/device_tag_ic.svg b/assets/icons/device_tag_ic.svg new file mode 100644 index 00000000..830b6129 --- /dev/null +++ b/assets/icons/device_tag_ic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/pages/device_managment/all_devices/models/device_sub_space.dart b/lib/pages/device_managment/all_devices/models/device_sub_space.dart new file mode 100644 index 00000000..96195f76 --- /dev/null +++ b/lib/pages/device_managment/all_devices/models/device_sub_space.dart @@ -0,0 +1,18 @@ +class DeviceSubSpace { + String? id; + String? createdAt; + String? updatedAt; + String? subspaceName; + bool? disabled; + + DeviceSubSpace({this.id, this.createdAt, this.updatedAt, this.subspaceName, this.disabled}); + + DeviceSubSpace.fromJson(Map json) { + id = json['uuid']?.toString() ?? ''; + createdAt = json['createdAt']?.toString() ?? ''; + updatedAt = json['updatedAt']?.toString() ?? ''; + subspaceName = json['subspaceName']?.toString() ?? ''; + subspaceName = json['subspaceName']?.toString() ?? ''; + disabled = json['disabled'] ?? false; + } +} diff --git a/lib/pages/device_managment/all_devices/models/device_tag_model.dart b/lib/pages/device_managment/all_devices/models/device_tag_model.dart new file mode 100644 index 00000000..12580d3f --- /dev/null +++ b/lib/pages/device_managment/all_devices/models/device_tag_model.dart @@ -0,0 +1,20 @@ +class DeviceTagModel { + String? id; + String? createdAt; + String? updatedAt; + String? name; + + DeviceTagModel({ + this.id, + this.createdAt, + this.updatedAt, + this.name, + }); + + DeviceTagModel.fromJson(Map json) { + id = json['uuid']?.toString() ?? ''; + createdAt = json['createdAt']?.toString() ?? ''; + updatedAt = json['updatedAt']?.toString() ?? ''; + name = json['name']?.toString() ?? ''; + } +} diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index 2ea085c5..c7c35eca 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -1,6 +1,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_community.model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_space_model.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_sub_space.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_subspace.model.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_tag_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; @@ -78,38 +80,41 @@ class AllDevicesModel { int? batteryLevel; String? productName; List? spaces; + List? deviceTags; + DeviceSubSpace? deviceSubSpace; - AllDevicesModel({ - this.room, - this.subspace, - this.unit, - this.community, - this.productUuid, - this.productType, - this.permissionType, - this.activeTime, - this.category, - this.categoryName, - this.createTime, - this.gatewayId, - this.icon, - this.ip, - this.lat, - this.localKey, - this.lon, - this.model, - this.name, - this.nodeId, - this.online, - this.ownerId, - this.sub, - this.timeZone, - this.updateTime, - this.uuid, - this.batteryLevel, - this.productName, - this.spaces, - }); + AllDevicesModel( + {this.room, + this.subspace, + this.unit, + this.community, + this.productUuid, + this.productType, + this.permissionType, + this.activeTime, + this.category, + this.categoryName, + this.createTime, + this.gatewayId, + this.icon, + this.ip, + this.lat, + this.localKey, + this.lon, + this.model, + this.name, + this.nodeId, + this.online, + this.ownerId, + this.sub, + this.timeZone, + this.updateTime, + this.uuid, + this.batteryLevel, + this.productName, + this.spaces, + this.deviceTags, + this.deviceSubSpace}); AllDevicesModel.fromJson(Map json) { room = (json['room'] != null && (json['room'] is Map)) @@ -147,12 +152,15 @@ class AllDevicesModel { updateTime = int.tryParse(json['updateTime']?.toString() ?? ''); uuid = json['uuid']?.toString(); batteryLevel = int.tryParse(json['battery']?.toString() ?? ''); - productName = json['productName']?.toString(); + deviceTags = json['deviceTag'] != null && json['deviceTag'] is List + ? (json['deviceTag'] as List).map((tag) => DeviceTagModel.fromJson(tag)).toList() + : []; + deviceSubSpace = json['subspace'] != null + ? DeviceSubSpace.fromJson(json['subspace']) + : DeviceSubSpace(subspaceName: ''); if (json['spaces'] != null && json['spaces'] is List) { - spaces = (json['spaces'] as List) - .map((space) => DeviceSpaceModel.fromJson(space)) - .toList(); + spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList(); } } @@ -200,8 +208,7 @@ SOS String tempIcon = ''; if (type == DeviceType.LightBulb) { tempIcon = Assets.lightBulb; - } else if (type == DeviceType.CeilingSensor || - type == DeviceType.WallSensor) { + } else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) { tempIcon = Assets.sensors; } else if (type == DeviceType.AC) { tempIcon = Assets.ac; @@ -248,76 +255,51 @@ SOS switch (productType) { case 'AC': return [ - SwitchFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ModeFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - TempSetFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - CurrentTempFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), - LevelFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ChildLockFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + SwitchFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ModeFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + TempSetFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + CurrentTempFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + LevelFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ]; case '1G': return [ OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''), - OneGangCountdownFunction( - deviceId: uuid ?? '', deviceName: name ?? ''), + OneGangCountdownFunction(deviceId: uuid ?? '', deviceName: name ?? ''), ]; case '2G': return [ TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''), TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''), - TwoGangCountdown1Function( - deviceId: uuid ?? '', deviceName: name ?? ''), - TwoGangCountdown2Function( - deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''), ]; case '3G': return [ - ThreeGangSwitch1Function( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ThreeGangSwitch2Function( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ThreeGangSwitch3Function( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ThreeGangCountdown1Function( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ThreeGangCountdown2Function( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ThreeGangCountdown3Function( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ThreeGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ThreeGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ThreeGangSwitch3Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ThreeGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ThreeGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ThreeGangCountdown3Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ]; case 'WPS': return [ //IF Functions - PresenceStateFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), - CurrentDistanceFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), - IlluminanceValueFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), - PresenceTimeFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + PresenceStateFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + CurrentDistanceFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + IlluminanceValueFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + PresenceTimeFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), //THEN Functions - FarDetectionFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), - MotionSensitivityFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), - MotionLessSensitivityFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), - IndicatorFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - NoOneTimeFunction( - deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), - + FarDetectionFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + MotionSensitivityFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + MotionLessSensitivityFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + IndicatorFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + NoOneTimeFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), ]; case 'GW': return [ diff --git a/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart b/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart index a196ff27..bb4a7a1e 100644 --- a/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart +++ b/lib/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart @@ -30,6 +30,7 @@ class FunctionBloc extends Bloc { condition: event.functionData.condition ?? existingData.condition, ); } else { + functions.clear(); functions.add(event.functionData); } diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index 2664d026..b698c19e 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -64,8 +64,7 @@ class RoutineBloc extends Bloc { TriggerSwitchTabsEvent event, Emitter emit, ) { - emit(state.copyWith( - routineTab: event.isRoutineTab, createRoutineView: false)); + emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false)); add(ResetRoutineState()); if (event.isRoutineTab) { add(const LoadScenes()); @@ -91,8 +90,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; @@ -101,21 +100,18 @@ class RoutineBloc extends Bloc { } if (event.isTabToRun) { - emit(state.copyWith( - ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); + emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false)); } else { - emit(state.copyWith( - ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); + emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true)); } } - void _onAddToThenContainer( - AddToThenContainer event, Emitter emit) { + void _onAddToThenContainer(AddToThenContainer event, Emitter emit) { final currentItems = List>.from(state.thenItems); // Find the index of the item in teh current itemsList - int index = currentItems.indexWhere( - (map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); + int index = + currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); // Replace the map if the index is valid if (index != -1) { currentItems[index] = event.item; @@ -126,45 +122,42 @@ 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 functionCode = []; - for (int i = 0; i < selectedFunction.length; i++) { - for (int j = 0; j < currentFunctions.length; j++) { - if (selectedFunction[i].functionCode == - currentFunctions[j].functionCode) { - currentFunctions[j] = selectedFunction[i]; - if (!functionCode.contains(currentFunctions[j].functionCode)) { - functionCode.add(currentFunctions[j].functionCode); - } - } - } - } + // if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { + // List currentFunctions = + // List.from(currentSelectedFunctions[event.uniqueCustomId] ?? []); - for (int i = 0; i < functionCode.length; i++) { - selectedFunction - .removeWhere((code) => code.functionCode == functionCode[i]); - } + // 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) { + // currentFunctions[j] = selectedFunction[i]; + // if (!functionCode.contains(currentFunctions[j].functionCode)) { + // functionCode.add(currentFunctions[j].functionCode); + // } + // } + // } + // } - currentSelectedFunctions[event.uniqueCustomId] = - List.from(currentFunctions)..addAll(selectedFunction); - } else { - currentSelectedFunctions[event.uniqueCustomId] = - List.from(event.functions); - } + // for (int i = 0; i < functionCode.length; i++) { + // selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]); + // } + + // 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)); } catch (e) { @@ -172,30 +165,24 @@ 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 { BuildContext context = NavigationService.navigatorKey.currentContext!; var createRoutineBloc = context.read(); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - if (createRoutineBloc.selectedSpaceId == '' && - createRoutineBloc.selectedCommunityId == '') { + if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') { var spaceBloc = context.read(); for (var communityId in spaceBloc.state.selectedCommunities) { - List spacesList = - spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; for (var spaceId in spacesList) { - scenes.addAll( - await SceneApi.getScenes(spaceId, communityId, projectUuid)); + scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid)); } } } else { scenes.addAll(await SceneApi.getScenes( - createRoutineBloc.selectedSpaceId, - createRoutineBloc.selectedCommunityId, - projectUuid)); + createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectUuid)); } emit(state.copyWith( @@ -212,8 +199,7 @@ class RoutineBloc extends Bloc { } } - Future _onLoadAutomation( - LoadAutomation event, Emitter emit) async { + Future _onLoadAutomation(LoadAutomation event, Emitter emit) async { emit(state.copyWith(isLoading: true, errorMessage: null)); List automations = []; final projectId = await ProjectManager.getProjectUUID() ?? ''; @@ -221,23 +207,17 @@ class RoutineBloc extends Bloc { BuildContext context = NavigationService.navigatorKey.currentContext!; var createRoutineBloc = context.read(); try { - - if (createRoutineBloc.selectedSpaceId == '' && - createRoutineBloc.selectedCommunityId == '') { + if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') { var spaceBloc = context.read(); for (var communityId in spaceBloc.state.selectedCommunities) { - List spacesList = - spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; for (var spaceId in spacesList) { - automations.addAll( - await SceneApi.getAutomation(spaceId, communityId, projectId)); + automations.addAll(await SceneApi.getAutomation(spaceId, communityId, projectId)); } } } else { automations.addAll(await SceneApi.getAutomation( - createRoutineBloc.selectedSpaceId, - createRoutineBloc.selectedCommunityId, - projectId)); + createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectId)); } emit(state.copyWith( automations: automations, @@ -253,16 +233,14 @@ 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)); } @@ -276,8 +254,7 @@ 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)) { @@ -290,8 +267,7 @@ 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; @@ -367,8 +343,7 @@ 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) { @@ -390,8 +365,7 @@ 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'); @@ -482,8 +456,7 @@ 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()); @@ -504,21 +477,17 @@ 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); @@ -529,8 +498,7 @@ class RoutineBloc extends Bloc { isAutomation: false, isTabToRun: false)); } else { - emit(state.copyWith( - ifItems: ifItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); } } } @@ -542,141 +510,138 @@ 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 actions, - List? conditions, - Map> currentFunctions, - bool isTabToRun, - ) { - final ifItems = isTabToRun - ? [ - { - 'entityId': 'tab_to_run', - 'uniqueCustomId': const Uuid().v4(), - 'deviceId': 'tab_to_run', - 'title': 'Tab to run', - 'productType': 'tab_to_run', - 'imagePath': Assets.tabToRun, - } - ] - : conditions?.map((condition) { - final matchingDevice = state.devices.firstWhere( - (device) => device.uuid == condition.entityId, - orElse: () => AllDevicesModel( - uuid: condition.entityId, - name: condition.entityId, - productType: condition.entityType, - ), - ); + // ( + // List>, + // List>, + // Map> + // ) _createCardData( + // List actions, + // List? conditions, + // Map> currentFunctions, + // bool isTabToRun, + // ) { + // final ifItems = isTabToRun + // ? [ + // { + // 'entityId': 'tab_to_run', + // 'uniqueCustomId': const Uuid().v4(), + // 'deviceId': 'tab_to_run', + // 'title': 'Tab to run', + // 'productType': 'tab_to_run', + // 'imagePath': Assets.tabToRun, + // } + // ] + // : conditions?.map((condition) { + // final matchingDevice = state.devices.firstWhere( + // (device) => device.uuid == condition.entityId, + // orElse: () => AllDevicesModel( + // uuid: condition.entityId, + // name: condition.entityId, + // productType: condition.entityType, + // ), + // ); - final cardData = { - 'entityId': condition.entityId, - 'uniqueCustomId': const Uuid().v4(), - 'deviceId': condition.entityId, - 'title': matchingDevice.name ?? condition.entityId, - 'productType': condition.entityType, - 'imagePath': - matchingDevice.getDefaultIcon(condition.entityType), - }; + // final cardData = { + // 'entityId': condition.entityId, + // 'uniqueCustomId': const Uuid().v4(), + // 'deviceId': condition.entityId, + // 'title': matchingDevice.name ?? condition.entityId, + // 'productType': condition.entityType, + // 'imagePath': + // matchingDevice.getDefaultIcon(condition.entityType), + // }; - final functions = matchingDevice.functions; + // final functions = matchingDevice.functions; - for (var function in functions) { - if (function.code == condition.expr.statusCode) { - currentFunctions[cardData['uniqueCustomId'] ?? ''] = [ - DeviceFunctionData( - entityId: condition.entityId, - functionCode: condition.expr.statusCode, - value: condition.expr.statusValue, - operationName: function.operationName, - ), - ]; - break; - } - } + // for (var function in functions) { + // if (function.code == condition.expr.statusCode) { + // currentFunctions[cardData['uniqueCustomId'] ?? ''] = [ + // DeviceFunctionData( + // entityId: condition.entityId, + // functionCode: condition.expr.statusCode, + // value: condition.expr.statusValue, + // operationName: function.operationName, + // ), + // ]; + // break; + // } + // } - return cardData; - }).toList() ?? - []; + // return cardData; + // }).toList() ?? + // []; - final thenItems = actions.map((action) { - final matchingDevice = state.devices.firstWhere( - (device) => device.uuid == action.entityId, - orElse: () => AllDevicesModel( - uuid: action.entityId, - name: action.entityId, - productType: action.productType, - ), - ); + // final thenItems = actions.map((action) { + // final matchingDevice = state.devices.firstWhere( + // (device) => device.uuid == action.entityId, + // orElse: () => AllDevicesModel( + // uuid: action.entityId, + // name: action.entityId, + // productType: action.productType, + // ), + // ); - final cardData = { - 'entityId': action.entityId, - 'uniqueCustomId': const Uuid().v4(), - 'deviceId': - action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'title': action.actionExecutor == 'delay' - ? 'Delay' - : (matchingDevice.name ?? 'Device'), - 'productType': action.productType, - 'imagePath': matchingDevice.getDefaultIcon(action.productType), - }; + // final cardData = { + // 'entityId': action.entityId, + // 'uniqueCustomId': const Uuid().v4(), + // 'deviceId': + // action.actionExecutor == 'delay' ? 'delay' : action.entityId, + // 'title': action.actionExecutor == 'delay' + // ? 'Delay' + // : (matchingDevice.name ?? 'Device'), + // 'productType': action.productType, + // 'imagePath': matchingDevice.getDefaultIcon(action.productType), + // }; - final functions = matchingDevice.functions; + // final functions = matchingDevice.functions; - if (action.executorProperty != null && action.actionExecutor != 'delay') { - final functionCode = action.executorProperty!.functionCode; - for (var function in functions) { - if (function.code == functionCode) { - currentFunctions[cardData['uniqueCustomId'] ?? ''] = [ - DeviceFunctionData( - entityId: action.entityId, - functionCode: functionCode ?? '', - value: action.executorProperty!.functionValue, - operationName: function.operationName, - ), - ]; - break; - } - } - } else if (action.actionExecutor == 'delay') { - final delayFunction = DelayFunction( - deviceId: action.entityId, - deviceName: 'Delay', - ); - currentFunctions[cardData['uniqueCustomId'] ?? ''] = [ - DeviceFunctionData( - entityId: action.entityId, - functionCode: 'delay', - value: action.executorProperty?.delaySeconds ?? 0, - operationName: delayFunction.operationName, - ), - ]; - } + // if (action.executorProperty != null && action.actionExecutor != 'delay') { + // final functionCode = action.executorProperty!.functionCode; + // for (var function in functions) { + // if (function.code == functionCode) { + // currentFunctions[cardData['uniqueCustomId'] ?? ''] = [ + // DeviceFunctionData( + // entityId: action.entityId, + // functionCode: functionCode ?? '', + // value: action.executorProperty!.functionValue, + // operationName: function.operationName, + // ), + // ]; + // break; + // } + // } + // } else if (action.actionExecutor == 'delay') { + // final delayFunction = DelayFunction( + // deviceId: action.entityId, + // deviceName: 'Delay', + // ); + // currentFunctions[cardData['uniqueCustomId'] ?? ''] = [ + // DeviceFunctionData( + // entityId: action.entityId, + // functionCode: 'delay', + // value: action.executorProperty?.delaySeconds ?? 0, + // operationName: delayFunction.operationName, + // ), + // ]; + // } - return cardData; - }).toList(); + // return cardData; + // }).toList(); - return (thenItems, ifItems, currentFunctions); - } + // return (thenItems, ifItems, currentFunctions); + // } - Future _onGetSceneDetails( - GetSceneDetails event, Emitter emit) async { + Future _onGetSceneDetails(GetSceneDetails event, Emitter emit) async { try { emit(state.copyWith( isLoading: true, @@ -724,12 +689,10 @@ class RoutineBloc extends Bloc { if (!deviceCards.containsKey(deviceId)) { deviceCards[deviceId] = { 'entityId': action.entityId, - 'deviceId': - action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'uniqueCustomId': - action.type == 'automation' || action.actionExecutor == 'delay' - ? const Uuid().v4() - : action.entityId, + 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay' + ? const Uuid().v4() + : action.entityId, 'title': action.actionExecutor == 'delay' ? 'Delay' : action.type == 'automation' @@ -764,16 +727,15 @@ class RoutineBloc extends Bloc { ), ); // emit(state.copyWith(automationActionExecutor: action.actionExecutor)); - } else if (action.executorProperty != null && - action.actionExecutor != 'delay') { - if (!updatedFunctions.containsKey(uniqueCustomId)) { - updatedFunctions[uniqueCustomId] = []; - } + } else if (action.executorProperty != null && action.actionExecutor != 'delay') { + // if (!updatedFunctions.containsKey(uniqueCustomId)) { + // updatedFunctions[uniqueCustomId] = []; + // } final functions = matchingDevice?.functions; final functionCode = action.executorProperty?.functionCode; for (DeviceFunction function in functions ?? []) { if (function.code == functionCode) { - updatedFunctions[uniqueCustomId]!.add( + updatedFunctions[const Uuid().v4()]!.add( DeviceFunctionData( entityId: action.entityId, functionCode: functionCode ?? '', @@ -837,8 +799,7 @@ class RoutineBloc extends Bloc { } } - FutureOr _onResetRoutineState( - ResetRoutineState event, Emitter emit) { + FutureOr _onResetRoutineState(ResetRoutineState event, Emitter emit) { emit(state.copyWith( ifItems: [], thenItems: [], @@ -861,6 +822,7 @@ class RoutineBloc extends Bloc { isUpdate: false, createRoutineView: false)); } + FutureOr _deleteScene(DeleteScene event, Emitter emit) async { try { final projectId = await ProjectManager.getProjectUUID() ?? ''; @@ -900,7 +862,7 @@ class RoutineBloc extends Bloc { )); } } - + // FutureOr _deleteAutomation(DeleteAutomation event, Emitter emit) { // try { // emit(state.copyWith(isLoading: true)); @@ -915,8 +877,7 @@ class RoutineBloc extends Bloc { // } // } - FutureOr _fetchDevices( - FetchDevicesInRoutine event, Emitter emit) async { + FutureOr _fetchDevices(FetchDevicesInRoutine event, Emitter emit) async { emit(state.copyWith(isLoading: true)); try { final projectUuid = await ProjectManager.getProjectUUID() ?? ''; @@ -925,21 +886,17 @@ class RoutineBloc extends Bloc { var createRoutineBloc = context.read(); var spaceBloc = context.read(); - if (createRoutineBloc.selectedSpaceId == '' && - createRoutineBloc.selectedCommunityId == '') { + if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') { for (var communityId in spaceBloc.state.selectedCommunities) { - List spacesList = - spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; + List spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; for (var spaceId in spacesList) { - devices.addAll(await DevicesManagementApi() - .fetchDevices(communityId, spaceId, projectUuid)); + devices.addAll( + await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid)); } } } else { devices.addAll(await DevicesManagementApi().fetchDevices( - createRoutineBloc.selectedCommunityId, - createRoutineBloc.selectedSpaceId, - projectUuid)); + createRoutineBloc.selectedCommunityId, createRoutineBloc.selectedSpaceId, projectUuid)); } emit(state.copyWith(isLoading: false, devices: devices)); @@ -948,8 +905,7 @@ 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)) { @@ -963,8 +919,7 @@ 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; @@ -1017,8 +972,7 @@ class RoutineBloc extends Bloc { actions: actions, ); - final result = - await SceneApi.updateScene(createSceneModel, state.sceneId ?? ''); + final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? ''); if (result['success']) { add(ResetRoutineState()); add(const LoadScenes()); @@ -1037,8 +991,7 @@ class RoutineBloc extends Bloc { } } - FutureOr _onUpdateAutomation( - UpdateAutomation event, Emitter emit) async { + FutureOr _onUpdateAutomation(UpdateAutomation event, Emitter emit) async { try { if (state.routineName == null || state.routineName!.isEmpty) { emit(state.copyWith( @@ -1253,15 +1206,13 @@ 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' @@ -1281,7 +1232,7 @@ class RoutineBloc extends Bloc { : action.type == 'automation' ? 'automation' : 'action', - 'icon': action.icon ?? '' + 'icon': action.icon ?? '', }; } @@ -1292,8 +1243,7 @@ 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) { @@ -1335,14 +1285,10 @@ 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( @@ -1364,8 +1310,7 @@ class RoutineBloc extends Bloc { } } - Future _onSceneTrigger( - SceneTrigger event, Emitter emit) async { + Future _onSceneTrigger(SceneTrigger event, Emitter emit) async { emit(state.copyWith(loadingSceneId: event.sceneId)); try { @@ -1407,29 +1352,24 @@ class RoutineBloc extends Bloc { if (success) { final updatedAutomations = await SceneApi.getAutomationByUnitId( - event.automationStatusUpdate.spaceUuid, - event.communityId, - projectId); + event.automationStatusUpdate.spaceUuid, event.communityId, projectId); // Remove from loading set safely - final updatedLoadingIds = {...state.loadingAutomationIds!} - ..remove(event.automationId); + final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId); emit(state.copyWith( automations: updatedAutomations, loadingAutomationIds: updatedLoadingIds, )); } else { - final updatedLoadingIds = {...state.loadingAutomationIds!} - ..remove(event.automationId); + final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId); emit(state.copyWith( loadingAutomationIds: updatedLoadingIds, errorMessage: 'Update failed', )); } } catch (e) { - final updatedLoadingIds = {...state.loadingAutomationIds!} - ..remove(event.automationId); + final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId); emit(state.copyWith( loadingAutomationIds: updatedLoadingIds, errorMessage: 'Update error: ${e.toString()}', diff --git a/lib/pages/routines/widgets/dragable_card.dart b/lib/pages/routines/widgets/dragable_card.dart index 77786429..039732b0 100644 --- a/lib/pages/routines/widgets/dragable_card.dart +++ b/lib/pages/routines/widgets/dragable_card.dart @@ -6,6 +6,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class DraggableCard extends StatelessWidget { @@ -70,7 +71,7 @@ class DraggableCard extends StatelessWidget { child: Container( padding: padding ?? const EdgeInsets.all(16), width: 110, - height: deviceFunctions.isEmpty ? 123 : null, + height: deviceFunctions.isEmpty ? 160 : 170, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, @@ -78,6 +79,7 @@ class DraggableCard extends StatelessWidget { children: [ Column( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( height: 50, @@ -101,19 +103,82 @@ class DraggableCard extends StatelessWidget { const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(horizontal: 3), - child: Text( - deviceData['title'] ?? deviceData['name'] ?? title, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontSize: 12, + child: Flexible( + child: Text( + deviceData['title'] ?? deviceData['name'] ?? title, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: 12, + ), ), ), ), + const SizedBox( + height: 4, + ), + Visibility( + visible: deviceData['tag'] != null, + child: Row( + spacing: 2, + children: [ + SizedBox( + width: 8, height: 8, child: SvgPicture.asset(Assets.deviceTagIcon)), + Flexible( + child: Text( + deviceData['tag'] ?? '', + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.lightGreyColor, + fontSize: 9, + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), + ), + Visibility( + visible: deviceData['subSpace'] != null || deviceData['subSpace'] != '', + child: const SizedBox( + height: 4, + ), + ), + Visibility( + visible: deviceData['subSpace'] != null || deviceData['subSpace'] != '', + child: Row( + spacing: 2, + children: [ + SizedBox( + width: 8, + height: 8, + child: SvgPicture.asset(Assets.spaceLocationIcon)), + Flexible( + child: Text( + deviceData['subSpace'] ?? '', + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.lightGreyColor, + fontSize: 9, + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), + ), ], ), + if (deviceFunctions.isNotEmpty) + const SizedBox( + height: 4, + ), if (deviceFunctions.isNotEmpty) ...deviceFunctions.map((function) => Row( mainAxisSize: MainAxisSize.min, @@ -123,7 +188,7 @@ class DraggableCard extends StatelessWidget { '${function.operationName}: ${_formatFunctionValue(function)}', style: context.textTheme.bodySmall?.copyWith( fontSize: 9, - color: ColorsManager.textGray, + color: ColorsManager.lightGreyColor, height: 1.2, ), maxLines: 2, diff --git a/lib/pages/routines/widgets/routine_devices.dart b/lib/pages/routines/widgets/routine_devices.dart index 84fd44c0..f22c8ae3 100644 --- a/lib/pages/routines/widgets/routine_devices.dart +++ b/lib/pages/routines/widgets/routine_devices.dart @@ -51,12 +51,12 @@ class _RoutineDevicesState extends State { 'productType': device.productType, 'functions': device.functions, 'uniqueCustomId': '', + 'tag': device.deviceTags!.isNotEmpty ? device.deviceTags![0].name : '', + 'subSpace': device.deviceSubSpace?.subspaceName ?? '', }; if (state.searchText != null && state.searchText!.isNotEmpty) { - return device.name! - .toLowerCase() - .contains(state.searchText!.toLowerCase()) + return device.name!.toLowerCase().contains(state.searchText!.toLowerCase()) ? DraggableCard( imagePath: deviceData['imagePath'] as String, title: deviceData['title'] as String, diff --git a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart index d0fcde28..5bc374a3 100644 --- a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -5,7 +5,6 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart'; -import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart'; @@ -25,23 +24,21 @@ class OneGangSwitchHelper { required String uniqueCustomId, required bool removeComparetors, }) async { - List oneGangFunctions = - functions.whereType().toList(); + List oneGangFunctions = functions.whereType().toList(); return showDialog?>( context: context, builder: (BuildContext context) { return BlocProvider( - create: (_) => FunctionBloc() - ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])), child: AlertDialog( contentPadding: EdgeInsets.zero, content: BlocBuilder( builder: (context, state) { final selectedFunction = state.selectedFunction; final selectedOperationName = state.selectedOperationName; - final selectedFunctionData = state.addedFunctions - .firstWhere((f) => f.functionCode == selectedFunction, + final selectedFunctionData = + state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction, orElse: () => DeviceFunctionData( entityId: '', functionCode: selectedFunction ?? '', @@ -88,12 +85,9 @@ class OneGangSwitchHelper { color: ColorsManager.textGray, ), onTap: () { - context - .read() - .add(SelectFunction( + context.read().add(SelectFunction( functionCode: function.code, - operationName: - function.operationName, + operationName: function.operationName, )); }, ); @@ -226,11 +220,11 @@ class OneGangSwitchHelper { selectedFunctionData, ), const SizedBox(height: 20), - _buildCountDownDisplay(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + _buildCountDownDisplay( + context, initialValue, device, operationName, selectedFunctionData, selectCode), const SizedBox(height: 20), - _buildCountDownSlider(context, initialValue, device, operationName, - selectedFunctionData, selectCode), + _buildCountDownSlider( + context, initialValue, device, operationName, selectedFunctionData, selectCode), ], ); } @@ -271,8 +265,7 @@ class OneGangSwitchHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: - conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -320,8 +313,7 @@ class OneGangSwitchHelper { value: (initialValue ?? 0).toDouble(), min: operationalValues.minValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: (((operationalValues.maxValue ?? 0) - - (operationalValues.minValue ?? 0)) / + divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / (operationalValues.stepValue ?? 1)) .round(), onChanged: (value) { @@ -373,13 +365,9 @@ class OneGangSwitchHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, size: 24, - color: isSelected - ? ColorsManager.primaryColorWithOpacity - : ColorsManager.textGray, + color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray, ), onTap: () { if (!isSelected) { @@ -391,8 +379,7 @@ class OneGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: - selectedFunctionData?.valueDescription, + valueDescription: selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index d9788ee8..12d699b4 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -13,12 +13,10 @@ class Assets { static const String rightLine = "assets/images/right_line.png"; static const String google = "assets/images/google.svg"; static const String facebook = "assets/images/facebook.svg"; - static const String invisiblePassword = - "assets/images/Password_invisible.svg"; + static const String invisiblePassword = "assets/images/Password_invisible.svg"; static const String visiblePassword = "assets/images/password_visible.svg"; static const String accessIcon = "assets/images/access_icon.svg"; - static const String spaseManagementIcon = - "assets/images/spase_management_icon.svg"; + static const String spaseManagementIcon = "assets/images/spase_management_icon.svg"; static const String devicesIcon = "assets/images/devices_icon.svg"; static const String moveinIcon = "assets/images/movein_icon.svg"; static const String constructionIcon = "assets/images/construction_icon.svg"; @@ -31,15 +29,13 @@ class Assets { static const String emptyTable = "assets/images/empty_table.svg"; // General assets - static const String motionlessDetection = - "assets/icons/motionless_detection.svg"; + static const String motionlessDetection = "assets/icons/motionless_detection.svg"; static const String acHeating = "assets/icons/ac_heating.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg"; static const String resetOff = "assets/icons/reset_off.svg"; - static const String sensitivityOperationIcon = - "assets/icons/sesitivity_operation_icon.svg"; + static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg"; static const String motionDetection = "assets/icons/motion_detection.svg"; static const String freezing = "assets/icons/freezing.svg"; static const String indicator = "assets/icons/indicator.svg"; @@ -60,8 +56,7 @@ class Assets { static const String celsiusDegrees = "assets/icons/celsius_degrees.svg"; static const String masterState = "assets/icons/master_state.svg"; static const String acPower = "assets/icons/ac_power.svg"; - static const String farDetectionFunction = - "assets/icons/far_detection_function.svg"; + static const String farDetectionFunction = "assets/icons/far_detection_function.svg"; static const String nobodyTime = "assets/icons/nobody_time.svg"; // Automation functions @@ -69,47 +64,33 @@ class Assets { "assets/icons/automation_functions/temp_password_unlock.svg"; static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg"; - static const String doorbell = - "assets/icons/automation_functions/doorbell.svg"; + static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg"; - static const String doubleLock = - "assets/icons/automation_functions/double_lock.svg"; - static const String selfTestResult = - "assets/icons/automation_functions/self_test_result.svg"; - static const String lockAlarm = - "assets/icons/automation_functions/lock_alarm.svg"; - static const String presenceState = - "assets/icons/automation_functions/presence_state.svg"; - static const String currentTemp = - "assets/icons/automation_functions/current_temp.svg"; - static const String presence = - "assets/icons/automation_functions/presence.svg"; + static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; + static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; + static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; + static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; + static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; + static const String presence = "assets/icons/automation_functions/presence.svg"; static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; - static const String hijackAlarm = - "assets/icons/automation_functions/hijack_alarm.svg"; - static const String passwordUnlock = - "assets/icons/automation_functions/password_unlock.svg"; + static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg"; + static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg"; static const String remoteUnlockRequest = "assets/icons/automation_functions/remote_unlock_req.svg"; - static const String cardUnlock = - "assets/icons/automation_functions/card_unlock.svg"; + static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg"; static const String motion = "assets/icons/automation_functions/motion.svg"; static const String fingerprintUnlock = "assets/icons/automation_functions/fingerprint_unlock.svg"; // Presence Sensor Assets static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; - static const String sensorPresenceIcon = - "assets/icons/sensor_presence_ic.svg"; + static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; - static const String illuminanceRecordIcon = - "assets/icons/illuminance_record_ic.svg"; - static const String presenceRecordIcon = - "assets/icons/presence_record_ic.svg"; - static const String helpDescriptionIcon = - "assets/icons/help_description_ic.svg"; + static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; + static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; static const String lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -159,12 +140,10 @@ class Assets { static const String unit = 'assets/icons/unit_icon.svg'; static const String villa = 'assets/icons/villa_icon.svg'; static const String iconEdit = 'assets/icons/icon_edit_icon.svg'; - static const String textFieldSearch = - 'assets/icons/textfield_search_icon.svg'; + static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg'; static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg'; static const String addIcon = 'assets/icons/add_icon.svg'; - static const String smartThermostatIcon = - 'assets/icons/smart_thermostat_icon.svg'; + static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg'; static const String smartLightIcon = 'assets/icons/smart_light_icon.svg'; static const String presenceSensor = 'assets/icons/presence_sensor.svg'; static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg'; @@ -212,8 +191,7 @@ class Assets { //assets/icons/water_leak_normal.svg static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg'; //assets/icons/water_leak_detected.svg - static const String waterLeakDetected = - 'assets/icons/water_leak_detected.svg'; + static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg'; //assets/icons/automation_records.svg static const String automationRecords = 'assets/icons/automation_records.svg'; @@ -280,64 +258,40 @@ class Assets { static const String delay = 'assets/icons/routine/delay.svg'; // Assets for functions_icons - static const String assetsSensitivityFunction = - "assets/icons/functions_icons/sensitivity.svg"; + static const String assetsSensitivityFunction = "assets/icons/functions_icons/sensitivity.svg"; static const String assetsSensitivityOperationIcon = "assets/icons/functions_icons/sesitivity_operation_icon.svg"; - static const String assetsAcPower = - "assets/icons/functions_icons/ac_power.svg"; - static const String assetsAcPowerOFF = - "assets/icons/functions_icons/ac_power_off.svg"; - static const String assetsChildLock = - "assets/icons/functions_icons/child_lock.svg"; - static const String assetsFreezing = - "assets/icons/functions_icons/freezing.svg"; - static const String assetsFanSpeed = - "assets/icons/functions_icons/fan_speed.svg"; - static const String assetsAcCooling = - "assets/icons/functions_icons/ac_cooling.svg"; - static const String assetsAcHeating = - "assets/icons/functions_icons/ac_heating.svg"; - static const String assetsCelsiusDegrees = - "assets/icons/functions_icons/celsius_degrees.svg"; - static const String assetsTempreture = - "assets/icons/functions_icons/tempreture.svg"; - static const String assetsAcFanLow = - "assets/icons/functions_icons/ac_fan_low.svg"; - static const String assetsAcFanMiddle = - "assets/icons/functions_icons/ac_fan_middle.svg"; - static const String assetsAcFanHigh = - "assets/icons/functions_icons/ac_fan_high.svg"; - static const String assetsAcFanAuto = - "assets/icons/functions_icons/ac_fan_auto.svg"; - static const String assetsSceneChildLock = - "assets/icons/functions_icons/scene_child_lock.svg"; + static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg"; + static const String assetsAcPowerOFF = "assets/icons/functions_icons/ac_power_off.svg"; + static const String assetsChildLock = "assets/icons/functions_icons/child_lock.svg"; + static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg"; + static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg"; + static const String assetsAcCooling = "assets/icons/functions_icons/ac_cooling.svg"; + static const String assetsAcHeating = "assets/icons/functions_icons/ac_heating.svg"; + static const String assetsCelsiusDegrees = "assets/icons/functions_icons/celsius_degrees.svg"; + static const String assetsTempreture = "assets/icons/functions_icons/tempreture.svg"; + static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg"; + static const String assetsAcFanMiddle = "assets/icons/functions_icons/ac_fan_middle.svg"; + static const String assetsAcFanHigh = "assets/icons/functions_icons/ac_fan_high.svg"; + static const String assetsAcFanAuto = "assets/icons/functions_icons/ac_fan_auto.svg"; + static const String assetsSceneChildLock = "assets/icons/functions_icons/scene_child_lock.svg"; static const String assetsSceneChildUnlock = "assets/icons/functions_icons/scene_child_unlock.svg"; - static const String assetsSceneRefresh = - "assets/icons/functions_icons/scene_refresh.svg"; - static const String assetsLightCountdown = - "assets/icons/functions_icons/light_countdown.svg"; - static const String assetsFarDetection = - "assets/icons/functions_icons/far_detection.svg"; + static const String assetsSceneRefresh = "assets/icons/functions_icons/scene_refresh.svg"; + static const String assetsLightCountdown = "assets/icons/functions_icons/light_countdown.svg"; + static const String assetsFarDetection = "assets/icons/functions_icons/far_detection.svg"; static const String assetsFarDetectionFunction = "assets/icons/functions_icons/far_detection_function.svg"; - static const String assetsIndicator = - "assets/icons/functions_icons/indicator.svg"; - static const String assetsMotionDetection = - "assets/icons/functions_icons/motion_detection.svg"; + static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg"; + static const String assetsMotionDetection = "assets/icons/functions_icons/motion_detection.svg"; static const String assetsMotionlessDetection = "assets/icons/functions_icons/motionless_detection.svg"; - static const String assetsNobodyTime = - "assets/icons/functions_icons/nobody_time.svg"; - static const String assetsFactoryReset = - "assets/icons/functions_icons/factory_reset.svg"; - static const String assetsMasterState = - "assets/icons/functions_icons/master_state.svg"; + static const String assetsNobodyTime = "assets/icons/functions_icons/nobody_time.svg"; + static const String assetsFactoryReset = "assets/icons/functions_icons/factory_reset.svg"; + static const String assetsMasterState = "assets/icons/functions_icons/master_state.svg"; static const String assetsSwitchAlarmSound = "assets/icons/functions_icons/switch_alarm_sound.svg"; - static const String assetsResetOff = - "assets/icons/functions_icons/reset_off.svg"; + static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg"; // Assets for automation_functions static const String assetsCardUnlock = @@ -368,8 +322,7 @@ class Assets { "assets/icons/functions_icons/automation_functions/self_test_result.svg"; static const String assetsPresence = "assets/icons/functions_icons/automation_functions/presence.svg"; - static const String assetsMotion = - "assets/icons/functions_icons/automation_functions/motion.svg"; + static const String assetsMotion = "assets/icons/functions_icons/automation_functions/motion.svg"; static const String assetsCurrentTemp = "assets/icons/functions_icons/automation_functions/current_temp.svg"; static const String assetsPresenceState = @@ -381,16 +334,12 @@ class Assets { static const String activeUser = 'assets/icons/active_user.svg'; static const String deActiveUser = 'assets/icons/deactive_user.svg'; static const String invitedIcon = 'assets/icons/invited_icon.svg'; - static const String rectangleCheckBox = - 'assets/icons/rectangle_check_box.png'; + static const String rectangleCheckBox = 'assets/icons/rectangle_check_box.png'; static const String CheckBoxChecked = 'assets/icons/box_checked.png'; static const String emptyBox = 'assets/icons/empty_box.png'; - static const String completeProcessIcon = - 'assets/icons/compleate_process_icon.svg'; - static const String currentProcessIcon = - 'assets/icons/current_process_icon.svg'; - static const String uncomplete_ProcessIcon = - 'assets/icons/uncompleate_process_icon.svg'; + static const String completeProcessIcon = 'assets/icons/compleate_process_icon.svg'; + static const String currentProcessIcon = 'assets/icons/current_process_icon.svg'; + static const String uncomplete_ProcessIcon = 'assets/icons/uncompleate_process_icon.svg'; static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg'; static const String arrowForward = 'assets/icons/arrow_forward.svg'; static const String arrowDown = 'assets/icons/arrow_down.svg'; @@ -403,17 +352,14 @@ class Assets { static const String duplicate = 'assets/icons/duplicate.svg'; static const String spaceDelete = 'assets/icons/space_delete.svg'; - static const String deleteSpaceLinkIcon = - 'assets/icons/delete_space_link_icon.svg'; + 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 spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg'; static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png'; - static const String scenesPlayIconCheck = - 'assets/icons/scenesPlayIconCheck.png'; + static const String scenesPlayIconCheck = 'assets/icons/scenesPlayIconCheck.png'; static const String presenceStateIcon = 'assets/icons/presence_state.svg'; - static const String currentDistanceIcon = - 'assets/icons/current_distance_icon.svg'; + static const String currentDistanceIcon = 'assets/icons/current_distance_icon.svg'; static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg'; static const String motionDetectionSensitivityIcon = @@ -423,11 +369,11 @@ class Assets { 'assets/icons/motionless_detection_sensitivity_icon.svg'; static const String IndicatorIcon = 'assets/icons/Indicator_icon.svg'; - static const String motionDetectionSensitivityValueIcon = 'assets/icons/motion_detection_sensitivity_value_icon.svg'; - static const String presenceTimeIcon = 'assets/icons/presence_time_icon.svg'; - static const String IlluminanceIcon = 'assets/icons/Illuminance_icon.svg'; - static const String gear = 'assets/icons/gear.svg'; - static const String activeBell='assets/icons/active_bell.svg'; - - + static const String motionDetectionSensitivityValueIcon = + 'assets/icons/motion_detection_sensitivity_value_icon.svg'; + static const String presenceTimeIcon = 'assets/icons/presence_time_icon.svg'; + static const String IlluminanceIcon = 'assets/icons/Illuminance_icon.svg'; + static const String gear = 'assets/icons/gear.svg'; + static const String activeBell = 'assets/icons/active_bell.svg'; + static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg'; } From ee12980b473ad67c7b700f19900e3f60e1cf94c6 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 10:03:21 +0300 Subject: [PATCH 113/238] Fix value parsing in CpsDialogSliderSelector to ensure two decimal precision --- .../ceiling_sensor/cps_dialog_slider_selector.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index 1c549d9f..a2d11f79 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -56,7 +56,7 @@ class CpsDialogSliderSelector extends StatelessWidget { entityId: device?.uuid ?? '', functionCode: selectedFunction, operationName: operationName, - value: value, + value: double.parse(value.toStringAsFixed(2)), condition: selectedFunctionData.condition, ), ), From ceb1e1d23a203924ff5e11331cbf66e08a87b2fd Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 10:03:37 +0300 Subject: [PATCH 114/238] Remove TODO comments. --- .../routine_dialogs/ceiling_sensor/cps_slider_helpers.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart index 03b7b5af..7c7571f6 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart @@ -84,7 +84,4 @@ abstract final class CpsSliderHelpers { _ => '${parsedValue?.toStringAsFixed(0) ?? 0}', }; } - // TODO: Sports Para causes 400 in IF and THEN / sports_para - // TODO: Detection range causes 400 in IF / moving_range - // TODO: Distance of moving objects causes 400 in IF / presence_range } From e2ec986bb9ae8de3a618c5cbe725c6b1a3d26edb Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 10:03:47 +0300 Subject: [PATCH 115/238] Refactor mappable stepped functions to a static constant for improved readability and maintainability --- .../ceiling_sensor/ceiling_sensor_dialog.dart | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index 52665dfa..e54f348e 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -153,23 +153,27 @@ class _CeilingSensorDialogState extends State { ); } + static const _mappableSteppedFunctions = { + 'static_max_dis', + 'presence_reference', + 'moving_reference', + 'perceptual_boundary', + 'moving_boundary', + 'moving_rigger_time', + 'moving_static_time', + 'none_body_time', + 'moving_max_dis', + }; + List _updateValuesForAddedFunctions( List addedFunctions, ) { - const mappableSteppedFunctions = { - 'static_max_dis', - 'presence_reference', - 'moving_reference', - 'perceptual_boundary', - 'moving_boundary', - 'moving_rigger_time', - 'moving_static_time', - 'none_body_time', - 'moving_max_dis', - }; return addedFunctions.map((function) { - if (mappableSteppedFunctions.contains(function.functionCode)) { - final mappedValue = mapSteppedValue( + final shouldMapValue = _mappableSteppedFunctions.contains( + function.functionCode, + ); + if (shouldMapValue) { + final mappedValue = _mapSteppedValue( value: function.value, inputStep: CpsSliderHelpers.dividendOfRange(function.functionCode), inputRange: CpsSliderHelpers.sliderRange(function.functionCode), @@ -189,7 +193,7 @@ class _CeilingSensorDialogState extends State { }).toList(); } - int mapSteppedValue({ + int _mapSteppedValue({ required (double min, double max) inputRange, required double inputStep, required (double min, double max, double dividend) outputRange, From 6bea4c2f4adbea34e666b440185280104994b038 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 10:34:05 +0300 Subject: [PATCH 116/238] Add mappings for moving_range and presence_range in slider helpers --- .../routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart | 2 ++ .../routine_dialogs/ceiling_sensor/cps_slider_helpers.dart | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index e54f348e..7ce6a9b1 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -163,6 +163,8 @@ class _CeilingSensorDialogState extends State { 'moving_static_time', 'none_body_time', 'moving_max_dis', + 'moving_range', + 'presence_range', }; List _updateValuesForAddedFunctions( diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart index 7c7571f6..fcb363c9 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart @@ -12,6 +12,8 @@ abstract final class CpsSliderHelpers { 'moving_static_time' => (0, 60000, 1000), 'none_body_time' => (0, 300000, 5000), 'moving_max_dis' => (0, 500, 50), + 'moving_range' => (0, 255, 5), + 'presence_range' => (0, 255, 5), _ => (defaultMin, defaultMax, defaultDivdidend), }; } From dba89027e365e65fbfc85c82d2922d66db6734f8 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 15 Apr 2025 11:09:48 +0300 Subject: [PATCH 117/238] Updated the if statement for the tag and the location --- lib/pages/routines/widgets/dragable_card.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/routines/widgets/dragable_card.dart b/lib/pages/routines/widgets/dragable_card.dart index 039732b0..7395d2d6 100644 --- a/lib/pages/routines/widgets/dragable_card.dart +++ b/lib/pages/routines/widgets/dragable_card.dart @@ -120,7 +120,7 @@ class DraggableCard extends StatelessWidget { height: 4, ), Visibility( - visible: deviceData['tag'] != null, + visible: deviceData['tag'] != null && deviceData['tag'] != '', child: Row( spacing: 2, children: [ @@ -143,13 +143,13 @@ class DraggableCard extends StatelessWidget { ), ), Visibility( - visible: deviceData['subSpace'] != null || deviceData['subSpace'] != '', + visible: deviceData['subSpace'] != null && deviceData['subSpace'] != '', child: const SizedBox( height: 4, ), ), Visibility( - visible: deviceData['subSpace'] != null || deviceData['subSpace'] != '', + visible: deviceData['subSpace'] != null && deviceData['subSpace'] != '', child: Row( spacing: 2, children: [ From 254e03e3c770da9f7661b88a20751f8e851023d8 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 11:19:58 +0300 Subject: [PATCH 118/238] Add mapping for 'sports_para' in slider range function --- .../routine_dialogs/ceiling_sensor/cps_slider_helpers.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart index fcb363c9..fd637c28 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart @@ -35,6 +35,7 @@ abstract final class CpsSliderHelpers { 'moving_rigger_time' => (0, 2), 'moving_static_time' => (0, 50), 'none_body_time' => (0, 300), + 'sports_para' => (0, 100), _ => (0, 300), }; From be0533645eee2ec131a32dba7b496c9bbee06b7b Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 11:20:15 +0300 Subject: [PATCH 119/238] Refactor dialog header text assignment in `CeilingSensorDialog` for clarity and readability. --- .../ceiling_sensor/ceiling_sensor_dialog.dart | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index 7ce6a9b1..c18706f0 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -35,6 +35,7 @@ class CeilingSensorDialog extends StatefulWidget { class _CeilingSensorDialogState extends State { late final List _cpsFunctions; + late final String _dialogHeaderText; @override void initState() { @@ -46,6 +47,9 @@ class _CeilingSensorDialogState extends State { } return function.type == 'IF' || function.type == 'BOTH'; }).toList(); + + final isIfDialog = widget.dialogType == 'IF'; + _dialogHeaderText = isIfDialog ? 'Conditions' : 'Functions'; } @override @@ -67,18 +71,18 @@ class _CeilingSensorDialogState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - DialogHeader( - 'Presence Sensor ${widget.dialogType == "IF" ? 'Conditions' : 'Functions'}', - ), + DialogHeader('Presence Sensor $_dialogHeaderText'), Expanded(child: _buildMainContent(context, state)), DialogFooter( onCancel: () => Navigator.pop(context), onConfirm: state.addedFunctions.isNotEmpty ? () { + final functions = _updateValuesForAddedFunctions( + state.addedFunctions, + ); context.read().add( AddFunctionToRoutine( - _updateValuesForAddedFunctions( - state.addedFunctions), + functions, '${widget.uniqueCustomId}', ), ); From 616adccfdda7fd8611a4a5e1c2eb36d896439644 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 12:58:20 +0300 Subject: [PATCH 120/238] Applied the correct scenario of tapping add community icon button. --- .../widgets/sidebar_add_community_button.dart | 23 ++----------- .../all_spaces/widgets/sidebar_header.dart | 11 +++++-- .../all_spaces/widgets/sidebar_widget.dart | 32 ++++++++++++++++--- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart index 5c769d48..ae1eb2bf 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_add_community_button.dart @@ -1,19 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; -import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class SidebarAddCommunityButton extends StatelessWidget { const SidebarAddCommunityButton({ - required this.existingCommunityNames, + required this.onTap, super.key, }); - final List existingCommunityNames; + final void Function() onTap; @override Widget build(BuildContext context) { @@ -30,22 +26,9 @@ class SidebarAddCommunityButton extends StatelessWidget { ), ), ), - onPressed: () => _showCreateCommunityDialog(context), + onPressed: onTap, icon: SvgPicture.asset(Assets.addIcon), ), ); } - - void _showCreateCommunityDialog(BuildContext context) => showDialog( - context: context, - builder: (context) => CreateCommunityDialog( - isEditMode: false, - existingCommunityNames: existingCommunityNames, - onCreateCommunity: (name, description) { - context.read().add( - CreateCommunityEvent(name, description, context), - ); - }, - ), - ); } diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart index 135be109..1706d51a 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_header.dart @@ -5,9 +5,12 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class SidebarHeader extends StatelessWidget { - const SidebarHeader({required this.existingCommunityNames, super.key}); + const SidebarHeader({ + required this.onAddCommunity, + super.key, + }); - final List existingCommunityNames; + final void Function() onAddCommunity; @override Widget build(BuildContext context) { @@ -23,7 +26,9 @@ class SidebarHeader extends StatelessWidget { color: ColorsManager.blackColor, ), ), - SidebarAddCommunityButton(existingCommunityNames: existingCommunityNames), + SidebarAddCommunityButton( + onTap: onAddCommunity, + ), ], ), ); diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 35bb8ad2..b30a8c96 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_header.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -94,10 +95,7 @@ class _SidebarWidgetState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - SidebarHeader( - existingCommunityNames: - widget.communities.map((community) => community.name).toList(), - ), + SidebarHeader(onAddCommunity: _onAddCommunity), CustomSearchBar( onSearchChanged: (query) => setState(() => _searchQuery = query), ), @@ -179,4 +177,30 @@ class _SidebarWidgetState extends State { ), ); } + + void _onAddCommunity() => _selectedId?.isNotEmpty ?? true + ? _clearSelection() + : _showCreateCommunityDialog(); + + void _clearSelection() { + setState(() => _selectedId = ''); + context.read().add( + NewCommunityEvent(communities: widget.communities), + ); + } + + void _showCreateCommunityDialog() { + showDialog( + context: context, + builder: (context) => CreateCommunityDialog( + isEditMode: false, + existingCommunityNames: widget.communities.map((e) => e.name).toList(), + onCreateCommunity: (name, description) { + context.read().add( + CreateCommunityEvent(name, description, context), + ); + }, + ), + ); + } } From 7dcaa20da12ace15f71e1a9693a4bd9dc031f490 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 13:06:30 +0300 Subject: [PATCH 121/238] Enhanced the code and look of `DialogFooter` buttons. --- lib/pages/routines/widgets/dialog_footer.dart | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/lib/pages/routines/widgets/dialog_footer.dart b/lib/pages/routines/widgets/dialog_footer.dart index 15db9732..e5a548f7 100644 --- a/lib/pages/routines/widgets/dialog_footer.dart +++ b/lib/pages/routines/widgets/dialog_footer.dart @@ -8,12 +8,12 @@ class DialogFooter extends StatelessWidget { final int? dialogWidth; const DialogFooter({ - Key? key, + super.key, required this.onCancel, required this.onConfirm, required this.isConfirmEnabled, this.dialogWidth, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -28,21 +28,19 @@ class DialogFooter extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Expanded( - child: _buildFooterButton( - context, - 'Cancel', - onCancel, - ), + _buildFooterButton( + context: context, + text: 'Cancel', + onTap: onCancel, ), if (isConfirmEnabled) ...[ Container(width: 1, height: 50, color: ColorsManager.greyColor), - Expanded( - child: _buildFooterButton( - context, - 'Confirm', - onConfirm, - ), + _buildFooterButton( + context: context, + text: 'Confirm', + onTap: onConfirm, + textColor: + isConfirmEnabled ? ColorsManager.primaryColorWithOpacity : Colors.red, ), ], ], @@ -50,24 +48,24 @@ class DialogFooter extends StatelessWidget { ); } - Widget _buildFooterButton( - BuildContext context, - String text, - VoidCallback? onTap, - ) { - return GestureDetector( - onTap: onTap, - child: SizedBox( - height: 50, - child: Center( - child: Text( - text, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: text == 'Confirm' - ? ColorsManager.primaryColorWithOpacity - : ColorsManager.textGray, - ), - ), + Widget _buildFooterButton({ + required BuildContext context, + required String text, + required VoidCallback? onTap, + Color? textColor, + }) { + return Expanded( + child: TextButton( + style: TextButton.styleFrom( + foregroundColor: ColorsManager.primaryColorWithOpacity, + disabledForegroundColor: ColorsManager.primaryColor, + ), + onPressed: onTap, + child: Text( + text, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: textColor ?? ColorsManager.textGray, + ), ), ), ); From 1bfab8cc768557c5da701f7d997b837beaf0bc68 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 14:38:06 +0300 Subject: [PATCH 122/238] SP-1189-Fix tapping ok and nothing happening bug by taking the action out of the widget. --- .../widgets/loaded_space_widget.dart | 7 + .../all_spaces/widgets/sidebar_widget.dart | 8 +- .../views/create_subspace_model_dialog.dart | 231 ++++-------------- .../create_subspace_model_chips_box.dart | 64 +++++ .../create_subspace_model_footer_buttons.dart | 53 ++++ .../widgets/subspace_chip.dart | 58 +++++ .../widgets/subspaces_textfield.dart | 68 ++++++ 7 files changed, 297 insertions(+), 192 deletions(-) create mode 100644 lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart create mode 100644 lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_footer_buttons.dart create mode 100644 lib/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart create mode 100644 lib/pages/spaces_management/create_subspace_model/widgets/subspaces_textfield.dart diff --git a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart index 66b2d6da..5ef9c79b 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart @@ -2,6 +2,8 @@ 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/view/space_tree_view.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; @@ -107,6 +109,11 @@ class _LoadedSpaceViewState extends State { selectedSpaceUuid: widget.selectedSpace?.uuid ?? widget.selectedCommunity?.uuid ?? '', + onCreateCommunity: (name, description) { + context.read().add( + CreateCommunityEvent(name, description, context), + ); + }, ), CommunityStructureArea( selectedCommunity: widget.selectedCommunity, diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index b30a8c96..3eb1c001 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -16,9 +16,11 @@ import 'package:syncrow_web/utils/style.dart'; class SidebarWidget extends StatefulWidget { final List communities; final String? selectedSpaceUuid; + final void Function(String name, String description) onCreateCommunity; const SidebarWidget({ required this.communities, + required this.onCreateCommunity, this.selectedSpaceUuid, super.key, }); @@ -195,11 +197,7 @@ class _SidebarWidgetState extends State { builder: (context) => CreateCommunityDialog( isEditMode: false, existingCommunityNames: widget.communities.map((e) => e.name).toList(), - onCreateCommunity: (name, description) { - context.read().add( - CreateCommunityEvent(name, description, context), - ); - }, + onCreateCommunity: widget.onCreateCommunity, ), ); } diff --git a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart index 7a39891b..66acdf3d 100644 --- a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; -import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_footer_buttons.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class CreateSubSpaceModelDialog extends StatelessWidget { final bool isEdit; @@ -14,211 +15,67 @@ class CreateSubSpaceModelDialog extends StatelessWidget { final List? existingSubSpaces; final void Function(List newSubspaces)? onUpdate; - const CreateSubSpaceModelDialog( - {Key? key, - required this.isEdit, - required this.dialogTitle, - this.existingSubSpaces, - this.onUpdate}) - : super(key: key); + const CreateSubSpaceModelDialog({ + required this.isEdit, + required this.dialogTitle, + this.existingSubSpaces, + this.onUpdate, + super.key, + }); @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - final textController = TextEditingController(); return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: BlocProvider( - create: (_) { + create: (context) { final bloc = SubSpaceModelBloc(); if (existingSubSpaces != null) { - for (var subSpace in existingSubSpaces!) { + for (final subSpace in existingSubSpaces ?? []) { bloc.add(AddSubSpaceModel(subSpace)); } } return bloc; }, child: BlocBuilder( - builder: (context, state) { - return Container( - color: ColorsManager.whiteColors, - child: SizedBox( - width: screenWidth * 0.3, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - dialogTitle, - style: Theme.of(context) - .textTheme - .headlineLarge - ?.copyWith(color: ColorsManager.blackColor), - ), - const SizedBox(height: 16), - Container( - width: screenWidth * 0.35, - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 16.0), - decoration: BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.circular(10), - ), - child: Wrap( - spacing: 8.0, - runSpacing: 8.0, - children: [ - ...state.subSpaces.asMap().entries.map( - (entry) { - final index = entry.key; - final subSpace = entry.value; - - final lowerName = - subSpace.subspaceName.toLowerCase(); - - final duplicateIndices = state.subSpaces - .asMap() - .entries - .where((e) => - e.value.subspaceName.toLowerCase() == - lowerName) - .map((e) => e.key) - .toList(); - final isDuplicate = - duplicateIndices.length > 1 && - duplicateIndices.indexOf(index) != 0; - - return Chip( - label: Text(subSpace.subspaceName, - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith( - color: ColorsManager.spaceColor, - )), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - side: BorderSide( - color: isDuplicate - ? ColorsManager.red - : ColorsManager.transparentColor, - width: 0, - ), - ), - deleteIcon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager.lightGrayColor, - width: 1.5, - ), - ), - child: const Icon( - Icons.close, - size: 16, - color: ColorsManager.lightGrayColor, - ), - ), - onDeleted: () => context - .read() - .add(RemoveSubSpaceModel(subSpace)), - ); - }, - ), - SizedBox( - width: 200, - child: TextField( - controller: textController, - decoration: InputDecoration( - border: InputBorder.none, - hintText: state.subSpaces.isEmpty - ? 'Please enter the name' - : null, - hintStyle: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( - color: ColorsManager - .lightGrayColor)), - onSubmitted: (value) { - if (value.trim().isNotEmpty) { - context.read().add( - AddSubSpaceModel( - SubspaceTemplateModel( - subspaceName: value.trim(), - disabled: false))); - textController.clear(); - } - }, - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith( - color: ColorsManager.blackColor)), - ), - ], - ), - ), - if (state.errorMessage.isNotEmpty) - Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: Text(state.errorMessage, - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith( - color: ColorsManager.red, - )), - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () async { - Navigator.of(context).pop(); - }, - ), - ), - const SizedBox(width: 10), - Expanded( - child: DefaultButton( - onPressed: (state.errorMessage.isNotEmpty) - ? null - : () async { - final subSpaces = context - .read() - .state - .subSpaces; - Navigator.of(context).pop(); - if (onUpdate != null) { - onUpdate!(subSpaces); - } - }, - backgroundColor: ColorsManager.secondaryColor, - borderRadius: 10, - foregroundColor: state.errorMessage.isNotEmpty - ? ColorsManager.whiteColorsWithOpacity - : ColorsManager.whiteColors, - child: const Text('OK'), - ), - ), - ], - ), - ], + builder: (context, state) => Container( + color: ColorsManager.whiteColors, + width: screenWidth * 0.3, + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + dialogTitle, + style: context.textTheme.headlineLarge?.copyWith( + color: ColorsManager.blackColor, + ), + ), + const SizedBox(height: 16), + CreateSubspaceModelChipsBox(subSpaces: state.subSpaces), + if (state.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Text( + state.errorMessage, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.red, + ), ), ), - )); - }, + const SizedBox(height: 16), + CreateSubspaceModelFooterButtons( + onUpdate: onUpdate, + errorMessage: state.errorMessage, + ), + ], + ), + ), ), ), ); diff --git a/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart b/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart new file mode 100644 index 00000000..a18fc8d8 --- /dev/null +++ b/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspaces_textfield.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CreateSubspaceModelChipsBox extends StatelessWidget { + const CreateSubspaceModelChipsBox({ + required this.subSpaces, + super.key, + }); + + final List subSpaces; + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return Container( + width: screenWidth * 0.35, + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 16, + ), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(10), + ), + child: Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + ...subSpaces.asMap().entries.map( + (entry) { + final index = entry.key; + final subSpace = entry.value; + + final lowerName = subSpace.subspaceName.toLowerCase(); + + final duplicateIndices = subSpaces + .asMap() + .entries + .where((e) => e.value.subspaceName.toLowerCase() == lowerName) + .map((e) => e.key) + .toList(); + final isDuplicate = duplicateIndices.length > 1 && + duplicateIndices.indexOf(index) != 0; + + return SubspaceChip( + subSpace: subSpace, + isDuplicate: isDuplicate, + ); + }, + ), + SubspacesTextfield( + hintText: subSpaces.isEmpty ? 'Please enter the name' : null, + ), + ], + ), + ); + } +} diff --git a/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_footer_buttons.dart b/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_footer_buttons.dart new file mode 100644 index 00000000..a8dcf89c --- /dev/null +++ b/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_footer_buttons.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CreateSubspaceModelFooterButtons extends StatelessWidget { + const CreateSubspaceModelFooterButtons({ + required this.onUpdate, + required this.errorMessage, + super.key, + }); + + final void Function(List newSubspaces)? onUpdate; + final String errorMessage; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: errorMessage.isEmpty + ? () { + Navigator.of(context).pop(); + if (onUpdate != null) { + final subSpaces = + context.read().state.subSpaces; + onUpdate!(subSpaces); + } + } + : null, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: errorMessage.isNotEmpty + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], + ); + } +} diff --git a/lib/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart b/lib/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart new file mode 100644 index 00000000..b54e0712 --- /dev/null +++ b/lib/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class SubspaceChip extends StatelessWidget { + const SubspaceChip({ + required this.subSpace, + required this.isDuplicate, + super.key, + }); + + final SubspaceTemplateModel subSpace; + final bool isDuplicate; + + @override + Widget build(BuildContext context) { + return Chip( + label: Text( + subSpace.subspaceName, + style: context.textTheme.bodySmall?.copyWith( + color: isDuplicate ? ColorsManager.red : ColorsManager.spaceColor, + ), + ), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: BorderSide( + color: isDuplicate ? ColorsManager.red : ColorsManager.transparentColor, + width: 0, + ), + ), + deleteIcon: Container( + padding: const EdgeInsetsDirectional.all(1), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager.lightGrayColor, + width: 1.5, + ), + ), + child: const FittedBox( + fit: BoxFit.scaleDown, + child: Icon( + Icons.close, + color: ColorsManager.lightGrayColor, + ), + ), + ), + onDeleted: () => context.read().add( + RemoveSubSpaceModel(subSpace), + ), + ); + } +} diff --git a/lib/pages/spaces_management/create_subspace_model/widgets/subspaces_textfield.dart b/lib/pages/spaces_management/create_subspace_model/widgets/subspaces_textfield.dart new file mode 100644 index 00000000..d654b960 --- /dev/null +++ b/lib/pages/spaces_management/create_subspace_model/widgets/subspaces_textfield.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class SubspacesTextfield extends StatefulWidget { + const SubspacesTextfield({ + required this.hintText, + super.key, + }); + + final String? hintText; + + @override + State createState() => _SubspacesTextfieldState(); +} + +class _SubspacesTextfieldState extends State { + late final TextEditingController _controller; + @override + void initState() { + _controller = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 100, + child: TextField( + controller: _controller, + decoration: InputDecoration( + border: InputBorder.none, + hintText: widget.hintText, + hintStyle: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.lightGrayColor, + ), + ), + onSubmitted: (value) { + final trimmedValue = value.trim(); + if (trimmedValue.isNotEmpty) { + context.read().add( + AddSubSpaceModel( + SubspaceTemplateModel( + subspaceName: trimmedValue, + disabled: false, + ), + ), + ); + _controller.clear(); + } + }, + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.blackColor, + ), + ), + ); + } +} From 748c67fd8b475530dc75b8abf5df102e022980d2 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 14:52:20 +0300 Subject: [PATCH 123/238] SP-1333 --- .../widgets/tag_chips_display_widget.dart | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index fc6a8c88..fc085237 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -88,12 +88,7 @@ class TagChipDisplay extends StatelessWidget { ), ), ), - EditChip(onTap: () async { - // Use the Navigator's context for showDialog - Navigator.of(context).pop(); - - await showDialog( - barrierDismissible: false, + EditChip(onTap: () => showDialog( context: context, builder: (context) => AssignTagModelsDialog( products: products, @@ -110,18 +105,14 @@ class TagChipDisplay extends StatelessWidget { spaceModel?.tags ?? [], subspaces), spaceName: spaceModel?.modelName ?? '', projectTags: projectTags, - )); - }) + ))) ], ), ), ) : TextButton( - onPressed: () async { - Navigator.of(context).pop(); - - await showDialog( - barrierDismissible: false, + onPressed: () { + showDialog( context: context, builder: (context) => AddDeviceTypeModelWidget( products: products, From 117f6190dd62175b45de191c9d1af24f557dfe30 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:02:58 +0300 Subject: [PATCH 124/238] removed unnecessary `BuildContext` from `TagChipDisplay` constructor, sorted its properies, and converted to using `super.key`. --- .../dialog/create_space_model_dialog.dart | 1 - .../widgets/tag_chips_display_widget.dart | 26 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 98251382..3afb0c6f 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -145,7 +145,6 @@ class CreateSpaceModelDialog extends StatelessWidget { ), const SizedBox(height: 10), TagChipDisplay( - context, screenWidth: screenWidth, spaceModel: updatedSpaceModel, products: products, diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index fc085237..3c1a22ec 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -23,19 +23,19 @@ class TagChipDisplay extends StatelessWidget { final List? allSpaceModels; final List projectTags; - const TagChipDisplay(BuildContext context, - {Key? key, - required this.screenWidth, - required this.spaceModel, - required this.products, - required this.subspaces, - required this.allTags, - required this.spaceNameController, - this.pageContext, - this.otherSpaceModels, - this.allSpaceModels, - required this.projectTags}) - : super(key: key); + const TagChipDisplay({ + required this.screenWidth, + required this.spaceModel, + required this.products, + required this.subspaces, + required this.allTags, + required this.spaceNameController, + required this.projectTags, + this.pageContext, + this.otherSpaceModels, + this.allSpaceModels, + super.key, + }); @override Widget build(BuildContext context) { From 7d0e50fb1d2b0795631fd3c1112c1c32bb645e52 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:03:27 +0300 Subject: [PATCH 125/238] removed unnecessary comments. --- .../space_model/widgets/tag_chips_display_widget.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 3c1a22ec..57dd6318 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -51,14 +51,13 @@ class TagChipDisplay extends StatelessWidget { borderRadius: BorderRadius.circular(15), border: Border.all( color: ColorsManager.textFieldGreyColor, - width: 3.0, // Border width + width: 3.0, ), ), child: Wrap( spacing: 8.0, runSpacing: 8.0, children: [ - // Combine tags from spaceModel and subspaces ...TagHelper.groupTags([ ...?spaceModel?.tags, ...?spaceModel?.subspaceModels?.expand((subspace) => subspace.tags ?? []) @@ -73,7 +72,7 @@ class TagChipDisplay extends StatelessWidget { ), ), label: Text( - 'x${entry.value}', // Show count + 'x${entry.value}', style: Theme.of(context) .textTheme .bodySmall! From 7945cefe5374a0a4152fe939708f3353992d2eb3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:05:06 +0300 Subject: [PATCH 126/238] added trailing commas wherever necessary. --- .../widgets/tag_chips_display_widget.dart | 78 ++++++++++--------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 57dd6318..98996d5f 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -24,23 +24,24 @@ class TagChipDisplay extends StatelessWidget { final List projectTags; const TagChipDisplay({ - required this.screenWidth, - required this.spaceModel, - required this.products, - required this.subspaces, - required this.allTags, - required this.spaceNameController, - required this.projectTags, - this.pageContext, - this.otherSpaceModels, - this.allSpaceModels, - super.key, - }); + required this.screenWidth, + required this.spaceModel, + required this.products, + required this.subspaces, + required this.allTags, + required this.spaceNameController, + required this.projectTags, + this.pageContext, + this.otherSpaceModels, + this.allSpaceModels, + super.key, + }); @override Widget build(BuildContext context) { return (spaceModel?.tags?.isNotEmpty == true || - spaceModel?.subspaceModels?.any((subspace) => subspace.tags?.isNotEmpty == true) == + spaceModel?.subspaceModels + ?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) ? SizedBox( width: screenWidth * 0.25, @@ -60,7 +61,7 @@ class TagChipDisplay extends StatelessWidget { children: [ ...TagHelper.groupTags([ ...?spaceModel?.tags, - ...?spaceModel?.subspaceModels?.expand((subspace) => subspace.tags ?? []) + ...?spaceModel?.subspaceModels?.expand((e) => e.tags ?? []) ]).entries.map( (entry) => Chip( avatar: SizedBox( @@ -73,10 +74,9 @@ class TagChipDisplay extends StatelessWidget { ), label: Text( 'x${entry.value}', - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(color: ColorsManager.spaceColor), + style: Theme.of(context).textTheme.bodySmall!.copyWith( + color: ColorsManager.spaceColor, + ), ), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( @@ -87,24 +87,30 @@ class TagChipDisplay extends StatelessWidget { ), ), ), - EditChip(onTap: () => showDialog( - context: context, - builder: (context) => AssignTagModelsDialog( - products: products, - allSpaceModels: allSpaceModels, - subspaces: subspaces, - pageContext: pageContext, - allTags: allTags, - spaceModel: spaceModel, - otherSpaceModels: otherSpaceModels, - initialTags: TagHelper.generateInitialTags( - subspaces: subspaces, spaceTagModels: spaceModel?.tags ?? []), - title: 'Edit Device', - addedProducts: TagHelper.createInitialSelectedProducts( - spaceModel?.tags ?? [], subspaces), - spaceName: spaceModel?.modelName ?? '', - projectTags: projectTags, - ))) + EditChip( + onTap: () => showDialog( + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + allSpaceModels: allSpaceModels, + subspaces: subspaces, + pageContext: pageContext, + allTags: allTags, + spaceModel: spaceModel, + otherSpaceModels: otherSpaceModels, + initialTags: TagHelper.generateInitialTags( + subspaces: subspaces, + spaceTagModels: spaceModel?.tags ?? []), + title: 'Edit Device', + addedProducts: TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], + subspaces, + ), + spaceName: spaceModel?.modelName ?? '', + projectTags: projectTags, + ), + ), + ), ], ), ), From c0a963ded5529c5280d8b47737f0ad31b208de40 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:05:31 +0300 Subject: [PATCH 127/238] refactor: use context extension for text theme. --- .../space_model/widgets/tag_chips_display_widget.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 98996d5f..19bdf64f 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class TagChipDisplay extends StatelessWidget { final double screenWidth; @@ -74,9 +75,9 @@ class TagChipDisplay extends StatelessWidget { ), label: Text( 'x${entry.value}', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.spaceColor, - ), + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.spaceColor, + ), ), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( From a66784473fd16f27ecab567f0691d6a3a9052be0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:06:01 +0300 Subject: [PATCH 128/238] Replaced conditional with an if statement in `TagChipDisplay`. --- .../widgets/tag_chips_display_widget.dart | 186 +++++++++--------- 1 file changed, 94 insertions(+), 92 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 19bdf64f..b3f3d978 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -40,106 +40,108 @@ class TagChipDisplay extends StatelessWidget { @override Widget build(BuildContext context) { - return (spaceModel?.tags?.isNotEmpty == true || - spaceModel?.subspaceModels - ?.any((subspace) => subspace.tags?.isNotEmpty == true) == - true) - ? SizedBox( - width: screenWidth * 0.25, - child: Container( - padding: const EdgeInsets.all(8.0), - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - borderRadius: BorderRadius.circular(15), - border: Border.all( - color: ColorsManager.textFieldGreyColor, - width: 3.0, - ), - ), - child: Wrap( - spacing: 8.0, - runSpacing: 8.0, - children: [ - ...TagHelper.groupTags([ - ...?spaceModel?.tags, - ...?spaceModel?.subspaceModels?.expand((e) => e.tags ?? []) - ]).entries.map( - (entry) => Chip( - avatar: SizedBox( - width: 24, - height: 24, - child: SvgPicture.asset( - entry.key.icon ?? 'assets/icons/gateway.svg', - fit: BoxFit.contain, - ), - ), - label: Text( - 'x${entry.value}', - style: context.textTheme.bodySmall!.copyWith( - color: ColorsManager.spaceColor, - ), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide( - color: ColorsManager.spaceColor, - ), - ), + if ((spaceModel?.tags?.isNotEmpty == true || + spaceModel?.subspaceModels + ?.any((subspace) => subspace.tags?.isNotEmpty == true) == + true)) { + return SizedBox( + width: screenWidth * 0.25, + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(15), + border: Border.all( + color: ColorsManager.textFieldGreyColor, + width: 3.0, + ), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + ...TagHelper.groupTags([ + ...?spaceModel?.tags, + ...?spaceModel?.subspaceModels?.expand((e) => e.tags ?? []) + ]).entries.map( + (entry) => Chip( + avatar: SizedBox( + width: 24, + height: 24, + child: SvgPicture.asset( + entry.key.icon ?? 'assets/icons/gateway.svg', + fit: BoxFit.contain, ), ), - EditChip( - onTap: () => showDialog( - context: context, - builder: (context) => AssignTagModelsDialog( - products: products, - allSpaceModels: allSpaceModels, - subspaces: subspaces, - pageContext: pageContext, - allTags: allTags, - spaceModel: spaceModel, - otherSpaceModels: otherSpaceModels, - initialTags: TagHelper.generateInitialTags( - subspaces: subspaces, - spaceTagModels: spaceModel?.tags ?? []), - title: 'Edit Device', - addedProducts: TagHelper.createInitialSelectedProducts( - spaceModel?.tags ?? [], - subspaces, + label: Text( + 'x${entry.value}', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.spaceColor, + ), + ), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: const BorderSide( + color: ColorsManager.spaceColor, ), - spaceName: spaceModel?.modelName ?? '', - projectTags: projectTags, ), ), ), - ], - ), - ), - ) - : TextButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AddDeviceTypeModelWidget( - products: products, - subspaces: subspaces, - allTags: allTags, - spaceName: spaceNameController.text, - pageContext: pageContext, - isCreate: true, - spaceModel: spaceModel, - otherSpaceModels: otherSpaceModels, - projectTags: projectTags, + EditChip( + onTap: () => showDialog( + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + allSpaceModels: allSpaceModels, + subspaces: subspaces, + pageContext: pageContext, + allTags: allTags, + spaceModel: spaceModel, + otherSpaceModels: otherSpaceModels, + initialTags: TagHelper.generateInitialTags( + subspaces: subspaces, + spaceTagModels: spaceModel?.tags ?? []), + title: 'Edit Device', + addedProducts: TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], + subspaces, + ), + spaceName: spaceModel?.modelName ?? '', + projectTags: projectTags, + ), ), - ); - }, - style: TextButton.styleFrom( - padding: EdgeInsets.zero, - ), - child: const ButtonContentWidget( - icon: Icons.add, - label: 'Add Devices', + ), + ], + ), + ), + ); + } else { + return TextButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => AddDeviceTypeModelWidget( + products: products, + subspaces: subspaces, + allTags: allTags, + spaceName: spaceNameController.text, + pageContext: pageContext, + isCreate: true, + spaceModel: spaceModel, + otherSpaceModels: otherSpaceModels, + projectTags: projectTags, ), ); + }, + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Add Devices', + ), + ); + } } } From be168aed9314585f8e4e327c91f09c1262b4a4c6 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:08:30 +0300 Subject: [PATCH 129/238] refactor: simplify tag checking logic to enhance readability. --- .../space_model/widgets/tag_chips_display_widget.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index b3f3d978..1eaa3425 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -40,10 +40,11 @@ class TagChipDisplay extends StatelessWidget { @override Widget build(BuildContext context) { - if ((spaceModel?.tags?.isNotEmpty == true || - spaceModel?.subspaceModels - ?.any((subspace) => subspace.tags?.isNotEmpty == true) == - true)) { + final hasTags = spaceModel?.tags?.isNotEmpty ?? false; + final hasSubspaceTags = + spaceModel?.subspaceModels?.any((e) => e.tags?.isNotEmpty ?? false) ?? false; + + if (hasTags || hasSubspaceTags) { return SizedBox( width: screenWidth * 0.25, child: Container( From f57348e5cda17a30d7fb79d431d6ba533238e9da Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:13:47 +0300 Subject: [PATCH 130/238] converted to using expressions wherever possible in `TagChipDisplay`. --- .../widgets/tag_chips_display_widget.dart | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 1eaa3425..1472a05b 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -38,6 +38,11 @@ class TagChipDisplay extends StatelessWidget { super.key, }); + Map get _groupedTags => TagHelper.groupTags([ + ...?spaceModel?.tags, + ...?spaceModel?.subspaceModels?.expand((e) => e.tags ?? []) + ]); + @override Widget build(BuildContext context) { final hasTags = spaceModel?.tags?.isNotEmpty ?? false; @@ -61,36 +66,33 @@ class TagChipDisplay extends StatelessWidget { spacing: 8.0, runSpacing: 8.0, children: [ - ...TagHelper.groupTags([ - ...?spaceModel?.tags, - ...?spaceModel?.subspaceModels?.expand((e) => e.tags ?? []) - ]).entries.map( - (entry) => Chip( - avatar: SizedBox( - width: 24, - height: 24, - child: SvgPicture.asset( - entry.key.icon ?? 'assets/icons/gateway.svg', - fit: BoxFit.contain, - ), - ), - label: Text( - 'x${entry.value}', - style: context.textTheme.bodySmall!.copyWith( - color: ColorsManager.spaceColor, - ), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide( - color: ColorsManager.spaceColor, - ), - ), + ..._groupedTags.entries.map((entry) { + return Chip( + avatar: SizedBox( + width: 24, + height: 24, + child: SvgPicture.asset( + entry.key.icon ?? 'assets/icons/gateway.svg', + fit: BoxFit.contain, ), ), + label: Text( + 'x${entry.value}', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.spaceColor, + ), + ), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: const BorderSide( + color: ColorsManager.spaceColor, + ), + ), + ); + }), EditChip( - onTap: () => showDialog( + onTap: () => showDialog( context: context, builder: (context) => AssignTagModelsDialog( products: products, @@ -119,22 +121,20 @@ class TagChipDisplay extends StatelessWidget { ); } else { return TextButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AddDeviceTypeModelWidget( - products: products, - subspaces: subspaces, - allTags: allTags, - spaceName: spaceNameController.text, - pageContext: pageContext, - isCreate: true, - spaceModel: spaceModel, - otherSpaceModels: otherSpaceModels, - projectTags: projectTags, - ), - ); - }, + onPressed: () => showDialog( + context: context, + builder: (context) => AddDeviceTypeModelWidget( + products: products, + subspaces: subspaces, + allTags: allTags, + spaceName: spaceNameController.text, + pageContext: pageContext, + isCreate: true, + spaceModel: spaceModel, + otherSpaceModels: otherSpaceModels, + projectTags: projectTags, + ), + ), style: TextButton.styleFrom( padding: EdgeInsets.zero, ), From 08e5e17910a6b3e56c207300ba9952b701a42159 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:23:36 +0300 Subject: [PATCH 131/238] Extracted `Add Devices` button into a private method. --- .../widgets/tag_chips_display_widget.dart | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 1472a05b..1f75a047 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -53,18 +53,18 @@ class TagChipDisplay extends StatelessWidget { return SizedBox( width: screenWidth * 0.25, child: Container( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: ColorsManager.textFieldGreyColor, borderRadius: BorderRadius.circular(15), border: Border.all( color: ColorsManager.textFieldGreyColor, - width: 3.0, + width: 3, ), ), child: Wrap( - spacing: 8.0, - runSpacing: 8.0, + spacing: 8, + runSpacing: 8, children: [ ..._groupedTags.entries.map((entry) { return Chip( @@ -120,29 +120,33 @@ class TagChipDisplay extends StatelessWidget { ), ); } else { - return TextButton( - onPressed: () => showDialog( - context: context, - builder: (context) => AddDeviceTypeModelWidget( - products: products, - subspaces: subspaces, - allTags: allTags, - spaceName: spaceNameController.text, - pageContext: pageContext, - isCreate: true, - spaceModel: spaceModel, - otherSpaceModels: otherSpaceModels, - projectTags: projectTags, - ), - ), - style: TextButton.styleFrom( - padding: EdgeInsets.zero, - ), - child: const ButtonContentWidget( - icon: Icons.add, - label: 'Add Devices', - ), - ); + return _buildAddDevicesButton(context); } } + + Widget _buildAddDevicesButton(BuildContext context) { + return TextButton( + onPressed: () => showDialog( + context: context, + builder: (context) => AddDeviceTypeModelWidget( + products: products, + subspaces: subspaces, + allTags: allTags, + spaceName: spaceNameController.text, + pageContext: pageContext, + isCreate: true, + spaceModel: spaceModel, + otherSpaceModels: otherSpaceModels, + projectTags: projectTags, + ), + ), + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Add Devices', + ), + ); + } } From 90e0d2f52be8c575be2b228111a85035130058df Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:24:36 +0300 Subject: [PATCH 132/238] Extracted Chip into a private method. --- .../widgets/tag_chips_display_widget.dart | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 1f75a047..200cd659 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -66,31 +66,7 @@ class TagChipDisplay extends StatelessWidget { spacing: 8, runSpacing: 8, children: [ - ..._groupedTags.entries.map((entry) { - return Chip( - avatar: SizedBox( - width: 24, - height: 24, - child: SvgPicture.asset( - entry.key.icon ?? 'assets/icons/gateway.svg', - fit: BoxFit.contain, - ), - ), - label: Text( - 'x${entry.value}', - style: context.textTheme.bodySmall!.copyWith( - color: ColorsManager.spaceColor, - ), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide( - color: ColorsManager.spaceColor, - ), - ), - ); - }), + ..._groupedTags.entries.map((entry) => _buildChip(context, entry)), EditChip( onTap: () => showDialog( context: context, @@ -124,6 +100,35 @@ class TagChipDisplay extends StatelessWidget { } } + Chip _buildChip( + BuildContext context, + MapEntry entry, + ) { + return Chip( + avatar: SizedBox( + width: 24, + height: 24, + child: SvgPicture.asset( + entry.key.icon ?? 'assets/icons/gateway.svg', + fit: BoxFit.contain, + ), + ), + label: Text( + '${entry.value}', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.spaceColor, + ), + ), + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: const BorderSide( + color: ColorsManager.spaceColor, + ), + ), + ); + } + Widget _buildAddDevicesButton(BuildContext context) { return TextButton( onPressed: () => showDialog( From a4432656ab05cf39351240d1134cb033e65fbc5b Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:25:19 +0300 Subject: [PATCH 133/238] refactor: extract EditChip into a private method for improved readability --- .../widgets/tag_chips_display_widget.dart | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 200cd659..8cf52c4f 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -67,30 +67,7 @@ class TagChipDisplay extends StatelessWidget { runSpacing: 8, children: [ ..._groupedTags.entries.map((entry) => _buildChip(context, entry)), - EditChip( - onTap: () => showDialog( - context: context, - builder: (context) => AssignTagModelsDialog( - products: products, - allSpaceModels: allSpaceModels, - subspaces: subspaces, - pageContext: pageContext, - allTags: allTags, - spaceModel: spaceModel, - otherSpaceModels: otherSpaceModels, - initialTags: TagHelper.generateInitialTags( - subspaces: subspaces, - spaceTagModels: spaceModel?.tags ?? []), - title: 'Edit Device', - addedProducts: TagHelper.createInitialSelectedProducts( - spaceModel?.tags ?? [], - subspaces, - ), - spaceName: spaceModel?.modelName ?? '', - projectTags: projectTags, - ), - ), - ), + _buildEditChip(context), ], ), ), @@ -100,7 +77,35 @@ class TagChipDisplay extends StatelessWidget { } } - Chip _buildChip( + Widget _buildEditChip(BuildContext context) { + return EditChip( + onTap: () => showDialog( + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + allSpaceModels: allSpaceModels, + subspaces: subspaces, + pageContext: pageContext, + allTags: allTags, + spaceModel: spaceModel, + otherSpaceModels: otherSpaceModels, + initialTags: TagHelper.generateInitialTags( + subspaces: subspaces, + spaceTagModels: spaceModel?.tags ?? [], + ), + title: 'Edit Device', + addedProducts: TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], + subspaces, + ), + spaceName: spaceModel?.modelName ?? '', + projectTags: projectTags, + ), + ), + ); + } + + Widget _buildChip( BuildContext context, MapEntry entry, ) { From d3902d622e2680016440dbadedfae320844925cd Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:27:45 +0300 Subject: [PATCH 134/238] Moved constructor to be the first element in `TagChipDisplay`. --- .../widgets/tag_chips_display_widget.dart | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 8cf52c4f..63eab144 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -13,17 +13,6 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class TagChipDisplay extends StatelessWidget { - final double screenWidth; - final SpaceTemplateModel? spaceModel; - final List? products; - final List? subspaces; - final List? allTags; - final TextEditingController spaceNameController; - final BuildContext? pageContext; - final List? otherSpaceModels; - final List? allSpaceModels; - final List projectTags; - const TagChipDisplay({ required this.screenWidth, required this.spaceModel, @@ -38,6 +27,17 @@ class TagChipDisplay extends StatelessWidget { super.key, }); + final double screenWidth; + final SpaceTemplateModel? spaceModel; + final List? products; + final List? subspaces; + final List? allTags; + final TextEditingController spaceNameController; + final BuildContext? pageContext; + final List? otherSpaceModels; + final List? allSpaceModels; + final List projectTags; + Map get _groupedTags => TagHelper.groupTags([ ...?spaceModel?.tags, ...?spaceModel?.subspaceModels?.expand((e) => e.tags ?? []) From 920827d763702884c9b0b49a3111188b95340c97 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:28:12 +0300 Subject: [PATCH 135/238] Removed unnecessary SizedBox from `TagChipDisplay`. --- .../widgets/tag_chips_display_widget.dart | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 63eab144..a369636a 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -50,27 +50,25 @@ class TagChipDisplay extends StatelessWidget { spaceModel?.subspaceModels?.any((e) => e.tags?.isNotEmpty ?? false) ?? false; if (hasTags || hasSubspaceTags) { - return SizedBox( + return Container( width: screenWidth * 0.25, - child: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + borderRadius: BorderRadius.circular(15), + border: Border.all( color: ColorsManager.textFieldGreyColor, - borderRadius: BorderRadius.circular(15), - border: Border.all( - color: ColorsManager.textFieldGreyColor, - width: 3, - ), - ), - child: Wrap( - spacing: 8, - runSpacing: 8, - children: [ - ..._groupedTags.entries.map((entry) => _buildChip(context, entry)), - _buildEditChip(context), - ], + width: 3, ), ), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ + ..._groupedTags.entries.map((entry) => _buildChip(context, entry)), + _buildEditChip(context), + ], + ), ); } else { return _buildAddDevicesButton(context); From fa5bb350c335bb2a38098440e149ef6e7195df08 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:29:25 +0300 Subject: [PATCH 136/238] refactor: replace spaceNameController with spaceName in `TagChipDisplay`. --- .../widgets/dialog/create_space_model_dialog.dart | 2 +- .../space_model/widgets/tag_chips_display_widget.dart | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 3afb0c6f..09374855 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -150,7 +150,7 @@ class CreateSpaceModelDialog extends StatelessWidget { products: products, subspaces: subspaces, allTags: allTags, - spaceNameController: spaceNameController, + spaceName: spaceNameController.text, pageContext: pageContext, otherSpaceModels: otherSpaceModels, allSpaceModels: allSpaceModels, diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index a369636a..f952d289 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -19,7 +19,7 @@ class TagChipDisplay extends StatelessWidget { required this.products, required this.subspaces, required this.allTags, - required this.spaceNameController, + required this.spaceName, required this.projectTags, this.pageContext, this.otherSpaceModels, @@ -32,7 +32,7 @@ class TagChipDisplay extends StatelessWidget { final List? products; final List? subspaces; final List? allTags; - final TextEditingController spaceNameController; + final String spaceName; final BuildContext? pageContext; final List? otherSpaceModels; final List? allSpaceModels; @@ -140,7 +140,7 @@ class TagChipDisplay extends StatelessWidget { products: products, subspaces: subspaces, allTags: allTags, - spaceName: spaceNameController.text, + spaceName: spaceName, pageContext: pageContext, isCreate: true, spaceModel: spaceModel, From db8e5a4aa6b7b340036fd6ca82233a05f86eb522 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:32:31 +0300 Subject: [PATCH 137/238] Refactor `TagChipDisplay._groupedTags` to enhance readabaility. --- .../widgets/tag_chips_display_widget.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index f952d289..7a591695 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -38,10 +38,14 @@ class TagChipDisplay extends StatelessWidget { final List? allSpaceModels; final List projectTags; - Map get _groupedTags => TagHelper.groupTags([ - ...?spaceModel?.tags, - ...?spaceModel?.subspaceModels?.expand((e) => e.tags ?? []) - ]); + Map get _groupedTags { + final spaceTags = spaceModel?.tags ?? []; + + final subspaces = spaceModel?.subspaceModels ?? []; + final subspaceTags = subspaces.expand((e) => e.tags ?? []).toList(); + + return TagHelper.groupTags([...spaceTags, ...subspaceTags]); + } @override Widget build(BuildContext context) { From d1a21be9832dfe566c03d6e0307d82ddb702c149 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:35:24 +0300 Subject: [PATCH 138/238] removed `else` from `TagChipDisplay.build` --- .../space_model/widgets/tag_chips_display_widget.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 7a591695..ceab79dc 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -74,9 +74,9 @@ class TagChipDisplay extends StatelessWidget { ], ), ); - } else { - return _buildAddDevicesButton(context); } + + return _buildAddDevicesButton(context); } Widget _buildEditChip(BuildContext context) { From 7699453e6d41ae72db413065c02ab43f11b5e000 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:43:50 +0300 Subject: [PATCH 139/238] moved styling of `_buildChip` up, and removed unnecessary `SizedBox`. --- .../widgets/tag_chips_display_widget.dart | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index ceab79dc..ec2a99b1 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class TagChipDisplay extends StatelessWidget { @@ -112,20 +113,6 @@ class TagChipDisplay extends StatelessWidget { MapEntry entry, ) { return Chip( - avatar: SizedBox( - width: 24, - height: 24, - child: SvgPicture.asset( - entry.key.icon ?? 'assets/icons/gateway.svg', - fit: BoxFit.contain, - ), - ), - label: Text( - '${entry.value}', - style: context.textTheme.bodySmall!.copyWith( - color: ColorsManager.spaceColor, - ), - ), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -133,6 +120,18 @@ class TagChipDisplay extends StatelessWidget { color: ColorsManager.spaceColor, ), ), + avatar: SvgPicture.asset( + entry.key.icon ?? Assets.gateway, + fit: BoxFit.contain, + height: 24, + width: 24, + ), + label: Text( + '${entry.value}', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.spaceColor, + ), + ), ); } From 9044645f952c8ae744840c8c5d5cdd343fb2c744 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 15:45:47 +0300 Subject: [PATCH 140/238] remove screenWidth parameter from `TagChipDisplay` and use `context.screenWidth` instead. --- .../space_model/widgets/dialog/create_space_model_dialog.dart | 1 - .../space_model/widgets/tag_chips_display_widget.dart | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 09374855..70dde231 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -145,7 +145,6 @@ class CreateSpaceModelDialog extends StatelessWidget { ), const SizedBox(height: 10), TagChipDisplay( - screenWidth: screenWidth, spaceModel: updatedSpaceModel, products: products, subspaces: subspaces, diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index ec2a99b1..86f99e02 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -15,7 +15,6 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; class TagChipDisplay extends StatelessWidget { const TagChipDisplay({ - required this.screenWidth, required this.spaceModel, required this.products, required this.subspaces, @@ -28,7 +27,6 @@ class TagChipDisplay extends StatelessWidget { super.key, }); - final double screenWidth; final SpaceTemplateModel? spaceModel; final List? products; final List? subspaces; @@ -56,7 +54,7 @@ class TagChipDisplay extends StatelessWidget { if (hasTags || hasSubspaceTags) { return Container( - width: screenWidth * 0.25, + width: context.screenWidth * 0.25, padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: ColorsManager.textFieldGreyColor, From 86b87716945e41be605461fc80076d5cf84dd85c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 16:12:01 +0300 Subject: [PATCH 141/238] bump-v of web deployment action. --- .../workflows/azure-static-web-apps-polite-smoke-017c65c10.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml b/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml index e28d1bb2..28cf00a2 100644 --- a/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml +++ b/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml @@ -25,7 +25,7 @@ jobs: - name: Set up Flutter uses: subosito/flutter-action@v2 with: - flutter-version: '3.22.2' # Specify the Flutter version you want to use + flutter-version: '3.27.3' # Specify the Flutter version you want to use - name: Install dependencies run: flutter pub get From 4989a0e95cdf1358110d9f8ef85845d4cd5192dc Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 16:24:46 +0300 Subject: [PATCH 142/238] removed the use of `Flexible` that was causing an exception. --- lib/pages/routines/widgets/dragable_card.dart | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/pages/routines/widgets/dragable_card.dart b/lib/pages/routines/widgets/dragable_card.dart index 7395d2d6..46f5ecfd 100644 --- a/lib/pages/routines/widgets/dragable_card.dart +++ b/lib/pages/routines/widgets/dragable_card.dart @@ -103,16 +103,14 @@ class DraggableCard extends StatelessWidget { const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(horizontal: 3), - child: Flexible( - child: Text( - deviceData['title'] ?? deviceData['name'] ?? title, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontSize: 12, - ), + child: Text( + deviceData['title'] ?? deviceData['name'] ?? title, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: 12, ), ), ), From 9eefd522b7ee33da7dede28d74251c1036f72faf Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 15 Apr 2025 16:25:00 +0300 Subject: [PATCH 143/238] bump flutter version on production github action. --- .../workflows/azure-static-web-apps-mango-bush-01e607f10.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml b/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml index 95e9346d..f0379c95 100644 --- a/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml +++ b/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml @@ -25,7 +25,7 @@ jobs: - name: Set up Flutter uses: subosito/flutter-action@v2 with: - flutter-version: '3.22.2' # Specify the Flutter version you want to use + flutter-version: '3.27.3' # Specify the Flutter version you want to use - name: Install dependencies run: flutter pub get From d4a7dd5854d123aea08724c857c5bac164b0d78c Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Wed, 16 Apr 2025 03:46:10 +0300 Subject: [PATCH 144/238] Fixed design issues, added tag and location to the save dialog --- .../all_devices/models/devices_model.dart | 2 - .../bloc/routine_bloc/routine_bloc.dart | 164 ++++++++-------- .../routines/helper/save_routine_helper.dart | 176 ++++++++++++------ lib/pages/routines/view/routines_view.dart | 15 +- lib/pages/routines/widgets/dragable_card.dart | 26 ++- lib/pages/routines/widgets/if_container.dart | 173 ++++++++--------- .../main_routine_view/routine_view_card.dart | 29 +-- 7 files changed, 311 insertions(+), 274 deletions(-) diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index c7c35eca..0ac3f776 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -238,8 +238,6 @@ SOS // tempIcon = Assets.gang3touch; } else if (type == DeviceType.WaterLeak) { tempIcon = Assets.waterLeakNormal; - } else if (type == DeviceType.WaterLeak) { - tempIcon = Assets.waterLeakNormal; } else { tempIcon = Assets.logoHorizontal; } diff --git a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart index b698c19e..d60f8d64 100644 --- a/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routines/bloc/routine_bloc/routine_bloc.dart @@ -684,40 +684,45 @@ class RoutineBloc extends Bloc { ? '${action.entityId}_automation' : action.actionExecutor == 'delay' ? '${action.entityId}_delay' - : action.entityId; + : const Uuid().v4(); - 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, - 'title': action.actionExecutor == 'delay' - ? 'Delay' - : action.type == 'automation' - ? action.name ?? 'Automation' - : (matchingDevice?.name ?? 'Device'), - 'productType': action.productType, - 'functions': matchingDevice?.functions, - 'imagePath': action.type == 'automation' - ? Assets.automation - : action.actionExecutor == 'delay' - ? Assets.delay - : matchingDevice?.getDefaultIcon(action.productType), - 'device': matchingDevice, - 'name': action.name, - 'type': action.type, - }; - } + // if (!deviceCards.containsKey(deviceId)) { + deviceCards[deviceId] = { + 'entityId': action.entityId, + 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay' + ? action.entityId + : const Uuid().v4(), + 'title': action.actionExecutor == 'delay' + ? 'Delay' + : action.type == 'automation' + ? action.name ?? 'Automation' + : (matchingDevice?.name ?? 'Device'), + 'productType': action.productType, + 'functions': matchingDevice?.functions, + 'imagePath': action.type == 'automation' + ? Assets.automation + : action.actionExecutor == 'delay' + ? Assets.delay + : matchingDevice?.getDefaultIcon(action.productType), + 'device': matchingDevice, + 'name': action.name, + 'type': action.type, + 'tag': matchingDevice?.deviceTags?.isNotEmpty ?? false + ? matchingDevice?.deviceTags![0].name ?? '' + : '', + 'subSpace': matchingDevice?.deviceSubSpace?.subspaceName ?? '', + }; + // } final cardData = deviceCards[deviceId]!; final uniqueCustomId = cardData['uniqueCustomId'].toString(); + if (!updatedFunctions.containsKey(uniqueCustomId)) { + updatedFunctions[uniqueCustomId] = []; + } + if (action.type == 'automation') { - if (!updatedFunctions.containsKey(uniqueCustomId)) { - updatedFunctions[uniqueCustomId] = []; - } updatedFunctions[uniqueCustomId]!.add( DeviceFunctionData( entityId: action.entityId, @@ -728,14 +733,11 @@ class RoutineBloc extends Bloc { ); // emit(state.copyWith(automationActionExecutor: action.actionExecutor)); } else if (action.executorProperty != null && action.actionExecutor != 'delay') { - // if (!updatedFunctions.containsKey(uniqueCustomId)) { - // updatedFunctions[uniqueCustomId] = []; - // } - final functions = matchingDevice?.functions; + final functions = matchingDevice?.functions ?? []; final functionCode = action.executorProperty?.functionCode; - for (DeviceFunction function in functions ?? []) { + for (DeviceFunction function in functions) { if (function.code == functionCode) { - updatedFunctions[const Uuid().v4()]!.add( + updatedFunctions[uniqueCustomId]!.add( DeviceFunctionData( entityId: action.entityId, functionCode: functionCode ?? '', @@ -747,9 +749,6 @@ class RoutineBloc extends Bloc { } } } else if (action.actionExecutor == 'delay') { - if (!updatedFunctions.containsKey(uniqueCustomId)) { - updatedFunctions[uniqueCustomId] = []; - } final delayFunction = DelayFunction( deviceId: action.entityId, deviceName: 'Delay', @@ -1156,21 +1155,25 @@ class RoutineBloc extends Bloc { ), ); - final deviceId = condition.entityId; + final deviceId = const Uuid().v4(); - if (!deviceIfCards.containsKey(deviceId)) { - deviceIfCards[deviceId] = { - 'entityId': condition.entityId, - 'deviceId': condition.entityId, - 'uniqueCustomId': const Uuid().v4(), - 'title': matchingDevice.name ?? 'Device', - 'productType': condition.productType, - 'functions': matchingDevice.functions, - 'imagePath': matchingDevice.getDefaultIcon(condition.productType), - 'device': matchingDevice, - 'type': 'condition', - }; - } + // if (!deviceIfCards.containsKey(deviceId)) { + deviceIfCards[deviceId] = { + 'entityId': condition.entityId, + 'deviceId': condition.entityId, + 'uniqueCustomId': const Uuid().v4(), + 'title': matchingDevice.name ?? 'Device', + 'productType': condition.productType, + 'functions': matchingDevice.functions, + 'imagePath': matchingDevice.getDefaultIcon(condition.productType), + 'device': matchingDevice, + 'type': 'condition', + 'tag': matchingDevice.deviceTags?.isNotEmpty ?? false + ? matchingDevice.deviceTags![0].name + : '', + 'subSpace': matchingDevice.deviceSubSpace?.subspaceName ?? '', + }; + // } final cardData = deviceIfCards[deviceId]!; final uniqueCustomId = cardData['uniqueCustomId'].toString(); @@ -1206,35 +1209,38 @@ class RoutineBloc extends Bloc { ), ); - final deviceId = - action.actionExecutor == 'delay' ? '${action.entityId}_delay' : action.entityId; + final deviceId = const Uuid().v4(); - if (!deviceThenCards.containsKey(deviceId)) { - deviceThenCards[deviceId] = { - 'entityId': action.entityId, - 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, - 'uniqueCustomId': const Uuid().v4(), - 'title': action.actionExecutor == 'delay' - ? 'Delay' - : (action.type == 'scene' || action.type == 'automation') - ? action.name - : (matchingDevice.name ?? 'Device'), - 'productType': action.productType, - 'functions': matchingDevice.functions, - 'imagePath': action.actionExecutor == 'delay' - ? Assets.delay - : action.type == 'automation' - ? Assets.automation - : matchingDevice.getDefaultIcon(action.productType), - 'device': matchingDevice, - 'type': action.type == 'scene' - ? 'scene' - : action.type == 'automation' - ? 'automation' - : 'action', - 'icon': action.icon ?? '', - }; - } + // if (!deviceThenCards.containsKey(deviceId)) { + deviceThenCards[deviceId] = { + 'entityId': action.entityId, + 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId, + 'uniqueCustomId': const Uuid().v4(), + 'title': action.actionExecutor == 'delay' + ? 'Delay' + : (action.type == 'scene' || action.type == 'automation') + ? action.name + : (matchingDevice.name ?? 'Device'), + 'productType': action.productType, + 'functions': matchingDevice.functions, + 'imagePath': action.actionExecutor == 'delay' + ? Assets.delay + : action.type == 'automation' + ? Assets.automation + : matchingDevice.getDefaultIcon(action.productType), + 'device': matchingDevice, + 'type': action.type == 'scene' + ? 'scene' + : action.type == 'automation' + ? 'automation' + : 'action', + 'icon': action.icon ?? '', + 'tag': matchingDevice.deviceTags?.isNotEmpty ?? false + ? matchingDevice.deviceTags![0].name + : '', + 'subSpace': matchingDevice.deviceSubSpace?.subspaceName ?? '', + }; + // } final cardData = deviceThenCards[deviceId]!; final uniqueCustomId = cardData['uniqueCustomId'].toString(); diff --git a/lib/pages/routines/helper/save_routine_helper.dart b/lib/pages/routines/helper/save_routine_helper.dart index e2ca5ede..457f74dc 100644 --- a/lib/pages/routines/helper/save_routine_helper.dart +++ b/lib/pages/routines/helper/save_routine_helper.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -20,7 +21,7 @@ class SaveRoutineHelper { return AlertDialog( contentPadding: EdgeInsets.zero, content: Container( - width: 600, + width: MediaQuery.sizeOf(context).width * 0.5, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), @@ -30,14 +31,15 @@ class SaveRoutineHelper { children: [ DialogHeader('Create a scene: ${state.routineName ?? ""}'), Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Left side - IF Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: ListView( + // crossAxisAlignment: CrossAxisAlignment.start, + shrinkWrap: true, children: [ const Text( 'IF:', @@ -59,26 +61,7 @@ class SaveRoutineHelper { ...state.ifItems.map((item) { final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; - return ListTile( - leading: SvgPicture.asset( - item['imagePath'], - width: 22, - height: 22, - ), - title: - Text(item['title'], style: const TextStyle(fontSize: 14)), - subtitle: Wrap( - children: functions - .map((f) => Text( - '${f.operationName}: ${f.value}, ', - style: const TextStyle( - color: ColorsManager.grayColor, fontSize: 8), - overflow: TextOverflow.ellipsis, - maxLines: 3, - )) - .toList(), - ), - ); + return functionRow(item, context, functions); }), ], ), @@ -87,8 +70,9 @@ class SaveRoutineHelper { // Right side - THEN items Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: ListView( + // crossAxisAlignment: CrossAxisAlignment.start, + shrinkWrap: true, children: [ const Text( 'THEN:', @@ -100,37 +84,7 @@ class SaveRoutineHelper { ...state.thenItems.map((item) { final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; - return ListTile( - leading: item['type'] == 'tap_to_run' || item['type'] == 'scene' - ? Image.memory( - base64Decode(item['icon']), - width: 22, - height: 22, - ) - : SvgPicture.asset( - item['imagePath'], - width: 22, - height: 22, - ), - title: Text( - item['title'], - style: context.textTheme.bodySmall?.copyWith( - fontSize: 14, - color: ColorsManager.grayColor, - ), - ), - subtitle: Wrap( - children: functions - .map((f) => Text( - '${f.operationName}: ${f.value}, ', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.grayColor, fontSize: 8), - overflow: TextOverflow.ellipsis, - maxLines: 3, - )) - .toList(), - ), - ); + return functionRow(item, context, functions); }), ], ), @@ -177,4 +131,112 @@ class SaveRoutineHelper { }, ); } + + static Widget functionRow( + dynamic item, BuildContext context, List functions) { + return Padding( + padding: const EdgeInsets.only(top: 6), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + spacing: 8, + children: [ + item['type'] == 'tap_to_run' || item['type'] == 'scene' + ? Image.memory( + base64Decode(item['icon']), + width: 22, + height: 22, + ) + : SvgPicture.asset( + item['imagePath'], + width: 22, + height: 22, + ), + Flexible( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 2, + children: [ + Text( + item['title'], + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: context.textTheme.bodySmall?.copyWith( + fontSize: 14, + color: ColorsManager.textPrimaryColor, + ), + ), + Wrap( + children: functions + .map((f) => Text( + '${f.operationName}: ${f.value}', + style: context.textTheme.bodySmall + ?.copyWith(color: ColorsManager.grayColor, fontSize: 8), + overflow: TextOverflow.ellipsis, + maxLines: 3, + )) + .toList(), + ), + ], + ), + ), + ], + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 2, + children: [ + Visibility( + visible: item['tag'] != null && item['tag'] != '', + child: Row( + spacing: 2, + children: [ + SizedBox(width: 8, height: 8, child: SvgPicture.asset(Assets.deviceTagIcon)), + Text( + item['tag'] ?? '', + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.lightGreyColor, + fontSize: 9, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + Visibility( + visible: item['subSpace'] != null && item['subSpace'] != '', + child: Row( + spacing: 2, + children: [ + SizedBox( + width: 8, height: 8, child: SvgPicture.asset(Assets.spaceLocationIcon)), + Text( + item['subSpace'] ?? '', + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.lightGreyColor, + fontSize: 9, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ); + } } diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index c398243c..2d6ee648 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -29,8 +29,7 @@ class _RoutinesViewState extends State { final spaceId = result['space']; final _bloc = BlocProvider.of(context); final routineBloc = context.read(); - _bloc.add(SaveCommunityIdAndSpaceIdEvent( - communityID: communityId, spaceID: spaceId)); + _bloc.add(SaveCommunityIdAndSpaceIdEvent(communityID: communityId, spaceID: spaceId)); await Future.delayed(const Duration(seconds: 1)); routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true)); } @@ -49,7 +48,8 @@ class _RoutinesViewState extends State { child: SpaceTreeView( onSelect: () => context.read() ..add(const LoadScenes()) - ..add(const LoadAutomation()), + ..add(const LoadAutomation()) + ..add(FetchDevicesInRoutine()), ), ), Expanded( @@ -64,11 +64,10 @@ class _RoutinesViewState extends State { children: [ Text( "Create New Routines", - style: - Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), ), const SizedBox(height: 10), RoutineViewCard( diff --git a/lib/pages/routines/widgets/dragable_card.dart b/lib/pages/routines/widgets/dragable_card.dart index 7395d2d6..7ac29375 100644 --- a/lib/pages/routines/widgets/dragable_card.dart +++ b/lib/pages/routines/widgets/dragable_card.dart @@ -69,9 +69,9 @@ class DraggableCard extends StatelessWidget { Card( color: ColorsManager.whiteColors, child: Container( - padding: padding ?? const EdgeInsets.all(16), + padding: const EdgeInsets.all(16), width: 110, - height: deviceFunctions.isEmpty ? 160 : 170, + height: deviceFunctions.isEmpty ? 160 : 180, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, @@ -103,16 +103,14 @@ class DraggableCard extends StatelessWidget { const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(horizontal: 3), - child: Flexible( - child: Text( - deviceData['title'] ?? deviceData['name'] ?? title, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontSize: 12, - ), + child: Text( + deviceData['title'] ?? deviceData['name'] ?? title, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: 12, ), ), ), @@ -131,7 +129,7 @@ class DraggableCard extends StatelessWidget { deviceData['tag'] ?? '', textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, - maxLines: 2, + maxLines: 1, style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.lightGreyColor, fontSize: 9, @@ -162,7 +160,7 @@ class DraggableCard extends StatelessWidget { deviceData['subSpace'] ?? '', textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, - maxLines: 2, + maxLines: 1, style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.lightGreyColor, fontSize: 9, diff --git a/lib/pages/routines/widgets/if_container.dart b/lib/pages/routines/widgets/if_container.dart index b65e99c7..007e4dc5 100644 --- a/lib/pages/routines/widgets/if_container.dart +++ b/lib/pages/routines/widgets/if_container.dart @@ -17,91 +17,86 @@ class IfContainer extends StatelessWidget { builder: (context, state) { return DragTarget>( builder: (context, candidateData, rejectedData) { - return Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text('IF', - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold)), - if (state.isAutomation && state.ifItems.isNotEmpty) - AutomationOperatorSelector( - selectedOperator: state.selectedAutomationOperator), - ], - ), - const SizedBox(height: 16), - if (state.isTabToRun) - const Row( - mainAxisAlignment: MainAxisAlignment.center, + return SingleChildScrollView( + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - DraggableCard( - imagePath: Assets.tabToRun, - title: 'Tab to run', - deviceData: {}, - ), + const Text('IF', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + if (state.isAutomation && state.ifItems.isNotEmpty) + AutomationOperatorSelector( + selectedOperator: state.selectedAutomationOperator), ], ), - if (!state.isTabToRun) - Wrap( - spacing: 8, - runSpacing: 8, - children: List.generate( - state.ifItems.length, - (index) => GestureDetector( - onTap: () async { - if (!state.isTabToRun) { - final result = await DeviceDialogHelper - .showDeviceDialog( - context: context, - data: state.ifItems[index], - removeComparetors: false, - dialogType: "IF"); + const SizedBox(height: 16), + if (state.isTabToRun) + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + DraggableCard( + imagePath: Assets.tabToRun, + title: 'Tab to run', + deviceData: {}, + ), + ], + ), + if (!state.isTabToRun) + Wrap( + spacing: 8, + runSpacing: 8, + children: List.generate( + state.ifItems.length, + (index) => GestureDetector( + onTap: () async { + if (!state.isTabToRun) { + final result = await DeviceDialogHelper.showDeviceDialog( + context: context, + data: state.ifItems[index], + removeComparetors: false, + dialogType: "IF"); - if (result != null) { - context.read().add( - AddToIfContainer( - state.ifItems[index], false)); - } else if (![ - 'AC', - '1G', - '2G', - '3G', - 'WPS' - 'GW', - ].contains( - state.ifItems[index]['productType'])) { - context.read().add( - AddToIfContainer( - state.ifItems[index], false)); + if (result != null) { + context + .read() + .add(AddToIfContainer(state.ifItems[index], false)); + } else if (![ + 'AC', + '1G', + '2G', + '3G', + 'WPS' + 'GW', + ].contains(state.ifItems[index]['productType'])) { + context + .read() + .add(AddToIfContainer(state.ifItems[index], false)); + } } - } - }, - child: DraggableCard( - imagePath: - state.ifItems[index]['imagePath'] ?? '', - title: state.ifItems[index]['title'] ?? '', - deviceData: state.ifItems[index], - padding: const EdgeInsets.symmetric( - horizontal: 4, vertical: 8), - isFromThen: false, - isFromIf: true, - onRemove: () { - context.read().add( - RemoveDragCard( - index: index, - isFromThen: false, - key: state.ifItems[index] - ['uniqueCustomId'])); }, - ), - )), - ), - ], + child: DraggableCard( + imagePath: state.ifItems[index]['imagePath'] ?? '', + title: state.ifItems[index]['title'] ?? '', + deviceData: state.ifItems[index], + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), + isFromThen: false, + isFromIf: true, + onRemove: () { + context.read().add(RemoveDragCard( + index: index, + isFromThen: false, + key: state.ifItems[index]['uniqueCustomId'])); + }, + ), + )), + ), + ], + ), ), ); }, @@ -116,9 +111,7 @@ class IfContainer extends StatelessWidget { if (!state.isTabToRun) { if (mutableData['deviceId'] == 'tab_to_run') { - context - .read() - .add(AddToIfContainer(mutableData, true)); + context.read().add(AddToIfContainer(mutableData, true)); } else { final result = await DeviceDialogHelper.showDeviceDialog( dialogType: 'IF', @@ -127,14 +120,10 @@ class IfContainer extends StatelessWidget { removeComparetors: false); if (result != null) { - context - .read() - .add(AddToIfContainer(mutableData, false)); + context.read().add(AddToIfContainer(mutableData, false)); } else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW'] .contains(mutableData['productType'])) { - context - .read() - .add(AddToIfContainer(mutableData, false)); + context.read().add(AddToIfContainer(mutableData, false)); } } } @@ -180,9 +169,7 @@ class AutomationOperatorSelector extends StatelessWidget { ), ), onPressed: () { - context - .read() - .add(const ChangeAutomationOperator(operator: 'or')); + context.read().add(const ChangeAutomationOperator(operator: 'or')); }, ), Container( @@ -208,9 +195,7 @@ class AutomationOperatorSelector extends StatelessWidget { ), ), onPressed: () { - context - .read() - .add(const ChangeAutomationOperator(operator: 'and')); + context.read().add(const ChangeAutomationOperator(operator: 'and')); }, ), ], 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 841ffa6e..7be5a959 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 @@ -121,8 +121,7 @@ class _RoutineViewCardState extends State { child: SizedBox( width: 16, height: 16, - child: - CircularProgressIndicator(strokeWidth: 2), + child: CircularProgressIndicator(strokeWidth: 2), ), ), ) @@ -159,9 +158,7 @@ class _RoutineViewCardState extends State { height: iconSize, width: iconSize, fit: BoxFit.contain, - errorBuilder: - (context, error, stackTrace) => - Image.asset( + errorBuilder: (context, error, stackTrace) => Image.asset( Assets.logo, height: iconSize, width: iconSize, @@ -174,8 +171,7 @@ class _RoutineViewCardState extends State { width: iconSize, fit: BoxFit.contain, ) - : (widget.icon is String && - widget.icon.endsWith('.svg')) + : (widget.icon is String && widget.icon.endsWith('.svg')) ? SvgPicture.asset( height: iconSize, width: iconSize, @@ -185,9 +181,7 @@ class _RoutineViewCardState extends State { : Icon( widget.icon, color: ColorsManager.dialogBlueTitle, - size: widget.isSmallScreenSize(context) - ? 30 - : 40, + size: widget.isSmallScreenSize(context) ? 30 : 40, ), ), ), @@ -200,11 +194,10 @@ class _RoutineViewCardState extends State { widget.textString, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, - maxLines: 2, + maxLines: 1, style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.blackColor, - fontSize: - widget.isSmallScreenSize(context) ? 10 : 12, + fontSize: widget.isSmallScreenSize(context) ? 10 : 12, ), ), if (widget.spaceName != '') @@ -220,14 +213,10 @@ class _RoutineViewCardState extends State { widget.spaceName, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, - maxLines: 2, - style: - context.textTheme.bodySmall?.copyWith( + maxLines: 1, + style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.blackColor, - fontSize: - widget.isSmallScreenSize(context) - ? 10 - : 12, + fontSize: widget.isSmallScreenSize(context) ? 10 : 12, ), ), ], From 91c4c772b5bf8f1af8f80dcd2cee38889a24a45d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 13:08:38 +0300 Subject: [PATCH 145/238] SP-1330. --- .../all_spaces/widgets/sidebar_widget.dart | 66 ++++++++++++++++--- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 3eb1c001..34cb9a70 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -11,6 +11,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_til import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class SidebarWidget extends StatefulWidget { @@ -30,6 +32,8 @@ class SidebarWidget extends StatefulWidget { } class _SidebarWidgetState extends State { + late final ScrollController _scrollController; + String _searchQuery = ''; String? _selectedSpaceUuid; String? _selectedId; @@ -37,9 +41,16 @@ class _SidebarWidgetState extends State { @override void initState() { _selectedId = widget.selectedSpaceUuid; + _scrollController = ScrollController(); super.initState(); } + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + @override void didUpdateWidget(covariant SidebarWidget oldWidget) { if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) { @@ -86,12 +97,14 @@ class _SidebarWidgetState extends State { return isSpaceSelected || anySubSpaceIsSelected; } + static const _width = 300.0; + @override Widget build(BuildContext context) { final filteredCommunities = _filteredCommunities(); return Container( - width: 300, + width: _width, decoration: subSectionContainerDecoration, child: Column( mainAxisSize: MainAxisSize.min, @@ -103,10 +116,42 @@ class _SidebarWidgetState extends State { ), const SizedBox(height: 16), Expanded( - child: ListView( - children: filteredCommunities - .map((community) => _buildCommunityTile(context, community)) - .toList(), + child: Visibility( + visible: filteredCommunities.isNotEmpty, + replacement: Center( + child: Text( + 'No results found', + textAlign: TextAlign.center, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.lightGreyColor, + fontWeight: FontWeight.w400, + ), + ), + ), + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: [ + SizedBox( + width: context.screenWidth * 0.5, + child: Scrollbar( + scrollbarOrientation: ScrollbarOrientation.left, + thumbVisibility: true, + controller: _scrollController, + child: ListView.builder( + padding: const EdgeInsetsDirectional.only(start: 16), + shrinkWrap: true, + itemCount: filteredCommunities.length, + controller: _scrollController, + itemBuilder: (context, index) => _buildCommunityTile( + context, + filteredCommunities[index], + ), + ), + ), + ), + ], + ), ), ), ], @@ -134,11 +179,12 @@ class _SidebarWidgetState extends State { }, onExpansionChanged: (title, expanded) {}, children: community.spaces - .where((space) { - final isDeleted = space.status != SpaceStatus.deleted; - final isParentDeleted = space.status != SpaceStatus.parentDeleted; - return (isDeleted || isParentDeleted); - }) + .where( + (space) => { + SpaceStatus.deleted, + SpaceStatus.parentDeleted, + }.contains(space.status), + ) .map((space) => _buildSpaceTile(space: space, community: community)) .toList(), ); From dce44e20ec6a87d173b1f84cfa4b80d75a393a6c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 13:11:56 +0300 Subject: [PATCH 146/238] Extracted `EmptyResultsWidget` into its own widget and file for reusability. --- .../widgets/empty_search_result_widget.dart | 26 +++++++++++++++++++ .../all_spaces/widgets/sidebar_widget.dart | 13 ++-------- 2 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 lib/common/widgets/empty_search_result_widget.dart diff --git a/lib/common/widgets/empty_search_result_widget.dart b/lib/common/widgets/empty_search_result_widget.dart new file mode 100644 index 00000000..493974d4 --- /dev/null +++ b/lib/common/widgets/empty_search_result_widget.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class EmptySearchResultWidget extends StatelessWidget { + const EmptySearchResultWidget({ + this.message = 'No results found', + super.key, + }); + + final String message; + + @override + Widget build(BuildContext context) { + return Center( + child: Text( + message, + textAlign: TextAlign.center, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.lightGreyColor, + fontWeight: FontWeight.w400, + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 34cb9a70..6d03c0d0 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/common/widgets/empty_search_result_widget.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; @@ -11,7 +12,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_til import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -118,16 +118,7 @@ class _SidebarWidgetState extends State { Expanded( child: Visibility( visible: filteredCommunities.isNotEmpty, - replacement: Center( - child: Text( - 'No results found', - textAlign: TextAlign.center, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.lightGreyColor, - fontWeight: FontWeight.w400, - ), - ), - ), + replacement: const EmptySearchResultWidget(), child: ListView( shrinkWrap: true, scrollDirection: Axis.horizontal, From fc1d394509b0866d3046e1105727738222dd504b Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 13:17:09 +0300 Subject: [PATCH 147/238] Extracted `SidebarCommunitiesList` into a reusable widget. --- .../widgets/sidebar_communities_list.dart | 55 +++++++++++++++++++ .../all_spaces/widgets/sidebar_widget.dart | 31 +++-------- 2 files changed, 62 insertions(+), 24 deletions(-) create mode 100644 lib/common/widgets/sidebar_communities_list.dart diff --git a/lib/common/widgets/sidebar_communities_list.dart b/lib/common/widgets/sidebar_communities_list.dart new file mode 100644 index 00000000..f5f34f24 --- /dev/null +++ b/lib/common/widgets/sidebar_communities_list.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class SidebarCommunitiesList extends StatefulWidget { + const SidebarCommunitiesList({ + required this.communities, + required this.itemBuilder, + super.key, + }); + + final List communities; + final Widget Function(BuildContext context, int index) itemBuilder; + + @override + State createState() => _SidebarCommunitiesListState(); +} + +class _SidebarCommunitiesListState extends State { + late final ScrollController _scrollController; + + @override + void initState() { + _scrollController = ScrollController(); + super.initState(); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SizedBox( + width: context.screenWidth * 0.5, + child: Scrollbar( + scrollbarOrientation: ScrollbarOrientation.left, + thumbVisibility: true, + controller: _scrollController, + child: ListView.builder( + padding: const EdgeInsetsDirectional.only(start: 16), + shrinkWrap: true, + itemCount: widget.communities.length, + controller: _scrollController, + itemBuilder: widget.itemBuilder, + ), + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 6d03c0d0..9be0c746 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/common/widgets/empty_search_result_widget.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart'; +import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; @@ -12,7 +13,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_til import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; class SidebarWidget extends StatefulWidget { @@ -119,29 +119,12 @@ class _SidebarWidgetState extends State { child: Visibility( visible: filteredCommunities.isNotEmpty, replacement: const EmptySearchResultWidget(), - child: ListView( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - children: [ - SizedBox( - width: context.screenWidth * 0.5, - child: Scrollbar( - scrollbarOrientation: ScrollbarOrientation.left, - thumbVisibility: true, - controller: _scrollController, - child: ListView.builder( - padding: const EdgeInsetsDirectional.only(start: 16), - shrinkWrap: true, - itemCount: filteredCommunities.length, - controller: _scrollController, - itemBuilder: (context, index) => _buildCommunityTile( - context, - filteredCommunities[index], - ), - ), - ), - ), - ], + child: SidebarCommunitiesList( + communities: filteredCommunities, + itemBuilder: (context, index) => _buildCommunityTile( + context, + filteredCommunities[index], + ), ), ), ), From afdd44e098b28ec148d6daf5a8b15fa91a653149 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 13:18:52 +0300 Subject: [PATCH 148/238] removed comments from `SpaceTreeView`. --- .../space_tree/view/space_tree_view.dart | 62 ------------------- 1 file changed, 62 deletions(-) diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index 5b70da06..5b6d5593 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -191,68 +191,6 @@ class _SpaceTreeViewState extends State { ), ), if (state.paginationIsLoading) const CircularProgressIndicator(), - // Expanded( - // child: Padding( - // padding: const EdgeInsets.all(8.0), - // child: list.isEmpty - // ? Center( - // child: Text( - // 'No results found', - // style: Theme.of(context).textTheme.bodySmall!.copyWith( - // color: ColorsManager.lightGrayColor, // Gray when not selected - // fontWeight: FontWeight.w400, - // ), - // ), - // ) - // : ListView( - // shrinkWrap: true, - // children: list - // .map( - // (community) => CustomExpansionTileSpaceTree( - // title: community.name, - // isSelected: - // state.selectedCommunities.contains(community.uuid), - // isSoldCheck: - // state.selectedCommunities.contains(community.uuid), - // onExpansionChanged: () { - // context - // .read() - // .add(OnCommunityExpanded(community.uuid)); - // }, - // isExpanded: - // state.expandedCommunities.contains(community.uuid), - // onItemSelected: () { - // context.read().add( - // OnCommunitySelected(community.uuid, community.spaces)); - - // onSelect(); - // }, - // children: community.spaces.map((space) { - // return CustomExpansionTileSpaceTree( - // title: space.name, - // isExpanded: state.expandedSpaces.contains(space.uuid), - // onItemSelected: () { - // context.read().add(OnSpaceSelected( - // community.uuid, space.uuid ?? '', space.children)); - // onSelect(); - // }, - // onExpansionChanged: () { - // context.read().add( - // OnSpaceExpanded(community.uuid, space.uuid ?? '')); - // }, - // isSelected: state.selectedSpaces.contains(space.uuid) || - // state.soldCheck.contains(space.uuid), - // isSoldCheck: state.soldCheck.contains(space.uuid), - // children: _buildNestedSpaces( - // context, state, space, community.uuid), - // ); - // }).toList(), - // ), - // ) - // .toList(), - // ), - // ), - // ), ], ), ); From 8b441aaf46a7697c10c4512722cb04aa1dc2898a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 14:09:36 +0300 Subject: [PATCH 149/238] Refactor SidebarCommunitiesList to be a StatelessWidget and update its usage across SpaceTreeView and SidebarWidget for improved performance and maintainability. --- .../widgets/sidebar_communities_list.dart | 48 ++- .../space_tree/view/space_tree_view.dart | 283 +++++++++--------- .../all_spaces/widgets/sidebar_widget.dart | 2 + 3 files changed, 167 insertions(+), 166 deletions(-) diff --git a/lib/common/widgets/sidebar_communities_list.dart b/lib/common/widgets/sidebar_communities_list.dart index f5f34f24..acc8471d 100644 --- a/lib/common/widgets/sidebar_communities_list.dart +++ b/lib/common/widgets/sidebar_communities_list.dart @@ -2,34 +2,19 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -class SidebarCommunitiesList extends StatefulWidget { +class SidebarCommunitiesList extends StatelessWidget { const SidebarCommunitiesList({ required this.communities, required this.itemBuilder, + required this.scrollController, + required this.onScrollToEnd, super.key, }); final List communities; final Widget Function(BuildContext context, int index) itemBuilder; - - @override - State createState() => _SidebarCommunitiesListState(); -} - -class _SidebarCommunitiesListState extends State { - late final ScrollController _scrollController; - - @override - void initState() { - _scrollController = ScrollController(); - super.initState(); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } + final ScrollController scrollController; + final void Function() onScrollToEnd; @override Widget build(BuildContext context) { @@ -40,13 +25,22 @@ class _SidebarCommunitiesListState extends State { child: Scrollbar( scrollbarOrientation: ScrollbarOrientation.left, thumbVisibility: true, - controller: _scrollController, - child: ListView.builder( - padding: const EdgeInsetsDirectional.only(start: 16), - shrinkWrap: true, - itemCount: widget.communities.length, - controller: _scrollController, - itemBuilder: widget.itemBuilder, + controller: scrollController, + child: NotificationListener( + onNotification: (notification) { + if (notification is ScrollEndNotification && + notification.metrics.extentAfter == 0) { + onScrollToEnd.call(); + } + return false; + }, + child: ListView.builder( + padding: const EdgeInsetsDirectional.only(start: 16), + shrinkWrap: true, + itemCount: communities.length, + controller: scrollController, + itemBuilder: itemBuilder, + ), ), ), ), diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index 5b6d5593..cae4af1c 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart'; +import 'package:syncrow_web/common/widgets/sidebar_communities_list.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'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart'; @@ -34,160 +35,158 @@ class _SpaceTreeViewState extends State { @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { - List list = - state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList; + final communities = state.searchQuery.isNotEmpty + ? state.filteredCommunity + : state.communityList; return Container( height: MediaQuery.sizeOf(context).height, decoration: widget.isSide == true - ? subSectionContainerDecoration.copyWith(color: ColorsManager.whiteColors) + ? subSectionContainerDecoration.copyWith( + color: ColorsManager.whiteColors) : const BoxDecoration(color: ColorsManager.whiteColors), child: state is SpaceTreeLoadingState ? const Center(child: CircularProgressIndicator()) : Column( children: [ - widget.isSide == true - ? Container( - decoration: const BoxDecoration( - color: ColorsManager.circleRolesBackground, - borderRadius: BorderRadius.only( - topRight: Radius.circular(20), topLeft: Radius.circular(20)), - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(20)), - border: Border.all(color: ColorsManager.grayBorder)), - child: TextFormField( - style: context.textTheme.bodyMedium - ?.copyWith(color: ColorsManager.blackColor), - onChanged: (value) { - context.read().add(SearchQueryEvent(value)); - }, - decoration: textBoxDecoration(radios: 20)!.copyWith( - fillColor: Colors.white, - suffixIcon: Padding( - padding: const EdgeInsets.only(right: 16), - child: SvgPicture.asset( - Assets.textFieldSearch, - width: 24, - height: 24, + if (widget.isSide == true) + Container( + decoration: const BoxDecoration( + color: ColorsManager.circleRolesBackground, + borderRadius: BorderRadius.only( + topRight: Radius.circular(20), + topLeft: Radius.circular(20), + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + border: Border.all( + color: ColorsManager.grayBorder, + ), + ), + child: TextFormField( + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.blackColor, + ), + onChanged: (value) => + context.read().add( + SearchQueryEvent(value), ), - ), - hintStyle: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w400, - fontSize: 12, - color: ColorsManager.textGray), + decoration: + textBoxDecoration(radios: 20)?.copyWith( + fillColor: Colors.white, + suffixIcon: Padding( + padding: const EdgeInsets.only(right: 16), + child: SvgPicture.asset( + Assets.textFieldSearch, + width: 24, + height: 24, ), ), + hintStyle: + context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w400, + fontSize: 12, + color: ColorsManager.textGray, + ), ), ), - ], + ), ), - ), - ) - : CustomSearchBar( - onSearchChanged: (query) { - context.read().add(SearchQueryEvent(query)); - }, + ], ), + ), + ) + else + CustomSearchBar( + onSearchChanged: (query) => context.read().add( + SearchQueryEvent(query), + ), + ), const SizedBox(height: 16), Expanded( child: state.isSearching ? const Center(child: CircularProgressIndicator()) - : ListView( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - children: [ - Container( - width: MediaQuery.sizeOf(context).width * 0.5, - padding: const EdgeInsets.all(8.0), - child: list.isEmpty - ? Center( - child: Text( - 'No results found', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.lightGrayColor, - fontWeight: FontWeight.w400, - ), - ), - ) - : Scrollbar( - scrollbarOrientation: ScrollbarOrientation.left, - thumbVisibility: true, - controller: _scrollController, - child: NotificationListener( - onNotification: (notification) { - if (notification is ScrollEndNotification && - notification.metrics.extentAfter == 0) { - // If the user has reached the end of the list Load more data - context.read().add(PaginationEvent( - state.paginationModel, state.communityList)); - } - return false; - }, - child: Padding( - padding: const EdgeInsets.only(left: 16), - child: ListView.builder( - shrinkWrap: true, - itemCount: list.length, - controller: _scrollController, - itemBuilder: (context, index) { - return CustomExpansionTileSpaceTree( - title: list[index].name, - isSelected: state.selectedCommunities - .contains(list[index].uuid), - isSoldCheck: state.selectedCommunities - .contains(list[index].uuid), - onExpansionChanged: () { - context.read().add( - OnCommunityExpanded(list[index].uuid)); - }, - isExpanded: state.expandedCommunities - .contains(list[index].uuid), - onItemSelected: () { - context.read().add( - OnCommunitySelected(list[index].uuid, - list[index].spaces)); - widget.onSelect(); - }, - children: list[index].spaces.map((space) { - return CustomExpansionTileSpaceTree( - title: space.name, - isExpanded: state.expandedSpaces - .contains(space.uuid), - onItemSelected: () { - context.read().add( - OnSpaceSelected( - list[index], - space.uuid ?? '', - space.children)); - widget.onSelect(); - }, - onExpansionChanged: () { - context.read().add( - OnSpaceExpanded(list[index].uuid, - space.uuid ?? '')); - }, - isSelected: state.selectedSpaces - .contains(space.uuid) || - state.soldCheck.contains(space.uuid), - isSoldCheck: - state.soldCheck.contains(space.uuid), - children: _buildNestedSpaces( - context, state, space, list[index]), - ); - }).toList(), - ); - }), + : SidebarCommunitiesList( + onScrollToEnd: () => context.read().add( + PaginationEvent( + state.paginationModel, + state.communityList, + ), + ), + scrollController: _scrollController, + communities: communities, + itemBuilder: (context, index) { + return CustomExpansionTileSpaceTree( + title: communities[index].name, + isSelected: state.selectedCommunities + .contains(communities[index].uuid), + isSoldCheck: state.selectedCommunities + .contains(communities[index].uuid), + onExpansionChanged: () => + context.read().add( + OnCommunityExpanded( + communities[index].uuid, ), ), + isExpanded: state.expandedCommunities.contains( + communities[index].uuid, + ), + onItemSelected: () { + context.read().add( + OnCommunitySelected( + communities[index].uuid, + communities[index].spaces, + ), + ); + widget.onSelect(); + }, + children: communities[index].spaces.map( + (space) { + return CustomExpansionTileSpaceTree( + title: space.name, + isExpanded: + state.expandedSpaces.contains(space.uuid), + onItemSelected: () { + context.read().add( + OnSpaceSelected( + communities[index], + space.uuid ?? '', + space.children, + ), + ); + widget.onSelect(); + }, + onExpansionChanged: () => + context.read().add( + OnSpaceExpanded( + communities[index].uuid, + space.uuid ?? '', + ), + ), + isSelected: state.selectedSpaces + .contains(space.uuid) || + state.soldCheck.contains(space.uuid), + isSoldCheck: + state.soldCheck.contains(space.uuid), + children: _buildNestedSpaces( + context, + state, + space, + communities[index], ), - ), - ], + ); + }, + ).toList(), + ); + }, ), ), if (state.paginationIsLoading) const CircularProgressIndicator(), @@ -198,22 +197,28 @@ class _SpaceTreeViewState extends State { } List _buildNestedSpaces( - BuildContext context, SpaceTreeState state, SpaceModel space, CommunityModel community) { + BuildContext context, + SpaceTreeState state, + SpaceModel space, + CommunityModel community, + ) { return space.children.map((child) { return CustomExpansionTileSpaceTree( - isSelected: - state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid), + isSelected: state.selectedSpaces.contains(child.uuid) || + state.soldCheck.contains(child.uuid), isSoldCheck: state.soldCheck.contains(child.uuid), title: child.name, isExpanded: state.expandedSpaces.contains(child.uuid), onItemSelected: () { - context - .read() - .add(OnSpaceSelected(community, child.uuid ?? '', child.children)); + context.read().add( + OnSpaceSelected(community, child.uuid ?? '', child.children), + ); widget.onSelect(); }, onExpansionChanged: () { - context.read().add(OnSpaceExpanded(community.uuid, child.uuid ?? '')); + context.read().add( + OnSpaceExpanded(community.uuid, child.uuid ?? ''), + ); }, children: _buildNestedSpaces(context, state, child, community), ); diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 9be0c746..198ebc51 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -120,6 +120,8 @@ class _SidebarWidgetState extends State { visible: filteredCommunities.isNotEmpty, replacement: const EmptySearchResultWidget(), child: SidebarCommunitiesList( + scrollController: _scrollController, + onScrollToEnd: () {}, communities: filteredCommunities, itemBuilder: (context, index) => _buildCommunityTile( context, From 4c38c5064989b4cf63301e6f067166c845cfadf0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 14:29:39 +0300 Subject: [PATCH 150/238] Refactor notification handling in SidebarCommunitiesList for improved readability and maintainability. --- .../widgets/sidebar_communities_list.dart | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/common/widgets/sidebar_communities_list.dart b/lib/common/widgets/sidebar_communities_list.dart index acc8471d..191aab25 100644 --- a/lib/common/widgets/sidebar_communities_list.dart +++ b/lib/common/widgets/sidebar_communities_list.dart @@ -16,6 +16,16 @@ class SidebarCommunitiesList extends StatelessWidget { final ScrollController scrollController; final void Function() onScrollToEnd; + bool _onNotification(ScrollEndNotification notification) { + final hasReachedEnd = notification.metrics.extentAfter == 0; + if (hasReachedEnd) { + onScrollToEnd.call(); + return true; + } + + return false; + } + @override Widget build(BuildContext context) { return SingleChildScrollView( @@ -26,17 +36,11 @@ class SidebarCommunitiesList extends StatelessWidget { scrollbarOrientation: ScrollbarOrientation.left, thumbVisibility: true, controller: scrollController, - child: NotificationListener( - onNotification: (notification) { - if (notification is ScrollEndNotification && - notification.metrics.extentAfter == 0) { - onScrollToEnd.call(); - } - return false; - }, + child: NotificationListener( + onNotification: _onNotification, child: ListView.builder( - padding: const EdgeInsetsDirectional.only(start: 16), shrinkWrap: true, + padding: const EdgeInsetsDirectional.only(start: 16), itemCount: communities.length, controller: scrollController, itemBuilder: itemBuilder, From b4f03ab6c3bdb1795c0d1d24cea662a2c80bd927 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 14:36:37 +0300 Subject: [PATCH 151/238] Initialize ScrollController in initState for better state management in SpaceTreeView. --- lib/pages/space_tree/view/space_tree_view.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index cae4af1c..3d5d00bf 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -24,7 +24,13 @@ class SpaceTreeView extends StatefulWidget { } class _SpaceTreeViewState extends State { - final ScrollController _scrollController = ScrollController(); + late final ScrollController _scrollController; + + @override + void initState() { + _scrollController = ScrollController(); + super.initState(); + } @override void dispose() { From f1667d445895ec9f3ae062e1653552e336f3e359 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 14:44:11 +0300 Subject: [PATCH 152/238] Refactor type annotations for onExpansionChanged and onItemSelected in CustomExpansionTileSpaceTree for improved clarity. --- lib/pages/space_tree/view/custom_expansion.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/space_tree/view/custom_expansion.dart b/lib/pages/space_tree/view/custom_expansion.dart index 515a8448..c06816b4 100644 --- a/lib/pages/space_tree/view/custom_expansion.dart +++ b/lib/pages/space_tree/view/custom_expansion.dart @@ -8,8 +8,8 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { final bool isSelected; final bool isSoldCheck; final bool isExpanded; - final Function? onExpansionChanged; - final Function? onItemSelected; + final void Function()? onExpansionChanged; + final void Function()? onItemSelected; const CustomExpansionTileSpaceTree( {super.key, From f709b92e1266901aa521bd83763d7576a5981cb6 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 14:44:31 +0300 Subject: [PATCH 153/238] Refactor constructor formatting and improve readability in CustomExpansionTileSpaceTree. --- .../space_tree/view/custom_expansion.dart | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/pages/space_tree/view/custom_expansion.dart b/lib/pages/space_tree/view/custom_expansion.dart index c06816b4..c9755180 100644 --- a/lib/pages/space_tree/view/custom_expansion.dart +++ b/lib/pages/space_tree/view/custom_expansion.dart @@ -11,16 +11,17 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { final void Function()? onExpansionChanged; final void Function()? onItemSelected; - const CustomExpansionTileSpaceTree( - {super.key, - this.spaceId, - required this.title, - this.children, - this.isExpanded = false, - this.onExpansionChanged, - this.onItemSelected, - required this.isSelected, - this.isSoldCheck = false}); + const CustomExpansionTileSpaceTree({ + required this.isSelected, + required this.title, + this.spaceId, + this.children, + this.onExpansionChanged, + this.onItemSelected, + this.isExpanded = false, + this.isSoldCheck = false, + super.key, + }); @override Widget build(BuildContext context) { @@ -56,7 +57,9 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { } }, child: Icon( - isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right, + isExpanded + ? Icons.keyboard_arrow_down + : Icons.keyboard_arrow_right, color: ColorsManager.lightGrayColor, size: 16.0, ), @@ -72,7 +75,8 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { _capitalizeFirstLetter(title), style: Theme.of(context).textTheme.bodySmall!.copyWith( color: isSelected - ? ColorsManager.blackColor // Change color to black when selected + ? ColorsManager + .blackColor // Change color to black when selected : ColorsManager.lightGrayColor, // Gray when not selected fontWeight: FontWeight.w400, ), From 3de7606a00a6252477b73a505b5117408e1c1495 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 14:46:37 +0300 Subject: [PATCH 154/238] Refactor expansion icon handling in CustomExpansionTileSpaceTree for improved readability and maintainability. --- .../space_tree/view/custom_expansion.dart | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/pages/space_tree/view/custom_expansion.dart b/lib/pages/space_tree/view/custom_expansion.dart index c9755180..2d969489 100644 --- a/lib/pages/space_tree/view/custom_expansion.dart +++ b/lib/pages/space_tree/view/custom_expansion.dart @@ -49,21 +49,7 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { }), checkColor: ColorsManager.whiteColors, ), - if (children != null && children!.isNotEmpty) - InkWell( - onTap: () { - if (onExpansionChanged != null) { - onExpansionChanged!(); - } - }, - child: Icon( - isExpanded - ? Icons.keyboard_arrow_down - : Icons.keyboard_arrow_right, - color: ColorsManager.lightGrayColor, - size: 16.0, - ), - ), + _buildExpansionIcon(), Expanded( child: GestureDetector( onTap: () { @@ -96,6 +82,20 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { ); } + Widget _buildExpansionIcon() { + return Visibility( + visible: children != null && children!.isNotEmpty, + child: InkWell( + onTap: onExpansionChanged, + child: Icon( + isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right, + color: ColorsManager.lightGrayColor, + size: 16.0, + ), + ), + ); + } + String _capitalizeFirstLetter(String text) { if (text.isEmpty) return text; return text[0].toUpperCase() + text.substring(1); From 0cc867a4ea1e863cb25127af81ac2b63305091de Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 14:47:19 +0300 Subject: [PATCH 155/238] Refactor text color assignment in CustomExpansionTileSpaceTree for improved readability. --- lib/pages/space_tree/view/custom_expansion.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/pages/space_tree/view/custom_expansion.dart b/lib/pages/space_tree/view/custom_expansion.dart index 2d969489..dc55cbe7 100644 --- a/lib/pages/space_tree/view/custom_expansion.dart +++ b/lib/pages/space_tree/view/custom_expansion.dart @@ -61,9 +61,8 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { _capitalizeFirstLetter(title), style: Theme.of(context).textTheme.bodySmall!.copyWith( color: isSelected - ? ColorsManager - .blackColor // Change color to black when selected - : ColorsManager.lightGrayColor, // Gray when not selected + ? ColorsManager.blackColor + : ColorsManager.lightGrayColor, fontWeight: FontWeight.w400, ), ), From 52e1ff94dedc9b8095815cfe3a4aee1678db097a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 14:47:42 +0300 Subject: [PATCH 156/238] Refactor onItemSelected handling in CustomExpansionTileSpaceTree for improved readability. --- lib/pages/space_tree/view/custom_expansion.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/pages/space_tree/view/custom_expansion.dart b/lib/pages/space_tree/view/custom_expansion.dart index dc55cbe7..4aee79e9 100644 --- a/lib/pages/space_tree/view/custom_expansion.dart +++ b/lib/pages/space_tree/view/custom_expansion.dart @@ -52,11 +52,7 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { _buildExpansionIcon(), Expanded( child: GestureDetector( - onTap: () { - if (onItemSelected != null) { - onItemSelected!(); - } - }, + onTap: onItemSelected, child: Text( _capitalizeFirstLetter(title), style: Theme.of(context).textTheme.bodySmall!.copyWith( From 3216d6b879ce6428ab721a0dc081127efc29b6e0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 14:47:52 +0300 Subject: [PATCH 157/238] Refactor fillColor assignment in CustomExpansionTileSpaceTree for improved readability. --- lib/pages/space_tree/view/custom_expansion.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/space_tree/view/custom_expansion.dart b/lib/pages/space_tree/view/custom_expansion.dart index 4aee79e9..43087e05 100644 --- a/lib/pages/space_tree/view/custom_expansion.dart +++ b/lib/pages/space_tree/view/custom_expansion.dart @@ -43,9 +43,9 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return ColorsManager.blue1; - } else { - return ColorsManager.checkBoxFillColor; } + + return ColorsManager.checkBoxFillColor; }), checkColor: ColorsManager.whiteColors, ), From 01d5cb48cc08973cf9d0437edb788844e6a5f9cc Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 16 Apr 2025 14:49:20 +0300 Subject: [PATCH 158/238] Refactor onChanged callback in Checkbox for improved readability in CustomExpansionTileSpaceTree. --- lib/pages/space_tree/view/custom_expansion.dart | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/pages/space_tree/view/custom_expansion.dart b/lib/pages/space_tree/view/custom_expansion.dart index 43087e05..e4e7f423 100644 --- a/lib/pages/space_tree/view/custom_expansion.dart +++ b/lib/pages/space_tree/view/custom_expansion.dart @@ -31,15 +31,11 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { children: [ Checkbox( value: isSoldCheck ? null : isSelected, - onChanged: (bool? value) { - if (onItemSelected != null) { - onItemSelected!(); - } - }, + onChanged: (value) => onItemSelected ?? () {}, tristate: true, - side: WidgetStateBorderSide.resolveWith((states) { - return const BorderSide(color: ColorsManager.grayBorder); - }), + side: WidgetStateBorderSide.resolveWith( + (states) => const BorderSide(color: ColorsManager.grayBorder), + ), fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return ColorsManager.blue1; From 72ae3b172718a6613bd65e6183006a78a2731247 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 16 Apr 2025 15:06:50 +0300 Subject: [PATCH 159/238] Refactor FactoryResetModel and MainDoorSensorBatchView - Refactor FactoryResetModel to include 'operationType' in toJson and toMap methods. - Refactor MainDoorSensorBatchView to use BlocProvider and Builder for better state management. --- .../models/factory_reset_model.dart | 3 ++ .../view/main_door_sensor_batch_view.dart | 53 +++++++++---------- lib/services/devices_mang_api.dart | 3 +- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/lib/pages/device_managment/all_devices/models/factory_reset_model.dart b/lib/pages/device_managment/all_devices/models/factory_reset_model.dart index 56c6c90b..1b5685a1 100644 --- a/lib/pages/device_managment/all_devices/models/factory_reset_model.dart +++ b/lib/pages/device_managment/all_devices/models/factory_reset_model.dart @@ -19,6 +19,7 @@ class FactoryResetModel { Map toJson() { return { 'devicesUuid': devicesUuid, + 'operationType': operationType, }; } @@ -33,6 +34,7 @@ class FactoryResetModel { Map toMap() { return { 'devicesUuid': devicesUuid, + 'operationType': operationType, }; } @@ -56,3 +58,4 @@ class FactoryResetModel { @override int get hashCode => devicesUuid.hashCode; } + diff --git a/lib/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart b/lib/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart index 7337c9fd..bbebabaa 100644 --- a/lib/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart +++ b/lib/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart @@ -4,7 +4,6 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; class MainDoorSensorBatchView extends StatelessWidget { const MainDoorSensorBatchView({super.key, required this.devicesIds}); @@ -13,35 +12,31 @@ class MainDoorSensorBatchView extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // SizedBox( - // width: 170, - // height: 140, - // child: FirmwareUpdateWidget( - // deviceId: devicesIds.first, - // version: 12, - // ), - // ), - // const SizedBox( - // width: 12, - // ), - SizedBox( - width: 170, - height: 140, - child: FactoryResetWidget( - callFactoryReset: () { - BlocProvider.of(context).add( - MainDoorSensorFactoryReset( - deviceId: devicesIds.first, - factoryReset: FactoryResetModel(devicesUuid: devicesIds), + return BlocProvider( + create: (context) => MainDoorSensorBloc(), + child: Builder( + builder: (innerContext) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 170, + height: 140, + child: FactoryResetWidget( + callFactoryReset: () { + BlocProvider.of(innerContext).add( + MainDoorSensorFactoryReset( + deviceId: devicesIds.first, + factoryReset: FactoryResetModel(devicesUuid: devicesIds), + ), + ); + }, ), - ); - }, - ), - ), - ], + ), + ], + ); + }, + ), ); } } diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 85e91759..b4de6326 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -321,13 +321,14 @@ class DevicesManagementApi { Future factoryReset(FactoryResetModel factoryReset, String uuid) async { try { final response = await HTTPService().post( - path: ApiEndpoints.factoryReset.replaceAll('{deviceUuid}', uuid), + path: ApiEndpoints.factoryReset, body: factoryReset.toMap(), showServerMessage: true, expectedResponseModel: (json) { return json['success'] ?? false; }, ); + return response; } catch (e) { debugPrint('Error fetching $e'); From 726c173a7659e3b12f58c4507c94106916f170d1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 09:33:10 +0300 Subject: [PATCH 160/238] SP-1433-FE-Text-Alignment-Issue-in-UI-Component-in-adding-subspace-in-a-space --- .../views/create_subspace_model_dialog.dart | 78 ++++++------------- .../create_subspace_model_chips_box.dart | 6 ++ .../widgets/subspace_chip.dart | 9 +-- 3 files changed, 33 insertions(+), 60 deletions(-) diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart index 0a2a01e5..fd7b890a 100644 --- a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -8,6 +8,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSubSpaceDialog extends StatelessWidget { @@ -20,15 +22,14 @@ class CreateSubSpaceDialog extends StatelessWidget { final Function(List?)? onSave; const CreateSubSpaceDialog( - {Key? key, + {super.key, required this.isEdit, required this.dialogTitle, this.existingSubSpaces, required this.spaceName, required this.spaceTags, required this.products, - required this.onSave}) - : super(key: key); + required this.onSave}); @override Widget build(BuildContext context) { @@ -78,8 +79,10 @@ class CreateSubSpaceDialog extends StatelessWidget { borderRadius: BorderRadius.circular(10), ), child: Wrap( - spacing: 8.0, - runSpacing: 8.0, + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.center, children: [ ...state.subSpaces.asMap().entries.map( (entry) { @@ -97,44 +100,14 @@ class CreateSubSpaceDialog extends StatelessWidget { lowerName) .map((e) => e.key) .toList(); - final isDuplicate = - duplicateIndices.length > 1 && - duplicateIndices.indexOf(index) != 0; - - return Chip( - label: Text(subSpace.subspaceName, - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith( - color: - ColorsManager.spaceColor)), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - side: BorderSide( - color: isDuplicate - ? ColorsManager.red - : ColorsManager.transparentColor, - width: 0, - ), - ), - deleteIcon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager.lightGrayColor, - width: 1.5, - ), - ), - child: const Icon( - Icons.close, - size: 16, - color: ColorsManager.lightGrayColor, - ), + final isDuplicate = duplicateIndices.length > 1 && + duplicateIndices.indexOf(index) != 0; + return SubspaceChip( + subSpace: SubspaceTemplateModel( + subspaceName: entry.value.subspaceName, + disabled: entry.value.disabled, ), + isDuplicate: isDuplicate, onDeleted: () => context .read() .add(RemoveSubSpace(subSpace)), @@ -154,19 +127,18 @@ class CreateSubSpaceDialog extends StatelessWidget { .textTheme .bodySmall ?.copyWith( - color: ColorsManager - .lightGrayColor)), + color: + ColorsManager.lightGrayColor)), onSubmitted: (value) { if (value.trim().isNotEmpty) { - context.read().add( - AddSubSpace(SubspaceModel( + context.read().add(AddSubSpace( + SubspaceModel( subspaceName: value.trim(), disabled: false))); textController.clear(); } }, - style: - Theme.of(context).textTheme.bodyMedium), + style: Theme.of(context).textTheme.bodyMedium), ), ], ), @@ -175,12 +147,10 @@ class CreateSubSpaceDialog extends StatelessWidget { Padding( padding: const EdgeInsets.only(top: 8.0), child: Text(state.errorMessage, - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith( - color: ColorsManager.warningRed, - )), + style: + Theme.of(context).textTheme.bodySmall?.copyWith( + color: ColorsManager.warningRed, + )), ), const SizedBox(height: 16), Row( diff --git a/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart b/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart index a18fc8d8..c6b302c0 100644 --- a/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart +++ b/lib/pages/spaces_management/create_subspace_model/widgets/create_subspace_model_chips_box.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspaces_textfield.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; @@ -51,6 +54,9 @@ class CreateSubspaceModelChipsBox extends StatelessWidget { return SubspaceChip( subSpace: subSpace, isDuplicate: isDuplicate, + onDeleted: () => context.read().add( + RemoveSubSpaceModel(subSpace), + ), ); }, ), diff --git a/lib/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart b/lib/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart index b54e0712..098b4804 100644 --- a/lib/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart +++ b/lib/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart @@ -1,7 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart'; -import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -11,10 +8,12 @@ class SubspaceChip extends StatelessWidget { required this.subSpace, required this.isDuplicate, super.key, + required this.onDeleted, }); final SubspaceTemplateModel subSpace; final bool isDuplicate; + final void Function() onDeleted; @override Widget build(BuildContext context) { @@ -50,9 +49,7 @@ class SubspaceChip extends StatelessWidget { ), ), ), - onDeleted: () => context.read().add( - RemoveSubSpaceModel(subSpace), - ), + onDeleted: onDeleted, ); } } From 62bf4f29445760002b708c64ef1785c4fffe23d5 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 09:34:03 +0300 Subject: [PATCH 161/238] Refactor CreateSubSpaceDialog to use context extension for screen width calculations --- .../views/create_subspace_model_dialog.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart index fd7b890a..11c747bc 100644 --- a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspac import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class CreateSubSpaceDialog extends StatelessWidget { final bool isEdit; @@ -19,7 +20,7 @@ class CreateSubSpaceDialog extends StatelessWidget { final String? spaceName; final List? spaceTags; final List? products; - final Function(List?)? onSave; + final void Function(List?)? onSave; const CreateSubSpaceDialog( {super.key, @@ -33,7 +34,6 @@ class CreateSubSpaceDialog extends StatelessWidget { @override Widget build(BuildContext context) { - final screenWidth = MediaQuery.of(context).size.width; final textController = TextEditingController(); return Dialog( @@ -55,7 +55,7 @@ class CreateSubSpaceDialog extends StatelessWidget { return Container( color: ColorsManager.whiteColors, child: SizedBox( - width: screenWidth * 0.35, + width: context.screenWidth * 0.35, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( @@ -71,7 +71,7 @@ class CreateSubSpaceDialog extends StatelessWidget { ), const SizedBox(height: 16), Container( - width: screenWidth * 0.35, + width: context.screenWidth * 0.35, padding: const EdgeInsets.symmetric( vertical: 10.0, horizontal: 16.0), decoration: BoxDecoration( From 18c886753dfac908b0696945f0300220953646ed Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 09:35:33 +0300 Subject: [PATCH 162/238] added trailing commas wherever neccessary in `CreateSubSpaceDialog`. --- .../views/create_subspace_model_dialog.dart | 292 +++++++++--------- 1 file changed, 151 insertions(+), 141 deletions(-) diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart index 11c747bc..336cc354 100644 --- a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -22,15 +22,16 @@ class CreateSubSpaceDialog extends StatelessWidget { final List? products; final void Function(List?)? onSave; - const CreateSubSpaceDialog( - {super.key, - required this.isEdit, - required this.dialogTitle, - this.existingSubSpaces, - required this.spaceName, - required this.spaceTags, - required this.products, - required this.onSave}); + const CreateSubSpaceDialog({ + super.key, + required this.isEdit, + required this.dialogTitle, + this.existingSubSpaces, + required this.spaceName, + required this.spaceTags, + required this.products, + required this.onSave, + }); @override Widget build(BuildContext context) { @@ -44,7 +45,7 @@ class CreateSubSpaceDialog extends StatelessWidget { create: (_) { final bloc = SubSpaceBloc(); if (existingSubSpaces != null) { - for (var subSpace in existingSubSpaces!) { + for (final subSpace in existingSubSpaces ?? []) { bloc.add(AddSubSpace(subSpace)); } } @@ -53,143 +54,152 @@ class CreateSubSpaceDialog extends StatelessWidget { child: BlocBuilder( builder: (context, state) { return Container( - color: ColorsManager.whiteColors, - child: SizedBox( - width: context.screenWidth * 0.35, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - dialogTitle, - style: Theme.of(context) - .textTheme - .headlineLarge - ?.copyWith(color: ColorsManager.blackColor), - ), - const SizedBox(height: 16), - Container( - width: context.screenWidth * 0.35, - padding: const EdgeInsets.symmetric( - vertical: 10.0, horizontal: 16.0), - decoration: BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.circular(10), - ), - child: Wrap( - spacing: 8, - runSpacing: 8, - alignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - ...state.subSpaces.asMap().entries.map( - (entry) { - final index = entry.key; - final subSpace = entry.value; - - final lowerName = - subSpace.subspaceName.toLowerCase(); - - final duplicateIndices = state.subSpaces - .asMap() - .entries - .where((e) => - e.value.subspaceName.toLowerCase() == - lowerName) - .map((e) => e.key) - .toList(); - final isDuplicate = duplicateIndices.length > 1 && - duplicateIndices.indexOf(index) != 0; - return SubspaceChip( - subSpace: SubspaceTemplateModel( - subspaceName: entry.value.subspaceName, - disabled: entry.value.disabled, - ), - isDuplicate: isDuplicate, - onDeleted: () => context - .read() - .add(RemoveSubSpace(subSpace)), - ); - }, - ), - SizedBox( - width: 200, - child: TextField( - controller: textController, - decoration: InputDecoration( - border: InputBorder.none, - hintText: state.subSpaces.isEmpty - ? 'Please enter the name' - : null, - hintStyle: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith( - color: - ColorsManager.lightGrayColor)), - onSubmitted: (value) { - if (value.trim().isNotEmpty) { - context.read().add(AddSubSpace( - SubspaceModel( - subspaceName: value.trim(), - disabled: false))); - textController.clear(); - } - }, - style: Theme.of(context).textTheme.bodyMedium), - ), - ], - ), - ), - if (state.errorMessage.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text(state.errorMessage, - style: - Theme.of(context).textTheme.bodySmall?.copyWith( - color: ColorsManager.warningRed, - )), - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () async { - Navigator.of(context).pop(); - }, - ), + color: ColorsManager.whiteColors, + child: SizedBox( + width: context.screenWidth * 0.35, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + dialogTitle, + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + color: ColorsManager.blackColor, ), - const SizedBox(width: 10), - Expanded( - child: DefaultButton( - onPressed: (state.errorMessage.isNotEmpty) - ? null - : () async { - final subSpaces = context - .read() - .state - .subSpaces; - onSave!(subSpaces); - Navigator.of(context).pop(); - }, - backgroundColor: ColorsManager.secondaryColor, - borderRadius: 10, - foregroundColor: state.errorMessage.isNotEmpty - ? ColorsManager.whiteColorsWithOpacity - : ColorsManager.whiteColors, - child: const Text('OK'), + ), + const SizedBox(height: 16), + Container( + width: context.screenWidth * 0.35, + padding: const EdgeInsets.symmetric( + vertical: 10.0, + horizontal: 16.0, + ), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(10), + ), + child: Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + ...state.subSpaces.asMap().entries.map( + (entry) { + final index = entry.key; + final subSpace = entry.value; + + final lowerName = + subSpace.subspaceName.toLowerCase(); + + final duplicateIndices = state.subSpaces + .asMap() + .entries + .where((e) => + e.value.subspaceName.toLowerCase() == + lowerName) + .map((e) => e.key) + .toList(); + final isDuplicate = duplicateIndices.length > 1 && + duplicateIndices.indexOf(index) != 0; + return SubspaceChip( + subSpace: SubspaceTemplateModel( + subspaceName: entry.value.subspaceName, + disabled: entry.value.disabled, + ), + isDuplicate: isDuplicate, + onDeleted: () => context.read().add( + RemoveSubSpace(subSpace), + ), + ); + }, + ), + SizedBox( + width: 200, + child: TextField( + controller: textController, + decoration: InputDecoration( + border: InputBorder.none, + hintText: state.subSpaces.isEmpty + ? 'Please enter the name' + : null, + hintStyle: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: ColorsManager.lightGrayColor, + ), + ), + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + context.read().add( + AddSubSpace( + SubspaceModel( + subspaceName: value.trim(), + disabled: false, + ), + ), + ); + textController.clear(); + } + }, + style: Theme.of(context).textTheme.bodyMedium, ), ), ], ), - ], - ), + ), + if (state.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + state.errorMessage, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: ColorsManager.warningRed, + ), + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: (state.errorMessage.isNotEmpty) + ? null + : () async { + final subSpaces = context + .read() + .state + .subSpaces; + onSave!(subSpaces); + Navigator.of(context).pop(); + }, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: state.errorMessage.isNotEmpty + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ], + ), + ], ), - )); + ), + ), + ); }, ), ), From ae95d06482ab2d117c4c4a0817f7491714c62521 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 09:35:47 +0300 Subject: [PATCH 163/238] Fix constructor parameter order in CreateSubSpaceDialog --- .../create_subspace/views/create_subspace_model_dialog.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart index 336cc354..3f7cc598 100644 --- a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -23,14 +23,14 @@ class CreateSubSpaceDialog extends StatelessWidget { final void Function(List?)? onSave; const CreateSubSpaceDialog({ - super.key, required this.isEdit, required this.dialogTitle, - this.existingSubSpaces, required this.spaceName, required this.spaceTags, required this.products, required this.onSave, + this.existingSubSpaces, + super.key, }); @override From a6fc99443b07635eaa76f26ba165abe4346648b6 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 09:38:54 +0300 Subject: [PATCH 164/238] Refactor CreateSubSpaceDialog layout for improved readability and maintainability --- .../views/create_subspace_model_dialog.dart | 253 +++++++++--------- 1 file changed, 121 insertions(+), 132 deletions(-) diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart index 3f7cc598..ef20fa56 100644 --- a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -54,150 +54,139 @@ class CreateSubSpaceDialog extends StatelessWidget { child: BlocBuilder( builder: (context, state) { return Container( + width: context.screenWidth * 0.35, color: ColorsManager.whiteColors, - child: SizedBox( - width: context.screenWidth * 0.35, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - dialogTitle, - style: Theme.of(context).textTheme.headlineLarge?.copyWith( - color: ColorsManager.blackColor, - ), - ), - const SizedBox(height: 16), - Container( - width: context.screenWidth * 0.35, - padding: const EdgeInsets.symmetric( - vertical: 10.0, - horizontal: 16.0, - ), - decoration: BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.circular(10), - ), - child: Wrap( - spacing: 8, - runSpacing: 8, - alignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - ...state.subSpaces.asMap().entries.map( - (entry) { - final index = entry.key; - final subSpace = entry.value; + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + dialogTitle, + style: context.textTheme.headlineLarge?.copyWith( + color: ColorsManager.blackColor, + ), + ), + const SizedBox(height: 16), + Container( + width: context.screenWidth * 0.35, + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 16, + ), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(10), + ), + child: Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + ...state.subSpaces.asMap().entries.map( + (entry) { + final index = entry.key; + final subSpace = entry.value; - final lowerName = - subSpace.subspaceName.toLowerCase(); + final lowerName = subSpace.subspaceName.toLowerCase(); - final duplicateIndices = state.subSpaces - .asMap() - .entries - .where((e) => - e.value.subspaceName.toLowerCase() == - lowerName) - .map((e) => e.key) - .toList(); - final isDuplicate = duplicateIndices.length > 1 && - duplicateIndices.indexOf(index) != 0; - return SubspaceChip( - subSpace: SubspaceTemplateModel( - subspaceName: entry.value.subspaceName, - disabled: entry.value.disabled, + final duplicateIndices = state.subSpaces + .asMap() + .entries + .where((e) => + e.value.subspaceName.toLowerCase() == lowerName) + .map((e) => e.key) + .toList(); + final isDuplicate = duplicateIndices.length > 1 && + duplicateIndices.indexOf(index) != 0; + return SubspaceChip( + subSpace: SubspaceTemplateModel( + subspaceName: entry.value.subspaceName, + disabled: entry.value.disabled, + ), + isDuplicate: isDuplicate, + onDeleted: () => context.read().add( + RemoveSubSpace(subSpace), ), - isDuplicate: isDuplicate, - onDeleted: () => context.read().add( - RemoveSubSpace(subSpace), - ), - ); - }, - ), - SizedBox( - width: 200, - child: TextField( - controller: textController, - decoration: InputDecoration( - border: InputBorder.none, - hintText: state.subSpaces.isEmpty - ? 'Please enter the name' - : null, - hintStyle: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith( - color: ColorsManager.lightGrayColor, - ), - ), - onSubmitted: (value) { - if (value.trim().isNotEmpty) { - context.read().add( - AddSubSpace( - SubspaceModel( - subspaceName: value.trim(), - disabled: false, - ), - ), - ); - textController.clear(); - } - }, - style: Theme.of(context).textTheme.bodyMedium, + ); + }, + ), + SizedBox( + width: 200, + child: TextField( + controller: textController, + decoration: InputDecoration( + border: InputBorder.none, + hintText: state.subSpaces.isEmpty + ? 'Please enter the name' + : null, + hintStyle: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.lightGrayColor, ), ), - ], + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + context.read().add( + AddSubSpace( + SubspaceModel( + subspaceName: value.trim(), + disabled: false, + ), + ), + ); + textController.clear(); + } + }, + style: context.textTheme.bodyMedium, + ), + ), + ], + ), + ), + if (state.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + state.errorMessage, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.warningRed, ), ), - if (state.errorMessage.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - state.errorMessage, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: ColorsManager.warningRed, - ), - ), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: CancelButton( + label: 'Cancel', + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + ), + const SizedBox(width: 10), + Expanded( + child: DefaultButton( + onPressed: state.errorMessage.isEmpty + ? () { + final subSpacesBloc = context.read(); + final subSpaces = subSpacesBloc.state.subSpaces; + onSave?.call(subSpaces); + Navigator.of(context).pop(); + } + : null, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: state.errorMessage.isNotEmpty + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, + child: const Text('OK'), ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () async { - Navigator.of(context).pop(); - }, - ), - ), - const SizedBox(width: 10), - Expanded( - child: DefaultButton( - onPressed: (state.errorMessage.isNotEmpty) - ? null - : () async { - final subSpaces = context - .read() - .state - .subSpaces; - onSave!(subSpaces); - Navigator.of(context).pop(); - }, - backgroundColor: ColorsManager.secondaryColor, - borderRadius: 10, - foregroundColor: state.errorMessage.isNotEmpty - ? ColorsManager.whiteColorsWithOpacity - : ColorsManager.whiteColors, - child: const Text('OK'), - ), - ), - ], ), ], ), - ), + ], ), ); }, From 2a77483f46dcc1a2e2d7a57ca2c0a3af23e6488a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 09:45:14 +0300 Subject: [PATCH 165/238] Refactor `CreateSubSpaceDialog` to improve widget structure and readability --- .../views/create_subspace_model_dialog.dart | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart index ef20fa56..e8ef9993 100644 --- a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -37,20 +37,20 @@ class CreateSubSpaceDialog extends StatelessWidget { Widget build(BuildContext context) { final textController = TextEditingController(); - return Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - child: BlocProvider( - create: (_) { - final bloc = SubSpaceBloc(); - if (existingSubSpaces != null) { - for (final subSpace in existingSubSpaces ?? []) { - bloc.add(AddSubSpace(subSpace)); - } + return BlocProvider( + create: (_) { + final bloc = SubSpaceBloc(); + if (existingSubSpaces != null) { + for (final subSpace in existingSubSpaces ?? []) { + bloc.add(AddSubSpace(subSpace)); } - return bloc; - }, + } + return bloc; + }, + child: Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), child: BlocBuilder( builder: (context, state) { return Container( From 024fbcdb837fd4db97a29038d7ce20e617235f2c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 11:25:39 +0300 Subject: [PATCH 166/238] Refactor `CreateSubSpaceDialog` to convert to StatefulWidget and manage text controller lifecycle --- .../views/create_subspace_model_dialog.dart | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart index e8ef9993..09951bd8 100644 --- a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -13,7 +13,7 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -class CreateSubSpaceDialog extends StatelessWidget { +class CreateSubSpaceDialog extends StatefulWidget { final bool isEdit; final String dialogTitle; final List? existingSubSpaces; @@ -34,14 +34,31 @@ class CreateSubSpaceDialog extends StatelessWidget { }); @override - Widget build(BuildContext context) { - final textController = TextEditingController(); + State createState() => _CreateSubSpaceDialogState(); +} +class _CreateSubSpaceDialogState extends State { + late final TextEditingController _subspaceNameController; + + @override + void initState() { + _subspaceNameController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _subspaceNameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { return BlocProvider( create: (_) { final bloc = SubSpaceBloc(); - if (existingSubSpaces != null) { - for (final subSpace in existingSubSpaces ?? []) { + if (widget.existingSubSpaces != null) { + for (final subSpace in widget.existingSubSpaces ?? []) { bloc.add(AddSubSpace(subSpace)); } } @@ -62,7 +79,7 @@ class CreateSubSpaceDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - dialogTitle, + widget.dialogTitle, style: context.textTheme.headlineLarge?.copyWith( color: ColorsManager.blackColor, ), @@ -115,7 +132,7 @@ class CreateSubSpaceDialog extends StatelessWidget { SizedBox( width: 200, child: TextField( - controller: textController, + controller: _subspaceNameController, decoration: InputDecoration( border: InputBorder.none, hintText: state.subSpaces.isEmpty @@ -126,16 +143,17 @@ class CreateSubSpaceDialog extends StatelessWidget { ), ), onSubmitted: (value) { - if (value.trim().isNotEmpty) { + final trimmedValue = value.trim(); + if (trimmedValue.isNotEmpty) { context.read().add( AddSubSpace( SubspaceModel( - subspaceName: value.trim(), + subspaceName: trimmedValue, disabled: false, ), ), ); - textController.clear(); + _subspaceNameController.clear(); } }, style: context.textTheme.bodyMedium, @@ -172,7 +190,7 @@ class CreateSubSpaceDialog extends StatelessWidget { ? () { final subSpacesBloc = context.read(); final subSpaces = subSpacesBloc.state.subSpaces; - onSave?.call(subSpaces); + widget.onSave?.call(subSpaces); Navigator.of(context).pop(); } : null, From ce253b2034e41fb43e1a105100708208ac4c54eb Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 11:27:37 +0300 Subject: [PATCH 167/238] Remove unused parameters from `CreateSubSpaceDialog` constructor --- .../all_spaces/widgets/dialogs/create_space_dialog.dart | 2 -- .../create_subspace/views/create_subspace_model_dialog.dart | 5 ----- 2 files changed, 7 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 56e1212e..a7f6e2ea 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -592,8 +592,6 @@ class CreateSpaceDialogState extends State { return CreateSubSpaceDialog( spaceName: name, dialogTitle: isEdit ? 'Edit Sub-space' : 'Create Sub-space', - spaceTags: spaceTags, - isEdit: isEdit, products: products, existingSubSpaces: existingSubSpaces, onSave: (slectedSubspaces) { diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart index 09951bd8..948028ed 100644 --- a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -4,7 +4,6 @@ import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart'; @@ -14,19 +13,15 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class CreateSubSpaceDialog extends StatefulWidget { - final bool isEdit; final String dialogTitle; final List? existingSubSpaces; final String? spaceName; - final List? spaceTags; final List? products; final void Function(List?)? onSave; const CreateSubSpaceDialog({ - required this.isEdit, required this.dialogTitle, required this.spaceName, - required this.spaceTags, required this.products, required this.onSave, this.existingSubSpaces, From 8136804694616a84d9065f14d96cebe158f5d715 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 12:18:09 +0300 Subject: [PATCH 168/238] bugfix. --- lib/pages/space_tree/view/custom_expansion.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/space_tree/view/custom_expansion.dart b/lib/pages/space_tree/view/custom_expansion.dart index e4e7f423..dab0a49f 100644 --- a/lib/pages/space_tree/view/custom_expansion.dart +++ b/lib/pages/space_tree/view/custom_expansion.dart @@ -31,7 +31,7 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { children: [ Checkbox( value: isSoldCheck ? null : isSelected, - onChanged: (value) => onItemSelected ?? () {}, + onChanged: (value) => onItemSelected?.call(), tristate: true, side: WidgetStateBorderSide.resolveWith( (states) => const BorderSide(color: ColorsManager.grayBorder), From 977875f1f2e82b1ba44365fc37d08150cf79f802 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 13:08:16 +0300 Subject: [PATCH 169/238] SP-1435 --- lib/pages/routines/view/routines_view.dart | 78 ++++---- .../fetch_routine_scenes_automation.dart | 168 ++++++++---------- .../main_routine_view/routine_view_card.dart | 104 ++++++----- 3 files changed, 170 insertions(+), 180 deletions(-) diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index 2d6ee648..9d4639ed 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -1,7 +1,7 @@ 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/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/create_new_routines/create_new_routines.dart'; import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart'; @@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/routines/widgets/main_routine_view/fetch_routi import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class RoutinesView extends StatefulWidget { const RoutinesView({super.key}); @@ -27,9 +28,10 @@ class _RoutinesViewState extends State { if (result == null) return; final communityId = result['community']; final spaceId = result['space']; - final _bloc = BlocProvider.of(context); + final bloc = BlocProvider.of(context); final routineBloc = context.read(); - _bloc.add(SaveCommunityIdAndSpaceIdEvent(communityID: communityId, spaceID: spaceId)); + bloc.add( + SaveCommunityIdAndSpaceIdEvent(communityID: communityId, spaceID: spaceId)); await Future.delayed(const Duration(seconds: 1)); routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true)); } @@ -54,43 +56,41 @@ class _RoutinesViewState extends State { ), Expanded( flex: 4, - 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()), - ], - ), + child: SizedBox( + height: context.screenHeight, + width: context.screenWidth, + child: SingleChildScrollView( + padding: const EdgeInsetsDirectional.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + spacing: 16, + children: [ + Text( + "Create New Routines", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + RoutineViewCard( + isLoading: false, + onChanged: (v) {}, + status: '', + spaceId: '', + automationId: '', + communityId: '', + sceneId: '', + cardType: '', + spaceName: '', + onTap: () => _handleRoutineCreation(context), + icon: Icons.add, + textString: '', + ), + const FetchRoutineScenesAutomation(), + ], ), - const SizedBox(height: 50), - ], + ), ), ) ], 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 92a837b6..0e25dcea 100644 --- a/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart +++ b/lib/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart @@ -12,8 +12,7 @@ class FetchRoutineScenesAutomation extends StatefulWidget { const FetchRoutineScenesAutomation({super.key}); @override - State createState() => - _FetchRoutineScenesState(); + State createState() => _FetchRoutineScenesState(); } class _FetchRoutineScenesState extends State @@ -55,20 +54,17 @@ class _FetchRoutineScenesState extends State ), if (state.scenes.isNotEmpty) SizedBox( - height: 200, + height: 200, child: ListView.builder( - shrinkWrap: true, scrollDirection: Axis.horizontal, itemCount: state.scenes.length, itemBuilder: (context, index) { final scene = state.scenes[index]; - final isLoading = - state.loadingSceneId == scene.id; + final isLoading = state.loadingSceneId == scene.id; return Padding( padding: EdgeInsets.only( - right: - isSmallScreenSize(context) ? 4.0 : 8.0, + right: isSmallScreenSize(context) ? 4.0 : 8.0, ), child: Column( children: [ @@ -76,31 +72,28 @@ class _FetchRoutineScenesState extends State isLoading: isLoading, sceneOnTap: () { context.read().add( - SceneTrigger( + SceneTrigger( sceneId: scene.id, - name: scene.name)); + name: scene.name, + ), + ); }, status: state.scenes[index].status, - communityId: - state.scenes[index].communityId ?? - '', + communityId: state.scenes[index].communityId, spaceId: state.scenes[index].spaceId, - sceneId: - state.scenes[index].sceneTuyaId!, + sceneId: state.scenes[index].sceneTuyaId!, automationId: state.scenes[index].id, cardType: 'scenes', - spaceName: - state.scenes[index].spaceName, + spaceName: state.scenes[index].spaceName, onTap: () { - BlocProvider.of(context) - .add( + BlocProvider.of(context).add( const CreateNewRoutineViewEvent( - createRoutineView: true), + createRoutineView: true, + ), ); context.read().add( GetSceneDetails( - sceneId: - state.scenes[index].id, + sceneId: state.scenes[index].id, isTabToRun: true, isUpdate: true, ), @@ -110,8 +103,7 @@ class _FetchRoutineScenesState extends State icon: state.scenes[index].icon ?? Assets.logoHorizontal, isFromScenes: true, - iconInBytes: - state.scenes[index].iconInBytes, + iconInBytes: state.scenes[index].iconInBytes, ), ], ), @@ -136,77 +128,69 @@ class _FetchRoutineScenesState extends State ), if (state.automations.isNotEmpty) SizedBox( - height: 200, - + height: 200, child: ListView.builder( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - itemCount: state.automations.length, - itemBuilder: (context, index) { - final isLoading = state.automations! - .contains(state.automations[index].id); + scrollDirection: Axis.horizontal, + itemCount: state.automations.length, + itemBuilder: (context, index) { + final isLoading = state.automations + .contains(state.automations[index].id); - return Column( - children: [ - Padding( - padding: EdgeInsets.only( - right: isSmallScreenSize(context) - ? 4.0 - : 8.0, - ), - child: RoutineViewCard( - isLoading: isLoading, - onChanged: (v) { - 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.automations[index].spaceName, - onTap: () { - BlocProvider.of(context) - .add( - const CreateNewRoutineViewEvent( - createRoutineView: true), - ); - 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, - ), + return Column( + children: [ + Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) ? 4.0 : 8.0, ), - ], - ); - }), + child: RoutineViewCard( + isLoading: isLoading, + onChanged: (v) { + 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.automations[index].spaceName, + onTap: () { + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent( + createRoutineView: true, + ), + ); + 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 7be5a959..4fc4bd0f 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,4 +1,5 @@ import 'dart:async'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -66,7 +67,6 @@ class _RoutineViewCardState extends State { @override Widget build(BuildContext context) { - // Use widget. instead of just final double cardWidth = widget.isSmallScreenSize(context) ? 120 : widget.isMediumScreenSize(context) @@ -127,22 +127,23 @@ class _RoutineViewCardState extends State { ) else CupertinoSwitch( - activeColor: ColorsManager.primaryColor, + activeTrackColor: ColorsManager.primaryColor, value: widget.status == 'enable', onChanged: widget.onChanged, ) ], ) : const SizedBox(), - InkWell( - onTap: widget.onTap, - child: Column( - children: [ - Center( + Column( + children: [ + Center( + child: InkWell( + customBorder: const CircleBorder(), + onTap: widget.onTap, child: Container( decoration: BoxDecoration( color: ColorsManager.graysColor, - borderRadius: BorderRadius.circular(120), + shape: BoxShape.circle, border: Border.all( color: ColorsManager.greyColor, width: 2.0, @@ -158,7 +159,8 @@ class _RoutineViewCardState extends State { height: iconSize, width: iconSize, fit: BoxFit.contain, - errorBuilder: (context, error, stackTrace) => Image.asset( + errorBuilder: (context, error, stackTrace) => + Image.asset( Assets.logo, height: iconSize, width: iconSize, @@ -171,7 +173,8 @@ class _RoutineViewCardState extends State { width: iconSize, fit: BoxFit.contain, ) - : (widget.icon is String && widget.icon.endsWith('.svg')) + : (widget.icon is String && + widget.icon.endsWith('.svg')) ? SvgPicture.asset( height: iconSize, width: iconSize, @@ -181,51 +184,54 @@ class _RoutineViewCardState extends State { : Icon( widget.icon, color: ColorsManager.dialogBlueTitle, - size: widget.isSmallScreenSize(context) ? 30 : 40, + 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: 1, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontSize: widget.isSmallScreenSize(context) ? 10 : 12, - ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3), + child: Column( + children: [ + Text( + widget.textString, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 1, + 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, + ), + 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: 1, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontSize: + widget.isSmallScreenSize(context) ? 10 : 12, ), - Text( - widget.spaceName, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontSize: widget.isSmallScreenSize(context) ? 10 : 12, - ), - ), - ], - ), - ], - ), + ), + ], + ), + ], ), - ], - ), + ), + ], ), ], ), From f912b41fd8c93e8c072fe444b9f702adcf3e7bc1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 13:12:09 +0300 Subject: [PATCH 170/238] Refactor visibility handling for scenes and automations in FetchRoutineScenesAutomation --- .../fetch_routine_scenes_automation.dart | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) 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 0e25dcea..c46ba377 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 @@ -17,10 +17,6 @@ class FetchRoutineScenesAutomation extends StatefulWidget { class _FetchRoutineScenesState extends State with HelperResponsiveLayout { - @override - void initState() { - super.initState(); - } @override Widget build(BuildContext context) { @@ -45,15 +41,15 @@ class _FetchRoutineScenesState extends State ), ), const SizedBox(height: 10), - if (state.scenes.isEmpty) - Text( + Visibility( + visible: state.scenes.isNotEmpty, + replacement: Text( "No scenes found", style: context.textTheme.bodyMedium?.copyWith( color: ColorsManager.grayColor, ), ), - if (state.scenes.isNotEmpty) - SizedBox( + child: SizedBox( height: 200, child: ListView.builder( scrollDirection: Axis.horizontal, @@ -110,6 +106,7 @@ class _FetchRoutineScenesState extends State ); }), ), + ), const SizedBox(height: 10), Text( "Automations", @@ -119,15 +116,15 @@ class _FetchRoutineScenesState extends State ), ), const SizedBox(height: 3), - if (state.automations.isEmpty) - Text( + Visibility( + visible: state.automations.isNotEmpty, + replacement: Text( "No automations found", style: context.textTheme.bodyMedium?.copyWith( color: ColorsManager.grayColor, ), ), - if (state.automations.isNotEmpty) - SizedBox( + child: SizedBox( height: 200, child: ListView.builder( scrollDirection: Axis.horizontal, @@ -192,6 +189,7 @@ class _FetchRoutineScenesState extends State }, ), ), + ) ], ), ), From 70f1f39fce8f84428e2fb60753fd4de90f47a6b1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 13:13:48 +0300 Subject: [PATCH 171/238] Improve loading state handling and in `FetchRoutineScenesAutomation`. --- .../fetch_routine_scenes_automation.dart | 336 +++++++++--------- 1 file changed, 163 insertions(+), 173 deletions(-) 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 c46ba377..3e5d4e7f 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 @@ -8,192 +8,182 @@ 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 FetchRoutineScenesAutomation extends StatefulWidget { - const FetchRoutineScenesAutomation({super.key}); - - @override - State createState() => _FetchRoutineScenesState(); -} - -class _FetchRoutineScenesState extends State +class FetchRoutineScenesAutomation extends StatelessWidget with HelperResponsiveLayout { + const FetchRoutineScenesAutomation({super.key}); @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return state.isLoading - ? const Center( - child: CircularProgressIndicator(), - ) - : SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Scenes (Tab to Run)", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - Visibility( - visible: state.scenes.isNotEmpty, - replacement: Text( - "No scenes found", - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, - ), - ), - child: SizedBox( - height: 200, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: state.scenes.length, - itemBuilder: (context, index) { - final scene = state.scenes[index]; - final isLoading = state.loadingSceneId == scene.id; + if (state.isLoading) const Center(child: CircularProgressIndicator()); - return Padding( - padding: EdgeInsets.only( - right: isSmallScreenSize(context) ? 4.0 : 8.0, - ), - child: Column( - children: [ - 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, - ), - ); - 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, - ), - ], - ), - ); - }), - ), + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Scenes (Tab to Run)", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, ), - const SizedBox(height: 10), - Text( - "Automations", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 3), - Visibility( - visible: state.automations.isNotEmpty, - replacement: Text( - "No automations found", - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, - ), - ), - child: SizedBox( - height: 200, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: state.automations.length, - itemBuilder: (context, index) { - final isLoading = state.automations - .contains(state.automations[index].id); + ), + const SizedBox(height: 10), + Visibility( + visible: state.scenes.isNotEmpty, + replacement: Text( + "No scenes found", + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), + ), + child: SizedBox( + height: 200, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: state.scenes.length, + itemBuilder: (context, index) { + final scene = state.scenes[index]; + final isLoading = state.loadingSceneId == scene.id; - return Column( - children: [ - Padding( - padding: EdgeInsets.only( - right: isSmallScreenSize(context) ? 4.0 : 8.0, - ), - child: RoutineViewCard( - isLoading: isLoading, - onChanged: (v) { - 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.automations[index].spaceName, - onTap: () { - BlocProvider.of(context).add( - const CreateNewRoutineViewEvent( - createRoutineView: true, + return Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) ? 4.0 : 8.0, + ), + child: Column( + children: [ + RoutineViewCard( + isLoading: isLoading, + sceneOnTap: () { + context.read().add( + SceneTrigger( + sceneId: scene.id, + name: scene.name, ), ); - 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, - ), - ), - ], - ); - }, - ), - ), - ) - ], + }, + 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, + ), + ); + 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: 10), + Text( + "Automations", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 3), + Visibility( + visible: state.automations.isNotEmpty, + replacement: Text( + "No automations found", + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), + ), + child: SizedBox( + height: 200, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: state.automations.length, + itemBuilder: (context, index) { + final isLoading = + state.automations.contains(state.automations[index].id); + + return Column( + children: [ + Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) ? 4.0 : 8.0, + ), + child: RoutineViewCard( + isLoading: isLoading, + onChanged: (v) { + 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.automations[index].spaceName, + onTap: () { + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent( + createRoutineView: true, + ), + ); + 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, + ), + ), + ], + ); + }, + ), + ), + ) + ], + ), + ), + ); }, ); } From 9431eb79c1ab7b7f062d58921b431a0738310386 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 13:18:44 +0300 Subject: [PATCH 172/238] Fix loading state handling and refactor scene/automation rendering in `FetchRoutineScenesAutomation` --- .../fetch_routine_scenes_automation.dart | 281 +++++++++--------- 1 file changed, 140 insertions(+), 141 deletions(-) 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 3e5d4e7f..f935fef8 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 @@ -16,7 +16,7 @@ class FetchRoutineScenesAutomation extends StatelessWidget Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - if (state.isLoading) const Center(child: CircularProgressIndicator()); + if (state.isLoading) return const Center(child: CircularProgressIndicator()); return SingleChildScrollView( child: Padding( @@ -25,159 +25,25 @@ class FetchRoutineScenesAutomation extends StatelessWidget crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text( - "Scenes (Tab to Run)", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), + _buildListTitle(context, "Scenes (Tab to Run)"), const SizedBox(height: 10), Visibility( visible: state.scenes.isNotEmpty, - replacement: Text( - "No scenes found", - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, - ), - ), + replacement: _buildEmptyState(context, "No scenes found"), child: SizedBox( height: 200, - child: ListView.builder( - 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: Column( - children: [ - 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, - ), - ); - 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, - ), - ], - ), - ); - }), + child: _buildScenes(state), ), ), const SizedBox(height: 10), - Text( - "Automations", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), + _buildListTitle(context, "Automations"), const SizedBox(height: 3), Visibility( visible: state.automations.isNotEmpty, - replacement: Text( - "No automations found", - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, - ), - ), + replacement: _buildEmptyState(context, "No automations found"), child: SizedBox( height: 200, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: state.automations.length, - itemBuilder: (context, index) { - final isLoading = - state.automations.contains(state.automations[index].id); - - return Column( - children: [ - Padding( - padding: EdgeInsets.only( - right: isSmallScreenSize(context) ? 4.0 : 8.0, - ), - child: RoutineViewCard( - isLoading: isLoading, - onChanged: (v) { - 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.automations[index].spaceName, - onTap: () { - BlocProvider.of(context).add( - const CreateNewRoutineViewEvent( - createRoutineView: true, - ), - ); - 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, - ), - ), - ], - ); - }, - ), + child: _buildAutomations(state), ), ) ], @@ -187,4 +53,137 @@ class FetchRoutineScenesAutomation extends StatelessWidget }, ); } + + Widget _buildAutomations(RoutineState state) { + return ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: state.automations.length, + itemBuilder: (context, index) { + final isLoading = state.automations.contains(state.automations[index].id); + + return Column( + children: [ + Padding( + padding: EdgeInsets.only( + right: isSmallScreenSize(context) ? 4.0 : 8.0, + ), + child: RoutineViewCard( + isLoading: isLoading, + onChanged: (v) { + 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.automations[index].spaceName, + onTap: () { + BlocProvider.of(context).add( + const CreateNewRoutineViewEvent( + createRoutineView: true, + ), + ); + 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, + ), + ), + ], + ); + }, + ); + } + + Widget _buildScenes(RoutineState state) { + return ListView.builder( + 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: Column( + children: [ + 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, + ), + ); + 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, + ), + ], + ), + ); + }); + } + + Widget _buildListTitle(BuildContext context, String title) { + return Text( + title, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ); + } + + Widget _buildEmptyState(BuildContext context, String title) { + return Text( + title, + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), + ); + } } From 1407c173b0c82442dc146716fb71201916ccd07c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 13:28:00 +0300 Subject: [PATCH 173/238] tapping bugfix. --- lib/pages/space_tree/view/custom_expansion.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/space_tree/view/custom_expansion.dart b/lib/pages/space_tree/view/custom_expansion.dart index e4e7f423..dab0a49f 100644 --- a/lib/pages/space_tree/view/custom_expansion.dart +++ b/lib/pages/space_tree/view/custom_expansion.dart @@ -31,7 +31,7 @@ class CustomExpansionTileSpaceTree extends StatelessWidget { children: [ Checkbox( value: isSoldCheck ? null : isSelected, - onChanged: (value) => onItemSelected ?? () {}, + onChanged: (value) => onItemSelected?.call(), tristate: true, side: WidgetStateBorderSide.resolveWith( (states) => const BorderSide(color: ColorsManager.grayBorder), From 34fa426163a3224774c4011331938b35ef18f9a3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 14:17:43 +0300 Subject: [PATCH 174/238] submitting password field in login logs the user in. --- lib/pages/auth/view/login_web_page.dart | 28 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart index 72cad7cc..a6de87cf 100644 --- a/lib/pages/auth/view/login_web_page.dart +++ b/lib/pages/auth/view/login_web_page.dart @@ -55,12 +55,12 @@ class _LoginWebPageState extends State with HelperResponsiveLayout final isSmallScreen = isSmallScreenSize(context); final isMediumScreen = isMediumScreenSize(context); Size size = MediaQuery.of(context).size; - late ScrollController _scrollController; - _scrollController = ScrollController(); + late ScrollController scrollController; + scrollController = ScrollController(); - void _scrollToCenter() { - final double middlePosition = _scrollController.position.maxScrollExtent / 2; - _scrollController.animateTo( + void scrollToCenter() { + final double middlePosition = scrollController.position.maxScrollExtent / 2; + scrollController.animateTo( middlePosition, duration: const Duration(seconds: 1), curve: Curves.easeInOut, @@ -68,7 +68,7 @@ class _LoginWebPageState extends State with HelperResponsiveLayout } WidgetsBinding.instance.addPostFrameCallback((_) { - _scrollToCenter(); + scrollToCenter(); }); return Stack( @@ -76,7 +76,7 @@ class _LoginWebPageState extends State with HelperResponsiveLayout FirstLayer( second: Center( child: ListView( - controller: _scrollController, + controller: scrollController, shrinkWrap: true, children: [ Container( @@ -199,7 +199,7 @@ class _LoginWebPageState extends State with HelperResponsiveLayout width: size.width * 0.9, child: DropdownButtonHideUnderline( child: DropdownButton2( - style: TextStyle(color: Colors.black), + style: const TextStyle(color: Colors.black), isExpanded: true, hint: Text( 'Select your region/country', @@ -336,6 +336,16 @@ class _LoginWebPageState extends State with HelperResponsiveLayout obscureText: loginBloc.obscureText, keyboardType: TextInputType.visiblePassword, controller: loginBloc.loginPasswordController, + onFieldSubmitted: (value) { + if (loginBloc.loginFormKey.currentState!.validate()) { + loginBloc.add(LoginButtonPressed( + username: loginBloc.loginEmailController.text, + password: value, + )); + } else { + loginBloc.add(ChangeValidateEvent()); + } + }, decoration: textBoxDecoration()!.copyWith( hintText: 'At least 8 characters', hintStyle: Theme.of(context) @@ -393,7 +403,7 @@ class _LoginWebPageState extends State with HelperResponsiveLayout Transform.scale( scale: 1.2, child: Checkbox( - fillColor: MaterialStateProperty.all(Colors.white), + fillColor: WidgetStateProperty.all(Colors.white), activeColor: Colors.white, value: loginBloc.isChecked, checkColor: Colors.black, From a0dd12855766eb273c89e9e1c1b01baf3fa7c0b8 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 14:30:30 +0300 Subject: [PATCH 175/238] matched design of Function and action in `SaveRoutineHelper.showSaveRoutineDialog` --- .../routines/helper/save_routine_helper.dart | 103 +++++++++++------- 1 file changed, 64 insertions(+), 39 deletions(-) diff --git a/lib/pages/routines/helper/save_routine_helper.dart b/lib/pages/routines/helper/save_routine_helper.dart index 457f74dc..57081230 100644 --- a/lib/pages/routines/helper/save_routine_helper.dart +++ b/lib/pages/routines/helper/save_routine_helper.dart @@ -5,8 +5,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; -import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.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'; @@ -31,7 +31,8 @@ class SaveRoutineHelper { children: [ DialogHeader('Create a scene: ${state.routineName ?? ""}'), Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -59,8 +60,9 @@ class SaveRoutineHelper { ), if (state.isAutomation) ...state.ifItems.map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = state.selectedFunctions[ + item['uniqueCustomId']] ?? + []; return functionRow(item, context, functions); }), ], @@ -68,10 +70,8 @@ class SaveRoutineHelper { ), const SizedBox(width: 16), // Right side - THEN items - Expanded( child: ListView( - // crossAxisAlignment: CrossAxisAlignment.start, shrinkWrap: true, children: [ const Text( @@ -82,8 +82,9 @@ class SaveRoutineHelper { ), const SizedBox(height: 8), ...state.thenItems.map((item) { - final functions = - state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = state.selectedFunctions[ + item['uniqueCustomId']] ?? + []; return functionRow(item, context, functions); }), ], @@ -92,33 +93,30 @@ class SaveRoutineHelper { ], ), ), - // if (state.errorMessage != null || state.errorMessage!.isNotEmpty) - // Padding( - // padding: const EdgeInsets.all(8.0), - // child: Text( - // state.errorMessage!, - // style: const TextStyle(color: Colors.red), - // ), - // ), DialogFooter( onCancel: () => Navigator.pop(context), onConfirm: () async { if (state.isAutomation) { if (state.isUpdate ?? false) { - context.read().add(const UpdateAutomation()); + context + .read() + .add(const UpdateAutomation()); } else { - context.read().add(const CreateAutomationEvent()); + context + .read() + .add(const CreateAutomationEvent()); } } else { if (state.isUpdate ?? false) { context.read().add(const UpdateScene()); } else { - context.read().add(const CreateSceneEvent()); + context + .read() + .add(const CreateSceneEvent()); } } - // if (state.errorMessage == null || state.errorMessage!.isEmpty) { + Navigator.pop(context); - // } }, isConfirmEnabled: true, ), @@ -133,7 +131,10 @@ class SaveRoutineHelper { } static Widget functionRow( - dynamic item, BuildContext context, List functions) { + dynamic item, + BuildContext context, + List functions, + ) { return Padding( padding: const EdgeInsets.only(top: 6), child: Row( @@ -142,19 +143,36 @@ class SaveRoutineHelper { Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.start, - spacing: 8, + spacing: 17, children: [ - item['type'] == 'tap_to_run' || item['type'] == 'scene' - ? Image.memory( - base64Decode(item['icon']), - width: 22, - height: 22, - ) - : SvgPicture.asset( - item['imagePath'], - width: 22, - height: 22, - ), + Container( + width: 22, + height: 22, + padding: const EdgeInsetsDirectional.all(4), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 1.5, + ), + ), + child: Center( + child: item['type'] == 'tap_to_run' || item['type'] == 'scene' + ? Image.memory( + base64Decode(item['icon']), + width: 12, + height: 22, + fit: BoxFit.scaleDown, + ) + : SvgPicture.asset( + item['imagePath'], + width: 12, + height: 12, + fit: BoxFit.scaleDown, + ), + ), + ), Flexible( child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -166,16 +184,18 @@ class SaveRoutineHelper { maxLines: 1, overflow: TextOverflow.ellipsis, style: context.textTheme.bodySmall?.copyWith( - fontSize: 14, + fontSize: 15, color: ColorsManager.textPrimaryColor, ), ), Wrap( + runSpacing: 16, + spacing: 4, children: functions .map((f) => Text( '${f.operationName}: ${f.value}', - style: context.textTheme.bodySmall - ?.copyWith(color: ColorsManager.grayColor, fontSize: 8), + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.grayColor, fontSize: 8), overflow: TextOverflow.ellipsis, maxLines: 3, )) @@ -197,7 +217,10 @@ class SaveRoutineHelper { child: Row( spacing: 2, children: [ - SizedBox(width: 8, height: 8, child: SvgPicture.asset(Assets.deviceTagIcon)), + SizedBox( + width: 8, + height: 8, + child: SvgPicture.asset(Assets.deviceTagIcon)), Text( item['tag'] ?? '', textAlign: TextAlign.center, @@ -218,7 +241,9 @@ class SaveRoutineHelper { spacing: 2, children: [ SizedBox( - width: 8, height: 8, child: SvgPicture.asset(Assets.spaceLocationIcon)), + width: 8, + height: 8, + child: SvgPicture.asset(Assets.spaceLocationIcon)), Text( item['subSpace'] ?? '', textAlign: TextAlign.center, From c46cfb48a83f92e8347431a9cf876a37ab3b4fba Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 14:38:04 +0300 Subject: [PATCH 176/238] refactor: extract IF and THEN sections into separate methods for better readability --- .../routines/helper/save_routine_helper.dart | 161 +++++++++--------- 1 file changed, 81 insertions(+), 80 deletions(-) diff --git a/lib/pages/routines/helper/save_routine_helper.dart b/lib/pages/routines/helper/save_routine_helper.dart index 57081230..0750ba07 100644 --- a/lib/pages/routines/helper/save_routine_helper.dart +++ b/lib/pages/routines/helper/save_routine_helper.dart @@ -36,90 +36,13 @@ class SaveRoutineHelper { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Left side - IF - Expanded( - child: ListView( - // crossAxisAlignment: CrossAxisAlignment.start, - shrinkWrap: true, - children: [ - const Text( - 'IF:', - style: TextStyle( - fontSize: 16, - ), - ), - const SizedBox(height: 8), - if (state.isTabToRun) - ListTile( - leading: SvgPicture.asset( - Assets.tabToRun, - width: 24, - height: 24, - ), - title: const Text('Tab to run'), - ), - if (state.isAutomation) - ...state.ifItems.map((item) { - final functions = state.selectedFunctions[ - item['uniqueCustomId']] ?? - []; - return functionRow(item, context, functions); - }), - ], - ), - ), + _buildIfConditions(state, context), const SizedBox(width: 16), - // Right side - THEN items - Expanded( - child: ListView( - shrinkWrap: true, - children: [ - const Text( - 'THEN:', - style: TextStyle( - fontSize: 16, - ), - ), - const SizedBox(height: 8), - ...state.thenItems.map((item) { - final functions = state.selectedFunctions[ - item['uniqueCustomId']] ?? - []; - return functionRow(item, context, functions); - }), - ], - ), - ), + _buildThenActions(state, context), ], ), ), - DialogFooter( - onCancel: () => Navigator.pop(context), - onConfirm: () async { - if (state.isAutomation) { - if (state.isUpdate ?? false) { - context - .read() - .add(const UpdateAutomation()); - } else { - context - .read() - .add(const CreateAutomationEvent()); - } - } else { - if (state.isUpdate ?? false) { - context.read().add(const UpdateScene()); - } else { - context - .read() - .add(const CreateSceneEvent()); - } - } - - Navigator.pop(context); - }, - isConfirmEnabled: true, - ), + _buildDialogFooter(context, state), ], ), ), @@ -130,6 +53,84 @@ class SaveRoutineHelper { ); } + static DialogFooter _buildDialogFooter(BuildContext context, RoutineState state) { + return DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: () async { + if (state.isAutomation) { + if (state.isUpdate ?? false) { + context.read().add(const UpdateAutomation()); + } else { + context.read().add(const CreateAutomationEvent()); + } + } else { + if (state.isUpdate ?? false) { + context.read().add(const UpdateScene()); + } else { + context.read().add(const CreateSceneEvent()); + } + } + + Navigator.pop(context); + }, + isConfirmEnabled: true, + ); + } + + static Expanded _buildThenActions(RoutineState state, BuildContext context) { + return Expanded( + child: ListView( + shrinkWrap: true, + children: [ + const Text( + 'THEN:', + style: TextStyle( + fontSize: 16, + ), + ), + const SizedBox(height: 8), + ...state.thenItems.map((item) { + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + return functionRow(item, context, functions); + }), + ], + ), + ); + } + + static Expanded _buildIfConditions(RoutineState state, BuildContext context) { + return Expanded( + child: ListView( + // crossAxisAlignment: CrossAxisAlignment.start, + shrinkWrap: true, + children: [ + const Text( + 'IF:', + style: TextStyle( + fontSize: 16, + ), + ), + const SizedBox(height: 8), + if (state.isTabToRun) + ListTile( + leading: SvgPicture.asset( + Assets.tabToRun, + width: 24, + height: 24, + ), + title: const Text('Tab to run'), + ), + if (state.isAutomation) + ...state.ifItems.map((item) { + final functions = + state.selectedFunctions[item['uniqueCustomId']] ?? []; + return functionRow(item, context, functions); + }), + ], + ), + ); + } + static Widget functionRow( dynamic item, BuildContext context, From 2e4f904d3a84388a9554f768e4d21b5298192ab9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 14:39:33 +0300 Subject: [PATCH 177/238] style: improve code formatting and readability in SaveRoutineHelper --- .../routines/helper/save_routine_helper.dart | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/lib/pages/routines/helper/save_routine_helper.dart b/lib/pages/routines/helper/save_routine_helper.dart index 0750ba07..e9d870d9 100644 --- a/lib/pages/routines/helper/save_routine_helper.dart +++ b/lib/pages/routines/helper/save_routine_helper.dart @@ -31,8 +31,10 @@ class SaveRoutineHelper { children: [ DialogHeader('Create a scene: ${state.routineName ?? ""}'), Padding( - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -56,7 +58,7 @@ class SaveRoutineHelper { static DialogFooter _buildDialogFooter(BuildContext context, RoutineState state) { return DialogFooter( onCancel: () => Navigator.pop(context), - onConfirm: () async { + onConfirm: () { if (state.isAutomation) { if (state.isUpdate ?? false) { context.read().add(const UpdateAutomation()); @@ -101,7 +103,6 @@ class SaveRoutineHelper { static Expanded _buildIfConditions(RoutineState state, BuildContext context) { return Expanded( child: ListView( - // crossAxisAlignment: CrossAxisAlignment.start, shrinkWrap: true, children: [ const Text( @@ -193,13 +194,17 @@ class SaveRoutineHelper { runSpacing: 16, spacing: 4, children: functions - .map((f) => Text( - '${f.operationName}: ${f.value}', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.grayColor, fontSize: 8), - overflow: TextOverflow.ellipsis, - maxLines: 3, - )) + .map( + (function) => Text( + '${function.operationName}: ${function.value}', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.grayColor, + fontSize: 8, + ), + overflow: TextOverflow.ellipsis, + maxLines: 3, + ), + ) .toList(), ), ], @@ -219,9 +224,12 @@ class SaveRoutineHelper { spacing: 2, children: [ SizedBox( - width: 8, - height: 8, - child: SvgPicture.asset(Assets.deviceTagIcon)), + width: 8, + height: 8, + child: SvgPicture.asset( + Assets.deviceTagIcon, + ), + ), Text( item['tag'] ?? '', textAlign: TextAlign.center, @@ -242,9 +250,12 @@ class SaveRoutineHelper { spacing: 2, children: [ SizedBox( - width: 8, - height: 8, - child: SvgPicture.asset(Assets.spaceLocationIcon)), + width: 8, + height: 8, + child: SvgPicture.asset( + Assets.spaceLocationIcon, + ), + ), Text( item['subSpace'] ?? '', textAlign: TextAlign.center, From 84264391d9a9bd4d20354926d981cb78a525ce89 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 16:14:26 +0300 Subject: [PATCH 178/238] progress towards matching the design of `save routine dialog`. --- .../routines/helper/save_routine_helper.dart | 133 +++++++++++------- lib/pages/routines/widgets/dialog_footer.dart | 32 +++-- lib/pages/routines/widgets/dialog_header.dart | 1 + 3 files changed, 105 insertions(+), 61 deletions(-) diff --git a/lib/pages/routines/helper/save_routine_helper.dart b/lib/pages/routines/helper/save_routine_helper.dart index e9d870d9..c568c6c1 100644 --- a/lib/pages/routines/helper/save_routine_helper.dart +++ b/lib/pages/routines/helper/save_routine_helper.dart @@ -6,7 +6,6 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; -import 'package:syncrow_web/pages/routines/widgets/dialog_header.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'; @@ -18,6 +17,10 @@ class SaveRoutineHelper { builder: (BuildContext context) { return BlocBuilder( builder: (context, state) { + final selectedConditionLabel = state.selectedAutomationOperator == 'and' + ? 'All Conditions are met' + : 'Any Condition is met'; + return AlertDialog( contentPadding: EdgeInsets.zero, content: Container( @@ -29,22 +32,35 @@ class SaveRoutineHelper { child: Column( mainAxisSize: MainAxisSize.min, children: [ - DialogHeader('Create a scene: ${state.routineName ?? ""}'), + const SizedBox(height: 18), + Text( + 'Create a scene: ${state.routineName ?? ""}', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 18), + _buildDivider(), + _buildListsLabelRow(selectedConditionLabel), Padding( - padding: const EdgeInsets.symmetric( + padding: const EdgeInsetsDirectional.symmetric( horizontal: 16, - vertical: 8, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ _buildIfConditions(state, context), - const SizedBox(width: 16), _buildThenActions(state, context), ], ), ), + _buildDivider(), + const SizedBox(height: 8), _buildDialogFooter(context, state), + const SizedBox(height: 8), ], ), ), @@ -55,63 +71,82 @@ class SaveRoutineHelper { ); } - static DialogFooter _buildDialogFooter(BuildContext context, RoutineState state) { - return DialogFooter( - onCancel: () => Navigator.pop(context), - onConfirm: () { - if (state.isAutomation) { - if (state.isUpdate ?? false) { - context.read().add(const UpdateAutomation()); - } else { - context.read().add(const CreateAutomationEvent()); - } - } else { - if (state.isUpdate ?? false) { - context.read().add(const UpdateScene()); - } else { - context.read().add(const CreateSceneEvent()); - } - } - - Navigator.pop(context); - }, - isConfirmEnabled: true, + static Container _buildDivider() { + return Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, ); } - static Expanded _buildThenActions(RoutineState state, BuildContext context) { - return Expanded( - child: ListView( - shrinkWrap: true, + static Widget _buildListsLabelRow(String selectedConditionLabel) { + const textStyle = TextStyle( + fontSize: 16, + ); + return Container( + color: ColorsManager.backgroundColor.withValues(alpha: 0.5), + padding: const EdgeInsetsDirectional.all(20), + child: Row( + spacing: 16, children: [ - const Text( - 'THEN:', - style: TextStyle( - fontSize: 16, - ), - ), - const SizedBox(height: 8), - ...state.thenItems.map((item) { - final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; - return functionRow(item, context, functions); - }), + Expanded(child: Text('IF: $selectedConditionLabel', style: textStyle)), + const Expanded(child: Text('THEN:', style: textStyle)), ], ), ); } - static Expanded _buildIfConditions(RoutineState state, BuildContext context) { + static Widget _buildDialogFooter(BuildContext context, RoutineState state) { + return Row( + spacing: 16, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + DialogFooterButton( + text: 'Cancel', + onTap: () => Navigator.pop(context), + ), + DialogFooterButton( + text: 'Confirm', + onTap: () { + if (state.isAutomation) { + if (state.isUpdate ?? false) { + context.read().add(const UpdateAutomation()); + } else { + context.read().add(const CreateAutomationEvent()); + } + } else { + if (state.isUpdate ?? false) { + context.read().add(const UpdateScene()); + } else { + context.read().add(const CreateSceneEvent()); + } + } + + Navigator.pop(context); + }, + textColor: ColorsManager.primaryColorWithOpacity, + ), + ], + ); + } + + static Widget _buildThenActions(RoutineState state, BuildContext context) { + return Expanded( + child: ListView( + shrinkWrap: true, + children: state.thenItems.map((item) { + final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + return functionRow(item, context, functions); + }).toList(), + ), + ); + } + + static Widget _buildIfConditions(RoutineState state, BuildContext context) { return Expanded( child: ListView( shrinkWrap: true, children: [ - const Text( - 'IF:', - style: TextStyle( - fontSize: 16, - ), - ), - const SizedBox(height: 8), if (state.isTabToRun) ListTile( leading: SvgPicture.asset( diff --git a/lib/pages/routines/widgets/dialog_footer.dart b/lib/pages/routines/widgets/dialog_footer.dart index e5a548f7..38178ee6 100644 --- a/lib/pages/routines/widgets/dialog_footer.dart +++ b/lib/pages/routines/widgets/dialog_footer.dart @@ -28,32 +28,40 @@ class DialogFooter extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - _buildFooterButton( - context: context, + DialogFooterButton( text: 'Cancel', onTap: onCancel, ), if (isConfirmEnabled) ...[ Container(width: 1, height: 50, color: ColorsManager.greyColor), - _buildFooterButton( - context: context, + DialogFooterButton( text: 'Confirm', onTap: onConfirm, - textColor: - isConfirmEnabled ? ColorsManager.primaryColorWithOpacity : Colors.red, + textColor: isConfirmEnabled + ? ColorsManager.primaryColorWithOpacity + : Colors.red, ), ], ], ), ); } +} - Widget _buildFooterButton({ - required BuildContext context, - required String text, - required VoidCallback? onTap, - Color? textColor, - }) { +class DialogFooterButton extends StatelessWidget { + const DialogFooterButton({ + required this.text, + required this.onTap, + this.textColor, + super.key, + }); + + final String text; + final VoidCallback? onTap; + final Color? textColor; + + @override + Widget build(BuildContext context) { return Expanded( child: TextButton( style: TextButton.styleFrom( diff --git a/lib/pages/routines/widgets/dialog_header.dart b/lib/pages/routines/widgets/dialog_header.dart index 4fe1f0b1..f1f5686a 100644 --- a/lib/pages/routines/widgets/dialog_header.dart +++ b/lib/pages/routines/widgets/dialog_header.dart @@ -16,6 +16,7 @@ class DialogHeader extends StatelessWidget { ), Text( title, + textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: ColorsManager.primaryColorWithOpacity, fontWeight: FontWeight.bold, From e45f57ca03d1649385e13fbf4ab7f46ef7e6f3c6 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 17 Apr 2025 16:55:44 +0300 Subject: [PATCH 179/238] SP-1441 --- lib/pages/routines/widgets/dragable_card.dart | 2 +- lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart | 2 +- .../ceiling_sensor/cps_dialog_slider_selector.dart | 2 +- .../widgets/routine_dialogs/one_gang_switch_dialog.dart | 2 +- .../widgets/routine_dialogs/two_gang_switch_dialog.dart | 2 +- .../routine_dialogs/wall_sensor/wps_value_selector_widget.dart | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/pages/routines/widgets/dragable_card.dart b/lib/pages/routines/widgets/dragable_card.dart index 7ac29375..9853df7c 100644 --- a/lib/pages/routines/widgets/dragable_card.dart +++ b/lib/pages/routines/widgets/dragable_card.dart @@ -225,7 +225,7 @@ class DraggableCard extends StatelessWidget { if (function.functionCode == 'temp_set' || function.functionCode == 'temp_current') { return '${(function.value / 10).toStringAsFixed(0)}°C'; } else if (function.functionCode.contains('countdown')) { - final seconds = function.value.toInt(); + final seconds = function.value?.toInt() ?? 0; if (seconds >= 3600) { final hours = (seconds / 3600).floor(); final remainingMinutes = ((seconds % 3600) / 60).floor(); diff --git a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart index bbaa645a..0bba956a 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart @@ -287,7 +287,7 @@ class ACHelper { functionCode: selectCode, operationName: operationName, condition: conditions[index], - value: selectedFunctionData?.value, + value: selectedFunctionData?.value ?? 0.0, valueDescription: selectedFunctionData?.valueDescription, ), ), diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart index a2d11f79..cd8e4c46 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_dialog_slider_selector.dart @@ -46,7 +46,7 @@ class CpsDialogSliderSelector extends StatelessWidget { functionCode: selectedFunction, operationName: operationName, condition: condition, - value: selectedFunctionData.value, + value: selectedFunctionData.value ?? 0, ), ), ), diff --git a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart index 5bc374a3..4290be37 100644 --- a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -250,7 +250,7 @@ class OneGangSwitchHelper { functionCode: selectCode, operationName: operationName, condition: conditions[index], - value: selectedFunctionData?.value, + value: selectedFunctionData?.value ?? 0.0, valueDescription: selectedFunctionData?.valueDescription, ), ), diff --git a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart index d4c47445..d5e7007c 100644 --- a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart @@ -250,7 +250,7 @@ class TwoGangSwitchHelper { functionCode: selectCode, operationName: operationName, condition: conditions[index], - value: selectedFunctionData?.value, + value: selectedFunctionData?.value ?? 0.0, valueDescription: selectedFunctionData?.valueDescription, ), ), diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart index fcdc991a..b07d38a2 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart @@ -44,7 +44,7 @@ class WpsValueSelectorWidget extends StatelessWidget { functionCode: selectedFunction, operationName: functionData.operationName, condition: condition, - value: functionData.value, + value: functionData.value ?? 0.0, ), ), ), From 065bd33511bccb7e856af928450938546339ca5b Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 21 Apr 2025 10:03:02 +0300 Subject: [PATCH 180/238] SP-1330. --- .../all_spaces/widgets/sidebar_widget.dart | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 198ebc51..6dc8ae9c 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -136,6 +136,17 @@ class _SidebarWidgetState extends State { } Widget _buildCommunityTile(BuildContext context, CommunityModel community) { + final spaces = community.spaces + .where( + (space) => + { + SpaceStatus.deleted, + SpaceStatus.parentDeleted, + }.contains(space.status) == + false, + ) + .map((space) => _buildSpaceTile(space: space, community: community)) + .toList(); return CommunityTile( title: community.name, key: ValueKey(community.uuid), @@ -154,15 +165,7 @@ class _SidebarWidgetState extends State { ); }, onExpansionChanged: (title, expanded) {}, - children: community.spaces - .where( - (space) => { - SpaceStatus.deleted, - SpaceStatus.parentDeleted, - }.contains(space.status), - ) - .map((space) => _buildSpaceTile(space: space, community: community)) - .toList(), + children: spaces, ); } From 62fb8b30975dc4af0e2a62ae6f07c6dc87d4f6fe Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 21 Apr 2025 10:28:44 +0300 Subject: [PATCH 181/238] SP-1441 --- .../widgets/routine_dialogs/ac_dialog.dart | 33 +++++++++---------- .../one_gang_switch_dialog.dart | 4 +-- .../three_gang_switch_dialog.dart | 4 +-- .../two_gang_switch_dialog.dart | 4 +-- .../wps_value_selector_widget.dart | 6 ++-- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart index 0bba956a..68cf857d 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart'; @@ -9,8 +11,6 @@ import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; class ACHelper { static Future?> showACFunctionsDialog({ @@ -74,10 +74,8 @@ class ACHelper { child: _buildFunctionsList( context: context, acFunctions: acFunctions, - onFunctionSelected: - (functionCode, operationName) => context - .read() - .add(SelectFunction( + onFunctionSelected: (functionCode, operationName) => + context.read().add(SelectFunction( functionCode: functionCode, operationName: operationName, )), @@ -191,8 +189,9 @@ class ACHelper { required String operationName, bool? removeComparators, }) { + final initialVal = selectedFunction == 'temp_set' ? 200 : -100; if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { - final initialValue = selectedFunctionData?.value ?? 250; + final initialValue = selectedFunctionData?.value ?? initialVal; return _buildTemperatureSelector( context: context, initialValue: initialValue, @@ -205,8 +204,7 @@ class ACHelper { ); } - final selectedFn = - acFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -287,7 +285,9 @@ class ACHelper { functionCode: selectCode, operationName: operationName, condition: conditions[index], - value: selectedFunctionData?.value ?? 0.0, + value: selectedFunctionData?.value ?? selectCode == 'temp_set' + ? 200 + : -100, valueDescription: selectedFunctionData?.valueDescription, ), ), @@ -302,8 +302,7 @@ class ACHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: - conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -317,6 +316,7 @@ class ACHelper { DeviceFunctionData? selectedFunctionData, String selectCode, ) { + final initialVal = selectCode == 'temp_set' ? 200 : -100; return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), decoration: BoxDecoration( @@ -324,7 +324,7 @@ class ACHelper { borderRadius: BorderRadius.circular(10), ), child: Text( - '${(initialValue ?? 200) / 10}°C', + '${(initialValue ?? initialVal) / 10}°C', style: context.textTheme.headlineMedium!.copyWith( color: ColorsManager.primaryColorWithOpacity, ), @@ -397,9 +397,7 @@ class ACHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, size: 24, color: isSelected ? ColorsManager.primaryColorWithOpacity @@ -415,8 +413,7 @@ class ACHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: - selectedFunctionData?.valueDescription, + valueDescription: selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart index 4290be37..e56da833 100644 --- a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -164,7 +164,7 @@ class OneGangSwitchHelper { required bool removeComparetors, }) { if (selectedFunction == 'countdown_1') { - final initialValue = selectedFunctionData?.value ?? 200; + final initialValue = selectedFunctionData?.value ?? 0; return _buildCountDownSelector( context: context, initialValue: initialValue, @@ -250,7 +250,7 @@ class OneGangSwitchHelper { functionCode: selectCode, operationName: operationName, condition: conditions[index], - value: selectedFunctionData?.value ?? 0.0, + value: selectedFunctionData?.value ?? 0, valueDescription: selectedFunctionData?.valueDescription, ), ), diff --git a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart index f1cbd6aa..ab62a52c 100644 --- a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -170,7 +170,7 @@ class ThreeGangSwitchHelper { if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2' || selectedFunction == 'countdown_3') { - final initialValue = selectedFunctionData?.value ?? 200; + final initialValue = selectedFunctionData?.value ?? 0; return _buildTemperatureSelector( context: context, initialValue: initialValue, @@ -251,7 +251,7 @@ class ThreeGangSwitchHelper { functionCode: selectCode, operationName: operationName, condition: conditions[index], - value: selectedFunctionData?.value, + value: selectedFunctionData?.value ?? 0, valueDescription: selectedFunctionData?.valueDescription, ), ), diff --git a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart index d5e7007c..d6715599 100644 --- a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart @@ -169,7 +169,7 @@ class TwoGangSwitchHelper { }) { if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') { - final initialValue = selectedFunctionData?.value ?? 200; + final initialValue = selectedFunctionData?.value ?? 0; return _buildTemperatureSelector( context: context, initialValue: initialValue, @@ -250,7 +250,7 @@ class TwoGangSwitchHelper { functionCode: selectCode, operationName: operationName, condition: conditions[index], - value: selectedFunctionData?.value ?? 0.0, + value: selectedFunctionData?.value ?? 0, valueDescription: selectedFunctionData?.valueDescription, ), ), diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart index b07d38a2..30232846 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart @@ -36,7 +36,7 @@ class WpsValueSelectorWidget extends StatelessWidget { dialogType: dialogType, sliderRange: sliderRange, displayedValue: getDisplayText, - initialValue: functionData.value ?? 250, + initialValue: functionData.value ?? 0.0, onConditionChanged: (condition) => context.read().add( AddFunction( functionData: DeviceFunctionData( @@ -44,7 +44,7 @@ class WpsValueSelectorWidget extends StatelessWidget { functionCode: selectedFunction, operationName: functionData.operationName, condition: condition, - value: functionData.value ?? 0.0, + value: functionData.value ?? 0, ), ), ), @@ -87,7 +87,7 @@ class WpsValueSelectorWidget extends StatelessWidget { final intValue = int.tryParse('${functionData.value ?? ''}'); return switch (functionData.functionCode) { 'presence_time' => '${intValue ?? '0'}', - 'dis_current' => '${intValue ?? '250'}', + 'dis_current' => '${intValue ?? '0'}', 'illuminance_value' => '${intValue ?? '0'}', _ => '$intValue', }; From cf20bdcd4289fa28d439ba566ff39cbe5c27a507 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 21 Apr 2025 10:57:46 +0300 Subject: [PATCH 182/238] SP-1440 --- .../routines/helper/save_routine_helper.dart | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/lib/pages/routines/helper/save_routine_helper.dart b/lib/pages/routines/helper/save_routine_helper.dart index c568c6c1..bc4764e3 100644 --- a/lib/pages/routines/helper/save_routine_helper.dart +++ b/lib/pages/routines/helper/save_routine_helper.dart @@ -14,7 +14,7 @@ class SaveRoutineHelper { static Future showSaveRoutineDialog(BuildContext context) async { return showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return BlocBuilder( builder: (context, state) { final selectedConditionLabel = state.selectedAutomationOperator == 'and' @@ -24,7 +24,8 @@ class SaveRoutineHelper { return AlertDialog( contentPadding: EdgeInsets.zero, content: Container( - width: MediaQuery.sizeOf(context).width * 0.5, + width: context.screenWidth * 0.5, + height: 500, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), @@ -44,17 +45,24 @@ class SaveRoutineHelper { const SizedBox(height: 18), _buildDivider(), _buildListsLabelRow(selectedConditionLabel), - Padding( - padding: const EdgeInsetsDirectional.symmetric( - horizontal: 16, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - _buildIfConditions(state, context), - _buildThenActions(state, context), - ], + Expanded( + child: Padding( + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 16, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + spacing: 24, + children: [ + _buildIfConditions(state, context), + Container( + width: 1, + color: ColorsManager.greyColor, + ), + _buildThenActions(state, context), + ], + ), ), ), _buildDivider(), @@ -133,7 +141,7 @@ class SaveRoutineHelper { static Widget _buildThenActions(RoutineState state, BuildContext context) { return Expanded( child: ListView( - shrinkWrap: true, + // shrinkWrap: true, children: state.thenItems.map((item) { final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; return functionRow(item, context, functions); @@ -145,7 +153,7 @@ class SaveRoutineHelper { static Widget _buildIfConditions(RoutineState state, BuildContext context) { return Expanded( child: ListView( - shrinkWrap: true, + // shrinkWrap: true, children: [ if (state.isTabToRun) ListTile( From 67a164e6d244f06917eecfe1926ec9226b7e9988 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 21 Apr 2025 10:59:09 +0300 Subject: [PATCH 183/238] modified divider color to match figma design. --- lib/pages/routines/helper/save_routine_helper.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/routines/helper/save_routine_helper.dart b/lib/pages/routines/helper/save_routine_helper.dart index bc4764e3..23920ba6 100644 --- a/lib/pages/routines/helper/save_routine_helper.dart +++ b/lib/pages/routines/helper/save_routine_helper.dart @@ -58,7 +58,7 @@ class SaveRoutineHelper { _buildIfConditions(state, context), Container( width: 1, - color: ColorsManager.greyColor, + color: ColorsManager.greyColor.withValues(alpha: 0.8), ), _buildThenActions(state, context), ], From d8afb562ebe436ff6d6605e4ab9356ba74e26553 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 21 Apr 2025 12:07:21 +0300 Subject: [PATCH 184/238] SP-1447, set the limit of gangs countdown to be 12 hours instead of 24 hours. --- .../widgets/routine_dialogs/one_gang_switch_dialog.dart | 3 ++- .../widgets/routine_dialogs/three_gang_switch_dialog.dart | 3 ++- .../widgets/routine_dialogs/two_gang_switch_dialog.dart | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart index e56da833..51a99f3b 100644 --- a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -301,12 +301,13 @@ class OneGangSwitchHelper { DeviceFunctionData? selectedFunctionData, String selectCode, ) { + const twelveHoursInSeconds = 43200.0; final operationalValues = SwitchOperationalValue( icon: '', description: "sec", value: 0.0, minValue: 0, - maxValue: 86400, + maxValue: twelveHoursInSeconds, stepValue: 1, ); return Slider( diff --git a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart index ab62a52c..a83fc4a0 100644 --- a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -303,12 +303,13 @@ class ThreeGangSwitchHelper { DeviceFunctionData? selectedFunctionData, String selectCode, ) { + const twelveHoursInSeconds = 43200.0; final operationalValues = SwitchOperationalValue( icon: '', description: "sec", value: 0.0, minValue: 0, - maxValue: 86400, + maxValue: twelveHoursInSeconds, stepValue: 1, ); return Slider( diff --git a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart index d6715599..2bb6b6e5 100644 --- a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart @@ -302,12 +302,13 @@ class TwoGangSwitchHelper { DeviceFunctionData? selectedFunctionData, String selectCode, ) { + const twelveHoursInSeconds = 43200.0; final operationalValues = SwitchOperationalValue( icon: '', description: "sec", value: 0.0, minValue: 0, - maxValue: 86400, + maxValue: twelveHoursInSeconds, stepValue: 1, ); return Slider( From b4ef22ef0a56b6dd68cc94d750903c0b367f9609 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 21 Apr 2025 14:23:40 +0300 Subject: [PATCH 185/238] SP-702/ clears user data on logging out. --- lib/pages/auth/bloc/auth_bloc.dart | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index 9e0ac2f9..35663557 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/auth/model/region_model.dart'; import 'package:syncrow_web/pages/auth/model/token.dart'; import 'package:syncrow_web/pages/auth/model/user_model.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/pages/home/bloc/home_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'; import 'package:syncrow_web/services/auth_api.dart'; @@ -432,9 +433,13 @@ class AuthBloc extends Bloc { } static Future logout(BuildContext context) async { - final storage = FlutterSecureStorage(); - ProjectManager.clearProjectUUID(); + const storage = FlutterSecureStorage(); context.read().add(ClearAllData()); - storage.deleteAll(); + user = null; + context.read().user = null; + await Future.wait([ + ProjectManager.clearProjectUUID(), + storage.deleteAll(), + ]); } } From b90179107913acf7cf83cb2c8dfaf5d2016089c3 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 22 Apr 2025 02:33:50 +0300 Subject: [PATCH 186/238] Update Flutter run and build commands, and added main_staging file --- ...e-static-web-apps-mango-bush-01e607f10.yml | 2 +- ...static-web-apps-polite-smoke-017c65c10.yml | 2 +- .vscode/launch.json | 39 +++++---- README.md | 9 +- lib/main_staging.dart | 86 +++++++++++++++++++ .../space_tree/view/space_tree_view.dart | 82 ++++++++---------- 6 files changed, 157 insertions(+), 63 deletions(-) create mode 100644 lib/main_staging.dart diff --git a/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml b/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml index f0379c95..cd1168fc 100644 --- a/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml +++ b/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml @@ -31,7 +31,7 @@ jobs: run: flutter pub get - name: Build Flutter Web App - run: flutter build web --release --dart-define=FLAVOR=production + run: flutter build web --web-renderer canvaskit --flavor production -t lib/main.dart - name: Build And Deploy id: builddeploy diff --git a/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml b/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml index 28cf00a2..1edcb974 100644 --- a/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml +++ b/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml @@ -31,7 +31,7 @@ jobs: run: flutter pub get - name: Build Flutter Web App - run: flutter build web --release --dart-define=FLAVOR=development + run: flutter build web --web-renderer canvaskit --flavor development -t lib/main_dev.dart - name: Build And Deploy id: builddeploy diff --git a/.vscode/launch.json b/.vscode/launch.json index b6f83bdc..ab5f8f0e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,11 +10,14 @@ "type": "dart", "args": [ - - "--dart-define", - - "FLAVOR=development" - + "-d", + "chrome", + "--web-port", + "3000", + "--flavor", + "development", + "-t", + "lib/main_dev.dart", ], "flutterMode": "debug" @@ -28,11 +31,14 @@ "type": "dart", "args": [ - - "--dart-define", - - "FLAVOR=staging" - + "-d", + "chrome", + "--web-port", + "3000", + "--flavor", + "staging", + "-t", + "lib/main_staging.dart", ], "flutterMode": "debug" @@ -46,11 +52,14 @@ "type": "dart", "args": [ - - "--dart-define", - - "FLAVOR=production" - + "-d", + "chrome", + "--web-port", + "3000", + "--flavor", + "production", + "-t", + "lib/main_staging.dart", ], "flutterMode": "debug" diff --git a/README.md b/README.md index 745fe6f0..6a3cc51b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,12 @@ samples, guidance on mobile development, and a full API reference. ## USEFUL COMMANDS -Run on chrome: flutter run -d chrome --dart-define=FLAVOR='ENV_NAME' +- Building for the Web + - CanvasKit + - `flutter build web --web-renderer canvaskit --flavor development -t lib/main_dev.dart --output=build/web_dev` - build for DEVELOPMENT. + - `flutter build web --web-renderer canvaskit --flavor staging -t lib/main_staging.dart --output=build/web_stg` - build for STAGING. + - `flutter build web --web-renderer canvaskit --flavor production -t lib/main.dart --output=build/web` - build for PRODUCTION. + +- run command: `flutter run -d chrome --flavor development --target=lib/main_dev.dart` -Build: flutter build web --release --dart-define=FLAVOR='ENV_NAME' diff --git a/lib/main_staging.dart b/lib/main_staging.dart new file mode 100644 index 00000000..b0505c85 --- /dev/null +++ b/lib/main_staging.dart @@ -0,0 +1,86 @@ +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:go_router/go_router.dart'; +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'; +import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; +import 'package:syncrow_web/services/locator.dart'; +import 'package:syncrow_web/utils/app_routes.dart'; +import 'package:syncrow_web/utils/constants/routes_const.dart'; +import 'package:syncrow_web/utils/theme/theme.dart'; + +Future main() async { + try { + const environment = String.fromEnvironment('FLAVOR', defaultValue: 'staging'); + await dotenv.load(fileName: '.env.$environment'); + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptionsDev.currentPlatform, + ); + initialSetup(); + } catch (_) {} + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + MyApp({ + super.key, + }); + + final GoRouter _router = GoRouter( + initialLocation: RoutesConst.auth, + routes: AppRoutes.getRoutes(), + redirect: (context, state) async { + String checkToken = await AuthBloc.getTokenAndValidate(); + final loggedIn = checkToken == 'Success'; + final goingToLogin = state.uri.toString() == RoutesConst.auth; + + if (!loggedIn && !goingToLogin) return RoutesConst.auth; + if (loggedIn && goingToLogin) return RoutesConst.home; + + return null; + }, + ); + + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => CreateRoutineBloc(), + ), + BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())), + BlocProvider( + create: (context) => VisitorPasswordBloc(), + ), + BlocProvider( + create: (context) => RoutineBloc(), + ), + BlocProvider( + create: (context) => SpaceTreeBloc()..add(InitialEvent()), + ), + ], + child: MaterialApp.router( + debugShowCheckedModeBanner: false, + scrollBehavior: const MaterialScrollBehavior().copyWith( + dragDevices: { + PointerDeviceKind.mouse, + PointerDeviceKind.touch, + PointerDeviceKind.stylus, + PointerDeviceKind.unknown, + }, + ), + theme: myTheme, + routerConfig: _router, + )); + } +} diff --git a/lib/pages/space_tree/view/space_tree_view.dart b/lib/pages/space_tree/view/space_tree_view.dart index 3d5d00bf..bfe3a2bd 100644 --- a/lib/pages/space_tree/view/space_tree_view.dart +++ b/lib/pages/space_tree/view/space_tree_view.dart @@ -41,14 +41,12 @@ class _SpaceTreeViewState extends State { @override Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { - final communities = state.searchQuery.isNotEmpty - ? state.filteredCommunity - : state.communityList; + final communities = + state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList; return Container( height: MediaQuery.sizeOf(context).height, decoration: widget.isSide == true - ? subSectionContainerDecoration.copyWith( - color: ColorsManager.whiteColors) + ? subSectionContainerDecoration.copyWith(color: ColorsManager.whiteColors) : const BoxDecoration(color: ColorsManager.whiteColors), child: state is SpaceTreeLoadingState ? const Center(child: CircularProgressIndicator()) @@ -81,12 +79,10 @@ class _SpaceTreeViewState extends State { style: context.textTheme.bodyMedium?.copyWith( color: ColorsManager.blackColor, ), - onChanged: (value) => - context.read().add( - SearchQueryEvent(value), - ), - decoration: - textBoxDecoration(radios: 20)?.copyWith( + onChanged: (value) => context.read().add( + SearchQueryEvent(value), + ), + decoration: textBoxDecoration(radios: 20)?.copyWith( fillColor: Colors.white, suffixIcon: Padding( padding: const EdgeInsets.only(right: 16), @@ -96,8 +92,7 @@ class _SpaceTreeViewState extends State { height: 24, ), ), - hintStyle: - context.textTheme.bodyMedium?.copyWith( + hintStyle: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w400, fontSize: 12, color: ColorsManager.textGray, @@ -121,27 +116,30 @@ class _SpaceTreeViewState extends State { child: state.isSearching ? const Center(child: CircularProgressIndicator()) : SidebarCommunitiesList( - onScrollToEnd: () => context.read().add( - PaginationEvent( - state.paginationModel, - state.communityList, - ), - ), + onScrollToEnd: () { + if (!state.paginationIsLoading) { + context.read().add( + PaginationEvent( + state.paginationModel, + state.communityList, + ), + ); + } + }, scrollController: _scrollController, communities: communities, itemBuilder: (context, index) { return CustomExpansionTileSpaceTree( title: communities[index].name, - isSelected: state.selectedCommunities - .contains(communities[index].uuid), - isSoldCheck: state.selectedCommunities - .contains(communities[index].uuid), - onExpansionChanged: () => - context.read().add( - OnCommunityExpanded( - communities[index].uuid, - ), - ), + isSelected: + state.selectedCommunities.contains(communities[index].uuid), + isSoldCheck: + state.selectedCommunities.contains(communities[index].uuid), + onExpansionChanged: () => context.read().add( + OnCommunityExpanded( + communities[index].uuid, + ), + ), isExpanded: state.expandedCommunities.contains( communities[index].uuid, ), @@ -158,8 +156,7 @@ class _SpaceTreeViewState extends State { (space) { return CustomExpansionTileSpaceTree( title: space.name, - isExpanded: - state.expandedSpaces.contains(space.uuid), + isExpanded: state.expandedSpaces.contains(space.uuid), onItemSelected: () { context.read().add( OnSpaceSelected( @@ -170,18 +167,15 @@ class _SpaceTreeViewState extends State { ); widget.onSelect(); }, - onExpansionChanged: () => - context.read().add( - OnSpaceExpanded( - communities[index].uuid, - space.uuid ?? '', - ), - ), - isSelected: state.selectedSpaces - .contains(space.uuid) || - state.soldCheck.contains(space.uuid), - isSoldCheck: + onExpansionChanged: () => context.read().add( + OnSpaceExpanded( + communities[index].uuid, + space.uuid ?? '', + ), + ), + isSelected: state.selectedSpaces.contains(space.uuid) || state.soldCheck.contains(space.uuid), + isSoldCheck: state.soldCheck.contains(space.uuid), children: _buildNestedSpaces( context, state, @@ -210,8 +204,8 @@ class _SpaceTreeViewState extends State { ) { return space.children.map((child) { return CustomExpansionTileSpaceTree( - isSelected: state.selectedSpaces.contains(child.uuid) || - state.soldCheck.contains(child.uuid), + isSelected: + state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid), isSoldCheck: state.soldCheck.contains(child.uuid), title: child.name, isExpanded: state.expandedSpaces.contains(child.uuid), From d7a37c65190837f95bdde1547cf0324b49649792 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 22 Apr 2025 03:01:43 +0300 Subject: [PATCH 187/238] Removed flavor from Flutter run and build commands --- .../azure-static-web-apps-mango-bush-01e607f10.yml | 2 +- .../azure-static-web-apps-polite-smoke-017c65c10.yml | 2 +- .vscode/launch.json | 8 +------- README.md | 8 ++++---- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml b/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml index cd1168fc..db94e74f 100644 --- a/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml +++ b/.github/workflows/azure-static-web-apps-mango-bush-01e607f10.yml @@ -31,7 +31,7 @@ jobs: run: flutter pub get - name: Build Flutter Web App - run: flutter build web --web-renderer canvaskit --flavor production -t lib/main.dart + run: flutter build web --web-renderer canvaskit -t lib/main.dart - name: Build And Deploy id: builddeploy diff --git a/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml b/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml index 1edcb974..738bd279 100644 --- a/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml +++ b/.github/workflows/azure-static-web-apps-polite-smoke-017c65c10.yml @@ -31,7 +31,7 @@ jobs: run: flutter pub get - name: Build Flutter Web App - run: flutter build web --web-renderer canvaskit --flavor development -t lib/main_dev.dart + run: flutter build web --web-renderer canvaskit -t lib/main_dev.dart - name: Build And Deploy id: builddeploy diff --git a/.vscode/launch.json b/.vscode/launch.json index ab5f8f0e..f81a9deb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,8 +14,6 @@ "chrome", "--web-port", "3000", - "--flavor", - "development", "-t", "lib/main_dev.dart", ], @@ -35,8 +33,6 @@ "chrome", "--web-port", "3000", - "--flavor", - "staging", "-t", "lib/main_staging.dart", ], @@ -56,10 +52,8 @@ "chrome", "--web-port", "3000", - "--flavor", - "production", "-t", - "lib/main_staging.dart", + "lib/main.dart", ], "flutterMode": "debug" diff --git a/README.md b/README.md index 6a3cc51b..91409775 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ samples, guidance on mobile development, and a full API reference. - Building for the Web - CanvasKit - - `flutter build web --web-renderer canvaskit --flavor development -t lib/main_dev.dart --output=build/web_dev` - build for DEVELOPMENT. - - `flutter build web --web-renderer canvaskit --flavor staging -t lib/main_staging.dart --output=build/web_stg` - build for STAGING. - - `flutter build web --web-renderer canvaskit --flavor production -t lib/main.dart --output=build/web` - build for PRODUCTION. + - `flutter build web --web-renderer canvaskit -t lib/main_dev.dart --output=build/web_dev` - build for DEVELOPMENT. + - `flutter build web --web-renderer canvaskit -t lib/main_staging.dart --output=build/web_stg` - build for STAGING. + - `flutter build web --web-renderer canvaskit -t lib/main.dart --output=build/web` - build for PRODUCTION. -- run command: `flutter run -d chrome --flavor development --target=lib/main_dev.dart` +- run command: `flutter run -d chrome --target=lib/main_dev.dart` From 177a4711fe52ca557f9abd593352d886e7426f44 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 22 Apr 2025 03:39:27 +0300 Subject: [PATCH 188/238] Synced main_dev and main_staging with main.dart --- lib/main_dev.dart | 15 +++++++-------- lib/main_staging.dart | 11 ++++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/main_dev.dart b/lib/main_dev.dart index a42d5d07..578b2c30 100644 --- a/lib/main_dev.dart +++ b/lib/main_dev.dart @@ -16,12 +16,12 @@ import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.da import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/app_routes.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart'; +import 'package:syncrow_web/utils/navigation_service.dart'; import 'package:syncrow_web/utils/theme/theme.dart'; Future main() async { try { - const environment = - String.fromEnvironment('FLAVOR', defaultValue: 'development'); + const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development'); await dotenv.load(fileName: '.env.$environment'); WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( @@ -33,9 +33,7 @@ Future main() async { } class MyApp extends StatelessWidget { - MyApp({ - super.key, - }); + MyApp({super.key}); final GoRouter _router = GoRouter( initialLocation: RoutesConst.auth, @@ -56,11 +54,10 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider( + BlocProvider( create: (context) => CreateRoutineBloc(), ), - BlocProvider( - create: (context) => HomeBloc()..add(const FetchUserInfo())), + BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())), BlocProvider( create: (context) => VisitorPasswordBloc(), ), @@ -81,6 +78,8 @@ class MyApp extends StatelessWidget { PointerDeviceKind.unknown, }, ), + key: NavigationService.navigatorKey, + // scaffoldMessengerKey: NavigationService.snackbarKey, theme: myTheme, routerConfig: _router, )); diff --git a/lib/main_staging.dart b/lib/main_staging.dart index b0505c85..e7f95c57 100644 --- a/lib/main_staging.dart +++ b/lib/main_staging.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:go_router/go_router.dart'; -import 'package:syncrow_web/firebase_options_dev.dart'; +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'; @@ -16,6 +16,7 @@ import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.da import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/app_routes.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart'; +import 'package:syncrow_web/utils/navigation_service.dart'; import 'package:syncrow_web/utils/theme/theme.dart'; Future main() async { @@ -24,7 +25,7 @@ Future main() async { await dotenv.load(fileName: '.env.$environment'); WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( - options: DefaultFirebaseOptionsDev.currentPlatform, + options: DefaultFirebaseOptionsStaging.currentPlatform, ); initialSetup(); } catch (_) {} @@ -32,9 +33,7 @@ Future main() async { } class MyApp extends StatelessWidget { - MyApp({ - super.key, - }); + MyApp({super.key}); final GoRouter _router = GoRouter( initialLocation: RoutesConst.auth, @@ -79,6 +78,8 @@ class MyApp extends StatelessWidget { PointerDeviceKind.unknown, }, ), + key: NavigationService.navigatorKey, + // scaffoldMessengerKey: NavigationService.snackbarKey, theme: myTheme, routerConfig: _router, )); From 05b31805107c29ce27ede2af21b5921718d09f64 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:22:41 +0300 Subject: [PATCH 189/238] Created `ControlDeviceService` interface and its remote and debounced implementation. --- lib/services/control_device_service.dart | 80 ++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 lib/services/control_device_service.dart diff --git a/lib/services/control_device_service.dart b/lib/services/control_device_service.dart new file mode 100644 index 00000000..b115990d --- /dev/null +++ b/lib/services/control_device_service.dart @@ -0,0 +1,80 @@ +import 'dart:developer'; + +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +abstract interface class ControlDeviceService { + Future controlDevice({ + required String deviceUuid, + required Status status, + }); +} + +final class RemoteControlDeviceService implements ControlDeviceService { + @override + Future controlDevice({ + required String deviceUuid, + required Status status, + }) async { + try { + final response = await HTTPService().post( + path: ApiEndpoints.deviceControl.replaceAll('{uuid}', deviceUuid), + body: status.toMap(), + showServerMessage: true, + expectedResponseModel: (json) { + return (json['success'] as bool?) ?? false; + }, + ); + return response; + } catch (e) { + log('Error fetching $e', name: 'ControlDeviceService'); + return false; + } + } +} + +final class DebouncedControlDeviceService implements ControlDeviceService { + final ControlDeviceService decoratee; + final Duration _debounceDuration; + final List<(String deviceUuid, Status status)> _pendingRequests = []; + bool _isProcessing = false; + + DebouncedControlDeviceService({ + required this.decoratee, + Duration debounceDuration = const Duration(milliseconds: 1500), + }) : _debounceDuration = debounceDuration; + + @override + Future controlDevice({ + required String deviceUuid, + required Status status, + }) async { + _pendingRequests.add((deviceUuid, status)); + + if (_isProcessing) { + log( + 'Request added to queue', + name: 'DebouncedControlDeviceService', + ); + return false; + } + + _isProcessing = true; + + await Future.delayed(_debounceDuration); + + final lastRequest = _pendingRequests.last; + _pendingRequests.clear(); + + try { + final ( lastRequestDeviceUuid, lastRequestStatus) = lastRequest; + return decoratee.controlDevice( + deviceUuid: lastRequestDeviceUuid, + status: lastRequestStatus, + ); + } finally { + _isProcessing = false; + } + } +} From 8c637e40ff7c3325ee18547d736842f7a932fabe Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:23:00 +0300 Subject: [PATCH 190/238] Created `FlushMountedPresenceSensorModel` model. --- .../flush_mounted_presence_sensor_model.dart | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 lib/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart new file mode 100644 index 00000000..975af0e8 --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart @@ -0,0 +1,99 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class FlushMountedPresenceSensorModel { + FlushMountedPresenceSensorModel({ + required this.presenceState, + required this.farDetection, + required this.illuminance, + required this.sensitivity, + required this.occurDistReduce, + required this.noneDelay, + required this.presenceDelay, + required this.nearDetection, + required this.sensiReduce, + required this.checkingResult, + }); + + static const String codePresenceState = 'presence_state'; + static const String codeSensitivity = 'sensitivity'; + static const String codeNearDetection = 'near_detection'; + static const String codeFarDetection = 'far_detection'; + static const String codeCheckingResult = 'checking_result'; + static const String codePresenceDelay = 'presence_delay'; + static const String codeNoneDelay = 'none_delay'; + static const String codeOccurDistReduce = 'occur_dist_reduce'; + static const String codeIlluminance = 'illum_value'; + static const String codeSensiReduce = 'sensi_reduce'; + + String presenceState; + int sensitivity; + int nearDetection; + int farDetection; + String checkingResult; + int presenceDelay; + int noneDelay; + int occurDistReduce; + int illuminance; + int sensiReduce; + + factory FlushMountedPresenceSensorModel.fromJson(List jsonList) { + String presenceState = 'none'; + int sensitivity = 0; + int nearDetection = 0; + int farDetection = 0; + String checkingResult = 'none'; + int presenceDelay = 0; + int noneDelay = 0; + int occurDistReduce = 0; + int illuminance = 0; + int sensiReduce = 0; + + for (var status in jsonList) { + switch (status.code) { + case codePresenceState: + presenceState = status.value ?? 'presence'; + break; + case codeSensitivity: + sensitivity = status.value ?? 0; + break; + case codeNearDetection: + nearDetection = status.value ?? 0; + break; + case codeFarDetection: + farDetection = status.value ?? 0; + break; + case codeCheckingResult: + checkingResult = status.value ?? 'check_success'; + break; + case codePresenceDelay: + presenceDelay = status.value ?? 0; + break; + case codeNoneDelay: + noneDelay = status.value ?? 0; + break; + case codeOccurDistReduce: + occurDistReduce = status.value ?? 0; + break; + case codeIlluminance: + illuminance = status.value ?? 0; + break; + case codeSensiReduce: + sensiReduce = status.value ?? 0; + break; + } + } + + return FlushMountedPresenceSensorModel( + presenceState: presenceState, + sensitivity: sensitivity, + nearDetection: nearDetection, + farDetection: farDetection, + checkingResult: checkingResult, + presenceDelay: presenceDelay, + noneDelay: noneDelay, + occurDistReduce: occurDistReduce, + illuminance: illuminance, + sensiReduce: sensiReduce, + ); + } +} From 367d6717e72780173f8be30a321e35867b0d69b7 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:23:28 +0300 Subject: [PATCH 191/238] Refactor `PresenceUpdateData` widget to support decimal values. --- .../shared/sensors_widgets/presence_update_data.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart b/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart index 16596a1f..4b4d5562 100644 --- a/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart +++ b/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart @@ -13,6 +13,7 @@ class PresenceUpdateData extends StatefulWidget { required this.maxValue, required this.steps, this.description, + this.valuesPercision = 0, }); final String title; @@ -22,6 +23,7 @@ class PresenceUpdateData extends StatefulWidget { final double steps; final Function action; final String? description; + final int valuesPercision; @override State createState() => _CurrentTempState(); @@ -45,7 +47,7 @@ class _CurrentTempState extends State { } void _onValueChanged(double newValue) { - widget.action(newValue.toInt()); + widget.action(newValue); } @override @@ -66,7 +68,7 @@ class _CurrentTempState extends State { color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 10), ), IncrementDecrementWidget( - value: widget.value.toString(), + value: widget.value.toStringAsFixed(widget.valuesPercision), description: widget.description ?? '', descriptionColor: ColorsManager.blackColor, onIncrement: () { From 1975a1b6f466349af147330c0ccaee6029f79cb8 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:24:14 +0300 Subject: [PATCH 192/238] created `FlushMountedPresenceSensorBloc`, events, and states for device management. --- .../flush_mounted_presence_sensor_bloc.dart | 266 ++++++++++++++++++ .../flush_mounted_presence_sensor_event.dart | 84 ++++++ .../flush_mounted_presence_sensor_state.dart | 76 +++++ 3 files changed, 426 insertions(+) create mode 100644 lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart create mode 100644 lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart create mode 100644 lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_state.dart diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart new file mode 100644 index 00000000..06be2666 --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart @@ -0,0 +1,266 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +part 'flush_mounted_presence_sensor_event.dart'; +part 'flush_mounted_presence_sensor_state.dart'; + +class FlushMountedPresenceSensorBloc + extends Bloc { + final String deviceId; + late FlushMountedPresenceSensorModel deviceStatus; + + final ControlDeviceService controlDeviceService; + + FlushMountedPresenceSensorBloc({ + required this.deviceId, + required this.controlDeviceService, + }) : super(FlushMountedPresenceSensorInitialState()) { + on( + _onFlushMountedPresenceSensorFetchStatusEvent, + ); + on( + _onFlushMountedPresenceSensorFetchBatchStatusEvent); + on( + _onFlushMountedPresenceSensorChangeValueEvent, + ); + on( + _onFlushMountedPresenceSensorBatchControlEvent, + ); + on( + _onFlushMountedPresenceSensorGetDeviceReportsEvent); + on( + _onFlushMountedPresenceSensorShowDescriptionEvent, + ); + on( + _onFlushMountedPresenceSensorBackToGridViewEvent, + ); + on( + _onFlushMountedPresenceSensorFactoryResetEvent, + ); + } + + void _onFlushMountedPresenceSensorFetchStatusEvent( + FlushMountedPresenceSensorFetchStatusEvent event, + Emitter emit, + ) async { + emit(FlushMountedPresenceSensorLoadingInitialState()); + try { + final response = await DevicesManagementApi().getDeviceStatus(deviceId); + deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status); + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + _listenToChanges(emit, deviceId); + } catch (e) { + emit(FlushMountedPresenceSensorFailedState(error: e.toString())); + return; + } + } + + Future _onFlushMountedPresenceSensorFetchBatchStatusEvent( + FlushMountedPresenceSensorFetchBatchStatusEvent event, + Emitter emit, + ) async { + emit(FlushMountedPresenceSensorLoadingInitialState()); + try { + var response = await DevicesManagementApi().getBatchStatus(event.devicesIds); + deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status); + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + } catch (e) { + emit(FlushMountedPresenceSensorFailedState(error: e.toString())); + } + } + + void _listenToChanges( + Emitter emit, + String deviceId, + ) { + try { + final ref = FirebaseDatabase.instance.ref( + 'device-status/$deviceId', + ); + + ref.onValue.listen((DatabaseEvent event) { + Map usersMap = + event.snapshot.value as Map; + List statusList = []; + + usersMap['status'].forEach((element) { + statusList.add(Status(code: element['code'], value: element['value'])); + }); + + deviceStatus = FlushMountedPresenceSensorModel.fromJson(statusList); + emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); + }); + } catch (_) {} + } + + void _onFlushMountedPresenceSensorChangeValueEvent( + FlushMountedPresenceSensorChangeValueEvent event, + Emitter emit, + ) async { + emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); + switch (event.code) { + case FlushMountedPresenceSensorModel.codeFarDetection: + deviceStatus.farDetection = event.value; + break; + case FlushMountedPresenceSensorModel.codeSensitivity: + log('updated sensitivity: ${deviceStatus.sensitivity}-${event.value}'); + deviceStatus.sensitivity = event.value; + break; + case FlushMountedPresenceSensorModel.codeNoneDelay: + log('updated none delay: ${deviceStatus.noneDelay}-${event.value}'); + deviceStatus.noneDelay = event.value; + break; + case FlushMountedPresenceSensorModel.codePresenceDelay: + deviceStatus.presenceDelay = event.value; + break; + case FlushMountedPresenceSensorModel.codeNearDetection: + deviceStatus.nearDetection = event.value; + break; + case FlushMountedPresenceSensorModel.codeOccurDistReduce: + deviceStatus.occurDistReduce = event.value; + break; + case FlushMountedPresenceSensorModel.codeSensiReduce: + deviceStatus.sensiReduce = event.value; + break; + default: + break; + } + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + await _runDeBouncer( + deviceId: deviceId, + code: event.code, + value: event.value, + isBatch: false, + emit: emit, + ); + } + + Future _onFlushMountedPresenceSensorBatchControlEvent( + FlushMountedPresenceSensorBatchControlEvent event, + Emitter emit, + ) async { + emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); + switch (event.code) { + case FlushMountedPresenceSensorModel.codeFarDetection: + deviceStatus.farDetection = event.value; + break; + case FlushMountedPresenceSensorModel.codeSensitivity: + deviceStatus.sensitivity = event.value; + break; + case FlushMountedPresenceSensorModel.codeNoneDelay: + deviceStatus.noneDelay = event.value; + break; + case FlushMountedPresenceSensorModel.codePresenceDelay: + deviceStatus.presenceDelay = event.value; + break; + case FlushMountedPresenceSensorModel.codeNearDetection: + deviceStatus.nearDetection = event.value; + break; + case FlushMountedPresenceSensorModel.codeOccurDistReduce: + deviceStatus.occurDistReduce = event.value; + break; + case FlushMountedPresenceSensorModel.codeSensiReduce: + deviceStatus.sensiReduce = event.value; + break; + default: + break; + } + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + await _runDeBouncer( + deviceId: event.deviceIds, + code: event.code, + value: event.value, + emit: emit, + isBatch: true, + ); + } + + Future _runDeBouncer({ + required dynamic deviceId, + required String code, + required dynamic value, + required Emitter emit, + required bool isBatch, + }) async { + try { + if (isBatch) { + await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + } else { + await controlDeviceService.controlDevice( + deviceUuid: deviceId, + status: Status(code: code, value: value), + ); + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(FlushMountedPresenceSensorFetchStatusEvent()); + } + } + + Future _onFlushMountedPresenceSensorGetDeviceReportsEvent( + FlushMountedPresenceSensorGetDeviceReportsEvent event, + Emitter emit, + ) async { + emit(FlushMountedPresenceSensorDeviceReportsLoadingState()); + + try { + await DevicesManagementApi.getDeviceReports(deviceId, event.code) + .then((value) { + emit(FlushMountedPresenceSensorDeviceReportsState( + deviceReport: value, code: event.code)); + }); + } catch (e) { + emit(FlushMountedPresenceSensorDeviceReportsFailedState(error: e.toString())); + return; + } + } + + void _onFlushMountedPresenceSensorShowDescriptionEvent( + FlushMountedPresenceSensorShowDescriptionEvent event, + Emitter emit, + ) { + emit(FlushMountedPresenceSensorShowDescriptionState( + description: event.description)); + } + + void _onFlushMountedPresenceSensorBackToGridViewEvent( + FlushMountedPresenceSensorBackToGridViewEvent event, + Emitter emit, + ) { + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + } + + Future _onFlushMountedPresenceSensorFactoryResetEvent( + FlushMountedPresenceSensorFactoryResetEvent event, + Emitter emit, + ) async { + emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.deviceId, + ); + if (!response) { + emit( + const FlushMountedPresenceSensorFailedState( + error: 'Something went wrong with factory reset, please try again', + ), + ); + } else { + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + } + } catch (e) { + emit(FlushMountedPresenceSensorFailedState(error: e.toString())); + } + } +} diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart new file mode 100644 index 00000000..f1636300 --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart @@ -0,0 +1,84 @@ +part of 'flush_mounted_presence_sensor_bloc.dart'; + +sealed class FlushMountedPresenceSensorEvent extends Equatable { + const FlushMountedPresenceSensorEvent(); + + @override + List get props => []; +} + +class FlushMountedPresenceSensorFetchStatusEvent + extends FlushMountedPresenceSensorEvent {} + +class FlushMountedPresenceSensorChangeValueEvent + extends FlushMountedPresenceSensorEvent { + final int value; + final String code; + final bool isBatchControl; + const FlushMountedPresenceSensorChangeValueEvent({ + required this.value, + required this.code, + this.isBatchControl = false, + }); + + @override + List get props => [value, code]; +} + +class FlushMountedPresenceSensorFetchBatchStatusEvent + extends FlushMountedPresenceSensorEvent { + final List devicesIds; + const FlushMountedPresenceSensorFetchBatchStatusEvent(this.devicesIds); + + @override + List get props => [devicesIds]; +} + +class FlushMountedPresenceSensorGetDeviceReportsEvent + extends FlushMountedPresenceSensorEvent { + final String deviceUuid; + final String code; + const FlushMountedPresenceSensorGetDeviceReportsEvent({ + required this.deviceUuid, + required this.code, + }); + + @override + List get props => [deviceUuid, code]; +} + +class FlushMountedPresenceSensorShowDescriptionEvent + extends FlushMountedPresenceSensorEvent { + final String description; + const FlushMountedPresenceSensorShowDescriptionEvent({required this.description}); +} + +class FlushMountedPresenceSensorBackToGridViewEvent + extends FlushMountedPresenceSensorEvent {} + +class FlushMountedPresenceSensorBatchControlEvent + extends FlushMountedPresenceSensorEvent { + final List deviceIds; + final String code; + final dynamic value; + + const FlushMountedPresenceSensorBatchControlEvent({ + required this.deviceIds, + required this.code, + required this.value, + }); + + @override + List get props => [deviceIds, code, value]; +} + +class FlushMountedPresenceSensorFactoryResetEvent + extends FlushMountedPresenceSensorEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + const FlushMountedPresenceSensorFactoryResetEvent({ + required this.deviceId, + required this.factoryReset, + }); +} diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_state.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_state.dart new file mode 100644 index 00000000..0fef07f2 --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_state.dart @@ -0,0 +1,76 @@ +part of 'flush_mounted_presence_sensor_bloc.dart'; + +sealed class FlushMountedPresenceSensorState extends Equatable { + const FlushMountedPresenceSensorState(); + + @override + List get props => []; +} + +class FlushMountedPresenceSensorInitialState + extends FlushMountedPresenceSensorState {} + +class FlushMountedPresenceSensorLoadingInitialState + extends FlushMountedPresenceSensorState {} + +class FlushMountedPresenceSensorUpdateState extends FlushMountedPresenceSensorState { + final FlushMountedPresenceSensorModel model; + const FlushMountedPresenceSensorUpdateState({required this.model}); + + @override + List get props => [model]; +} + +class FlushMountedPresenceSensorLoadingNewSate + extends FlushMountedPresenceSensorState { + final FlushMountedPresenceSensorModel model; + const FlushMountedPresenceSensorLoadingNewSate({required this.model}); + + @override + List get props => [model]; +} + +class FlushMountedPresenceSensorFailedState extends FlushMountedPresenceSensorState { + final String error; + + const FlushMountedPresenceSensorFailedState({required this.error}); + + @override + List get props => [error]; +} + +class FlushMountedPresenceSensorDeviceReportsLoadingState + extends FlushMountedPresenceSensorState {} + +class FlushMountedPresenceSensorDeviceReportsState + extends FlushMountedPresenceSensorState { + const FlushMountedPresenceSensorDeviceReportsState({ + required this.deviceReport, + required this.code, + }); + + final DeviceReport deviceReport; + final String code; + + @override + List get props => [deviceReport, code]; +} + +class FlushMountedPresenceSensorDeviceReportsFailedState + extends FlushMountedPresenceSensorState { + const FlushMountedPresenceSensorDeviceReportsFailedState({required this.error}); + + final String error; + + @override + List get props => [error]; +} + +class FlushMountedPresenceSensorShowDescriptionState + extends FlushMountedPresenceSensorState { + const FlushMountedPresenceSensorShowDescriptionState({required this.description}); + + final String description; + @override + List get props => [description]; +} From fb8ccdf0a67043f4203fd3519f50de5693f999d2 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:24:38 +0300 Subject: [PATCH 193/238] Add FlushMountedPresenceSensorControlView for managing presence sensor settings --- .../helper/route_controls_based_code.dart | 4 + ..._mounted_presence_sensor_control_view.dart | 225 ++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index e38ac582..81405451 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart'; @@ -104,6 +105,9 @@ mixin RouteControlsBasedCode { ); case 'SOS': return SosDeviceControlsView(device: device); + + case 'NCPS': + return FlushMountedPresenceSensorControlView(device: device); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart new file mode 100644 index 00000000..5a355067 --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart @@ -0,0 +1,225 @@ +import 'package:flutter/material.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/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_status.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/description_view.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class FlushMountedPresenceSensorControlView extends StatelessWidget + with HelperResponsiveLayout { + const FlushMountedPresenceSensorControlView({super.key, required this.device}); + + final AllDevicesModel device; + + @override + Widget build(BuildContext context) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return BlocProvider( + create: (context) => FlushMountedPresenceSensorBloc( + deviceId: device.uuid!, + controlDeviceService: DebouncedControlDeviceService( + decoratee: RemoteControlDeviceService(), + ), + )..add(FlushMountedPresenceSensorFetchStatusEvent()), + child: BlocBuilder( + builder: (context, state) { + if (state is FlushMountedPresenceSensorLoadingInitialState || + state is FlushMountedPresenceSensorDeviceReportsLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is FlushMountedPresenceSensorUpdateState) { + return _buildGridView( + context, state.model, isExtraLarge, isLarge, isMedium); + } else if (state is FlushMountedPresenceSensorDeviceReportsState) { + return ReportsTable( + report: state.deviceReport, + thirdColumnTitle: + state.code == 'illuminance_value' ? "Value" : 'Status', + thirdColumnDescription: + state.code == 'illuminance_value' ? "Lux" : null, + onRowTap: (index) {}, + onClose: () { + context + .read() + .add(FlushMountedPresenceSensorBackToGridViewEvent()); + }, + ); + } else if (state is FlushMountedPresenceSensorShowDescriptionState) { + return DescriptionView( + description: state.description, + onClose: () { + context + .read() + .add(FlushMountedPresenceSensorBackToGridViewEvent()); + }, + ); + } else if (state is FlushMountedPresenceSensorDeviceReportsFailedState) { + final model = + context.read().deviceStatus; + return _buildGridView( + context, + model, + isExtraLarge, + isLarge, + isMedium, + ); + } + return const Center( + child: Text('Error fetching status', textAlign: TextAlign.center), + ); + }, + ), + ); + } + + Widget _buildGridView( + BuildContext context, + FlushMountedPresenceSensorModel model, + bool isExtraLarge, + bool isLarge, + bool isMedium, + ) { + return GridView( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + PresenceState( + value: model.presenceState, + ), + PresenceDisplayValue( + value: model.illuminance.toString(), + postfix: 'Lux', + description: 'Illuminance Value', + ), + PresenceUpdateData( + value: model.sensitivity.toDouble(), + title: 'Sensitivity:', + minValue: 0, + maxValue: 9, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeSensitivity, + value: value, + ), + ), + ), + PresenceUpdateData( + value: (model.nearDetection).toDouble(), + title: 'Nearest Detect Dist:', + description: 'm', + minValue: 0.0, + maxValue: 950, + steps: 10, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeNearDetection, + value: (value).toInt(), + ), + ), + ), + PresenceUpdateData( + value: (model.farDetection).toDouble(), + title: 'Max Detect Dist:', + description: 'm', + minValue: 0.0, + maxValue: 950, + steps: 10, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeFarDetection, + value: (value).toInt(), + ), + ), + ), + PresenceUpdateData( + value: (model.presenceDelay.toDouble()), + title: 'Trigger Level:', + minValue: 0, + maxValue: 3, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codePresenceDelay, + value: value, + ), + ), + ), + PresenceUpdateData( + value: (model.occurDistReduce.toDouble()), + title: 'Indent Level:', + minValue: 0, + maxValue: 3, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeOccurDistReduce, + value: value, + ), + ), + ), + PresenceUpdateData( + value: (model.sensiReduce.toDouble()), + title: 'Target Confirm Time:', + description: 's', + minValue: 0, + maxValue: 3, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeSensiReduce, + value: value, + ), + ), + ), + PresenceUpdateData( + value: ((model.noneDelay).toDouble()), + description: 's', + title: 'Disappe Delay:', + minValue: 0, + maxValue: 3000, + steps: 10, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeNoneDelay, + value: value, + ), + ), + ), + GestureDetector( + onTap: () => context.read().add( + FlushMountedPresenceSensorGetDeviceReportsEvent( + code: 'presence_state', + deviceUuid: device.uuid!, + ), + ), + child: const PresenceStaticWidget( + icon: Assets.presenceRecordIcon, + description: 'Presence Record', + ), + ), + ], + ); + } +} From 8c960bd5f11872ac7bd42c3ffa0c9e8594a31859 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:26:17 +0300 Subject: [PATCH 194/238] created `BatchControlDevicesService` interface. --- lib/services/batch_control_devices_service.dart | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 lib/services/batch_control_devices_service.dart diff --git a/lib/services/batch_control_devices_service.dart b/lib/services/batch_control_devices_service.dart new file mode 100644 index 00000000..3049ec7e --- /dev/null +++ b/lib/services/batch_control_devices_service.dart @@ -0,0 +1,7 @@ +abstract interface class BatchControlDevicesService { + Future batchControlDevices({ + required List uuids, + required String code, + required Object value, + }); +} From 24130be66589ad110e100e52caa4b7ddb3c50ba0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:26:55 +0300 Subject: [PATCH 195/238] organized instances in bloc. --- .../bloc/flush_mounted_presence_sensor_bloc.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart index 06be2666..d7720937 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart @@ -17,10 +17,9 @@ part 'flush_mounted_presence_sensor_state.dart'; class FlushMountedPresenceSensorBloc extends Bloc { final String deviceId; - late FlushMountedPresenceSensorModel deviceStatus; - final ControlDeviceService controlDeviceService; + late FlushMountedPresenceSensorModel deviceStatus; FlushMountedPresenceSensorBloc({ required this.deviceId, required this.controlDeviceService, From b11d4186fb9efc55e5b2a587515c9d50b17075c3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:31:26 +0300 Subject: [PATCH 196/238] Add BatchControlDevicesService integration to FlushMountedPresenceSensorBloc and ControlView --- .../flush_mounted_presence_sensor_bloc.dart | 9 +- ..._mounted_presence_sensor_control_view.dart | 4 + .../batch_control_devices_service.dart | 83 +++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart index d7720937..98cfa977 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_rep import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart'; +import 'package:syncrow_web/services/batch_control_devices_service.dart'; import 'package:syncrow_web/services/control_device_service.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; @@ -18,11 +19,13 @@ class FlushMountedPresenceSensorBloc extends Bloc { final String deviceId; final ControlDeviceService controlDeviceService; + final BatchControlDevicesService batchControlDevicesService; late FlushMountedPresenceSensorModel deviceStatus; FlushMountedPresenceSensorBloc({ required this.deviceId, required this.controlDeviceService, + required this.batchControlDevicesService, }) : super(FlushMountedPresenceSensorInitialState()) { on( _onFlushMountedPresenceSensorFetchStatusEvent, @@ -193,7 +196,11 @@ class FlushMountedPresenceSensorBloc }) async { try { if (isBatch) { - await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + await batchControlDevicesService.batchControlDevices( + uuids: deviceId, + code: code, + value: value, + ); } else { await controlDeviceService.controlDevice( deviceUuid: deviceId, diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart index 5a355067..c02b4141 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart @@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presen import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/description_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; +import 'package:syncrow_web/services/batch_control_devices_service.dart'; import 'package:syncrow_web/services/control_device_service.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; @@ -30,6 +31,9 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget controlDeviceService: DebouncedControlDeviceService( decoratee: RemoteControlDeviceService(), ), + batchControlDevicesService: DebouncedBatchControlDevicesService( + decoratee: RemoteBatchControlDevicesService(), + ), )..add(FlushMountedPresenceSensorFetchStatusEvent()), child: BlocBuilder( diff --git a/lib/services/batch_control_devices_service.dart b/lib/services/batch_control_devices_service.dart index 3049ec7e..c8d0bfc2 100644 --- a/lib/services/batch_control_devices_service.dart +++ b/lib/services/batch_control_devices_service.dart @@ -1,3 +1,8 @@ +import 'dart:developer'; + +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + abstract interface class BatchControlDevicesService { Future batchControlDevices({ required List uuids, @@ -5,3 +10,81 @@ abstract interface class BatchControlDevicesService { required Object value, }); } + +class RemoteBatchControlDevicesService implements BatchControlDevicesService { + @override + Future batchControlDevices({ + required List uuids, + required String code, + required Object value, + }) async { + try { + final body = { + 'devicesUuid': uuids, + 'code': code, + 'value': value, + 'operationType': 'COMMAND', + }; + + final response = await HTTPService().post( + path: ApiEndpoints.deviceBatchControl, + body: body, + showServerMessage: true, + expectedResponseModel: (json) => (json['success'] as bool?) ?? false, + ); + + return response; + } catch (e) { + log('Error fetching $e', name: 'BatchControlDevicesService'); + return false; + } + } +} + +final class DebouncedBatchControlDevicesService + implements BatchControlDevicesService { + final BatchControlDevicesService decoratee; + final Duration _debounceDuration; + final List<(List uuids, String code, Object value)> _pendingRequests = []; + bool _isProcessing = false; + + DebouncedBatchControlDevicesService({ + required this.decoratee, + Duration debounceDuration = const Duration(milliseconds: 1500), + }) : _debounceDuration = debounceDuration; + + @override + Future batchControlDevices({ + required List uuids, + required String code, + required Object value, + }) async { + _pendingRequests.add((uuids, code, value)); + + if (_isProcessing) { + log( + 'Request added to queue', + name: 'DebouncedBatchControlDevicesService', + ); + return false; + } + + _isProcessing = true; + + await Future.delayed(_debounceDuration); + + final lastRequest = _pendingRequests.last; + _pendingRequests.clear(); + + try { + final (lastRequestUuids, lastRequestCode, lastRequestValue) = lastRequest; + return decoratee.batchControlDevices( + uuids: lastRequestUuids, + code: lastRequestCode, + value: lastRequestValue, + ); + } finally { + _isProcessing = false; + } + } +} From 42d6b64e588a96210a131dd050ea6cc63739234c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:32:49 +0300 Subject: [PATCH 197/238] Refactor FlushMountedPresenceSensorBloc to replace _runDeBouncer with _controlDevice for handling device control logic --- .../bloc/flush_mounted_presence_sensor_bloc.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart index 98cfa977..f15c14d6 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart @@ -138,7 +138,7 @@ class FlushMountedPresenceSensorBloc break; } emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); - await _runDeBouncer( + await _controlDevice( deviceId: deviceId, code: event.code, value: event.value, @@ -178,7 +178,7 @@ class FlushMountedPresenceSensorBloc break; } emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); - await _runDeBouncer( + await _controlDevice( deviceId: event.deviceIds, code: event.code, value: event.value, @@ -187,7 +187,7 @@ class FlushMountedPresenceSensorBloc ); } - Future _runDeBouncer({ + Future _controlDevice({ required dynamic deviceId, required String code, required dynamic value, From 91f93d439528eab3479a53580a81ce3f35d52c2a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:44:11 +0300 Subject: [PATCH 198/238] Refactor FlushMountedPresenceSensorBloc to streamline device control logic and remove redundant code --- .../flush_mounted_presence_sensor_bloc.dart | 141 +++++++----------- 1 file changed, 51 insertions(+), 90 deletions(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart index f15c14d6..a3c15aa0 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:developer'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; @@ -110,41 +109,16 @@ class FlushMountedPresenceSensorBloc Emitter emit, ) async { emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); - switch (event.code) { - case FlushMountedPresenceSensorModel.codeFarDetection: - deviceStatus.farDetection = event.value; - break; - case FlushMountedPresenceSensorModel.codeSensitivity: - log('updated sensitivity: ${deviceStatus.sensitivity}-${event.value}'); - deviceStatus.sensitivity = event.value; - break; - case FlushMountedPresenceSensorModel.codeNoneDelay: - log('updated none delay: ${deviceStatus.noneDelay}-${event.value}'); - deviceStatus.noneDelay = event.value; - break; - case FlushMountedPresenceSensorModel.codePresenceDelay: - deviceStatus.presenceDelay = event.value; - break; - case FlushMountedPresenceSensorModel.codeNearDetection: - deviceStatus.nearDetection = event.value; - break; - case FlushMountedPresenceSensorModel.codeOccurDistReduce: - deviceStatus.occurDistReduce = event.value; - break; - case FlushMountedPresenceSensorModel.codeSensiReduce: - deviceStatus.sensiReduce = event.value; - break; - default: - break; - } + _updateDeviceFunctionFromCode(event.code, event.value); emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); - await _controlDevice( - deviceId: deviceId, - code: event.code, - value: event.value, - isBatch: false, - emit: emit, - ); + try { + await controlDeviceService.controlDevice( + deviceUuid: deviceId, + status: Status(code: event.code, value: event.value), + ); + } catch (_) { + await _reloadDeviceStatus(); + } } Future _onFlushMountedPresenceSensorBatchControlEvent( @@ -152,67 +126,54 @@ class FlushMountedPresenceSensorBloc Emitter emit, ) async { emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); - switch (event.code) { - case FlushMountedPresenceSensorModel.codeFarDetection: - deviceStatus.farDetection = event.value; - break; - case FlushMountedPresenceSensorModel.codeSensitivity: - deviceStatus.sensitivity = event.value; - break; - case FlushMountedPresenceSensorModel.codeNoneDelay: - deviceStatus.noneDelay = event.value; - break; - case FlushMountedPresenceSensorModel.codePresenceDelay: - deviceStatus.presenceDelay = event.value; - break; - case FlushMountedPresenceSensorModel.codeNearDetection: - deviceStatus.nearDetection = event.value; - break; - case FlushMountedPresenceSensorModel.codeOccurDistReduce: - deviceStatus.occurDistReduce = event.value; - break; - case FlushMountedPresenceSensorModel.codeSensiReduce: - deviceStatus.sensiReduce = event.value; - break; - default: - break; - } + _updateDeviceFunctionFromCode(event.code, event.value); emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); - await _controlDevice( - deviceId: event.deviceIds, - code: event.code, - value: event.value, - emit: emit, - isBatch: true, - ); + + try { + await batchControlDevicesService.batchControlDevices( + uuids: event.deviceIds, + code: event.code, + value: event.value, + ); + } catch (_) { + await _reloadDeviceStatus(); + } } - Future _controlDevice({ - required dynamic deviceId, - required String code, - required dynamic value, - required Emitter emit, - required bool isBatch, - }) async { - try { - if (isBatch) { - await batchControlDevicesService.batchControlDevices( - uuids: deviceId, - code: code, - value: value, - ); - } else { - await controlDeviceService.controlDevice( - deviceUuid: deviceId, - status: Status(code: code, value: value), - ); - } - } catch (_) { - await Future.delayed(const Duration(milliseconds: 500)); - add(FlushMountedPresenceSensorFetchStatusEvent()); + void _updateDeviceFunctionFromCode(String code, int value) { + switch (code) { + case FlushMountedPresenceSensorModel.codeFarDetection: + deviceStatus.farDetection = value; + break; + case FlushMountedPresenceSensorModel.codeSensitivity: + deviceStatus.sensitivity = value; + break; + case FlushMountedPresenceSensorModel.codeNoneDelay: + deviceStatus.noneDelay = value; + break; + case FlushMountedPresenceSensorModel.codePresenceDelay: + deviceStatus.presenceDelay = value; + break; + case FlushMountedPresenceSensorModel.codeNearDetection: + deviceStatus.nearDetection = value; + break; + case FlushMountedPresenceSensorModel.codeOccurDistReduce: + deviceStatus.occurDistReduce = value; + break; + case FlushMountedPresenceSensorModel.codeSensiReduce: + deviceStatus.sensiReduce = value; + break; + default: + return; } } + Future _reloadDeviceStatus() async { + await Future.delayed(const Duration(milliseconds: 500), () { + add(FlushMountedPresenceSensorFetchStatusEvent()); + }); + } + Future _onFlushMountedPresenceSensorGetDeviceReportsEvent( FlushMountedPresenceSensorGetDeviceReportsEvent event, Emitter emit, From 7adce3b94ca4e3f963756f430a512c3fc57b2094 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:44:44 +0300 Subject: [PATCH 199/238] Refactor _onFlushMountedPresenceSensorFetchBatchStatusEvent to use final for response variable --- .../bloc/flush_mounted_presence_sensor_bloc.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart index a3c15aa0..9b62cb4e 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart @@ -72,7 +72,7 @@ class FlushMountedPresenceSensorBloc ) async { emit(FlushMountedPresenceSensorLoadingInitialState()); try { - var response = await DevicesManagementApi().getBatchStatus(event.devicesIds); + final response = await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status); emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); } catch (e) { From d1bb7b129ffce822e507d3cad65de9ddefa5f6a1 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 23 Apr 2025 10:46:56 +0300 Subject: [PATCH 200/238] Refactor widget lifecycle methods for temperature control and presence sensor --- .../ac/view/control_list/current_temp.dart | 10 +++- .../view/ceiling_sensor_controls.dart | 34 ++++++++------ .../sensors_widgets/presense_nobody_time.dart | 10 ++++ .../wall_sensor/bloc/wall_bloc.dart | 46 +++++++++++-------- .../wall_sensor/bloc/wall_event.dart | 6 +++ 5 files changed, 72 insertions(+), 34 deletions(-) diff --git a/lib/pages/device_managment/ac/view/control_list/current_temp.dart b/lib/pages/device_managment/ac/view/control_list/current_temp.dart index 7618846c..fdd51164 100644 --- a/lib/pages/device_managment/ac/view/control_list/current_temp.dart +++ b/lib/pages/device_managment/ac/view/control_list/current_temp.dart @@ -60,7 +60,15 @@ class _CurrentTempState extends State { ); }); } - + @override + void didUpdateWidget(CurrentTemp oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.tempSet != widget.tempSet) { + setState(() { + _adjustedValue = _initialAdjustedValue(widget.tempSet); + }); + } + } @override void dispose() { _debounce?.cancel(); diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart index 845c326b..36b676e9 100644 --- a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart @@ -16,7 +16,8 @@ import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dar import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLayout { +class CeilingSensorControlsView extends StatelessWidget + with HelperResponsiveLayout { const CeilingSensorControlsView({super.key, required this.device}); final AllDevicesModel device; @@ -31,29 +32,35 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay ..add(CeilingInitialEvent(device.uuid ?? '')), child: BlocBuilder( builder: (context, state) { - if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) { + if (state is CeilingLoadingInitialState || + state is CeilingReportsLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is CeilingUpdateState) { - return _buildGridView( - context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium); + return _buildGridView(context, state.ceilingSensorModel, + isExtraLarge, isLarge, isMedium); } else if (state is CeilingReportsState) { return ReportsTable( report: state.deviceReport, onRowTap: (index) {}, onClose: () { - context.read().add(BackToCeilingGridViewEvent()); + context + .read() + .add(BackToCeilingGridViewEvent()); }, ); } else if (state is ShowCeilingDescriptionState) { return DescriptionView( description: state.description, onClose: () { - context.read().add(BackToCeilingGridViewEvent()); + context + .read() + .add(BackToCeilingGridViewEvent()); }, ); } else if (state is CeilingReportsFailedState) { final model = context.read().deviceStatus; - return _buildGridView(context, model, isExtraLarge, isLarge, isMedium); + return _buildGridView( + context, model, isExtraLarge, isLarge, isMedium); } return const Center(child: Text('Error fetching status')); }, @@ -61,8 +68,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay ); } - Widget _buildGridView(BuildContext context, CeilingSensorModel model, bool isExtraLarge, - bool isLarge, bool isMedium) { + Widget _buildGridView(BuildContext context, CeilingSensorModel model, + bool isExtraLarge, bool isLarge, bool isMedium) { return GridView( padding: const EdgeInsets.symmetric(horizontal: 50), shrinkWrap: true, @@ -143,8 +150,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay ), GestureDetector( onTap: () { - context.read().add( - GetCeilingDeviceReportsEvent(code: 'presence_state', deviceUuid: device.uuid!)); + context.read().add(GetCeilingDeviceReportsEvent( + code: 'presence_state', deviceUuid: device.uuid!)); }, child: const PresenceStaticWidget( icon: Assets.illuminanceRecordIcon, @@ -153,9 +160,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay ), GestureDetector( onTap: () { - context - .read() - .add(GetCeilingDeviceReportsEvent(code: '', deviceUuid: device.uuid!)); + context.read().add(GetCeilingDeviceReportsEvent( + code: '', deviceUuid: device.uuid!)); }, child: const PresenceStaticWidget( icon: Assets.helpDescriptionIcon, diff --git a/lib/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart b/lib/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart index 4e64ee1e..9cc23505 100644 --- a/lib/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart +++ b/lib/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart @@ -84,6 +84,16 @@ class _PresenceUpdateDataState extends State { } } + @override + void didUpdateWidget(PresenceNoBodyTime oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.value != widget.value) { + setState(() { + _currentValue = widget.value; + }); + } + } + @override Widget build(BuildContext context) { return DeviceControlsContainer( diff --git a/lib/pages/device_managment/wall_sensor/bloc/wall_bloc.dart b/lib/pages/device_managment/wall_sensor/bloc/wall_bloc.dart index ea2813d7..3c144142 100644 --- a/lib/pages/device_managment/wall_sensor/bloc/wall_bloc.dart +++ b/lib/pages/device_managment/wall_sensor/bloc/wall_bloc.dart @@ -21,6 +21,7 @@ class WallSensorBloc extends Bloc { on(_showDescription); on(_backToGridView); on(_onFactoryReset); + on(_onRealtimeUpdate); } void _fetchWallSensorStatus( @@ -30,7 +31,7 @@ class WallSensorBloc extends Bloc { var response = await DevicesManagementApi().getDeviceStatus(deviceId); deviceStatus = WallSensorModel.fromJson(response.status); emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); - _listenToChanges(emit, deviceId); + _listenToChanges(deviceId); } catch (e) { emit(WallSensorFailedState(error: e.toString())); return; @@ -52,28 +53,27 @@ class WallSensorBloc extends Bloc { } } - _listenToChanges(Emitter emit, deviceId) { - try { - DatabaseReference ref = - FirebaseDatabase.instance.ref('device-status/$deviceId'); - Stream stream = ref.onValue; + void _listenToChanges(String deviceId) { + DatabaseReference ref = + FirebaseDatabase.instance.ref('device-status/$deviceId'); + ref.onValue.listen((DatabaseEvent event) { + final data = event.snapshot.value as Map?; + if (data == null) return; - stream.listen((DatabaseEvent event) { - Map usersMap = - event.snapshot.value as Map; - List statusList = []; + final statusList = (data['status'] as List?) + ?.map((e) => Status(code: e['code'], value: e['value'])) + .toList(); - usersMap['status'].forEach((element) { - statusList - .add(Status(code: element['code'], value: element['value'])); - }); - - deviceStatus = WallSensorModel.fromJson(statusList); - emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); - }); - } catch (_) {} + if (statusList != null) { + final updatedDeviceStatus = WallSensorModel.fromJson(statusList); + if (!isClosed) { + add(WallSensorRealtimeUpdateEvent(updatedDeviceStatus)); + } + } + }); } + void _changeValue( WallSensorChangeValueEvent event, Emitter emit) async { emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); @@ -195,4 +195,12 @@ class WallSensorBloc extends Bloc { emit(WallSensorFailedState(error: e.toString())); } } + + void _onRealtimeUpdate( + WallSensorRealtimeUpdateEvent event, + Emitter emit, + ) { + deviceStatus = event.deviceStatus; + emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); + } } diff --git a/lib/pages/device_managment/wall_sensor/bloc/wall_event.dart b/lib/pages/device_managment/wall_sensor/bloc/wall_event.dart index 17d85d43..fd71d7d4 100644 --- a/lib/pages/device_managment/wall_sensor/bloc/wall_event.dart +++ b/lib/pages/device_managment/wall_sensor/bloc/wall_event.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; abstract class WallSensorEvent extends Equatable { const WallSensorEvent(); @@ -70,3 +71,8 @@ class WallSensorFactoryResetEvent extends WallSensorEvent { required this.factoryReset, }); } + +class WallSensorRealtimeUpdateEvent extends WallSensorEvent { + final WallSensorModel deviceStatus; + const WallSensorRealtimeUpdateEvent(this.deviceStatus); +} From 46860619a07f58e4a0e0e57cb35696c9e3ab03a3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:48:24 +0300 Subject: [PATCH 201/238] Add bloc dependency to pubspec.yaml --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index fd7ed797..ec8660b6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,6 +60,7 @@ dependencies: firebase_core: ^3.11.0 firebase_crashlytics: ^4.3.2 firebase_database: ^11.3.2 + bloc: ^8.1.4 dev_dependencies: From 2bb7a6950a6e1cc7d74e6db66e408efad1f85c40 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 10:52:08 +0300 Subject: [PATCH 202/238] Refactor debounce duration handling in BatchControlDevicesService and ControlDeviceService --- .../batch_control_devices_service.dart | 23 ++++++++----------- lib/services/control_device_service.dart | 23 ++++++++----------- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/lib/services/batch_control_devices_service.dart b/lib/services/batch_control_devices_service.dart index c8d0bfc2..76dbe480 100644 --- a/lib/services/batch_control_devices_service.dart +++ b/lib/services/batch_control_devices_service.dart @@ -11,7 +11,7 @@ abstract interface class BatchControlDevicesService { }); } -class RemoteBatchControlDevicesService implements BatchControlDevicesService { +final class RemoteBatchControlDevicesService implements BatchControlDevicesService { @override Future batchControlDevices({ required List uuids, @@ -44,14 +44,15 @@ class RemoteBatchControlDevicesService implements BatchControlDevicesService { final class DebouncedBatchControlDevicesService implements BatchControlDevicesService { final BatchControlDevicesService decoratee; - final Duration _debounceDuration; - final List<(List uuids, String code, Object value)> _pendingRequests = []; - bool _isProcessing = false; + final Duration debounceDuration; + + final _pendingRequests = <(List uuids, String code, Object value)>[]; + var _isProcessing = false; DebouncedBatchControlDevicesService({ required this.decoratee, - Duration debounceDuration = const Duration(milliseconds: 1500), - }) : _debounceDuration = debounceDuration; + this.debounceDuration = const Duration(milliseconds: 1500), + }); @override Future batchControlDevices({ @@ -61,17 +62,11 @@ final class DebouncedBatchControlDevicesService }) async { _pendingRequests.add((uuids, code, value)); - if (_isProcessing) { - log( - 'Request added to queue', - name: 'DebouncedBatchControlDevicesService', - ); - return false; - } + if (_isProcessing) return false; _isProcessing = true; - await Future.delayed(_debounceDuration); + await Future.delayed(debounceDuration); final lastRequest = _pendingRequests.last; _pendingRequests.clear(); diff --git a/lib/services/control_device_service.dart b/lib/services/control_device_service.dart index b115990d..ab04a398 100644 --- a/lib/services/control_device_service.dart +++ b/lib/services/control_device_service.dart @@ -36,14 +36,15 @@ final class RemoteControlDeviceService implements ControlDeviceService { final class DebouncedControlDeviceService implements ControlDeviceService { final ControlDeviceService decoratee; - final Duration _debounceDuration; - final List<(String deviceUuid, Status status)> _pendingRequests = []; - bool _isProcessing = false; + final Duration debounceDuration; DebouncedControlDeviceService({ required this.decoratee, - Duration debounceDuration = const Duration(milliseconds: 1500), - }) : _debounceDuration = debounceDuration; + this.debounceDuration = const Duration(milliseconds: 1500), + }); + + final _pendingRequests = <(String deviceUuid, Status status)>[]; + var _isProcessing = false; @override Future controlDevice({ @@ -52,23 +53,17 @@ final class DebouncedControlDeviceService implements ControlDeviceService { }) async { _pendingRequests.add((deviceUuid, status)); - if (_isProcessing) { - log( - 'Request added to queue', - name: 'DebouncedControlDeviceService', - ); - return false; - } + if (_isProcessing) return false; _isProcessing = true; - await Future.delayed(_debounceDuration); + await Future.delayed(debounceDuration); final lastRequest = _pendingRequests.last; _pendingRequests.clear(); try { - final ( lastRequestDeviceUuid, lastRequestStatus) = lastRequest; + final (lastRequestDeviceUuid, lastRequestStatus) = lastRequest; return decoratee.controlDevice( deviceUuid: lastRequestDeviceUuid, status: lastRequestStatus, From 4a5176cf2238b1e2a04a769c7d43e14b22476458 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 12:11:13 +0300 Subject: [PATCH 203/238] Refactor presence update data handling for improved precision and scaling --- ...ed_presence_sensor_batch_control_view.dart | 187 ++++++++++++++++++ ..._mounted_presence_sensor_control_view.dart | 52 ++--- .../sensors_widgets/presence_update_data.dart | 8 +- 3 files changed, 221 insertions(+), 26 deletions(-) create mode 100644 lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart new file mode 100644 index 00000000..0ef4cdbc --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart @@ -0,0 +1,187 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; +import 'package:syncrow_web/services/batch_control_devices_service.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class FlushMountedPresenceSensorBatchControlView extends StatelessWidget + with HelperResponsiveLayout { + const FlushMountedPresenceSensorBatchControlView({ + required this.devicesIds, + super.key, + }); + + final List devicesIds; + + @override + Widget build(BuildContext context) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return BlocProvider( + create: (context) => FlushMountedPresenceSensorBloc( + deviceId: devicesIds.first, + controlDeviceService: DebouncedControlDeviceService( + decoratee: RemoteControlDeviceService(), + ), + batchControlDevicesService: DebouncedBatchControlDevicesService( + decoratee: RemoteBatchControlDevicesService(), + ), + )..add(FlushMountedPresenceSensorFetchStatusEvent()), + child: BlocBuilder( + builder: (context, state) { + if (state is FlushMountedPresenceSensorLoadingInitialState || + state is FlushMountedPresenceSensorDeviceReportsLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is FlushMountedPresenceSensorUpdateState) { + return _buildGridView( + context, state.model, isExtraLarge, isLarge, isMedium); + } + return const Center(child: Text('Error fetching status')); + }, + ), + ); + } + + Widget _buildGridView( + BuildContext context, + FlushMountedPresenceSensorModel model, + bool isExtraLarge, + bool isLarge, + bool isMedium, + ) { + return GridView( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + PresenceUpdateData( + value: model.sensitivity.toDouble(), + title: 'Sensitivity:', + minValue: 0, + maxValue: 9, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeSensitivity, + value: value, + ), + ), + ), + PresenceUpdateData( + value: (model.nearDetection / 100).toDouble(), + title: 'Nearest Detect Dist:', + description: 'm', + minValue: 0.0, + maxValue: 9.5, + steps: 0.1, + valuesPercision: 1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeNearDetection, + value: (value * 100).toInt(), + ), + ), + ), + PresenceUpdateData( + value: (model.farDetection / 100).toDouble(), + title: 'Max Detect Dist:', + description: 'm', + minValue: 0.0, + maxValue: 9.5, + steps: 0.1, + valuesPercision: 1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeFarDetection, + value: (value * 100).toInt(), + ), + ), + ), + PresenceUpdateData( + value: model.presenceDelay.toDouble(), + title: 'Trigger Level:', + minValue: 0, + maxValue: 3, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codePresenceDelay, + value: value, + ), + ), + ), + PresenceUpdateData( + value: (model.occurDistReduce.toDouble()), + title: 'Indent Level:', + minValue: 0, + maxValue: 3, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeOccurDistReduce, + value: value, + ), + ), + ), + PresenceUpdateData( + value: (model.sensiReduce.toDouble()), + title: 'Target Confirm Time:', + description: 's', + minValue: 0, + maxValue: 3, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeSensiReduce, + value: value, + ), + ), + ), + PresenceUpdateData( + value: ((model.noneDelay / 10).toDouble()), + description: 's', + title: 'Disappe Delay:', + minValue: 20, + maxValue: 300, + steps: 1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeNoneDelay, + value: (value * 10).round(), + ), + ), + ), + FactoryResetWidget( + callFactoryReset: () { + context.read().add( + FlushMountedPresenceSensorFactoryResetEvent( + deviceId: devicesIds.first, + factoryReset: FactoryResetModel(devicesUuid: devicesIds), + ), + ); + }, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart index c02b4141..dab887b2 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart @@ -130,32 +130,36 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget ), ), PresenceUpdateData( - value: (model.nearDetection).toDouble(), + value: (model.nearDetection / 100).toDouble(), title: 'Nearest Detect Dist:', description: 'm', minValue: 0.0, - maxValue: 950, - steps: 10, - action: (int value) => context.read().add( - FlushMountedPresenceSensorChangeValueEvent( - code: FlushMountedPresenceSensorModel.codeNearDetection, - value: (value).toInt(), - ), - ), + maxValue: 9.5, + steps: 0.1, + valuesPercision: 1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeNearDetection, + value: (value * 100).toInt(), + ), + ), ), PresenceUpdateData( - value: (model.farDetection).toDouble(), + value: (model.farDetection / 100).toDouble(), title: 'Max Detect Dist:', description: 'm', minValue: 0.0, - maxValue: 950, - steps: 10, - action: (int value) => context.read().add( - FlushMountedPresenceSensorChangeValueEvent( - code: FlushMountedPresenceSensorModel.codeFarDetection, - value: (value).toInt(), - ), - ), + maxValue: 9.5, + steps: 0.1, + valuesPercision: 1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeFarDetection, + value: (value * 100).toInt(), + ), + ), ), PresenceUpdateData( value: (model.presenceDelay.toDouble()), @@ -198,16 +202,16 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget ), ), PresenceUpdateData( - value: ((model.noneDelay).toDouble()), + value: ((model.noneDelay / 10).toDouble()), description: 's', title: 'Disappe Delay:', - minValue: 0, - maxValue: 3000, - steps: 10, - action: (int value) => context.read().add( + minValue: 20, + maxValue: 300, + steps: 1, + action: (double value) => context.read().add( FlushMountedPresenceSensorChangeValueEvent( code: FlushMountedPresenceSensorModel.codeNoneDelay, - value: value, + value: (value * 10).round(), ), ), ), diff --git a/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart b/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart index 4b4d5562..e535612d 100644 --- a/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart +++ b/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class PresenceUpdateData extends StatefulWidget { const PresenceUpdateData({ @@ -64,8 +65,11 @@ class _CurrentTempState extends State { children: [ Text( widget.title, - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 10), + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 10, + ), ), IncrementDecrementWidget( value: widget.value.toStringAsFixed(widget.valuesPercision), From 86164e746a77e81ffe27cdc8b430cffda28c68f1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 12:15:50 +0300 Subject: [PATCH 204/238] Refactor FlushMountedPresenceSensorBloc creation to use factory method and streamline dependency injection --- ..._mounted_presence_sensor_bloc_factory.dart | 21 ++++++++ ...ed_presence_sensor_batch_control_view.dart | 25 +++------- ..._mounted_presence_sensor_control_view.dart | 48 +++++++------------ 3 files changed, 44 insertions(+), 50 deletions(-) create mode 100644 lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart new file mode 100644 index 00000000..537189af --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart @@ -0,0 +1,21 @@ +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart'; +import 'package:syncrow_web/services/batch_control_devices_service.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; + +abstract final class FlushMountedPresenceSensorBlocFactory { + const FlushMountedPresenceSensorBlocFactory._(); + + static FlushMountedPresenceSensorBloc create({ + required String deviceId, + }) { + return FlushMountedPresenceSensorBloc( + deviceId: '', + controlDeviceService: DebouncedControlDeviceService( + decoratee: RemoteControlDeviceService(), + ), + batchControlDevicesService: DebouncedBatchControlDevicesService( + decoratee: RemoteBatchControlDevicesService(), + ), + )..add(FlushMountedPresenceSensorFetchStatusEvent()); + } +} diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart index 0ef4cdbc..7f1e726c 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart @@ -2,11 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart'; import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; -import 'package:syncrow_web/services/batch_control_devices_service.dart'; -import 'package:syncrow_web/services/control_device_service.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class FlushMountedPresenceSensorBatchControlView extends StatelessWidget @@ -20,19 +19,10 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget @override Widget build(BuildContext context) { - final isExtraLarge = isExtraLargeScreenSize(context); - final isLarge = isLargeScreenSize(context); - final isMedium = isMediumScreenSize(context); return BlocProvider( - create: (context) => FlushMountedPresenceSensorBloc( + create: (context) => FlushMountedPresenceSensorBlocFactory.create( deviceId: devicesIds.first, - controlDeviceService: DebouncedControlDeviceService( - decoratee: RemoteControlDeviceService(), - ), - batchControlDevicesService: DebouncedBatchControlDevicesService( - decoratee: RemoteBatchControlDevicesService(), - ), - )..add(FlushMountedPresenceSensorFetchStatusEvent()), + ), child: BlocBuilder( builder: (context, state) { @@ -40,8 +30,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget state is FlushMountedPresenceSensorDeviceReportsLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is FlushMountedPresenceSensorUpdateState) { - return _buildGridView( - context, state.model, isExtraLarge, isLarge, isMedium); + return _buildGridView(context, state.model); } return const Center(child: Text('Error fetching status')); }, @@ -52,10 +41,10 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget Widget _buildGridView( BuildContext context, FlushMountedPresenceSensorModel model, - bool isExtraLarge, - bool isLarge, - bool isMedium, ) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); return GridView( padding: const EdgeInsets.symmetric(horizontal: 50), shrinkWrap: true, diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart index dab887b2..bdefa507 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.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/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart'; import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart'; @@ -9,8 +10,6 @@ import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presen import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/description_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; -import 'package:syncrow_web/services/batch_control_devices_service.dart'; -import 'package:syncrow_web/services/control_device_service.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; @@ -22,19 +21,10 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget @override Widget build(BuildContext context) { - final isExtraLarge = isExtraLargeScreenSize(context); - final isLarge = isLargeScreenSize(context); - final isMedium = isMediumScreenSize(context); return BlocProvider( - create: (context) => FlushMountedPresenceSensorBloc( - deviceId: device.uuid!, - controlDeviceService: DebouncedControlDeviceService( - decoratee: RemoteControlDeviceService(), - ), - batchControlDevicesService: DebouncedBatchControlDevicesService( - decoratee: RemoteBatchControlDevicesService(), - ), - )..add(FlushMountedPresenceSensorFetchStatusEvent()), + create: (context) => FlushMountedPresenceSensorBlocFactory.create( + deviceId: device.uuid ?? '-1', + ), child: BlocBuilder( builder: (context, state) { @@ -42,8 +32,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget state is FlushMountedPresenceSensorDeviceReportsLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is FlushMountedPresenceSensorUpdateState) { - return _buildGridView( - context, state.model, isExtraLarge, isLarge, isMedium); + return _buildGridView(context, state.model); } else if (state is FlushMountedPresenceSensorDeviceReportsState) { return ReportsTable( report: state.deviceReport, @@ -70,13 +59,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget } else if (state is FlushMountedPresenceSensorDeviceReportsFailedState) { final model = context.read().deviceStatus; - return _buildGridView( - context, - model, - isExtraLarge, - isLarge, - isMedium, - ); + return _buildGridView(context, model); } return const Center( child: Text('Error fetching status', textAlign: TextAlign.center), @@ -89,10 +72,10 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget Widget _buildGridView( BuildContext context, FlushMountedPresenceSensorModel model, - bool isExtraLarge, - bool isLarge, - bool isMedium, ) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); return GridView( padding: const EdgeInsets.symmetric(horizontal: 50), shrinkWrap: true, @@ -208,12 +191,13 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget minValue: 20, maxValue: 300, steps: 1, - action: (double value) => context.read().add( - FlushMountedPresenceSensorChangeValueEvent( - code: FlushMountedPresenceSensorModel.codeNoneDelay, - value: (value * 10).round(), - ), - ), + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeNoneDelay, + value: (value * 10).round(), + ), + ), ), GestureDetector( onTap: () => context.read().add( From e1a24651307b67943c2ac4df6e051953acdcb735 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 13:11:25 +0300 Subject: [PATCH 205/238] Fix deviceId assignment in FlushMountedPresenceSensorBlocFactory and update _listenToChanges method for async handling --- .../flush_mounted_presence_sensor_bloc.dart | 30 +++++++++++-------- ..._mounted_presence_sensor_bloc_factory.dart | 2 +- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart index 9b62cb4e..0bc15cd2 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; @@ -80,28 +81,31 @@ class FlushMountedPresenceSensorBloc } } - void _listenToChanges( + Future _listenToChanges( Emitter emit, String deviceId, - ) { - try { - final ref = FirebaseDatabase.instance.ref( - 'device-status/$deviceId', - ); + ) async { + final ref = FirebaseDatabase.instance.ref( + 'device-status/$deviceId', + ); - ref.onValue.listen((DatabaseEvent event) { + await ref.onValue.listen( + (DatabaseEvent event) async { Map usersMap = event.snapshot.value as Map; List statusList = []; - - usersMap['status'].forEach((element) { + + (usersMap['status'] as List?)?.forEach((element) { statusList.add(Status(code: element['code'], value: element['value'])); }); - + deviceStatus = FlushMountedPresenceSensorModel.fromJson(statusList); - emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); - }); - } catch (_) {} + if (!emit.isDone) { + emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); + } + }, + onError: (error) => log(error.toString(), name: 'FirebaseDatabaseError'), + ).asFuture(); } void _onFlushMountedPresenceSensorChangeValueEvent( diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart index 537189af..f1342eec 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart @@ -9,7 +9,7 @@ abstract final class FlushMountedPresenceSensorBlocFactory { required String deviceId, }) { return FlushMountedPresenceSensorBloc( - deviceId: '', + deviceId: deviceId, controlDeviceService: DebouncedControlDeviceService( decoratee: RemoteControlDeviceService(), ), From 2c684a94953d9076f7ad0c1d82a6f8051b215351 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 23 Apr 2025 16:58:50 +0300 Subject: [PATCH 206/238] SP-1441 rework. --- .../widgets/routine_dialogs/ac_dialog.dart | 29 +++++++++-- .../ceiling_sensor/ceiling_sensor_dialog.dart | 6 ++- .../ceiling_sensor/cps_functions_list.dart | 40 ++++++++++++--- .../one_gang_switch_dialog.dart | 50 ++++++++++++++----- .../three_gang_switch_dialog.dart | 43 ++++++++++------ .../two_gang_switch_dialog.dart | 45 ++++++++++------- .../wall_sensor/wall_presence_sensor.dart | 43 +++++++++++++--- 7 files changed, 192 insertions(+), 64 deletions(-) diff --git a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart index 68cf857d..11bedf24 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart @@ -74,11 +74,33 @@ class ACHelper { child: _buildFunctionsList( context: context, acFunctions: acFunctions, - onFunctionSelected: (functionCode, operationName) => - context.read().add(SelectFunction( + device: device, + onFunctionSelected: (functionCode, operationName) { + context.read().add( + SelectFunction( functionCode: functionCode, operationName: operationName, - )), + ), + ); + if (functionCode == 'temp_set' || + functionCode == 'temp_current') { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: functionCode, + operationName: operationName, + value: functionCode == 'temp_set' + ? 200 + : -100, + condition: '==', + valueDescription: selectedFunctionData + .valueDescription, + ), + ), + ); + } + }, ), ), // Value selector @@ -137,6 +159,7 @@ class ACHelper { required BuildContext context, required List acFunctions, required Function(String, String) onFunctionSelected, + required AllDevicesModel? device, }) { return ListView.separated( shrinkWrap: false, diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index c18706f0..4e174265 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -131,7 +131,11 @@ class _CeilingSensorDialogState extends State { return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - CpsFunctionsList(cpsFunctions: _cpsFunctions), + CpsFunctionsList( + cpsFunctions: _cpsFunctions, + device: widget.device, + selectedFunctionData: selectedFunctionData, + ), if (state.selectedFunction != null) Expanded( child: isToggleFunction diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart index 2a35428a..707246c4 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart @@ -1,14 +1,24 @@ import 'package:flutter/material.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/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CpsFunctionsList extends StatelessWidget { - const CpsFunctionsList({required this.cpsFunctions, super.key}); + const CpsFunctionsList({ + required this.cpsFunctions, + required this.device, + required this.selectedFunctionData, + super.key, + }); final List cpsFunctions; + final AllDevicesModel? device; + final DeviceFunctionData? selectedFunctionData; @override Widget build(BuildContext context) { @@ -26,12 +36,28 @@ class CpsFunctionsList extends StatelessWidget { return RoutineDialogFunctionListTile( iconPath: function.icon, operationName: function.operationName, - onTap: () => context.read().add( - SelectFunction( - functionCode: function.code, - operationName: function.operationName, - ), - ), + onTap: () { + context.read().add( + SelectFunction( + functionCode: function.code, + operationName: function.operationName, + ), + ); + if (!CeilingSensorHelper.toggleCodes.contains(function.code)) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: function.code, + operationName: function.operationName, + value: 0, + condition: '==', + valueDescription: selectedFunctionData?.valueDescription, + ), + ), + ); + } + }, ); }, ), diff --git a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart index 51a99f3b..a2d6d8db 100644 --- a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -24,21 +24,23 @@ class OneGangSwitchHelper { required String uniqueCustomId, required bool removeComparetors, }) async { - List oneGangFunctions = functions.whereType().toList(); + List oneGangFunctions = + functions.whereType().toList(); return showDialog?>( context: context, builder: (BuildContext context) { return BlocProvider( - create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])), + create: (_) => FunctionBloc() + ..add(InitializeFunctions(deviceSelectedFunctions ?? [])), child: AlertDialog( contentPadding: EdgeInsets.zero, content: BlocBuilder( builder: (context, state) { final selectedFunction = state.selectedFunction; final selectedOperationName = state.selectedOperationName; - final selectedFunctionData = - state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction, + final selectedFunctionData = state.addedFunctions + .firstWhere((f) => f.functionCode == selectedFunction, orElse: () => DeviceFunctionData( entityId: '', functionCode: selectedFunction ?? '', @@ -85,10 +87,29 @@ class OneGangSwitchHelper { color: ColorsManager.textGray, ), onTap: () { - context.read().add(SelectFunction( + context + .read() + .add(SelectFunction( functionCode: function.code, operationName: function.operationName, )); + if (function.code == 'countdown_1') { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: function.code, + operationName: + function.operationName, + value: 0, + condition: '==', + valueDescription: + selectedFunctionData + .valueDescription, + ), + ), + ); + } }, ); }, @@ -220,11 +241,11 @@ class OneGangSwitchHelper { selectedFunctionData, ), const SizedBox(height: 20), - _buildCountDownDisplay( - context, initialValue, device, operationName, selectedFunctionData, selectCode), + _buildCountDownDisplay(context, initialValue, device, operationName, + selectedFunctionData, selectCode), const SizedBox(height: 20), - _buildCountDownSlider( - context, initialValue, device, operationName, selectedFunctionData, selectCode), + _buildCountDownSlider(context, initialValue, device, operationName, + selectedFunctionData, selectCode), ], ); } @@ -314,9 +335,10 @@ class OneGangSwitchHelper { value: (initialValue ?? 0).toDouble(), min: operationalValues.minValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / - (operationalValues.stepValue ?? 1)) - .round(), + divisions: + (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / + (operationalValues.stepValue ?? 1)) + .round(), onChanged: (value) { context.read().add( AddFunction( @@ -368,7 +390,9 @@ class OneGangSwitchHelper { trailing: Icon( isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, size: 24, - color: isSelected ? ColorsManager.primaryColorWithOpacity : ColorsManager.textGray, + color: isSelected + ? ColorsManager.primaryColorWithOpacity + : ColorsManager.textGray, ), onTap: () { if (!isSelected) { diff --git a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart index a83fc4a0..fdc2cb98 100644 --- a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -90,9 +90,27 @@ class ThreeGangSwitchHelper { .read() .add(SelectFunction( functionCode: function.code, - operationName: - function.operationName, + operationName: function.operationName, )); + if (function.code == 'countdown_1' || + function.code == 'countdown_2' || + function.code == 'countdown_3') { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: function.code, + operationName: + function.operationName, + value: 0, + condition: '==', + valueDescription: + selectedFunctionData + .valueDescription, + ), + ), + ); + } }, ); }, @@ -183,8 +201,7 @@ class ThreeGangSwitchHelper { ); } - final selectedFn = - switchFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -266,8 +283,7 @@ class ThreeGangSwitchHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: - conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -316,10 +332,10 @@ class ThreeGangSwitchHelper { value: (initialValue ?? 0).toDouble(), min: operationalValues.minValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: (((operationalValues.maxValue ?? 0) - - (operationalValues.minValue ?? 0)) / - (operationalValues.stepValue ?? 1)) - .round(), + divisions: + (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / + (operationalValues.stepValue ?? 1)) + .round(), onChanged: (value) { context.read().add( AddFunction( @@ -369,9 +385,7 @@ class ThreeGangSwitchHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, size: 24, color: isSelected ? ColorsManager.primaryColorWithOpacity @@ -387,8 +401,7 @@ class ThreeGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: - selectedFunctionData?.valueDescription, + valueDescription: selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart index 2bb6b6e5..14e9f4a7 100644 --- a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart @@ -90,9 +90,26 @@ class TwoGangSwitchHelper { .read() .add(SelectFunction( functionCode: function.code, - operationName: - function.operationName, + operationName: function.operationName, )); + if (function.code == 'countdown_1' || + function.code == 'countdown_2') { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: function.code, + operationName: + function.operationName, + value: 0, + condition: '==', + valueDescription: + selectedFunctionData + .valueDescription, + ), + ), + ); + } }, ); }, @@ -167,8 +184,7 @@ class TwoGangSwitchHelper { required String operationName, required bool removeComparetors, }) { - if (selectedFunction == 'countdown_1' || - selectedFunction == 'countdown_2') { + if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') { final initialValue = selectedFunctionData?.value ?? 0; return _buildTemperatureSelector( context: context, @@ -182,8 +198,7 @@ class TwoGangSwitchHelper { ); } - final selectedFn = - switchFunctions.firstWhere((f) => f.code == selectedFunction); + final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction); final values = selectedFn.getOperationalValues(); return _buildOperationalValuesList( @@ -265,8 +280,7 @@ class TwoGangSwitchHelper { minHeight: 40.0, minWidth: 40.0, ), - isSelected: - conditions.map((c) => c == (currentCondition ?? "==")).toList(), + isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).toList(), ); } @@ -315,10 +329,10 @@ class TwoGangSwitchHelper { value: (initialValue ?? 0).toDouble(), min: operationalValues.minValue?.toDouble() ?? 0.0, max: operationalValues.maxValue?.toDouble() ?? 0.0, - divisions: (((operationalValues.maxValue ?? 0) - - (operationalValues.minValue ?? 0)) / - (operationalValues.stepValue ?? 1)) - .round(), + divisions: + (((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) / + (operationalValues.stepValue ?? 1)) + .round(), onChanged: (value) { context.read().add( AddFunction( @@ -368,9 +382,7 @@ class TwoGangSwitchHelper { style: context.textTheme.bodyMedium, ), trailing: Icon( - isSelected - ? Icons.radio_button_checked - : Icons.radio_button_unchecked, + isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, size: 24, color: isSelected ? ColorsManager.primaryColorWithOpacity @@ -386,8 +398,7 @@ class TwoGangSwitchHelper { operationName: operationName, value: value.value, condition: selectedFunctionData?.condition, - valueDescription: - selectedFunctionData?.valueDescription, + valueDescription: selectedFunctionData?.valueDescription, ), ), ); diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart index 91abb34e..44f1efcc 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart @@ -111,13 +111,23 @@ class _WallPresenceSensorState extends State { return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - _buildFunctionList(context), + _buildFunctionList(context, state), if (state.selectedFunction != null) _buildValueSelector(context, state), ], ); } - Widget _buildFunctionList(BuildContext context) { + Widget _buildFunctionList(BuildContext context, FunctionBlocState state) { + final selectedFunction = state.selectedFunction; + final selectedFunctionData = state.addedFunctions.firstWhere( + (f) => f.functionCode == selectedFunction, + orElse: () => DeviceFunctionData( + entityId: '', + functionCode: selectedFunction ?? '', + operationName: '', + value: null, + ), + ); return SizedBox( width: 360, child: ListView.separated( @@ -149,12 +159,29 @@ class _WallPresenceSensorState extends State { size: 16, color: ColorsManager.textGray, ), - onTap: () => context.read().add( - SelectFunction( - functionCode: function.code, - operationName: function.operationName, - ), - ), + onTap: () { + context.read().add( + SelectFunction( + functionCode: function.code, + operationName: function.operationName, + ), + ); + if (['dis_current', 'presence_time', 'illuminance_value'] + .contains(function.code)) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: widget.device?.uuid ?? '', + functionCode: function.code, + operationName: function.operationName, + value: 0, + condition: '==', + valueDescription: selectedFunctionData.valueDescription, + ), + ), + ); + } + }, ); }, ), From c03b8f290c4ac7799e0e84519c133e8bd6b23f75 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 24 Apr 2025 10:25:41 +0300 Subject: [PATCH 207/238] refactor function tap handlers to use `RoutineTapFunctionHelper` for improved code reuse and readability, and to remove code duplication. --- .../widgets/routine_dialogs/ac_dialog.dart | 42 +++++++-------- .../ceiling_sensor/ceiling_sensor_dialog.dart | 1 + .../ceiling_sensor/cps_functions_list.dart | 53 +++++++++---------- .../helpers/routine_tap_function_helper.dart | 46 ++++++++++++++++ .../one_gang_switch_dialog.dart | 39 +++++--------- .../three_gang_switch_dialog.dart | 43 ++++++--------- .../two_gang_switch_dialog.dart | 41 ++++++-------- .../wall_sensor/wall_presence_sensor.dart | 36 +++++-------- 8 files changed, 149 insertions(+), 152 deletions(-) create mode 100644 lib/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart diff --git a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart index 11bedf24..1de9b0d4 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart @@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -76,30 +77,23 @@ class ACHelper { acFunctions: acFunctions, device: device, onFunctionSelected: (functionCode, operationName) { - context.read().add( - SelectFunction( - functionCode: functionCode, - operationName: operationName, - ), - ); - if (functionCode == 'temp_set' || - functionCode == 'temp_current') { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: functionCode, - operationName: operationName, - value: functionCode == 'temp_set' - ? 200 - : -100, - condition: '==', - valueDescription: selectedFunctionData - .valueDescription, - ), - ), - ); - } + RoutineTapFunctionHelper.onTapFunction( + context, + functionCode: functionCode, + functionOperationName: operationName, + functionValueDescription: + selectedFunctionData.valueDescription, + deviceUuid: device?.uuid, + codesToAddIntoFunctionsWithDefaultValue: [ + 'temp_set', + 'temp_current', + ], + defaultValue: functionCode == 'temp_set' + ? 200 + : functionCode == 'temp_current' + ? -100 + : 0, + ); }, ), ), diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart index 4e174265..f3d07a66 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_dialog.dart @@ -135,6 +135,7 @@ class _CeilingSensorDialogState extends State { cpsFunctions: _cpsFunctions, device: widget.device, selectedFunctionData: selectedFunctionData, + dialogType: widget.dialogType, ), if (state.selectedFunction != null) Expanded( diff --git a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart index 707246c4..efc57653 100644 --- a/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart +++ b/lib/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_functions_list.dart @@ -1,24 +1,24 @@ import 'package:flutter/material.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/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialog_function_list_tile.dart'; -import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CpsFunctionsList extends StatelessWidget { const CpsFunctionsList({ required this.cpsFunctions, - required this.device, - required this.selectedFunctionData, + required this.device, + required this.selectedFunctionData, + required this.dialogType, super.key, }); final List cpsFunctions; final AllDevicesModel? device; final DeviceFunctionData? selectedFunctionData; + final String dialogType; @override Widget build(BuildContext context) { @@ -36,28 +36,27 @@ class CpsFunctionsList extends StatelessWidget { return RoutineDialogFunctionListTile( iconPath: function.icon, operationName: function.operationName, - onTap: () { - context.read().add( - SelectFunction( - functionCode: function.code, - operationName: function.operationName, - ), - ); - if (!CeilingSensorHelper.toggleCodes.contains(function.code)) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: function.code, - operationName: function.operationName, - value: 0, - condition: '==', - valueDescription: selectedFunctionData?.valueDescription, - ), - ), - ); - } - }, + onTap: () => RoutineTapFunctionHelper.onTapFunction( + context, + functionCode: function.code, + functionOperationName: function.operationName, + functionValueDescription: selectedFunctionData?.valueDescription, + deviceUuid: device?.uuid, + codesToAddIntoFunctionsWithDefaultValue: [ + 'static_max_dis', + 'presence_reference', + 'moving_reference', + 'perceptual_boundary', + 'moving_boundary', + 'moving_rigger_time', + 'moving_static_time', + 'none_body_time', + 'moving_max_dis', + 'moving_range', + 'presence_range', + if (dialogType == "IF") 'sensitivity', + ], + ), ); }, ), diff --git a/lib/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart b/lib/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart new file mode 100644 index 00000000..2b09f579 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; + +abstract final class RoutineTapFunctionHelper { + const RoutineTapFunctionHelper._(); + + static void onTapFunction( + BuildContext context, { + required String functionCode, + required String functionOperationName, + required String? functionValueDescription, + required String? deviceUuid, + required List codesToAddIntoFunctionsWithDefaultValue, + int defaultValue = 0, + }) { + final functionsBloc = context.read(); + functionsBloc.add( + SelectFunction( + functionCode: functionCode, + operationName: functionOperationName, + ), + ); + final addedFunctions = functionsBloc.state.addedFunctions; + final isFunctionAlreadyAdded = addedFunctions.any( + (e) => e.functionCode == functionCode, + ); + final shouldAddFunction = + codesToAddIntoFunctionsWithDefaultValue.contains(functionCode); + if (!isFunctionAlreadyAdded && shouldAddFunction) { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: deviceUuid ?? '', + functionCode: functionCode, + operationName: functionOperationName, + value: defaultValue, + condition: '==', + valueDescription: functionValueDescription, + ), + ), + ); + } + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart index a2d6d8db..3c786045 100644 --- a/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart @@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/ import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -86,31 +87,19 @@ class OneGangSwitchHelper { size: 16, color: ColorsManager.textGray, ), - onTap: () { - context - .read() - .add(SelectFunction( - functionCode: function.code, - operationName: function.operationName, - )); - if (function.code == 'countdown_1') { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: function.code, - operationName: - function.operationName, - value: 0, - condition: '==', - valueDescription: - selectedFunctionData - .valueDescription, - ), - ), - ); - } - }, + onTap: () => + RoutineTapFunctionHelper.onTapFunction( + context, + functionCode: function.code, + functionOperationName: + function.operationName, + functionValueDescription: + selectedFunctionData.valueDescription, + deviceUuid: device?.uuid, + codesToAddIntoFunctionsWithDefaultValue: [ + 'countdown_1', + ], + ), ); }, ), diff --git a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart index fdc2cb98..44a367ef 100644 --- a/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart @@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -85,33 +86,21 @@ class ThreeGangSwitchHelper { size: 16, color: ColorsManager.textGray, ), - onTap: () { - context - .read() - .add(SelectFunction( - functionCode: function.code, - operationName: function.operationName, - )); - if (function.code == 'countdown_1' || - function.code == 'countdown_2' || - function.code == 'countdown_3') { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: function.code, - operationName: - function.operationName, - value: 0, - condition: '==', - valueDescription: - selectedFunctionData - .valueDescription, - ), - ), - ); - } - }, + onTap: () => + RoutineTapFunctionHelper.onTapFunction( + context, + functionCode: function.code, + functionOperationName: + function.operationName, + functionValueDescription: + selectedFunctionData.valueDescription, + deviceUuid: device?.uuid, + codesToAddIntoFunctionsWithDefaultValue: [ + 'countdown_1', + 'countdown_2', + 'countdown_3', + ], + ), ); }, ), diff --git a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart index 14e9f4a7..f551d21b 100644 --- a/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart +++ b/lib/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart @@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_func import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -85,32 +86,20 @@ class TwoGangSwitchHelper { size: 16, color: ColorsManager.textGray, ), - onTap: () { - context - .read() - .add(SelectFunction( - functionCode: function.code, - operationName: function.operationName, - )); - if (function.code == 'countdown_1' || - function.code == 'countdown_2') { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: device?.uuid ?? '', - functionCode: function.code, - operationName: - function.operationName, - value: 0, - condition: '==', - valueDescription: - selectedFunctionData - .valueDescription, - ), - ), - ); - } - }, + onTap: () => + RoutineTapFunctionHelper.onTapFunction( + context, + functionCode: function.code, + functionOperationName: + function.operationName, + functionValueDescription: + selectedFunctionData.valueDescription, + deviceUuid: device?.uuid, + codesToAddIntoFunctionsWithDefaultValue: [ + 'countdown_1', + 'countdown_2', + ], + ), ); }, ), diff --git a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart index 44f1efcc..4d04102d 100644 --- a/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart +++ b/lib/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_value_selector_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -159,29 +160,18 @@ class _WallPresenceSensorState extends State { size: 16, color: ColorsManager.textGray, ), - onTap: () { - context.read().add( - SelectFunction( - functionCode: function.code, - operationName: function.operationName, - ), - ); - if (['dis_current', 'presence_time', 'illuminance_value'] - .contains(function.code)) { - context.read().add( - AddFunction( - functionData: DeviceFunctionData( - entityId: widget.device?.uuid ?? '', - functionCode: function.code, - operationName: function.operationName, - value: 0, - condition: '==', - valueDescription: selectedFunctionData.valueDescription, - ), - ), - ); - } - }, + onTap: () => RoutineTapFunctionHelper.onTapFunction( + context, + functionCode: function.code, + functionOperationName: function.operationName, + functionValueDescription: selectedFunctionData.valueDescription, + deviceUuid: widget.device?.uuid, + codesToAddIntoFunctionsWithDefaultValue: [ + 'dis_current', + 'presence_time', + 'illuminance_value', + ], + ), ); }, ), From 75b0b245433219b278473917640cb9e7babdc7d0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 24 Apr 2025 14:13:13 +0300 Subject: [PATCH 208/238] Add Flush Mounted Presence Sensor support and update event handling --- .../helper/route_controls_based_code.dart | 5 ++ .../flush_mounted_presence_sensor_bloc.dart | 56 ++++++++++++------- .../flush_mounted_presence_sensor_event.dart | 10 ++++ ...ed_presence_sensor_batch_control_view.dart | 23 +++++--- 4 files changed, 65 insertions(+), 29 deletions(-) diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 81405451..5586a310 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart'; @@ -198,6 +199,10 @@ mixin RouteControlsBasedCode { return SOSBatchControlView( deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(), ); + case 'NCPS': + return FlushMountedPresenceSensorBatchControlView( + devicesIds: devices.where((e) => (e.productType == 'NCPS')).map((e) => e.uuid!).toList(), + ); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart index 0bc15cd2..d692ed2b 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart @@ -49,6 +49,9 @@ class FlushMountedPresenceSensorBloc on( _onFlushMountedPresenceSensorFactoryResetEvent, ); + on( + _onFlushMountedPresenceSensorStatusUpdatedEvent, + ); } void _onFlushMountedPresenceSensorFetchStatusEvent( @@ -60,7 +63,7 @@ class FlushMountedPresenceSensorBloc final response = await DevicesManagementApi().getDeviceStatus(deviceId); deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status); emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); - _listenToChanges(emit, deviceId); + _listenToChanges(deviceId); } catch (e) { emit(FlushMountedPresenceSensorFailedState(error: e.toString())); return; @@ -76,36 +79,39 @@ class FlushMountedPresenceSensorBloc final response = await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status); emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + _listenToChanges(event.devicesIds.first); } catch (e) { emit(FlushMountedPresenceSensorFailedState(error: e.toString())); } } - Future _listenToChanges( - Emitter emit, - String deviceId, - ) async { - final ref = FirebaseDatabase.instance.ref( - 'device-status/$deviceId', - ); + void _listenToChanges(String deviceId) { + try { + final ref = FirebaseDatabase.instance.ref( + 'device-status/$deviceId', + ); + + ref.onValue.listen((event) { + final eventsMap = event.snapshot.value as Map; - await ref.onValue.listen( - (DatabaseEvent event) async { - Map usersMap = - event.snapshot.value as Map; List statusList = []; - - (usersMap['status'] as List?)?.forEach((element) { - statusList.add(Status(code: element['code'], value: element['value'])); + eventsMap['status'].forEach((element) { + statusList.add( + Status(code: element['code'], value: element['value']), + ); }); - + deviceStatus = FlushMountedPresenceSensorModel.fromJson(statusList); - if (!emit.isDone) { - emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); + if (!isClosed) { + add(FlushMountedPresenceSensorStatusUpdatedEvent(deviceStatus)); } - }, - onError: (error) => log(error.toString(), name: 'FirebaseDatabaseError'), - ).asFuture(); + }); + } catch (_) { + log( + 'Error listening to changes', + name: 'FlushMountedPresenceSensorBloc._listenToChanges', + ); + } } void _onFlushMountedPresenceSensorChangeValueEvent( @@ -234,4 +240,12 @@ class FlushMountedPresenceSensorBloc emit(FlushMountedPresenceSensorFailedState(error: e.toString())); } } + + void _onFlushMountedPresenceSensorStatusUpdatedEvent( + FlushMountedPresenceSensorStatusUpdatedEvent event, + Emitter emit, + ) { + deviceStatus = event.model; + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + } } diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart index f1636300..f70e7f3a 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart @@ -10,6 +10,16 @@ sealed class FlushMountedPresenceSensorEvent extends Equatable { class FlushMountedPresenceSensorFetchStatusEvent extends FlushMountedPresenceSensorEvent {} +class FlushMountedPresenceSensorStatusUpdatedEvent + extends FlushMountedPresenceSensorEvent { + const FlushMountedPresenceSensorStatusUpdatedEvent(this.model); + + final FlushMountedPresenceSensorModel model; + + @override + List get props => [model]; +} + class FlushMountedPresenceSensorChangeValueEvent extends FlushMountedPresenceSensorEvent { final int value; diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart index 7f1e726c..25cbba09 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart @@ -67,7 +67,8 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget maxValue: 9, steps: 1, action: (int value) => context.read().add( - FlushMountedPresenceSensorChangeValueEvent( + FlushMountedPresenceSensorBatchControlEvent( + deviceIds: devicesIds, code: FlushMountedPresenceSensorModel.codeSensitivity, value: value, ), @@ -83,7 +84,8 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget valuesPercision: 1, action: (double value) => context.read().add( - FlushMountedPresenceSensorChangeValueEvent( + FlushMountedPresenceSensorBatchControlEvent( + deviceIds: devicesIds, code: FlushMountedPresenceSensorModel.codeNearDetection, value: (value * 100).toInt(), ), @@ -99,7 +101,8 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget valuesPercision: 1, action: (double value) => context.read().add( - FlushMountedPresenceSensorChangeValueEvent( + FlushMountedPresenceSensorBatchControlEvent( + deviceIds: devicesIds, code: FlushMountedPresenceSensorModel.codeFarDetection, value: (value * 100).toInt(), ), @@ -112,20 +115,22 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget maxValue: 3, steps: 1, action: (int value) => context.read().add( - FlushMountedPresenceSensorChangeValueEvent( + FlushMountedPresenceSensorBatchControlEvent( + deviceIds: devicesIds, code: FlushMountedPresenceSensorModel.codePresenceDelay, value: value, ), ), ), PresenceUpdateData( - value: (model.occurDistReduce.toDouble()), + value: model.occurDistReduce.toDouble(), title: 'Indent Level:', minValue: 0, maxValue: 3, steps: 1, action: (int value) => context.read().add( - FlushMountedPresenceSensorChangeValueEvent( + FlushMountedPresenceSensorBatchControlEvent( + deviceIds: devicesIds, code: FlushMountedPresenceSensorModel.codeOccurDistReduce, value: value, ), @@ -139,7 +144,8 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget maxValue: 3, steps: 1, action: (int value) => context.read().add( - FlushMountedPresenceSensorChangeValueEvent( + FlushMountedPresenceSensorBatchControlEvent( + deviceIds: devicesIds, code: FlushMountedPresenceSensorModel.codeSensiReduce, value: value, ), @@ -154,7 +160,8 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget steps: 1, action: (double value) => context.read().add( - FlushMountedPresenceSensorChangeValueEvent( + FlushMountedPresenceSensorBatchControlEvent( + deviceIds: devicesIds, code: FlushMountedPresenceSensorModel.codeNoneDelay, value: (value * 10).round(), ), From ba20998067fc0a6aad9ba38c04ff01f63a6e0de4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 24 Apr 2025 14:21:38 +0300 Subject: [PATCH 209/238] disabled realtime on batch control. --- .../bloc/flush_mounted_presence_sensor_bloc.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart index d692ed2b..aea800dd 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart @@ -79,7 +79,6 @@ class FlushMountedPresenceSensorBloc final response = await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status); emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); - _listenToChanges(event.devicesIds.first); } catch (e) { emit(FlushMountedPresenceSensorFailedState(error: e.toString())); } From c8e540e9383d3c39bd64ce1a084ae1b0e6180623 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 24 Apr 2025 14:29:18 +0300 Subject: [PATCH 210/238] Remove unnecessary event dispatch in FlushMountedPresenceSensorBlocFactory creation --- .../factories/flush_mounted_presence_sensor_bloc_factory.dart | 2 +- .../views/flush_mounted_presence_sensor_batch_control_view.dart | 2 +- .../views/flush_mounted_presence_sensor_control_view.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart index f1342eec..49fb517f 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart @@ -16,6 +16,6 @@ abstract final class FlushMountedPresenceSensorBlocFactory { batchControlDevicesService: DebouncedBatchControlDevicesService( decoratee: RemoteBatchControlDevicesService(), ), - )..add(FlushMountedPresenceSensorFetchStatusEvent()); + ); } } diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart index 25cbba09..78db6e2e 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart @@ -22,7 +22,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget return BlocProvider( create: (context) => FlushMountedPresenceSensorBlocFactory.create( deviceId: devicesIds.first, - ), + )..add(FlushMountedPresenceSensorFetchBatchStatusEvent(devicesIds)), child: BlocBuilder( builder: (context, state) { diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart index bdefa507..f0815c60 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart @@ -24,7 +24,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget return BlocProvider( create: (context) => FlushMountedPresenceSensorBlocFactory.create( deviceId: device.uuid ?? '-1', - ), + )..add(FlushMountedPresenceSensorFetchStatusEvent()), child: BlocBuilder( builder: (context, state) { From 778257644d003c900515e7248ee823199dca433f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 24 Apr 2025 15:13:10 +0300 Subject: [PATCH 211/238] reduced debounce duration. --- lib/services/batch_control_devices_service.dart | 2 +- lib/services/control_device_service.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/batch_control_devices_service.dart b/lib/services/batch_control_devices_service.dart index 76dbe480..de5af9ee 100644 --- a/lib/services/batch_control_devices_service.dart +++ b/lib/services/batch_control_devices_service.dart @@ -51,7 +51,7 @@ final class DebouncedBatchControlDevicesService DebouncedBatchControlDevicesService({ required this.decoratee, - this.debounceDuration = const Duration(milliseconds: 1500), + this.debounceDuration = const Duration(milliseconds: 800), }); @override diff --git a/lib/services/control_device_service.dart b/lib/services/control_device_service.dart index ab04a398..9913b52e 100644 --- a/lib/services/control_device_service.dart +++ b/lib/services/control_device_service.dart @@ -40,7 +40,7 @@ final class DebouncedControlDeviceService implements ControlDeviceService { DebouncedControlDeviceService({ required this.decoratee, - this.debounceDuration = const Duration(milliseconds: 1500), + this.debounceDuration = const Duration(milliseconds: 800), }); final _pendingRequests = <(String deviceUuid, Status status)>[]; From 09fb604acc86d378a0f0dc91470233954904145b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 25 Apr 2025 10:49:25 +0400 Subject: [PATCH 212/238] added filtering --- .../all_spaces/widgets/sidebar_widget.dart | 93 ++++++++++--------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 6dc8ae9c..374f0a0a 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/common/widgets/empty_search_result_widget.dart'; import 'package:syncrow_web/common/widgets/search_bar.dart'; import 'package:syncrow_web/common/widgets/sidebar_communities_list.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; @@ -15,6 +16,8 @@ import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/cent import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart'; import 'package:syncrow_web/utils/style.dart'; +import '../../../space_tree/bloc/space_tree_event.dart'; + class SidebarWidget extends StatefulWidget { final List communities; final String? selectedSpaceUuid; @@ -40,13 +43,31 @@ class _SidebarWidgetState extends State { @override void initState() { - _selectedId = widget.selectedSpaceUuid; _scrollController = ScrollController(); + _scrollController.addListener(_onScroll); + _selectedId = widget.selectedSpaceUuid; super.initState(); } + void _onScroll() { + if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) { + // Trigger pagination event + final bloc = context.read(); + if (!bloc.state.paginationIsLoading && bloc.state.paginationModel?.hasNext == true) { + bloc.add( + PaginationEvent( + bloc.state.paginationModel!, + bloc.state.communityList, + ), + ); + } + } + } + @override void dispose() { + _scrollController.removeListener(_onScroll); + _scrollController.dispose(); super.dispose(); } @@ -59,38 +80,6 @@ class _SidebarWidgetState extends State { super.didUpdateWidget(oldWidget); } - List _filteredCommunities() { - if (_searchQuery.isEmpty) { - _selectedSpaceUuid = null; - return widget.communities; - } - - return widget.communities.where((community) { - final containsQueryInCommunity = - community.name.toLowerCase().contains(_searchQuery.toLowerCase()); - final containsQueryInSpaces = community.spaces.any((space) => - _containsQuery(space: space, query: _searchQuery.toLowerCase())); - - return containsQueryInCommunity || containsQueryInSpaces; - }).toList(); - } - - bool _containsQuery({ - required SpaceModel space, - required String query, - }) { - final matchesSpace = space.name.toLowerCase().contains(query); - final matchesChildren = space.children.any( - (child) => _containsQuery(space: child, query: query), - ); - - if (matchesSpace || matchesChildren) { - _selectedSpaceUuid = space.uuid; - } - - return matchesSpace || matchesChildren; - } - bool _isSpaceOrChildSelected(SpaceModel space) { final isSpaceSelected = _selectedSpaceUuid == space.uuid; final anySubSpaceIsSelected = space.children.any(_isSpaceOrChildSelected); @@ -101,7 +90,10 @@ class _SidebarWidgetState extends State { @override Widget build(BuildContext context) { - final filteredCommunities = _filteredCommunities(); + final spaceTreeState = context.watch().state; + final filteredCommunities = spaceTreeState.isSearching + ? spaceTreeState.filteredCommunity + : spaceTreeState.communityList; return Container( width: _width, @@ -112,7 +104,13 @@ class _SidebarWidgetState extends State { children: [ SidebarHeader(onAddCommunity: _onAddCommunity), CustomSearchBar( - onSearchChanged: (query) => setState(() => _searchQuery = query), + onSearchChanged: (query) { + setState(() { + _searchQuery = query; + }); + + context.read().add(SearchQueryEvent(query)); + }, ), const SizedBox(height: 16), Expanded( @@ -120,14 +118,18 @@ class _SidebarWidgetState extends State { visible: filteredCommunities.isNotEmpty, replacement: const EmptySearchResultWidget(), child: SidebarCommunitiesList( - scrollController: _scrollController, - onScrollToEnd: () {}, - communities: filteredCommunities, - itemBuilder: (context, index) => _buildCommunityTile( - context, - filteredCommunities[index], - ), - ), + scrollController: _scrollController, + onScrollToEnd: () {}, + communities: filteredCommunities, + itemBuilder: (context, index) { + if (index == filteredCommunities.length) { + return const Padding( + padding: EdgeInsets.all(8.0), + child: Center(child: CircularProgressIndicator()), + ); + } + return _buildCommunityTile(context, filteredCommunities[index]); + }), ), ), ], @@ -205,9 +207,8 @@ class _SidebarWidgetState extends State { ); } - void _onAddCommunity() => _selectedId?.isNotEmpty ?? true - ? _clearSelection() - : _showCreateCommunityDialog(); + void _onAddCommunity() => + _selectedId?.isNotEmpty ?? true ? _clearSelection() : _showCreateCommunityDialog(); void _clearSelection() { setState(() => _selectedId = ''); From ff07e7509de5c0e95ee54bb112f259b43b889c7d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sat, 26 Apr 2025 16:04:41 +0400 Subject: [PATCH 213/238] fixed the issue in aligning child space --- .../widgets/community_structure_widget.dart | 183 +++++++++++++----- 1 file changed, 136 insertions(+), 47 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 70b86074..5fb648e3 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -336,6 +336,7 @@ class _CommunityStructureAreaState extends State { } spaces.add(newSpace); _updateNodePosition(newSpace, newSpace.position); + realignTree(); }); }, ); @@ -450,7 +451,6 @@ class _CommunityStructureAreaState extends State { void _saveSpaces() { if (widget.selectedCommunity == null) { - debugPrint("No community selected for saving spaces."); return; } @@ -530,34 +530,82 @@ class _CommunityStructureAreaState extends State { } Offset getBalancedChildPosition(SpaceModel parent) { - int totalSiblings = parent.children.length + 1; - double totalWidth = (totalSiblings - 1) * 250; // Horizontal spacing - double startX = parent.position.dx - (totalWidth / 2); + const double nodeWidth = 200; + const double verticalGap = 180; - Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180); + if (parent.children.isEmpty) { + // First child → exactly center + return Offset(parent.position.dx, parent.position.dy + verticalGap); + } else { + // More children → arrange them spaced horizontally + double totalWidth = (parent.children.length) * (nodeWidth + 60); + double startX = parent.position.dx - (totalWidth / 2); - // Check for overlaps & adjust - while (spaces.any((s) => (s.position - position).distance < 250)) { - position = Offset(position.dx + 250, position.dy); + double childX = startX + (parent.children.length * (nodeWidth + 60)); + return Offset(childX, parent.position.dy + verticalGap); } - - return position; } void realignTree() { - void updatePositions(SpaceModel node, double x, double y) { - node.position = Offset(x, y); + const double nodeWidth = 200; + const double nodeHeight = 100; + const double horizontalGap = 60; + const double verticalGap = 180; - int numChildren = node.children.length; - double childStartX = x - ((numChildren - 1) * 250) / 2; + double canvasRightEdge = 1000; + double canvasBottomEdge = 1000; - for (int i = 0; i < numChildren; i++) { - updatePositions(node.children[i], childStartX + (i * 250), y + 180); + double _calculateSubtreeWidth(SpaceModel node) { + if (node.children.isEmpty) return nodeWidth; + double totalWidth = 0; + for (var child in node.children) { + totalWidth += _calculateSubtreeWidth(child) + horizontalGap; + } + return totalWidth - horizontalGap; + } + + void _layoutTree(SpaceModel node, double startX, double y) { + double subtreeWidth = _calculateSubtreeWidth(node); + double centerX = startX + subtreeWidth / 2 - nodeWidth / 2; + node.position = Offset(centerX, y); + + canvasRightEdge = + centerX + nodeWidth > canvasRightEdge ? centerX + nodeWidth : canvasRightEdge; + canvasBottomEdge = y + nodeHeight > canvasBottomEdge ? y + nodeHeight : canvasBottomEdge; + + if (node.children.length == 1) { + final child = node.children.first; + double parentCenterX = node.position.dx + nodeWidth / 2; + double childX = parentCenterX - nodeWidth / 2; + double childY = y + verticalGap; + + child.position = Offset(childX, childY); + _layoutTree(child, childX, childY); + } else { + double childX = startX; + for (var child in node.children) { + double childWidth = _calculateSubtreeWidth(child); + _layoutTree(child, childX, y + verticalGap); + childX += childWidth + horizontalGap; + } } } - if (spaces.isNotEmpty) { - updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy); + final SpaceModel? root = spaces.firstWhere( + (s) => s.parent == null, + orElse: () => spaces.first, + ); + + if (root != null) { + double totalTreeWidth = _calculateSubtreeWidth(root); + double startingX = (canvasWidth / 2) - (totalTreeWidth / 2); + + _layoutTree(root, startingX, 100); + + setState(() { + canvasWidth = canvasRightEdge + 200; + canvasHeight = canvasBottomEdge + 200; + }); } } @@ -650,38 +698,87 @@ class _CommunityStructureAreaState extends State { /// **Find a new position ensuring no overlap** Offset getBalancedChildPosition(SpaceModel parent) { - int totalSiblings = parent.children.length + 1; - double totalWidth = (totalSiblings - 1) * horizontalGap; - double startX = parent.position.dx - (totalWidth / 2); - Offset position = Offset( - startX + (parent.children.length * horizontalGap), parent.position.dy + verticalGap); + const double nodeWidth = 200; + const double horizontalGap = 60; + const double verticalGap = 180; - // **Check for overlaps & adjust** - while (spaces.any((s) => (s.position - position).distance < horizontalGap)) { - position = Offset(position.dx + horizontalGap, position.dy); - } + int numSiblings = parent.children.length; + double totalWidth = numSiblings * (nodeWidth + horizontalGap); - print("🔹 New position for ${parent.name}: (${position.dx}, ${position.dy})"); - return position; + // Calculate position directly beneath the parent, spaced horizontally + double x = parent.position.dx - (totalWidth / 2) + numSiblings * (nodeWidth + horizontalGap); + double y = parent.position.dy + verticalGap; + + return Offset(x, y); } /// **Realign the entire tree after duplication** void realignTree() { - void updatePositions(SpaceModel node, double x, double y) { - node.position = Offset(x, y); - print("✅ Adjusted ${node.name} to (${x}, ${y})"); + const double nodeWidth = 200; + const double nodeHeight = 100; + const double horizontalGap = 60; + const double verticalGap = 180; - int numChildren = node.children.length; - double childStartX = x - ((numChildren - 1) * horizontalGap) / 2; + double canvasRightEdge = 1000; + double canvasBottomEdge = 1000; - for (int i = 0; i < numChildren; i++) { - updatePositions(node.children[i], childStartX + (i * horizontalGap), y + verticalGap); + // Step 1: Calculate the width of any subtree + double _calculateSubtreeWidth(SpaceModel node) { + if (node.children.isEmpty) return nodeWidth; + double totalWidth = 0; + for (var child in node.children) { + totalWidth += _calculateSubtreeWidth(child) + horizontalGap; + } + return totalWidth - horizontalGap; + } + + void _layoutTree(SpaceModel node, double startX, double y) { + double subtreeWidth = _calculateSubtreeWidth(node); + double centerX = startX + subtreeWidth / 2 - nodeWidth / 2; + node.position = Offset(centerX, y); + + canvasRightEdge = + centerX + nodeWidth > canvasRightEdge ? centerX + nodeWidth : canvasRightEdge; + canvasBottomEdge = y + nodeHeight > canvasBottomEdge ? y + nodeHeight : canvasBottomEdge; + + if (node.children.length == 1) { + final child = node.children.first; + + double parentCenterX = node.position.dx + nodeWidth / 2; + double childX = parentCenterX - nodeWidth / 2; + double childY = y + verticalGap; + + child.position = Offset(childX, childY); + + _layoutTree(child, childX, childY); + } else { + double childX = startX; + for (var child in node.children) { + double childWidth = _calculateSubtreeWidth(child); + _layoutTree(child, childX, y + verticalGap); + childX += childWidth + horizontalGap; + } } } - if (spaces.isNotEmpty) { - print("🔄 Realigning tree..."); - updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy); + // Step 3: Start centered + final SpaceModel? root = spaces.firstWhere( + (s) => s.parent == null, + orElse: () => spaces.first, + ); + + if (root != null) { + // Calculate total width of full tree + double totalTreeWidth = _calculateSubtreeWidth(root); + + double startingX = (canvasWidth / 2) - (totalTreeWidth / 2); + + _layoutTree(root, startingX, 100); + + setState(() { + canvasWidth = canvasRightEdge + 200; // give breathing space + canvasHeight = canvasBottomEdge + 200; + }); } } @@ -692,8 +789,6 @@ class _CommunityStructureAreaState extends State { : getBalancedChildPosition(duplicatedParent); final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); - print( - "🟡 Duplicating ${original.name} → ${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})"); final duplicated = SpaceModel( name: duplicatedName, @@ -722,7 +817,6 @@ class _CommunityStructureAreaState extends State { duplicated.incomingConnection = newConnection; duplicatedParent.addOutgoingConnection(newConnection); duplicatedParent.children.add(duplicated); - print("🔗 Created connection: ${duplicatedParent.name} → ${duplicated.name}"); } // **Recalculate the whole tree to avoid overlaps** @@ -739,19 +833,14 @@ class _CommunityStructureAreaState extends State { /// **Handle root duplication** if (space.parent == null) { - print("🟠 Duplicating root node: ${space.name}"); SpaceModel duplicatedRoot = duplicateRecursive(space, null); setState(() { spaces.add(duplicatedRoot); realignTree(); }); - - print("✅ Root duplication successful: ${duplicatedRoot.name}"); } else { duplicateRecursive(space, space.parent); } - - print("🟢 Finished duplication process for: ${space.name}"); } } From 976d6e385a23830cb14dfc752f3ebcbf1f9b87ee Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 27 Apr 2025 11:12:03 +0400 Subject: [PATCH 214/238] duplicated space --- .../widgets/community_structure_widget.dart | 245 +++++++----------- 1 file changed, 91 insertions(+), 154 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 5fb648e3..6d742e69 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -1,4 +1,6 @@ // Flutter imports +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -551,62 +553,62 @@ class _CommunityStructureAreaState extends State { const double nodeHeight = 100; const double horizontalGap = 60; const double verticalGap = 180; + const double rootGap = 400; // extra space between different roots double canvasRightEdge = 1000; double canvasBottomEdge = 1000; - double _calculateSubtreeWidth(SpaceModel node) { + double calculateSubtreeWidth(SpaceModel node) { if (node.children.isEmpty) return nodeWidth; double totalWidth = 0; for (var child in node.children) { - totalWidth += _calculateSubtreeWidth(child) + horizontalGap; + totalWidth += calculateSubtreeWidth(child) + horizontalGap; } return totalWidth - horizontalGap; } - void _layoutTree(SpaceModel node, double startX, double y) { - double subtreeWidth = _calculateSubtreeWidth(node); + void layoutSubtree(SpaceModel node, double startX, double y) { + double subtreeWidth = calculateSubtreeWidth(node); double centerX = startX + subtreeWidth / 2 - nodeWidth / 2; node.position = Offset(centerX, y); - canvasRightEdge = - centerX + nodeWidth > canvasRightEdge ? centerX + nodeWidth : canvasRightEdge; - canvasBottomEdge = y + nodeHeight > canvasBottomEdge ? y + nodeHeight : canvasBottomEdge; + canvasRightEdge = max(canvasRightEdge, centerX + nodeWidth); + canvasBottomEdge = max(canvasBottomEdge, y + nodeHeight); if (node.children.length == 1) { final child = node.children.first; - double parentCenterX = node.position.dx + nodeWidth / 2; - double childX = parentCenterX - nodeWidth / 2; - double childY = y + verticalGap; - - child.position = Offset(childX, childY); - _layoutTree(child, childX, childY); + layoutSubtree(child, centerX, y + verticalGap); } else { double childX = startX; for (var child in node.children) { - double childWidth = _calculateSubtreeWidth(child); - _layoutTree(child, childX, y + verticalGap); + double childWidth = calculateSubtreeWidth(child); + layoutSubtree(child, childX, y + verticalGap); childX += childWidth + horizontalGap; } } } - final SpaceModel? root = spaces.firstWhere( - (s) => s.parent == null, - orElse: () => spaces.first, - ); + // ⚡ New: layout each root separately + final List roots = spaces + .where((s) => + s.parent == null && + s.status != SpaceStatus.deleted && + s.status != SpaceStatus.parentDeleted) + .toList(); - if (root != null) { - double totalTreeWidth = _calculateSubtreeWidth(root); - double startingX = (canvasWidth / 2) - (totalTreeWidth / 2); + double currentX = 100; // start some margin from left + double currentY = 100; // top margin - _layoutTree(root, startingX, 100); - - setState(() { - canvasWidth = canvasRightEdge + 200; - canvasHeight = canvasBottomEdge + 200; - }); + for (var root in roots) { + layoutSubtree(root, currentX, currentY); + double rootWidth = calculateSubtreeWidth(root); + currentX += rootWidth + rootGap; } + + setState(() { + canvasWidth = canvasRightEdge + 400; + canvasHeight = canvasBottomEdge + 400; + }); } void _onDuplicate(BuildContext parentContext) { @@ -690,110 +692,19 @@ class _CommunityStructureAreaState extends State { } void _duplicateSpace(SpaceModel space) { - final Map originalToDuplicate = {}; - double horizontalGap = 250.0; // Increased spacing - double verticalGap = 180.0; // Adjusted for better visualization + final double horizontalGap = 250.0; + final double verticalGap = 180.0; + final double nodeWidth = 200; + final double nodeHeight = 100; + final double breathingSpace = 300.0; // extra gap after original tree - print("🟢 Duplicating: ${space.name}"); - - /// **Find a new position ensuring no overlap** - Offset getBalancedChildPosition(SpaceModel parent) { - const double nodeWidth = 200; - const double horizontalGap = 60; - const double verticalGap = 180; - - int numSiblings = parent.children.length; - double totalWidth = numSiblings * (nodeWidth + horizontalGap); - - // Calculate position directly beneath the parent, spaced horizontally - double x = parent.position.dx - (totalWidth / 2) + numSiblings * (nodeWidth + horizontalGap); - double y = parent.position.dy + verticalGap; - - return Offset(x, y); - } - - /// **Realign the entire tree after duplication** - void realignTree() { - const double nodeWidth = 200; - const double nodeHeight = 100; - const double horizontalGap = 60; - const double verticalGap = 180; - - double canvasRightEdge = 1000; - double canvasBottomEdge = 1000; - - // Step 1: Calculate the width of any subtree - double _calculateSubtreeWidth(SpaceModel node) { - if (node.children.isEmpty) return nodeWidth; - double totalWidth = 0; - for (var child in node.children) { - totalWidth += _calculateSubtreeWidth(child) + horizontalGap; - } - return totalWidth - horizontalGap; - } - - void _layoutTree(SpaceModel node, double startX, double y) { - double subtreeWidth = _calculateSubtreeWidth(node); - double centerX = startX + subtreeWidth / 2 - nodeWidth / 2; - node.position = Offset(centerX, y); - - canvasRightEdge = - centerX + nodeWidth > canvasRightEdge ? centerX + nodeWidth : canvasRightEdge; - canvasBottomEdge = y + nodeHeight > canvasBottomEdge ? y + nodeHeight : canvasBottomEdge; - - if (node.children.length == 1) { - final child = node.children.first; - - double parentCenterX = node.position.dx + nodeWidth / 2; - double childX = parentCenterX - nodeWidth / 2; - double childY = y + verticalGap; - - child.position = Offset(childX, childY); - - _layoutTree(child, childX, childY); - } else { - double childX = startX; - for (var child in node.children) { - double childWidth = _calculateSubtreeWidth(child); - _layoutTree(child, childX, y + verticalGap); - childX += childWidth + horizontalGap; - } - } - } - - // Step 3: Start centered - final SpaceModel? root = spaces.firstWhere( - (s) => s.parent == null, - orElse: () => spaces.first, - ); - - if (root != null) { - // Calculate total width of full tree - double totalTreeWidth = _calculateSubtreeWidth(root); - - double startingX = (canvasWidth / 2) - (totalTreeWidth / 2); - - _layoutTree(root, startingX, 100); - - setState(() { - canvasWidth = canvasRightEdge + 200; // give breathing space - canvasHeight = canvasBottomEdge + 200; - }); - } - } - - /// **Recursive duplication logic** + /// Helper to recursively duplicate a node and its children SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) { - Offset newPosition = duplicatedParent == null - ? Offset(original.position.dx + horizontalGap, original.position.dy) - : getBalancedChildPosition(duplicatedParent); - final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); - final duplicated = SpaceModel( name: duplicatedName, icon: original.icon, - position: newPosition, + position: Offset.zero, isPrivate: original.isPrivate, children: [], status: SpaceStatus.newSpace, @@ -803,27 +714,20 @@ class _CommunityStructureAreaState extends State { tags: original.tags, ); - setState(() { - spaces.add(duplicated); - _updateNodePosition(duplicated, duplicated.position); + spaces.add(duplicated); - if (duplicatedParent != null) { - final newConnection = Connection( - startSpace: duplicatedParent, - endSpace: duplicated, - direction: "down", - ); - connections.add(newConnection); - duplicated.incomingConnection = newConnection; - duplicatedParent.addOutgoingConnection(newConnection); - duplicatedParent.children.add(duplicated); - } + if (duplicatedParent != null) { + final newConnection = Connection( + startSpace: duplicatedParent, + endSpace: duplicated, + direction: "down", + ); + connections.add(newConnection); + duplicated.incomingConnection = newConnection; + duplicatedParent.addOutgoingConnection(newConnection); + duplicatedParent.children.add(duplicated); + } - // **Recalculate the whole tree to avoid overlaps** - realignTree(); - }); - - // Recursively duplicate children for (var child in original.children) { duplicateRecursive(child, duplicated); } @@ -831,16 +735,49 @@ class _CommunityStructureAreaState extends State { return duplicated; } - /// **Handle root duplication** - if (space.parent == null) { - SpaceModel duplicatedRoot = duplicateRecursive(space, null); + /// Layout a subtree rooted at node + void layoutSubtree(SpaceModel node, double startX, double startY) { + double calculateSubtreeWidth(SpaceModel n) { + if (n.children.isEmpty) return nodeWidth; + double width = 0; + for (var child in n.children) { + width += calculateSubtreeWidth(child) + horizontalGap; + } + return width - horizontalGap; + } - setState(() { - spaces.add(duplicatedRoot); - realignTree(); - }); - } else { - duplicateRecursive(space, space.parent); + void assignPositions(SpaceModel n, double x, double y) { + double subtreeWidth = calculateSubtreeWidth(n); + double centerX = x + subtreeWidth / 2 - nodeWidth / 2; + n.position = Offset(centerX, y); + + if (n.children.length == 1) { + assignPositions(n.children.first, centerX, y + verticalGap); + } else { + double childX = x; + for (var child in n.children) { + double childWidth = calculateSubtreeWidth(child); + assignPositions(child, childX, y + verticalGap); + childX += childWidth + horizontalGap; + } + } + } + + double totalSubtreeWidth = calculateSubtreeWidth(node); + assignPositions(node, startX, startY); } + + /// Actual duplication process + setState(() { + if (space.parent == null) { + // Duplicating a ROOT node + SpaceModel duplicatedRoot = duplicateRecursive(space, null); + realignTree(); + } else { + // Duplicating a CHILD node inside its parent + SpaceModel duplicated = duplicateRecursive(space, space.parent); + realignTree(); + } + }); } } From 756457927c51337a3f57561b17e016878b3f0784 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Apr 2025 10:13:53 +0300 Subject: [PATCH 215/238] removed unnecessary `isBatch` flag from `FlushMountedPresenceSensorChangeValueEvent`. --- .../bloc/flush_mounted_presence_sensor_event.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart index f70e7f3a..08a01615 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart @@ -24,11 +24,10 @@ class FlushMountedPresenceSensorChangeValueEvent extends FlushMountedPresenceSensorEvent { final int value; final String code; - final bool isBatchControl; + const FlushMountedPresenceSensorChangeValueEvent({ required this.value, required this.code, - this.isBatchControl = false, }); @override From 6fc35a7b9a36ed1d992c6256762ba72227e2d245 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Apr 2025 10:14:19 +0300 Subject: [PATCH 216/238] enhanced device debouncing to accomodate for multiple calls from the different devices functions calls. --- lib/services/control_device_service.dart | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/services/control_device_service.dart b/lib/services/control_device_service.dart index 9913b52e..49463306 100644 --- a/lib/services/control_device_service.dart +++ b/lib/services/control_device_service.dart @@ -40,7 +40,7 @@ final class DebouncedControlDeviceService implements ControlDeviceService { DebouncedControlDeviceService({ required this.decoratee, - this.debounceDuration = const Duration(milliseconds: 800), + this.debounceDuration = const Duration(milliseconds: 1500), }); final _pendingRequests = <(String deviceUuid, Status status)>[]; @@ -59,15 +59,24 @@ final class DebouncedControlDeviceService implements ControlDeviceService { await Future.delayed(debounceDuration); - final lastRequest = _pendingRequests.last; + final groupedRequests = {}; + for (final request in _pendingRequests) { + final (_, requestStatus) = request; + groupedRequests[requestStatus.code] = request; + } _pendingRequests.clear(); try { - final (lastRequestDeviceUuid, lastRequestStatus) = lastRequest; - return decoratee.controlDevice( - deviceUuid: lastRequestDeviceUuid, - status: lastRequestStatus, - ); + var allSuccessful = true; + for (final request in groupedRequests.values) { + final (lastRequestDeviceUuid, lastRequestStatus) = request; + final success = await decoratee.controlDevice( + deviceUuid: lastRequestDeviceUuid, + status: lastRequestStatus, + ); + if (!success) allSuccessful = false; + } + return allSuccessful; } finally { _isProcessing = false; } From b26928b3d5590d99b166ece44400a1af0eeecf22 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Apr 2025 10:14:35 +0300 Subject: [PATCH 217/238] fixed ui bugs. --- ..._mounted_presence_sensor_control_view.dart | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart index f0815c60..300854ab 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart @@ -113,7 +113,8 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget ), ), PresenceUpdateData( - value: (model.nearDetection / 100).toDouble(), + value: + ((model.nearDetection / 100).toDouble()).clamp(0.0, double.infinity), title: 'Nearest Detect Dist:', description: 'm', minValue: 0.0, @@ -129,7 +130,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget ), ), PresenceUpdateData( - value: (model.farDetection / 100).toDouble(), + value: ((model.farDetection / 100).toDouble()).clamp(0.0, double.infinity), title: 'Max Detect Dist:', description: 'm', minValue: 0.0, @@ -145,14 +146,14 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget ), ), PresenceUpdateData( - value: (model.presenceDelay.toDouble()), + value: (model.sensiReduce.toDouble()), title: 'Trigger Level:', minValue: 0, maxValue: 3, steps: 1, action: (int value) => context.read().add( FlushMountedPresenceSensorChangeValueEvent( - code: FlushMountedPresenceSensorModel.codePresenceDelay, + code: FlushMountedPresenceSensorModel.codeSensiReduce, value: value, ), ), @@ -171,18 +172,20 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget ), ), PresenceUpdateData( - value: (model.sensiReduce.toDouble()), + value: (model.presenceDelay / 10).toDouble(), + valuesPercision: 1, title: 'Target Confirm Time:', description: 's', - minValue: 0, - maxValue: 3, - steps: 1, - action: (int value) => context.read().add( - FlushMountedPresenceSensorChangeValueEvent( - code: FlushMountedPresenceSensorModel.codeSensiReduce, - value: value, - ), - ), + minValue: 0.0, + maxValue: 0.5, + steps: 0.1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codePresenceDelay, + value: (value * 10).toInt(), + ), + ), ), PresenceUpdateData( value: ((model.noneDelay / 10).toDouble()), From 233fb2ee2c806705dc5168fa7c2c930888305bd9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Apr 2025 10:55:03 +0300 Subject: [PATCH 218/238] refactor: improve formatting of clamp method for near and far detection values --- .../views/flush_mounted_presence_sensor_control_view.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart index 300854ab..0360b642 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart @@ -113,8 +113,10 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget ), ), PresenceUpdateData( - value: - ((model.nearDetection / 100).toDouble()).clamp(0.0, double.infinity), + value: (model.nearDetection / 100).clamp( + 0.0, + double.infinity, + ), title: 'Nearest Detect Dist:', description: 'm', minValue: 0.0, @@ -130,7 +132,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget ), ), PresenceUpdateData( - value: ((model.farDetection / 100).toDouble()).clamp(0.0, double.infinity), + value: (model.farDetection / 100).clamp(0.0, double.infinity), title: 'Max Detect Dist:', description: 'm', minValue: 0.0, From 6e0b1775f05ca5ef567a9d066273f4e0362775f0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Apr 2025 10:55:18 +0300 Subject: [PATCH 219/238] fix: reorder constructor parameters for consistency in FlushMountedPresenceSensorControlView --- .../views/flush_mounted_presence_sensor_control_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart index 0360b642..c521bd3b 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart @@ -15,7 +15,7 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la class FlushMountedPresenceSensorControlView extends StatelessWidget with HelperResponsiveLayout { - const FlushMountedPresenceSensorControlView({super.key, required this.device}); + const FlushMountedPresenceSensorControlView({required this.device, super.key}); final AllDevicesModel device; From 43802a9fad77795408b9d57ef234e848249cb3c1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Apr 2025 10:57:44 +0300 Subject: [PATCH 220/238] refactor: update detection value calculations and adjust parameters for presence delay --- ...ed_presence_sensor_batch_control_view.dart | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart index 78db6e2e..4f27183d 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart @@ -75,7 +75,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget ), ), PresenceUpdateData( - value: (model.nearDetection / 100).toDouble(), + value: (model.nearDetection / 100).clamp(0.0, double.infinity), title: 'Nearest Detect Dist:', description: 'm', minValue: 0.0, @@ -92,7 +92,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget ), ), PresenceUpdateData( - value: (model.farDetection / 100).toDouble(), + value: (model.farDetection / 100).clamp(0.0, double.infinity), title: 'Max Detect Dist:', description: 'm', minValue: 0.0, @@ -117,7 +117,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget action: (int value) => context.read().add( FlushMountedPresenceSensorBatchControlEvent( deviceIds: devicesIds, - code: FlushMountedPresenceSensorModel.codePresenceDelay, + code: FlushMountedPresenceSensorModel.codeSensiReduce, value: value, ), ), @@ -137,19 +137,21 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget ), ), PresenceUpdateData( - value: (model.sensiReduce.toDouble()), + value: (model.presenceDelay / 10).toDouble(), title: 'Target Confirm Time:', description: 's', - minValue: 0, - maxValue: 3, - steps: 1, - action: (int value) => context.read().add( - FlushMountedPresenceSensorBatchControlEvent( - deviceIds: devicesIds, - code: FlushMountedPresenceSensorModel.codeSensiReduce, - value: value, - ), - ), + minValue: 0.0, + maxValue: 0.5, + steps: 0.1, + valuesPercision: 1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorBatchControlEvent( + deviceIds: devicesIds, + code: FlushMountedPresenceSensorModel.codePresenceDelay, + value: (value * 10).toInt(), + ), + ), ), PresenceUpdateData( value: ((model.noneDelay / 10).toDouble()), From 3193fd26fefdc2f3505dba90c3b323c0b2fee774 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Apr 2025 11:04:54 +0300 Subject: [PATCH 221/238] refactor: update presence delay value and enhance request handling in DebouncedBatchControlDevicesService --- ...ed_presence_sensor_batch_control_view.dart | 2 +- .../batch_control_devices_service.dart | 32 ++++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart index 4f27183d..2860e5cc 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart @@ -109,7 +109,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget ), ), PresenceUpdateData( - value: model.presenceDelay.toDouble(), + value: model.sensiReduce.toDouble(), title: 'Trigger Level:', minValue: 0, maxValue: 3, diff --git a/lib/services/batch_control_devices_service.dart b/lib/services/batch_control_devices_service.dart index de5af9ee..f78cdef4 100644 --- a/lib/services/batch_control_devices_service.dart +++ b/lib/services/batch_control_devices_service.dart @@ -46,14 +46,14 @@ final class DebouncedBatchControlDevicesService final BatchControlDevicesService decoratee; final Duration debounceDuration; - final _pendingRequests = <(List uuids, String code, Object value)>[]; - var _isProcessing = false; - DebouncedBatchControlDevicesService({ required this.decoratee, - this.debounceDuration = const Duration(milliseconds: 800), + this.debounceDuration = const Duration(milliseconds: 1500), }); + final _pendingRequests = <(List uuids, String code, Object value)>[]; + var _isProcessing = false; + @override Future batchControlDevices({ required List uuids, @@ -68,16 +68,26 @@ final class DebouncedBatchControlDevicesService await Future.delayed(debounceDuration); - final lastRequest = _pendingRequests.last; + final groupedRequests = + uuids, String code, Object value)>{}; + for (final request in _pendingRequests) { + final (_, requestCode, requestValue) = request; + groupedRequests[requestCode] = request; + } _pendingRequests.clear(); try { - final (lastRequestUuids, lastRequestCode, lastRequestValue) = lastRequest; - return decoratee.batchControlDevices( - uuids: lastRequestUuids, - code: lastRequestCode, - value: lastRequestValue, - ); + var allSuccessful = true; + for (final request in groupedRequests.values) { + final (lastRequestUuids, lastRequestCode, lastRequestValue) = request; + final success = await decoratee.batchControlDevices( + uuids: lastRequestUuids, + code: lastRequestCode, + value: lastRequestValue, + ); + if (!success) allSuccessful = false; + } + return allSuccessful; } finally { _isProcessing = false; } From 34d4d892d913f59789e9eee1e1364ccbd527d342 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Apr 2025 11:11:38 +0300 Subject: [PATCH 222/238] refactor: streamline value calculations in FlushMountedPresenceSensorControlView --- .../flush_mounted_presence_sensor_control_view.dart | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart index c521bd3b..08ad809d 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart @@ -113,10 +113,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget ), ), PresenceUpdateData( - value: (model.nearDetection / 100).clamp( - 0.0, - double.infinity, - ), + value: (model.nearDetection / 100).clamp(0.0, double.infinity), title: 'Nearest Detect Dist:', description: 'm', minValue: 0.0, @@ -148,7 +145,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget ), ), PresenceUpdateData( - value: (model.sensiReduce.toDouble()), + value: model.sensiReduce.toDouble(), title: 'Trigger Level:', minValue: 0, maxValue: 3, @@ -161,7 +158,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget ), ), PresenceUpdateData( - value: (model.occurDistReduce.toDouble()), + value: model.occurDistReduce.toDouble(), title: 'Indent Level:', minValue: 0, maxValue: 3, @@ -190,7 +187,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget ), ), PresenceUpdateData( - value: ((model.noneDelay / 10).toDouble()), + value: (model.noneDelay / 10).toDouble(), description: 's', title: 'Disappe Delay:', minValue: 20, From 0f56273d99271361c4143fc1dc3364ba9396031b Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Apr 2025 12:10:38 +0300 Subject: [PATCH 223/238] SP-1408 --- .../all_devices/view/device_managment_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f41b5a4a..fd3a2574 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 @@ -108,7 +108,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { return DeviceManagementBody( devices: deviceState.filteredDevices); } else { - return const Center(child: Text('Error fetching Devices')); + return const DeviceManagementBody(devices: []); } }, ); From 51c52c66cba5f672834f6cd2f627b5870177746c Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Apr 2025 15:18:19 +0300 Subject: [PATCH 224/238] SP-1478-FE-On-devices-management-page-when-we-open-power-clamp-device-loading-indicator-remains-loading-and-no-data-is-displayed --- .../power_clamp/bloc/smart_power_bloc.dart | 32 +++++------ .../power_clamp/models/power_clamp_model.dart | 54 +++++++++---------- .../power_clamp/view/power_chart.dart | 24 ++++----- .../view/smart_power_device_control.dart | 53 +++++++++--------- 4 files changed, 84 insertions(+), 79 deletions(-) diff --git a/lib/pages/device_managment/power_clamp/bloc/smart_power_bloc.dart b/lib/pages/device_managment/power_clamp/bloc/smart_power_bloc.dart index 52cccff4..24c5138f 100644 --- a/lib/pages/device_managment/power_clamp/bloc/smart_power_bloc.dart +++ b/lib/pages/device_managment/power_clamp/bloc/smart_power_bloc.dart @@ -217,29 +217,31 @@ class SmartPowerBloc extends Bloc { try { var status = await DevicesManagementApi().getPowerClampInfo(event.deviceId); - deviceStatus = PowerClampModel.fromJson(status); - + deviceStatus = PowerClampModel.fromJson(status as Map? ??{}); + final phaseADataPoints = deviceStatus.status.phaseA.dataPoints; + final phaseBDataPoints = deviceStatus.status.phaseB.dataPoints; + final phaseCDataPoints = deviceStatus.status.phaseC.dataPoints; phaseData = [ { 'name': 'Phase A', - 'voltage': '${deviceStatus.status.phaseA.dataPoints[0].value / 10} V', - 'current': '${deviceStatus.status.phaseA.dataPoints[1].value / 10} A', - 'activePower': '${deviceStatus.status.phaseA.dataPoints[2].value} W', - 'powerFactor': '${deviceStatus.status.phaseA.dataPoints[3].value}', + 'voltage': '${(phaseADataPoints.elementAtOrNull(0)?.value as num? ?? 0) / 10} V', + 'current': '${(phaseADataPoints.elementAtOrNull(1)?.value as num? ?? 0) / 10} A', + 'activePower': '${phaseADataPoints.elementAtOrNull(2)?.value??'N/A'} W', + 'powerFactor': '${phaseADataPoints.elementAtOrNull(3)?.value??'N/A'}', }, { 'name': 'Phase B', - 'voltage': '${deviceStatus.status.phaseB.dataPoints[0].value / 10} V', - 'current': '${deviceStatus.status.phaseB.dataPoints[1].value / 10} A', - 'activePower': '${deviceStatus.status.phaseB.dataPoints[2].value} W', - 'powerFactor': '${deviceStatus.status.phaseB.dataPoints[3].value}', + 'voltage': '${(phaseBDataPoints .elementAtOrNull(0)?.value as num? ?? 0) / 10} V', + 'current': '${(phaseBDataPoints .elementAtOrNull(1)?.value as num? ?? 0) / 10} A', + 'activePower': '${phaseBDataPoints.elementAtOrNull(2)?.value??'N/A'} W', + 'powerFactor': '${phaseBDataPoints.elementAtOrNull(3)?.value??'N/A'}', }, { 'name': 'Phase C', - 'voltage': '${deviceStatus.status.phaseC.dataPoints[0].value / 10} V', - 'current': '${deviceStatus.status.phaseC.dataPoints[1].value / 10} A', - 'activePower': '${deviceStatus.status.phaseC.dataPoints[2].value} W', - 'powerFactor': '${deviceStatus.status.phaseC.dataPoints[3].value}', + 'voltage': '${(phaseCDataPoints.elementAtOrNull(0)?.value as num? ?? 0) / 10} V', + 'current': '${(phaseCDataPoints.elementAtOrNull(1)?.value as num? ?? 0) / 10} A', + 'activePower': '${phaseCDataPoints.elementAtOrNull(2)?.value ?? 'N/A'} W', + 'powerFactor': '${phaseCDataPoints.elementAtOrNull(3)?.value ?? 'N/A'}', }, ]; emit(GetDeviceStatus()); @@ -785,7 +787,7 @@ class SmartPowerBloc extends Bloc { void selectDateRange() async { DateTime startDate = dateTime!; DateTime endDate = DateTime(startDate.year, startDate.month + 1, 1) - .subtract(Duration(days: 1)); + .subtract(const Duration(days: 1)); String formattedEndDate = DateFormat('dd/MM/yyyy').format(endDate); endChartDate = ' - $formattedEndDate'; } diff --git a/lib/pages/device_managment/power_clamp/models/power_clamp_model.dart b/lib/pages/device_managment/power_clamp/models/power_clamp_model.dart index 914a255b..4318d5ee 100644 --- a/lib/pages/device_managment/power_clamp/models/power_clamp_model.dart +++ b/lib/pages/device_managment/power_clamp/models/power_clamp_model.dart @@ -12,9 +12,9 @@ class PowerClampModel { factory PowerClampModel.fromJson(Map json) { return PowerClampModel( - productUuid: json['productUuid'], - productType: json['productType'], - status: PowerStatus.fromJson(json['status']), + productUuid: json['productUuid'] as String? ?? '', + productType: json['productType'] as String? ?? '', + status: PowerStatus.fromJson(json['status'] as Map? ?? {}), ); } @@ -26,7 +26,7 @@ class PowerClampModel { return PowerClampModel( productUuid: productUuid ?? this.productUuid, productType: productType ?? this.productType, - status: statusPower ?? this.status, + status: statusPower ?? status, ); } } @@ -46,12 +46,10 @@ class PowerStatus { factory PowerStatus.fromJson(Map json) { return PowerStatus( - phaseA: Phase.fromJson(json['phaseA']), - phaseB: Phase.fromJson(json['phaseB']), - phaseC: Phase.fromJson(json['phaseC']), - general: Phase.fromJson(json['general'] - // List.from( - // json['general'].map((x) => DataPoint.fromJson(x))), + phaseA: Phase.fromJson(json['phaseA']as List? ?? []), + phaseB: Phase.fromJson(json['phaseB']as List? ?? []), + phaseC: Phase.fromJson(json['phaseC']as List? ?? []), + general: Phase.fromJson(json['general']as List? ?? [] )); } } @@ -69,30 +67,30 @@ class Phase { } class DataPoint { - dynamic code; - dynamic customName; - dynamic dpId; - dynamic time; - dynamic type; - dynamic value; + final String? code; + final String? customName; + final int? dpId; + final int? time; + final String? type; + final dynamic value; DataPoint({ - required this.code, - required this.customName, - required this.dpId, - required this.time, - required this.type, - required this.value, + this.code, + this.customName, + this.dpId, + this.time, + this.type, + this.value, }); factory DataPoint.fromJson(Map json) { return DataPoint( - code: json['code'], - customName: json['customName'], - dpId: json['dpId'], - time: json['time'], - type: json['type'], - value: json['value'], + code: json['code'] as String?, + customName: json['customName'] as String?, + dpId: json['dpId'] as int?, + time: json['time'] as int?, + type: json['type'] as String?, + value: json['value'] as dynamic, ); } } diff --git a/lib/pages/device_managment/power_clamp/view/power_chart.dart b/lib/pages/device_managment/power_clamp/view/power_chart.dart index 19050b8a..7d6371f4 100644 --- a/lib/pages/device_managment/power_clamp/view/power_chart.dart +++ b/lib/pages/device_managment/power_clamp/view/power_chart.dart @@ -1,5 +1,5 @@ -import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class EnergyConsumptionPage extends StatefulWidget { @@ -10,7 +10,8 @@ class EnergyConsumptionPage extends StatefulWidget { final Widget widget; final Function()? onTap; - EnergyConsumptionPage({ + const EnergyConsumptionPage({ + super.key, required this.chartData, required this.totalConsumption, required this.date, @@ -91,11 +92,12 @@ class _EnergyConsumptionPageState extends State { ], ), Column( + mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.only(top: 10), child: SizedBox( - height: MediaQuery.of(context).size.height * 0.11, + height: MediaQuery.sizeOf(context).height * 0.09, child: LineChart( LineChartData( lineTouchData: LineTouchData( @@ -151,7 +153,7 @@ class _EnergyConsumptionPageState extends State { child: RotatedBox( quarterTurns: -1, child: Text(_chartData[index].time, - style: TextStyle(fontSize: 10)), + style: const TextStyle(fontSize: 10)), ), ); } @@ -190,8 +192,8 @@ class _EnergyConsumptionPageState extends State { spots: _chartData .asMap() .entries - .map((entry) => FlSpot(entry.key.toDouble(), - entry.value.consumption)) + .map((entry) => FlSpot( + entry.key.toDouble(), entry.value.consumption)) .toList(), isCurved: true, color: ColorsManager.primaryColor.withOpacity(0.6), @@ -218,7 +220,7 @@ class _EnergyConsumptionPageState extends State { borderData: FlBorderData( show: false, border: Border.all( - color: Color(0xff023DFE).withOpacity(0.7), + color: const Color(0xff023DFE).withOpacity(0.7), width: 10, ), ), @@ -253,11 +255,9 @@ class _EnergyConsumptionPageState extends State { child: InkWell( onTap: widget.onTap, child: Center( - child: SizedBox( - child: Padding( - padding: const EdgeInsets.all(5), - child: Text(widget.date), - ), + child: Padding( + padding: const EdgeInsets.all(5), + child: Text(widget.date), ), ), ), diff --git a/lib/pages/device_managment/power_clamp/view/smart_power_device_control.dart b/lib/pages/device_managment/power_clamp/view/smart_power_device_control.dart index 03d649fa..67313802 100644 --- a/lib/pages/device_managment/power_clamp/view/smart_power_device_control.dart +++ b/lib/pages/device_managment/power_clamp/view/smart_power_device_control.dart @@ -12,8 +12,7 @@ import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; //Smart Power Clamp -class SmartPowerDeviceControl extends StatelessWidget - with HelperResponsiveLayout { +class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayout { final String deviceId; const SmartPowerDeviceControl({super.key, required this.deviceId}); @@ -25,27 +24,27 @@ class SmartPowerDeviceControl extends StatelessWidget ..add(SmartPowerFetchDeviceEvent(deviceId)), child: BlocBuilder( builder: (context, state) { - final _blocProvider = BlocProvider.of(context); + final blocProvider = BlocProvider.of(context); if (state is SmartPowerLoading) { return const Center(child: CircularProgressIndicator()); } else if (state is FakeState) { return _buildStatusControls( - currentPage: _blocProvider.currentPage, + currentPage: blocProvider.currentPage, context: context, - blocProvider: _blocProvider, + blocProvider: blocProvider, ); } else if (state is GetDeviceStatus) { return _buildStatusControls( - currentPage: _blocProvider.currentPage, + currentPage: blocProvider.currentPage, context: context, - blocProvider: _blocProvider, + blocProvider: blocProvider, ); } else if (state is FilterRecordsState) { return _buildStatusControls( - currentPage: _blocProvider.currentPage, + currentPage: blocProvider.currentPage, context: context, - blocProvider: _blocProvider, + blocProvider: blocProvider, ); } return const Center(child: CircularProgressIndicator()); @@ -60,7 +59,7 @@ class SmartPowerDeviceControl extends StatelessWidget required SmartPowerBloc blocProvider, required int currentPage, }) { - PageController _pageController = PageController(initialPage: currentPage); + PageController pageController = PageController(initialPage: currentPage); return Container( padding: const EdgeInsets.symmetric(horizontal: 50), child: DeviceControlsContainer( @@ -85,25 +84,31 @@ class SmartPowerDeviceControl extends StatelessWidget PowerClampInfoCard( iconPath: Assets.powerActiveIcon, title: 'Active', - value: blocProvider - .deviceStatus.status.general.dataPoints[2].value - .toString(), + value: blocProvider.deviceStatus.status.general.dataPoints + .elementAtOrNull(2) + ?.value + .toString() ?? + '', unit: '', ), PowerClampInfoCard( iconPath: Assets.voltMeterIcon, title: 'Current', - value: blocProvider - .deviceStatus.status.general.dataPoints[1].value - .toString(), + value: blocProvider.deviceStatus.status.general.dataPoints + .elementAtOrNull(1) + ?.value + .toString() ?? + '', unit: ' A', ), PowerClampInfoCard( iconPath: Assets.frequencyIcon, title: 'Frequency', - value: blocProvider - .deviceStatus.status.general.dataPoints[4].value - .toString(), + value: blocProvider.deviceStatus.status.general.dataPoints + .elementAtOrNull(4) + ?.value + .toString() ?? + '', unit: ' Hz', ), ], @@ -142,7 +147,7 @@ class SmartPowerDeviceControl extends StatelessWidget icon: const Icon(Icons.arrow_left), onPressed: () { blocProvider.add(SmartPowerArrowPressedEvent(-1)); - _pageController.previousPage( + pageController.previousPage( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); @@ -162,7 +167,7 @@ class SmartPowerDeviceControl extends StatelessWidget icon: const Icon(Icons.arrow_right), onPressed: () { blocProvider.add(SmartPowerArrowPressedEvent(1)); - _pageController.nextPage( + pageController.nextPage( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); @@ -177,7 +182,7 @@ class SmartPowerDeviceControl extends StatelessWidget Expanded( flex: 2, child: PageView( - controller: _pageController, + controller: pageController, onPageChanged: (int page) { blocProvider.add(SmartPowerPageChangedEvent(page)); }, @@ -190,8 +195,8 @@ class SmartPowerDeviceControl extends StatelessWidget blocProvider.add(SelectDateEvent(context: context)); blocProvider.add(FilterRecordsByDateEvent( selectedDate: blocProvider.dateTime!, - viewType: blocProvider - .views[blocProvider.currentIndex])); + viewType: + blocProvider.views[blocProvider.currentIndex])); }, widget: blocProvider.dateSwitcher(), chartData: blocProvider.energyDataList.isNotEmpty From ac2996629e5a0955bda6658d99f581be73b1ddbb Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 27 Apr 2025 15:42:50 +0300 Subject: [PATCH 225/238] resolved an exception that was thrown when resizing the `DeviceSearchFilters`. --- .../common/buttons/search_reset_buttons.dart | 4 ++- .../widgets/device_managment_body.dart | 1 + .../widgets/device_search_filters.dart | 32 +++++++++---------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/pages/common/buttons/search_reset_buttons.dart b/lib/pages/common/buttons/search_reset_buttons.dart index 7b63a485..6fdbfd2a 100644 --- a/lib/pages/common/buttons/search_reset_buttons.dart +++ b/lib/pages/common/buttons/search_reset_buttons.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/style.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; class SearchResetButtons extends StatelessWidget { const SearchResetButtons({ @@ -17,8 +17,10 @@ class SearchResetButtons extends StatelessWidget { @override Widget build(BuildContext context) { return Row( + mainAxisSize: MainAxisSize.min, children: [ Column( + mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(height: 25), diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 907e5390..a3c975c1 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -72,6 +72,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { child: state is DeviceManagementLoading ? const Center(child: CircularProgressIndicator()) : Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: isLargeScreenSize(context) diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index fbbe65ab..18d72fc9 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -14,29 +14,29 @@ class DeviceSearchFilters extends StatefulWidget { class _DeviceSearchFiltersState extends State with HelperResponsiveLayout { - final _unitNameController = TextEditingController(); - final _productNameController = TextEditingController(); + late final TextEditingController _unitNameController; + late final TextEditingController _productNameController; - List get _widgets => [ - _buildSearchField("Space Name", _unitNameController, 200), - _buildSearchField("Device Name / Product Name", _productNameController, 300), - _buildSearchResetButtons(), - ]; + @override + void initState() { + _unitNameController = TextEditingController(); + _productNameController = TextEditingController(); + super.initState(); + } @override Widget build(BuildContext context) { - if (isExtraLargeScreenSize(context)) { - return Row( - children: _widgets - .map((e) => Padding(padding: const EdgeInsets.all(10), child: e)) - .toList(), - ); - } - return Wrap( + alignment: WrapAlignment.start, + runAlignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.center, spacing: 20, runSpacing: 10, - children: _widgets, + children: [ + _buildSearchField("Space Name", _unitNameController, 200), + _buildSearchField("Device Name / Product Name", _productNameController, 300), + _buildSearchResetButtons(), + ], ); } From 52608b1f355227fc3e23170c20555e2ea6b53c46 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 27 Apr 2025 22:42:57 +0400 Subject: [PATCH 226/238] fixed search query --- .../all_spaces/widgets/sidebar_widget.dart | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 374f0a0a..612bf781 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/common/widgets/empty_search_result_widget.dart'; @@ -36,6 +38,7 @@ class SidebarWidget extends StatefulWidget { class _SidebarWidgetState extends State { late final ScrollController _scrollController; + Timer? _debounce; String _searchQuery = ''; String? _selectedSpaceUuid; @@ -43,10 +46,10 @@ class _SidebarWidgetState extends State { @override void initState() { + super.initState(); _scrollController = ScrollController(); _scrollController.addListener(_onScroll); _selectedId = widget.selectedSpaceUuid; - super.initState(); } void _onScroll() { @@ -67,11 +70,22 @@ class _SidebarWidgetState extends State { @override void dispose() { _scrollController.removeListener(_onScroll); - _scrollController.dispose(); + _debounce?.cancel(); super.dispose(); } + void _onSearchChanged(String query) { + if (_debounce?.isActive ?? false) _debounce?.cancel(); + + _debounce = Timer(const Duration(milliseconds: 500), () { + setState(() { + _searchQuery = query; + }); + context.read().add(SearchQueryEvent(query)); + }); + } + @override void didUpdateWidget(covariant SidebarWidget oldWidget) { if (widget.selectedSpaceUuid != oldWidget.selectedSpaceUuid) { @@ -91,7 +105,7 @@ class _SidebarWidgetState extends State { @override Widget build(BuildContext context) { final spaceTreeState = context.watch().state; - final filteredCommunities = spaceTreeState.isSearching + final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty ? spaceTreeState.filteredCommunity : spaceTreeState.communityList; @@ -104,13 +118,7 @@ class _SidebarWidgetState extends State { children: [ SidebarHeader(onAddCommunity: _onAddCommunity), CustomSearchBar( - onSearchChanged: (query) { - setState(() { - _searchQuery = query; - }); - - context.read().add(SearchQueryEvent(query)); - }, + onSearchChanged: _onSearchChanged, ), const SizedBox(height: 16), Expanded( @@ -123,10 +131,15 @@ class _SidebarWidgetState extends State { communities: filteredCommunities, itemBuilder: (context, index) { if (index == filteredCommunities.length) { - return const Padding( - padding: EdgeInsets.all(8.0), - child: Center(child: CircularProgressIndicator()), - ); + final spaceTreeState = context.read().state; + if (spaceTreeState.paginationIsLoading) { + return const Padding( + padding: EdgeInsets.all(8.0), + child: Center(child: CircularProgressIndicator()), + ); + } else { + return const SizedBox.shrink(); + } } return _buildCommunityTile(context, filteredCommunities[index]); }), From 6ef0b2f9d1477dca367c08c4135808081db1a2ca Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 28 Apr 2025 00:36:58 +0400 Subject: [PATCH 227/238] fixed the save issue --- .../bloc/space_management_bloc.dart | 70 ++++++++++++++----- .../widgets/community_structure_widget.dart | 1 - lib/services/space_mana_api.dart | 1 + 3 files changed, 52 insertions(+), 20 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 56949a19..bcb89857 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.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'; +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/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; @@ -246,7 +247,9 @@ class SpaceManagementBloc extends Bloc(); - List communities = await _waitForCommunityList(spaceBloc); + var spaceTreeState = event.context.read().state; + + List communities = await _waitForCommunityList(spaceBloc, spaceTreeState); await fetchSpaceModels(); await fetchTags(); @@ -277,11 +280,13 @@ class SpaceManagementBloc extends Bloc emit, ) async { + var spaceTreeState = event.context.read().state; var spaceBloc = event.context.read(); + _onloadProducts(); await fetchTags(); // Wait until `communityList` is loaded - List communities = await _waitForCommunityList(spaceBloc); + List communities = await _waitForCommunityList(spaceBloc, spaceTreeState); // Fetch space models after communities are available final prevSpaceModels = await fetchSpaceModels(); @@ -292,23 +297,38 @@ class SpaceManagementBloc extends Bloc> _waitForCommunityList(SpaceTreeBloc spaceBloc) async { + Future> _waitForCommunityList( + SpaceTreeBloc spaceBloc, SpaceTreeState spaceTreeState) async { // Check if communityList is already populated - if (spaceBloc.state.communityList.isNotEmpty) { - return spaceBloc.state.communityList; + final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty + ? spaceTreeState.filteredCommunity + : spaceTreeState.communityList; + if (filteredCommunities.isNotEmpty) { + return filteredCommunities; } final completer = Completer>(); final subscription = spaceBloc.stream.listen((state) { - if (state.communityList.isNotEmpty) { - completer.complete(state.communityList); + if (!completer.isCompleted && state.communityList.isNotEmpty) { + completer + .complete(state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList); } }); + try { + final communities = await completer.future.timeout( + const Duration(seconds: 10), + onTimeout: () { + if (!completer.isCompleted) { + completer.complete([]); + } + return []; + }, + ); - // Return the list once available, then cancel the listener - final communities = await completer.future; - await subscription.cancel(); - return communities; + return communities; + } finally { + await subscription.cancel(); + } } void _onCommunityDelete( @@ -491,12 +511,21 @@ class SpaceManagementBloc extends Bloc> saveSpacesHierarchically( BuildContext context, List spaces, String communityUuid) async { final orderedSpaces = flattenHierarchy(spaces); + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; - var spaceBloc = context.read(); - List communities = spaceBloc.state.communityList; - CommunityModel? selectedCommunity = communities.firstWhere( - (community) => community.uuid == communityUuid, - ); + CommunityModel? selectedCommunity; + try { + final spaceTreeState = context.read().state; + final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty + ? spaceTreeState.filteredCommunity + : spaceTreeState.communityList; + + selectedCommunity = filteredCommunities.firstWhere( + (community) => community.uuid == communityUuid, + ); + } catch (e) { + return []; + } final parentsToDelete = orderedSpaces.where((space) => space.status == SpaceStatus.deleted && @@ -669,9 +698,12 @@ class SpaceManagementBloc extends Bloc(); - List communities = spaceBloc.state.communityList; + final spaceTreeState = event.context.read().state; + final filteredCommunities = spaceTreeState.searchQuery.isNotEmpty + ? spaceTreeState.filteredCommunity + : spaceTreeState.communityList; + + List communities = filteredCommunities; var prevSpaceModels = await fetchSpaceModels(); diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 6d742e69..c1ef4fbc 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -467,7 +467,6 @@ class _CommunityStructureAreaState extends State { } String communityUuid = widget.selectedCommunity!.uuid; - context.read().add(SaveSpacesEvent( context, spaces: spacesToSave, diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 604c8cae..19e219b6 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -281,6 +281,7 @@ class CommunitySpaceManagementApi { return json['success'] ?? false; }, ); + return response; } catch (e) { debugPrint('Error updating space: $e'); From fe3db663b67a718d48603e4580214dbcae946de6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 28 Apr 2025 10:03:27 +0400 Subject: [PATCH 228/238] realign initially --- .../all_spaces/widgets/community_structure_widget.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index c1ef4fbc..6b349374 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -72,6 +72,8 @@ class _CommunityStructureAreaState extends State { _nameController = TextEditingController( text: widget.selectedCommunity?.name ?? '', ); + realignTree(); + _transformationController = TransformationController(); if (widget.selectedSpace != null) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -96,6 +98,7 @@ class _CommunityStructureAreaState extends State { spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; _adjustCanvasSizeForSpaces(); + realignTree(); }); } From 349fe6c55524e64cc485bc64cbef4e07e2953d7f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 28 Apr 2025 11:58:41 +0400 Subject: [PATCH 229/238] realignment --- .../widgets/community_structure_widget.dart | 62 +++++++++++++++++-- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 6b349374..12156b22 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -698,7 +698,7 @@ class _CommunityStructureAreaState extends State { final double verticalGap = 180.0; final double nodeWidth = 200; final double nodeHeight = 100; - final double breathingSpace = 300.0; // extra gap after original tree + final double breathingSpace = 300.0; /// Helper to recursively duplicate a node and its children SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) { @@ -706,7 +706,7 @@ class _CommunityStructureAreaState extends State { final duplicated = SpaceModel( name: duplicatedName, icon: original.icon, - position: Offset.zero, + position: original.position, isPrivate: original.isPrivate, children: [], status: SpaceStatus.newSpace, @@ -725,11 +725,13 @@ class _CommunityStructureAreaState extends State { direction: "down", ); connections.add(newConnection); + duplicated.incomingConnection = newConnection; duplicatedParent.addOutgoingConnection(newConnection); duplicatedParent.children.add(duplicated); } + // Duplicate its children recursively for (var child in original.children) { duplicateRecursive(child, duplicated); } @@ -773,13 +775,63 @@ class _CommunityStructureAreaState extends State { setState(() { if (space.parent == null) { // Duplicating a ROOT node - SpaceModel duplicatedRoot = duplicateRecursive(space, null); + duplicateRecursive(space, null); + connections = createConnections(spaces); realignTree(); } else { - // Duplicating a CHILD node inside its parent - SpaceModel duplicated = duplicateRecursive(space, space.parent); + final parent = space.parent!; + + final duplicated = duplicateRecursive(space, parent); + final newConnection = Connection( + startSpace: parent, + endSpace: duplicated, + direction: "down", + ); + connections.add(newConnection); + duplicated.incomingConnection = newConnection; + parent.addOutgoingConnection(newConnection); + parent.children.add(duplicated); + connections = createConnections(spaces); realignTree(); + //_realignSubtree(space.parent!); } }); } + + void _realignSubtree(SpaceModel parent) { + const double nodeWidth = 200; + const double horizontalGap = 60; + const double verticalGap = 180; + + double calculateSubtreeWidth(SpaceModel node) { + if (node.children.isEmpty) return nodeWidth; + double width = 0; + for (var child in node.children) { + width += calculateSubtreeWidth(child) + horizontalGap; + } + return width - horizontalGap; + } + + void assignPositions(SpaceModel node, double startX, double startY) { + double subtreeWidth = calculateSubtreeWidth(node); + double centerX = startX + subtreeWidth / 2 - nodeWidth / 2; + node.position = Offset(centerX, startY); + + if (node.children.length == 1) { + assignPositions(node.children.first, centerX, startY + verticalGap); + } else { + double childX = startX; + for (var child in node.children) { + double childWidth = calculateSubtreeWidth(child); + assignPositions(child, childX, startY + verticalGap); + childX += childWidth + horizontalGap; + } + } + } + + // Start laying out from parent + assignPositions(parent, parent.position.dx - 150, parent.position.dy); + + _adjustCanvasSizeForSpaces(); + } } From 7cbe20ae88e90fea9d8d5ac862d3472464384427 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 28 Apr 2025 12:56:29 +0400 Subject: [PATCH 230/238] remove unused code --- .../widgets/community_structure_widget.dart | 69 ------------------- 1 file changed, 69 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 12156b22..4b337961 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -739,38 +739,6 @@ class _CommunityStructureAreaState extends State { return duplicated; } - /// Layout a subtree rooted at node - void layoutSubtree(SpaceModel node, double startX, double startY) { - double calculateSubtreeWidth(SpaceModel n) { - if (n.children.isEmpty) return nodeWidth; - double width = 0; - for (var child in n.children) { - width += calculateSubtreeWidth(child) + horizontalGap; - } - return width - horizontalGap; - } - - void assignPositions(SpaceModel n, double x, double y) { - double subtreeWidth = calculateSubtreeWidth(n); - double centerX = x + subtreeWidth / 2 - nodeWidth / 2; - n.position = Offset(centerX, y); - - if (n.children.length == 1) { - assignPositions(n.children.first, centerX, y + verticalGap); - } else { - double childX = x; - for (var child in n.children) { - double childWidth = calculateSubtreeWidth(child); - assignPositions(child, childX, y + verticalGap); - childX += childWidth + horizontalGap; - } - } - } - - double totalSubtreeWidth = calculateSubtreeWidth(node); - assignPositions(node, startX, startY); - } - /// Actual duplication process setState(() { if (space.parent == null) { @@ -797,41 +765,4 @@ class _CommunityStructureAreaState extends State { } }); } - - void _realignSubtree(SpaceModel parent) { - const double nodeWidth = 200; - const double horizontalGap = 60; - const double verticalGap = 180; - - double calculateSubtreeWidth(SpaceModel node) { - if (node.children.isEmpty) return nodeWidth; - double width = 0; - for (var child in node.children) { - width += calculateSubtreeWidth(child) + horizontalGap; - } - return width - horizontalGap; - } - - void assignPositions(SpaceModel node, double startX, double startY) { - double subtreeWidth = calculateSubtreeWidth(node); - double centerX = startX + subtreeWidth / 2 - nodeWidth / 2; - node.position = Offset(centerX, startY); - - if (node.children.length == 1) { - assignPositions(node.children.first, centerX, startY + verticalGap); - } else { - double childX = startX; - for (var child in node.children) { - double childWidth = calculateSubtreeWidth(child); - assignPositions(child, childX, startY + verticalGap); - childX += childWidth + horizontalGap; - } - } - } - - // Start laying out from parent - assignPositions(parent, parent.position.dx - 150, parent.position.dy); - - _adjustCanvasSizeForSpaces(); - } } From 25614c3dd013de8eb98a658b8e81ef14605e6bcc Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 28 Apr 2025 12:59:16 +0400 Subject: [PATCH 231/238] fix the flatten --- .../all_spaces/widgets/community_structure_widget.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 4b337961..36a6aefe 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -407,21 +407,31 @@ class _CommunityStructureAreaState extends State { List flattenSpaces(List spaces) { List result = []; + Map idToSpace = {}; void flatten(SpaceModel space) { if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) { return; } result.add(space); + idToSpace[space.internalId] = space; + for (var child in space.children) { flatten(child); } } + for (var space in spaces) { flatten(space); } + for (var space in result) { + if (space.parent != null) { + space.parent = idToSpace[space.parent!.internalId]; + } + } + return result; } From ab23be9828341bf9dffe188a16af350d6ca72415 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 28 Apr 2025 14:36:27 +0400 Subject: [PATCH 232/238] fixed the issue in selecting space model and tag --- .../all_spaces/bloc/space_management_bloc.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index bcb89857..eb7e5925 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -634,11 +634,11 @@ class SpaceManagementBloc extends Bloc tagBodyModels = space.tags != null + List tagBodyModels = space.tags != null ? space.tags!.map((tag) => tag.toCreateTagBodyModel()).toList() : []; - final createSubspaceBodyModels = space.subspaces?.map((subspace) { + var createSubspaceBodyModels = space.subspaces?.map((subspace) { final tagBodyModels = subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? []; return CreateSubspaceModel() @@ -647,6 +647,11 @@ class SpaceManagementBloc extends Bloc Date: Mon, 28 Apr 2025 15:21:28 +0400 Subject: [PATCH 233/238] fixed blank page rendering --- .../all_spaces/bloc/space_management_bloc.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index eb7e5925..5df5c36d 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -466,6 +466,7 @@ class SpaceManagementBloc extends Bloc _updateLoadedState( + BuildContext context, SpaceManagementLoaded previousState, List allSpaces, String communityUuid, @@ -489,7 +491,10 @@ class SpaceManagementBloc extends Bloc.from(previousState.communities); + final spaceTreeState = context.read().state; + final communities = spaceTreeState.searchQuery.isNotEmpty + ? spaceTreeState.filteredCommunity + : spaceTreeState.communityList; for (var community in communities) { if (community.uuid == communityUuid) { @@ -504,6 +509,8 @@ class SpaceManagementBloc extends Bloc Date: Mon, 28 Apr 2025 16:25:43 +0400 Subject: [PATCH 234/238] fixed community search caching --- .../all_spaces/widgets/loaded_space_widget.dart | 7 +++---- .../all_spaces/widgets/sidebar_widget.dart | 2 ++ .../space_model/view/space_model_page.dart | 4 ++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart index 5ef9c79b..40876dd8 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart @@ -1,6 +1,7 @@ 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_event.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; @@ -45,7 +46,6 @@ class _LoadedSpaceViewState extends State { @override void initState() { super.initState(); - _spaceModels = List.from(widget.spaceModels ?? []); } @@ -106,9 +106,8 @@ class _LoadedSpaceViewState extends State { children: [ SidebarWidget( communities: widget.communities, - selectedSpaceUuid: widget.selectedSpace?.uuid ?? - widget.selectedCommunity?.uuid ?? - '', + selectedSpaceUuid: + widget.selectedSpace?.uuid ?? widget.selectedCommunity?.uuid ?? '', onCreateCommunity: (name, description) { context.read().add( CreateCommunityEvent(name, description, context), diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index 612bf781..8c184c13 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -50,6 +50,8 @@ class _SidebarWidgetState extends State { _scrollController = ScrollController(); _scrollController.addListener(_onScroll); _selectedId = widget.selectedSpaceUuid; + _searchQuery = ''; + context.read().add(ClearCachedData()); } void _onScroll() { diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index e1aa7a92..eef196be 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,5 +1,7 @@ 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_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart'; @@ -27,6 +29,8 @@ class SpaceModelPage extends StatelessWidget { return const Center(child: CircularProgressIndicator()); } else if (state is SpaceModelLoaded) { final spaceModels = state.spaceModels; + context.read().add(ClearCachedData()); + final allTagValues = _getAllTagValues(spaceModels); final allSpaceModelNames = _getAllSpaceModelName(spaceModels); From 63bc7a56debfbeda08330ebb8067e9293f73d926 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 28 Apr 2025 16:49:22 +0300 Subject: [PATCH 235/238] - Refactor the code in _RoutinesViewState to improve readability and maintainability. - Update the indentation and add padding to the child widgets in the Column. - Add a bottom padding to the empty state text in _buildEmptyState. --- lib/pages/routines/view/routines_view.dart | 64 ++++++++++--------- .../fetch_routine_scenes_automation.dart | 20 ++++-- 2 files changed, 47 insertions(+), 37 deletions(-) diff --git a/lib/pages/routines/view/routines_view.dart b/lib/pages/routines/view/routines_view.dart index 9d4639ed..f46ef15f 100644 --- a/lib/pages/routines/view/routines_view.dart +++ b/lib/pages/routines/view/routines_view.dart @@ -30,8 +30,8 @@ class _RoutinesViewState extends State { final spaceId = result['space']; final bloc = BlocProvider.of(context); final routineBloc = context.read(); - bloc.add( - SaveCommunityIdAndSpaceIdEvent(communityID: communityId, spaceID: spaceId)); + bloc.add(SaveCommunityIdAndSpaceIdEvent( + communityID: communityId, spaceID: spaceId)); await Future.delayed(const Duration(seconds: 1)); routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true)); } @@ -61,34 +61,38 @@ class _RoutinesViewState extends State { width: context.screenWidth, child: SingleChildScrollView( padding: const EdgeInsetsDirectional.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - spacing: 16, - children: [ - Text( - "Create New Routines", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: ColorsManager.grayColor, - fontWeight: FontWeight.bold, - ), - ), - RoutineViewCard( - isLoading: false, - onChanged: (v) {}, - status: '', - spaceId: '', - automationId: '', - communityId: '', - sceneId: '', - cardType: '', - spaceName: '', - onTap: () => _handleRoutineCreation(context), - icon: Icons.add, - textString: '', - ), - const FetchRoutineScenesAutomation(), - ], + child: Padding( + padding: const EdgeInsets.only(left: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + spacing: 16, + children: [ + Text( + "Create New Routines", + style: + Theme.of(context).textTheme.titleLarge?.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.bold, + ), + ), + RoutineViewCard( + isLoading: false, + onChanged: (v) {}, + status: '', + spaceId: '', + automationId: '', + communityId: '', + sceneId: '', + cardType: '', + spaceName: '', + onTap: () => _handleRoutineCreation(context), + icon: Icons.add, + textString: '', + ), + const 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 f935fef8..a67caef9 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 @@ -16,7 +16,8 @@ class FetchRoutineScenesAutomation extends StatelessWidget Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - if (state.isLoading) return const Center(child: CircularProgressIndicator()); + if (state.isLoading) + return const Center(child: CircularProgressIndicator()); return SingleChildScrollView( child: Padding( @@ -40,7 +41,8 @@ class FetchRoutineScenesAutomation extends StatelessWidget const SizedBox(height: 3), Visibility( visible: state.automations.isNotEmpty, - replacement: _buildEmptyState(context, "No automations found"), + replacement: + _buildEmptyState(context, "No automations found"), child: SizedBox( height: 200, child: _buildAutomations(state), @@ -59,7 +61,8 @@ class FetchRoutineScenesAutomation extends StatelessWidget scrollDirection: Axis.horizontal, itemCount: state.automations.length, itemBuilder: (context, index) { - final isLoading = state.automations.contains(state.automations[index].id); + final isLoading = + state.automations.contains(state.automations[index].id); return Column( children: [ @@ -179,10 +182,13 @@ class FetchRoutineScenesAutomation extends StatelessWidget } Widget _buildEmptyState(BuildContext context, String title) { - return Text( - title, - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.grayColor, + return Padding( + padding: const EdgeInsets.only(bottom: 100), + child: Text( + title, + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.grayColor, + ), ), ); } From acefe6b4338efb28ae87cdd28a4b269379cd5274 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 29 Apr 2025 10:13:11 +0400 Subject: [PATCH 236/238] fixed repeated duplication --- .../widgets/community_structure_widget.dart | 118 +++++++++--------- 1 file changed, 57 insertions(+), 61 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 36a6aefe..a93679d0 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -414,21 +414,20 @@ class _CommunityStructureAreaState extends State { return; } result.add(space); - idToSpace[space.internalId] = space; + idToSpace[space.internalId] = space; for (var child in space.children) { flatten(child); } } - for (var space in spaces) { flatten(space); } for (var space in result) { if (space.parent != null) { - space.parent = idToSpace[space.parent!.internalId]; + space.parent = idToSpace[space.parent!.internalId]; } } @@ -704,62 +703,26 @@ class _CommunityStructureAreaState extends State { } void _duplicateSpace(SpaceModel space) { - final double horizontalGap = 250.0; - final double verticalGap = 180.0; - final double nodeWidth = 200; - final double nodeHeight = 100; - final double breathingSpace = 300.0; - - /// Helper to recursively duplicate a node and its children - SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) { - final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); - final duplicated = SpaceModel( - name: duplicatedName, - icon: original.icon, - position: original.position, - isPrivate: original.isPrivate, - children: [], - status: SpaceStatus.newSpace, - parent: duplicatedParent, - spaceModel: original.spaceModel, - subspaces: original.subspaces, - tags: original.tags, - ); - - spaces.add(duplicated); - - if (duplicatedParent != null) { - final newConnection = Connection( - startSpace: duplicatedParent, - endSpace: duplicated, - direction: "down", - ); - connections.add(newConnection); - - duplicated.incomingConnection = newConnection; - duplicatedParent.addOutgoingConnection(newConnection); - duplicatedParent.children.add(duplicated); - } - - // Duplicate its children recursively - for (var child in original.children) { - duplicateRecursive(child, duplicated); - } - - return duplicated; - } - - /// Actual duplication process setState(() { - if (space.parent == null) { - // Duplicating a ROOT node - duplicateRecursive(space, null); - connections = createConnections(spaces); - realignTree(); - } else { - final parent = space.parent!; + SpaceModel? parent = space.parent; + + SpaceModel duplicated = _deepCloneSpaceTree(space, parent: parent); + + duplicated.position = Offset(space.position.dx + 300, space.position.dy + 100); + List duplicatedSubtree = []; + void collectSubtree(SpaceModel node) { + duplicatedSubtree.add(node); + for (var child in node.children) { + collectSubtree(child); + } + } + + collectSubtree(duplicated); + spaces.addAll(duplicatedSubtree); + + if (parent != null) { + parent.children.add(duplicated); - final duplicated = duplicateRecursive(space, parent); final newConnection = Connection( startSpace: parent, endSpace: duplicated, @@ -768,11 +731,44 @@ class _CommunityStructureAreaState extends State { connections.add(newConnection); duplicated.incomingConnection = newConnection; parent.addOutgoingConnection(newConnection); - parent.children.add(duplicated); - connections = createConnections(spaces); - realignTree(); - //_realignSubtree(space.parent!); } + + realignTree(); + connections = createConnections(spaces); }); } + + SpaceModel _deepCloneSpaceTree(SpaceModel original, {SpaceModel? parent}) { + final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); + + final newSpace = SpaceModel( + name: duplicatedName, + icon: original.icon, + position: original.position, + isPrivate: original.isPrivate, + children: [], + status: SpaceStatus.newSpace, + spaceModel: original.spaceModel, + subspaces: original.subspaces, + tags: original.tags, + parent: parent, + ); + + for (var child in original.children) { + final duplicatedChild = _deepCloneSpaceTree(child, parent: newSpace); + newSpace.children.add(duplicatedChild); + + final newConnection = Connection( + startSpace: newSpace, + endSpace: duplicatedChild, + direction: "down", + ); + connections.add(newConnection); + + duplicatedChild.incomingConnection = newConnection; + newSpace.addOutgoingConnection(newConnection); + } + + return newSpace; + } } From ccce7bb6714e9d5d6d304f96de1effa1388a56ac Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 29 Apr 2025 10:06:17 +0300 Subject: [PATCH 237/238] fix real time garage door and add flush sensor to routines --- assets/icons/disappe_delay_icon.svg | 17 + assets/icons/indent_level_icon.svg | 13 + assets/icons/target_confirm_time_icon.svg | 21 + assets/icons/trigger_level_icon.svg | 23 + .../all_devices/models/devices_model.dart | 111 +++-- .../flush_mounted_presence_sensor_model.dart | 4 + .../garage_door/bloc/garage_door_bloc.dart | 19 +- .../view/garage_door_control_view.dart | 43 +- .../dialog_helper/device_dialog_helper.dart | 10 + .../models/flush/flush_functions.dart | 407 ++++++++++++++++++ .../models/flush/flush_operational_value.dart | 11 + lib/pages/routines/widgets/if_container.dart | 72 ++-- .../routines/widgets/routine_devices.dart | 22 +- .../flush_operational_values_list.dart | 70 +++ .../flush_presence_sensor.dart | 208 +++++++++ .../flush_value_selector_widget.dart | 178 ++++++++ .../flush_presence_sensor/time_wheel.dart | 169 ++++++++ .../routines/widgets/then_container.dart | 58 +-- lib/utils/constants/assets.dart | 113 +++-- lib/utils/enum/device_types.dart | 4 +- pubspec.lock | 2 +- 21 files changed, 1435 insertions(+), 140 deletions(-) create mode 100644 assets/icons/disappe_delay_icon.svg create mode 100644 assets/icons/indent_level_icon.svg create mode 100644 assets/icons/target_confirm_time_icon.svg create mode 100644 assets/icons/trigger_level_icon.svg create mode 100644 lib/pages/routines/models/flush/flush_functions.dart create mode 100644 lib/pages/routines/models/flush/flush_operational_value.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart create mode 100644 lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/time_wheel.dart diff --git a/assets/icons/disappe_delay_icon.svg b/assets/icons/disappe_delay_icon.svg new file mode 100644 index 00000000..c65fc5a7 --- /dev/null +++ b/assets/icons/disappe_delay_icon.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/indent_level_icon.svg b/assets/icons/indent_level_icon.svg new file mode 100644 index 00000000..837cdac2 --- /dev/null +++ b/assets/icons/indent_level_icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/icons/target_confirm_time_icon.svg b/assets/icons/target_confirm_time_icon.svg new file mode 100644 index 00000000..61141654 --- /dev/null +++ b/assets/icons/target_confirm_time_icon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/trigger_level_icon.svg b/assets/icons/trigger_level_icon.svg new file mode 100644 index 00000000..56e343e8 --- /dev/null +++ b/assets/icons/trigger_level_icon.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index d07efc9b..485cb0ec 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart' import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart'; @@ -155,13 +156,17 @@ class AllDevicesModel { batteryLevel = int.tryParse(json['battery']?.toString() ?? ''); productName = json['productName']?.toString(); deviceTags = json['deviceTag'] != null && json['deviceTag'] is List - ? (json['deviceTag'] as List).map((tag) => DeviceTagModel.fromJson(tag)).toList() + ? (json['deviceTag'] as List) + .map((tag) => DeviceTagModel.fromJson(tag)) + .toList() : []; deviceSubSpace = json['subspace'] != null ? DeviceSubSpace.fromJson(json['subspace']) : DeviceSubSpace(subspaceName: ''); if (json['spaces'] != null && json['spaces'] is List) { - spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList(); + spaces = (json['spaces'] as List) + .map((space) => DeviceSpaceModel.fromJson(space)) + .toList(); } } @@ -209,7 +214,8 @@ SOS String tempIcon = ''; if (type == DeviceType.LightBulb) { tempIcon = Assets.lightBulb; - } else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) { + } else if (type == DeviceType.CeilingSensor || + type == DeviceType.WallSensor) { tempIcon = Assets.sensors; } else if (type == DeviceType.AC) { tempIcon = Assets.ac; @@ -239,6 +245,8 @@ SOS // tempIcon = Assets.gang3touch; } else if (type == DeviceType.WaterLeak) { tempIcon = Assets.waterLeakNormal; + } else if (type == DeviceType.NCPS) { + tempIcon = Assets.sensors; } else { tempIcon = Assets.logoHorizontal; } @@ -254,51 +262,75 @@ SOS switch (productType) { case 'AC': return [ - SwitchFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ModeFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - TempSetFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - CurrentTempFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), - LevelFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + SwitchFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ModeFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + TempSetFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + CurrentTempFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + LevelFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ChildLockFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ]; case '1G': return [ OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''), - OneGangCountdownFunction(deviceId: uuid ?? '', deviceName: name ?? ''), + OneGangCountdownFunction( + deviceId: uuid ?? '', deviceName: name ?? ''), ]; case '2G': return [ TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''), TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''), - TwoGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''), - TwoGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangCountdown1Function( + deviceId: uuid ?? '', deviceName: name ?? ''), + TwoGangCountdown2Function( + deviceId: uuid ?? '', deviceName: name ?? ''), ]; case '3G': return [ - ThreeGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ThreeGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ThreeGangSwitch3Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ThreeGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ThreeGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - ThreeGangCountdown3Function(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ThreeGangSwitch1Function( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ThreeGangSwitch2Function( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ThreeGangSwitch3Function( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ThreeGangCountdown1Function( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ThreeGangCountdown2Function( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + ThreeGangCountdown3Function( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), ]; case 'WPS': return [ //IF Functions - PresenceStateFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), - CurrentDistanceFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), - IlluminanceValueFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), - PresenceTimeFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + PresenceStateFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + CurrentDistanceFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + IlluminanceValueFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + PresenceTimeFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), //THEN Functions - FarDetectionFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), - MotionSensitivityFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), - MotionLessSensitivityFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), - IndicatorFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), - NoOneTimeFunction(deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + FarDetectionFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + MotionSensitivityFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + MotionLessSensitivityFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + IndicatorFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'), + NoOneTimeFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), ]; case 'GW': return [ @@ -323,6 +355,30 @@ SOS uuid: uuid ?? '', name: name ?? '', ); + case 'NCPS': + return [ + FlushPresenceDelayFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF',), + + FlushIlluminanceFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'), + //THEN Functions + FlushSensitivityFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + FlushNearDetectionFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + FlushMaxDetectDistFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + FlushTargetConfirmTimeFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + FlushDisappeDelayFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + FlushIndentLevelFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + FlushTriggerLevelFunction( + deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'), + ]; + default: return []; } @@ -454,5 +510,6 @@ SOS "3GT": DeviceType.ThreeTouch, "GD": DeviceType.GarageDoor, "WL": DeviceType.WaterLeak, + "NCPS": DeviceType.NCPS, }; } diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart index 975af0e8..bf97005d 100644 --- a/lib/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart @@ -97,3 +97,7 @@ class FlushMountedPresenceSensorModel { ); } } + + + + diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index 025de303..9083ffbe 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -106,14 +106,14 @@ class GarageDoorBloc extends Bloc { ScheduleEntry newSchedule = ScheduleEntry( category: event.category, time: formatTimeOfDayToISO(event.time), - function: Status(code: 'switch_1', value: event.functionOn), + function: Status(code: 'doorcontact_state', value: event.functionOn), days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), ); bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); if (success) { add(FetchGarageDoorSchedulesEvent( - deviceId: deviceId, category: 'switch_1')); + deviceId: deviceId, category: 'doorcontact_state')); } else { emit(GarageDoorLoadedState(status: deviceStatus)); } @@ -149,7 +149,8 @@ class GarageDoorBloc extends Bloc { final updatedSchedules = deviceStatus.schedules?.map((schedule) { if (schedule.scheduleId == event.scheduleId) { return schedule.copyWith( - function: Status(code: 'switch_1', value: event.functionOn), + function: + Status(code: 'doorcontact_state', value: event.functionOn), enable: event.enable, ); } @@ -274,7 +275,8 @@ class GarageDoorBloc extends Bloc { Future _onBatchControl( GarageDoorBatchControlEvent event, Emitter emit) async { - final oldValue = event.code == 'switch_1' ? deviceStatus.switch1 : false; + final oldValue = + event.code == 'doorcontact_state' ? deviceStatus.switch1 : false; _updateLocalValue(event.code, event.value); emit(GarageDoorBatchStatusLoaded(deviceStatus)); @@ -409,7 +411,7 @@ class GarageDoorBloc extends Bloc { void _revertValue( String code, dynamic oldValue, Emitter emit) { switch (code) { - case 'switch_1': + case 'doorcontact_state': if (oldValue is bool) { deviceStatus = deviceStatus.copyWith(switch1: oldValue); } @@ -468,10 +470,11 @@ class GarageDoorBloc extends Bloc { deviceStatus = deviceStatus.copyWith(voiceControl1: value); } break; - case 'door_contact_state': + case 'doorcontact_state': if (value is bool) { deviceStatus = deviceStatus.copyWith(doorContactState: value); } + default: break; } @@ -490,14 +493,14 @@ class GarageDoorBloc extends Bloc { scheduleId: event.scheduleId, category: event.category, time: formatTimeOfDayToISO(event.time), - function: Status(code: 'switch_1', value: event.functionOn), + function: Status(code: 'doorcontact_state', value: event.functionOn), days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), ); bool success = await DevicesManagementApi() .editScheduleRecord(deviceId, newSchedule); if (success) { add(FetchGarageDoorSchedulesEvent( - deviceId: deviceId, category: 'switch_1')); + deviceId: deviceId, category: 'doorcontact_state')); } else { emit(GarageDoorLoadedState(status: deviceStatus)); } diff --git a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart index 886ca9ae..54dd50e0 100644 --- a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart +++ b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart @@ -14,7 +14,8 @@ 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 GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout { +class GarageDoorControlView extends StatelessWidget + with HelperResponsiveLayout { final String deviceId; const GarageDoorControlView({required this.deviceId, super.key}); @@ -22,7 +23,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)), + create: (context) => GarageDoorBloc(deviceId: deviceId) + ..add(GarageDoorInitialEvent(deviceId)), child: BlocBuilder( builder: (context, state) { if (state is GarageDoorLoadingState) { @@ -34,7 +36,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout garageDoorSensor: true, onRowTap: (index) {}, onClose: () { - context.read().add(BackToGarageDoorGridViewEvent()); + context + .read() + .add(BackToGarageDoorGridViewEvent()); }, ); } else if (state is GarageDoorLoadedState) { @@ -71,11 +75,14 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout children: [ IconNameStatusContainer( isFullIcon: false, - name: status.switch1 ? 'Opened' : 'Closed', - icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor, + name: status.doorContactState ? 'Opened' : 'Closed', + icon: status.doorContactState ? Assets.openedDoor : Assets.closedDoor, onTap: () { context.read().add( - GarageDoorControlEvent(deviceId: status.uuid, value: !status.switch1, code: 'switch_1'), + GarageDoorControlEvent( + deviceId: status.uuid, + value: !status.switch1, + code: 'doorcontact_state'), ); }, status: status.switch1, @@ -84,7 +91,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout IconNameStatusContainer( onTap: () { context.read().add( - FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'), + FetchGarageDoorSchedulesEvent( + deviceId: deviceId, category: 'doorcontact_state'), ); showDialog( context: context, @@ -107,7 +115,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout children: [ IconButton( onPressed: () { - context.read().add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid)); + context + .read() + .add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid)); }, icon: const Icon( Icons.remove, @@ -125,7 +135,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout ), Text( 'h', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor), ), Text( (status.delay.inMinutes % 60).toString().padLeft(2, '0'), @@ -136,11 +147,14 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout ), Text( 'm', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor), ), IconButton( onPressed: () { - context.read().add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid)); + context + .read() + .add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid)); }, icon: const Icon( Icons.add, @@ -158,7 +172,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout onChange: (value) { context.read().add( GarageDoorControlEvent( - deviceId: status.uuid, value: value ? status.delay.inSeconds : 0, code: 'countdown_1'), + deviceId: status.uuid, + value: value ? status.delay.inSeconds : 0, + code: 'countdown_1'), ); }, ), @@ -167,7 +183,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout name: 'Records', icon: Assets.records, onTap: () { - context.read().add(FetchGarageDoorRecordsEvent(code: 'switch_1', deviceId: status.uuid)); + context.read().add(FetchGarageDoorRecordsEvent( + code: 'switch_1', deviceId: status.uuid)); }, status: false, textColor: ColorsManager.blackColor, diff --git a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart index b1afbc12..a94a312b 100644 --- a/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart +++ b/lib/pages/routines/helper/dialog_helper/device_dialog_helper.dart @@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ac_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/gateway/gateway_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_switch_dialog.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart'; @@ -116,6 +117,15 @@ class DeviceDialogHelper { deviceSelectedFunctions: deviceSelectedFunctions, device: data['device'], ); + case 'NCPS': + return FlushPresenceSensor.showFlushFunctionsDialog( + context: context, + functions: functions, + uniqueCustomId: data['uniqueCustomId'], + deviceSelectedFunctions: deviceSelectedFunctions, + dialogType: dialogType, + device: data['device'], + ); default: return null; diff --git a/lib/pages/routines/models/flush/flush_functions.dart b/lib/pages/routines/models/flush/flush_functions.dart new file mode 100644 index 00000000..5013c0b8 --- /dev/null +++ b/lib/pages/routines/models/flush/flush_functions.dart @@ -0,0 +1,407 @@ +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +abstract class FlushFunctions + extends DeviceFunction { + final String type; + + FlushFunctions({ + required super.deviceId, + required super.deviceName, + required super.code, + required super.operationName, + required super.icon, + required this.type, + }); + + List getOperationalValues(); +} + +class FlushPresenceDelayFunction extends FlushFunctions { + final int min; + FlushPresenceDelayFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 0, + super( + code: FlushMountedPresenceSensorModel.codePresenceState, + operationName: 'Presence State', + icon: Assets.presenceStateIcon, + ); + + @override + List getOperationalValues() { + return [ + FlushOperationalValue( + icon: Assets.nobodyTime, + description: 'None', + value: "none", + ), + FlushOperationalValue( + icon: Assets.presenceStateIcon, + description: 'Presence', + value: 'presence', + ), + ]; + } +} + +class FlushSensiReduceFunction extends FlushFunctions { + final int min; + final int max; + final int step; + + FlushSensiReduceFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 1, + max = 5, + step = 1, + super( + code: FlushMountedPresenceSensorModel.codeSensiReduce, + operationName: 'Sensitivity Reduction', + icon: Assets.motionlessDetectionSensitivityIcon, + ); + + @override + List getOperationalValues() { + return List.generate( + (max - min) ~/ step + 1, + (index) => FlushOperationalValue( + icon: Assets.currentDistanceIcon, + description: '${min + (index * step)}', + value: min + (index * step), + )); + } +} + +class FlushNoneDelayFunction extends FlushFunctions { + final int min; + final int max; + final String unit; + + FlushNoneDelayFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 10, + max = 10000, + unit = '秒', + super( + code: FlushMountedPresenceSensorModel.codeNoneDelay, + operationName: 'None Delay', + icon: Assets.nobodyTime, + ); + + @override + List getOperationalValues() { + return [ + FlushOperationalValue( + icon: icon, + description: 'Custom $unit', + value: null, + ) + ]; + } +} + +class FlushIlluminanceFunction extends FlushFunctions { + final int min; + final int max; + final int step; + + FlushIlluminanceFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 0, + max = 2000, + step = 0, + super( + code: FlushMountedPresenceSensorModel.codeIlluminance, + operationName: 'Illuminance', + icon: Assets.IlluminanceIcon, + ); + + @override + List getOperationalValues() { + List values = []; + for (int lux = min; lux <= max; lux += step) { + values.add(FlushOperationalValue( + icon: Assets.IlluminanceIcon, + description: "$lux Lux", + value: lux, + )); + } + return values; + } +} + +class FlushOccurDistReduceFunction extends FlushFunctions { + final int min; + final int max; + final int step; + + FlushOccurDistReduceFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 0, + max = 100, + step = 1, + super( + code: FlushMountedPresenceSensorModel.codeOccurDistReduce, + operationName: 'Occurrence Distance Reduction', + icon: Assets.assetsTempreture, + ); + + @override + List getOperationalValues() { + return List.generate( + (max - min) ~/ step + 1, + (index) => FlushOperationalValue( + icon: Assets.assetsTempreture, + description: '${min + (index * step)}', + value: min + (index * step), + )); + } +} + +// ==== then functions ==== +class FlushSensitivityFunction extends FlushFunctions { + final int min; + final int max; + final int step; + + FlushSensitivityFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 1, + max = 9, + step = 1, + super( + code: FlushMountedPresenceSensorModel.codeSensitivity, + operationName: 'Sensitivity', + icon: Assets.sensitivity, + ); + + @override + List getOperationalValues() { + return List.generate( + (max - min) ~/ step + 1, + (index) => FlushOperationalValue( + icon: Assets.motionDetectionSensitivityValueIcon, + description: '${min + (index * step)}', + value: min + (index * step), + )); + } +} + +class FlushNearDetectionFunction extends FlushFunctions { + final int min; + final double max; + final int step; + final String unit; + + FlushNearDetectionFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 0, + max = 9.5, + step = 1, + unit = 'm', + super( + code: FlushMountedPresenceSensorModel.codeNearDetection, + operationName: 'Nearest Detect Dist', + icon: Assets.currentDistanceIcon, + ); + + @override + List getOperationalValues() { + final values = []; + for (var value = min; value <= max; value += step) { + values.add(FlushOperationalValue( + icon: Assets.nobodyTime, + description: '$value $unit', + value: value * 10, + )); + } + return values; + } +} + +class FlushMaxDetectDistFunction extends FlushFunctions { + final int min; + final int max; + final int step; + final String unit; + + FlushMaxDetectDistFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 75, + max = 600, + step = 75, + unit = 'cm', + super( + code: FlushMountedPresenceSensorModel.codeFarDetection, + operationName: 'Max Detect Dist', + icon: Assets.currentDistanceIcon, + ); + + @override + List getOperationalValues() { + final values = []; + for (var value = min; value <= max; value += step) { + values.add(FlushOperationalValue( + icon: Assets.nobodyTime, + description: '$value $unit', + value: value, + )); + } + return values; + } +} + +class FlushTargetConfirmTimeFunction extends FlushFunctions { + final int min; + final int max; + final int step; + final String unit; + + FlushTargetConfirmTimeFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 75, + max = 600, + step = 75, + unit = 'cm', + super( + code: FlushMountedPresenceSensorModel.codePresenceDelay, + operationName: 'Target Confirm Time', + icon: Assets.targetConfirmTimeIcon, + ); + + @override + List getOperationalValues() { + final values = []; + for (var value = min; value <= max; value += step) { + values.add(FlushOperationalValue( + icon: Assets.nobodyTime, + description: '$value $unit', + value: value, + )); + } + return values; + } +} + +class FlushDisappeDelayFunction extends FlushFunctions { + final int min; + final int max; + final int step; + final String unit; + + FlushDisappeDelayFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 75, + max = 600, + step = 75, + unit = 'cm', + super( + code: FlushMountedPresenceSensorModel.codeNoneDelay, + operationName: 'Disappe Delay', + icon: Assets.DisappeDelayIcon, + ); + + @override + List getOperationalValues() { + final values = []; + for (var value = min; value <= max; value += step) { + values.add(FlushOperationalValue( + icon: Assets.nobodyTime, + description: '$value $unit', + value: value, + )); + } + return values; + } +} + +class FlushIndentLevelFunction extends FlushFunctions { + final int min; + final int max; + final int step; + final String unit; + + FlushIndentLevelFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 75, + max = 600, + step = 75, + unit = 'cm', + super( + code: FlushMountedPresenceSensorModel.codeOccurDistReduce, + operationName: 'Indent Level', + icon: Assets.indentLevelIcon, + ); + + @override + List getOperationalValues() { + final values = []; + for (var value = min; value <= max; value += step) { + values.add(FlushOperationalValue( + icon: Assets.nobodyTime, + description: '$value $unit', + value: value, + )); + } + return values; + } +} + +class FlushTriggerLevelFunction extends FlushFunctions { + final int min; + final int max; + final int step; + final String unit; + + FlushTriggerLevelFunction({ + required super.deviceId, + required super.deviceName, + required super.type, + }) : min = 75, + max = 600, + step = 75, + unit = 'cm', + super( + code: FlushMountedPresenceSensorModel.codeSensiReduce, + operationName: 'Trigger Level', + icon: Assets.triggerLevelIcon, + ); + + @override + List getOperationalValues() { + final values = []; + for (var value = min; value <= max; value += step) { + values.add(FlushOperationalValue( + icon: Assets.nobodyTime, + description: '$value $unit', + value: value, + )); + } + return values; + } +} diff --git a/lib/pages/routines/models/flush/flush_operational_value.dart b/lib/pages/routines/models/flush/flush_operational_value.dart new file mode 100644 index 00000000..af96d35b --- /dev/null +++ b/lib/pages/routines/models/flush/flush_operational_value.dart @@ -0,0 +1,11 @@ +class FlushOperationalValue { + final String icon; + final String description; + final dynamic value; + + FlushOperationalValue({ + required this.icon, + required this.description, + required this.value, + }); +} diff --git a/lib/pages/routines/widgets/if_container.dart b/lib/pages/routines/widgets/if_container.dart index f7a4ddc1..884c8d10 100644 --- a/lib/pages/routines/widgets/if_container.dart +++ b/lib/pages/routines/widgets/if_container.dart @@ -28,10 +28,12 @@ class IfContainer extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('IF', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), if (state.isAutomation && state.ifItems.isNotEmpty) AutomationOperatorSelector( - selectedOperator: state.selectedAutomationOperator), + selectedOperator: + state.selectedAutomationOperator), ], ), const SizedBox(height: 16), @@ -55,16 +57,17 @@ class IfContainer extends StatelessWidget { (index) => GestureDetector( onTap: () async { if (!state.isTabToRun) { - final result = await DeviceDialogHelper.showDeviceDialog( - context: context, - data: state.ifItems[index], - removeComparetors: false, - dialogType: "IF"); + final result = await DeviceDialogHelper + .showDeviceDialog( + context: context, + data: state.ifItems[index], + removeComparetors: false, + dialogType: "IF"); if (result != null) { - context - .read() - .add(AddToIfContainer(state.ifItems[index], false)); + context.read().add( + AddToIfContainer( + state.ifItems[index], false)); } else if (![ 'AC', '1G', @@ -73,25 +76,32 @@ class IfContainer extends StatelessWidget { 'WPS', 'GW', 'CPS', - ].contains(state.ifItems[index]['productType'])) { - context - .read() - .add(AddToIfContainer(state.ifItems[index], false)); + 'NCPS' + ].contains(state.ifItems[index] + ['productType'])) { + + context.read().add( + AddToIfContainer( + state.ifItems[index], false)); } } }, child: DraggableCard( - imagePath: state.ifItems[index]['imagePath'] ?? '', + imagePath: + state.ifItems[index]['imagePath'] ?? '', title: state.ifItems[index]['title'] ?? '', deviceData: state.ifItems[index], - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 8), isFromThen: false, isFromIf: true, onRemove: () { - context.read().add(RemoveDragCard( - index: index, - isFromThen: false, - key: state.ifItems[index]['uniqueCustomId'])); + context.read().add( + RemoveDragCard( + index: index, + isFromThen: false, + key: state.ifItems[index] + ['uniqueCustomId'])); }, ), )), @@ -112,7 +122,9 @@ class IfContainer extends StatelessWidget { if (!state.isTabToRun) { if (mutableData['deviceId'] == 'tab_to_run') { - context.read().add(AddToIfContainer(mutableData, true)); + context + .read() + .add(AddToIfContainer(mutableData, true)); } else { final result = await DeviceDialogHelper.showDeviceDialog( dialogType: 'IF', @@ -121,10 +133,14 @@ class IfContainer extends StatelessWidget { removeComparetors: false); if (result != null) { - context.read().add(AddToIfContainer(mutableData, false)); - } else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS'] + context + .read() + .add(AddToIfContainer(mutableData, false)); + } else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS', 'NCPS'] .contains(mutableData['productType'])) { - context.read().add(AddToIfContainer(mutableData, false)); + context + .read() + .add(AddToIfContainer(mutableData, false)); } } } @@ -170,7 +186,9 @@ class AutomationOperatorSelector extends StatelessWidget { ), ), onPressed: () { - context.read().add(const ChangeAutomationOperator(operator: 'or')); + context + .read() + .add(const ChangeAutomationOperator(operator: 'or')); }, ), Container( @@ -196,7 +214,9 @@ class AutomationOperatorSelector extends StatelessWidget { ), ), onPressed: () { - context.read().add(const ChangeAutomationOperator(operator: 'and')); + context + .read() + .add(const ChangeAutomationOperator(operator: 'and')); }, ), ], diff --git a/lib/pages/routines/widgets/routine_devices.dart b/lib/pages/routines/widgets/routine_devices.dart index 3294a73a..9192b422 100644 --- a/lib/pages/routines/widgets/routine_devices.dart +++ b/lib/pages/routines/widgets/routine_devices.dart @@ -17,7 +17,16 @@ class _RoutineDevicesState extends State { context.read().add(FetchDevicesInRoutine()); } - static const _allowedProductTypes = {'AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS'}; + static const _allowedProductTypes = { + 'AC', + '1G', + '2G', + '3G', + 'WPS', + 'GW', + 'CPS', + 'NCPS' + }; @override Widget build(BuildContext context) { @@ -34,7 +43,8 @@ class _RoutineDevicesState extends State { }); final deviceList = state.devices - .where((device) => _allowedProductTypes.contains(device.productType)) + .where( + (device) => _allowedProductTypes.contains(device.productType)) .toList(); return Wrap( @@ -51,12 +61,16 @@ class _RoutineDevicesState extends State { 'productType': device.productType, 'functions': device.functions, 'uniqueCustomId': '', - 'tag': device.deviceTags!.isNotEmpty ? device.deviceTags![0].name : '', + 'tag': device.deviceTags!.isNotEmpty + ? device.deviceTags![0].name + : '', 'subSpace': device.deviceSubSpace?.subspaceName ?? '', }; if (state.searchText != null && state.searchText!.isNotEmpty) { - return device.name!.toLowerCase().contains(state.searchText!.toLowerCase()) + return device.name! + .toLowerCase() + .contains(state.searchText!.toLowerCase()) ? DraggableCard( imagePath: deviceData['imagePath'] as String, title: deviceData['title'] as String, diff --git a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart new file mode 100644 index 00000000..1a96cfbb --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart'; + +class FlushOperationalValuesList extends StatelessWidget { + final List values; + final dynamic selectedValue; + final AllDevicesModel? device; + final String operationName; + final String selectCode; + final ValueChanged onSelect; + const FlushOperationalValuesList({ + required this.values, + required this.selectedValue, + required this.device, + required this.operationName, + required this.selectCode, + required this.onSelect, + super.key, + }); + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.all(20), + itemCount: values.length, + itemBuilder: (context, index) => + _buildValueItem(context, values[index]), + ); + } + + + + Widget _buildValueItem(BuildContext context, FlushOperationalValue value) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: _buildValueDescription(value)), + _buildValueRadio(context, value), + ], + ), + ); + } + + + + + Widget _buildValueDescription(FlushOperationalValue value) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text(value.description), + ); + } + + Widget _buildValueRadio(context, FlushOperationalValue value) { + return Radio( + value: value.value, + groupValue: selectedValue, + onChanged: (_) => onSelect(value)); + } + + +} diff --git a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart new file mode 100644 index 00000000..bf2146ad --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_presence_sensor.dart @@ -0,0 +1,208 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; +import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class FlushPresenceSensor extends StatefulWidget { + final List functions; + final AllDevicesModel? device; + final List? deviceSelectedFunctions; + final String? uniqueCustomId; + final String dialogType; + final bool removeComparetors; + + const FlushPresenceSensor({ + super.key, + required this.functions, + this.device, + this.deviceSelectedFunctions, + this.uniqueCustomId, + required this.dialogType, + this.removeComparetors = false, + }); + + static Future?> showFlushFunctionsDialog({ + required BuildContext context, + required List functions, + AllDevicesModel? device, + List? deviceSelectedFunctions, + String? uniqueCustomId, + required String dialogType, + bool removeComparetors = false, + }) async { + return showDialog?>( + context: context, + builder: (context) => FlushPresenceSensor( + functions: functions, + device: device, + deviceSelectedFunctions: deviceSelectedFunctions, + uniqueCustomId: uniqueCustomId, + removeComparetors: removeComparetors, + dialogType: dialogType, + ), + ); + } + + @override + State createState() => _WallPresenceSensorState(); +} + +class _WallPresenceSensorState extends State { + late final List _flushFunctions; + @override + void initState() { + super.initState(); + _flushFunctions = + widget.functions.whereType().where((function) { + if (widget.dialogType == 'THEN') { + return function.type == 'THEN' || function.type == 'BOTH'; + } + return function.type == 'IF' || function.type == 'BOTH'; + }).toList(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => FunctionBloc() + ..add(InitializeFunctions(widget.deviceSelectedFunctions ?? [])), + child: _buildDialogContent(), + ); + } + + Widget _buildDialogContent() { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: BlocBuilder( + builder: (context, state) { + final selectedFunction = state.selectedFunction; + return Container( + width: selectedFunction != null ? 600 : 360, + height: 450, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DialogHeader('Presence Sensor Condition'), + Expanded(child: _buildMainContent(context, state)), + _buildDialogFooter(context, state), + ], + ), + ); + }, + ), + ); + } + + Widget _buildMainContent(BuildContext context, FunctionBlocState state) { + return Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildFunctionList(context), + if (state.selectedFunction != null) _buildValueSelector(context, state), + ], + ); + } + + Widget _buildFunctionList(BuildContext context) { + return SizedBox( + width: 360, + child: ListView.separated( + shrinkWrap: false, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: _flushFunctions.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.symmetric(horizontal: 40.0), + child: Divider(color: ColorsManager.dividerColor), + ), + itemBuilder: (context, index) { + final function = _flushFunctions[index]; + return ListTile( + leading: SvgPicture.asset( + function.icon, + width: 24, + height: 24, + placeholderBuilder: (context) => const SizedBox( + width: 24, + height: 24, + ), + ), + title: Text( + function.operationName, + style: context.textTheme.bodyMedium, + ), + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.textGray, + ), + onTap: () => context.read().add( + SelectFunction( + functionCode: function.code, + operationName: function.operationName, + ), + ), + ); + }, + ), + ); + } + + Widget _buildValueSelector(BuildContext context, FunctionBlocState state) { + final selectedFunction = state.selectedFunction ?? ''; + final functionData = state.addedFunctions.firstWhere( + (f) => f.functionCode == selectedFunction, + orElse: () => DeviceFunctionData( + entityId: '', + functionCode: selectedFunction, + operationName: state.selectedOperationName ?? '', + value: null, + ), + ); + + return Expanded( + child: FlushValueSelectorWidget( + selectedFunction: selectedFunction, + functionData: functionData, + flushFunctions: _flushFunctions, + device: widget.device, + dialogType: widget.dialogType, + removeComparators: widget.removeComparetors, + ), + ); + } + + Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) { + return DialogFooter( + onCancel: () => Navigator.pop(context), + onConfirm: state.addedFunctions.isNotEmpty + ? () { + context.read().add( + AddFunctionToRoutine( + state.addedFunctions, + widget.uniqueCustomId!, + ), + ); + Navigator.pop( + context, + {'deviceId': widget.functions.first.deviceId}, + ); + } + : null, + isConfirmEnabled: state.selectedFunction != null, + ); + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart new file mode 100644 index 00000000..64f060e5 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_value_selector_widget.dart @@ -0,0 +1,178 @@ +import 'package:flutter/material.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/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart'; +import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; +import 'package:syncrow_web/pages/routines/models/device_functions.dart'; +import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart'; +import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart'; +import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; + +class FlushValueSelectorWidget extends StatelessWidget { + final String selectedFunction; + final DeviceFunctionData functionData; + final List flushFunctions; + final AllDevicesModel? device; + final String dialogType; + final bool removeComparators; + + const FlushValueSelectorWidget({ + required this.selectedFunction, + required this.functionData, + required this.flushFunctions, + required this.device, + required this.dialogType, + required this.removeComparators, + super.key, + }); + + (double, double) get sliderRange => switch (functionData.functionCode) { + FlushMountedPresenceSensorModel.codeOccurDistReduce => (0, 3), + FlushMountedPresenceSensorModel.codeNoneDelay => (200, 3000), + FlushMountedPresenceSensorModel.codeSensiReduce => (0, 3), + FlushMountedPresenceSensorModel.codePresenceDelay => (0, 5), + FlushMountedPresenceSensorModel.codeIlluminance => (0.0, 2000.0), + FlushMountedPresenceSensorModel.codeFarDetection => (0.0, 9.5), + FlushMountedPresenceSensorModel.codeNearDetection => (0.0, 9.5), + _ => (0.0, 100.0), + }; + + double get stepSize => switch (functionData.functionCode) { + FlushMountedPresenceSensorModel.codeNoneDelay => 10.0, + FlushMountedPresenceSensorModel.codeFarDetection => 0.5, + FlushMountedPresenceSensorModel.codeNearDetection => 0.5, + FlushMountedPresenceSensorModel.codePresenceDelay => 1.0, + FlushMountedPresenceSensorModel.codeOccurDistReduce => 1.0, + FlushMountedPresenceSensorModel.codeSensiReduce => 1.0, + _ => 1.0, + }; + + @override + Widget build(BuildContext context) { + final selectedFn = flushFunctions.firstWhere( + (f) => f.code == selectedFunction, + orElse: () => throw Exception('Function $selectedFunction not found'), + ); + + if (_isSliderFunction(selectedFunction)) { + final isNearDetection = + selectedFunction == FlushMountedPresenceSensorModel.codeNearDetection; + final isFarDetection = + selectedFunction == FlushMountedPresenceSensorModel.codeFarDetection; + + final isDistanceDetection = isNearDetection || isFarDetection; + double initialValue = (functionData.value as num?)?.toDouble() ?? 0.0; + + if (isDistanceDetection) { + initialValue = initialValue / 100; + } + return SliderValueSelector( + currentCondition: functionData.condition, + dialogType: dialogType, + sliderRange: sliderRange, + displayedValue: getDisplayText, + initialValue: initialValue, + onConditionChanged: (condition) => context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: functionData.operationName, + condition: condition, + value: functionData.value ?? 0, + ), + ), + ), + onSliderChanged: (value) { + final roundedValue = _roundToStep(value, stepSize); + final finalValue = + isDistanceDetection ? (roundedValue * 100).toInt() : roundedValue; + + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: functionData.operationName, + value: finalValue, + condition: functionData.condition, + ), + ), + ); + }, + unit: _unit, + dividendOfRange: stepSize, + ); + } + + return FlushOperationalValuesList( + values: selectedFn.getOperationalValues(), + selectedValue: functionData.value, + device: device, + operationName: selectedFn.operationName, + selectCode: selectedFunction, + onSelect: (selectedValue) async { + context.read().add( + AddFunction( + functionData: DeviceFunctionData( + entityId: device?.uuid ?? '', + functionCode: selectedFunction, + operationName: functionData.operationName, + value: selectedValue.value, + condition: functionData.condition, + ), + ), + ); + }, + ); + } + + double _roundToStep(double value, double step) { + return (value / step).roundToDouble() * step; + } + + bool _isSliderFunction(String function) => [ + FlushMountedPresenceSensorModel.codeOccurDistReduce, + FlushMountedPresenceSensorModel.codeSensiReduce, + FlushMountedPresenceSensorModel.codeNoneDelay, + FlushMountedPresenceSensorModel.codeIlluminance, + FlushMountedPresenceSensorModel.codePresenceDelay, + FlushMountedPresenceSensorModel.codeFarDetection, + FlushMountedPresenceSensorModel.codeNearDetection, + ].contains(function); + + String get _unit => switch (functionData.functionCode) { + FlushMountedPresenceSensorModel.codeOccurDistReduce => 'Min', + FlushMountedPresenceSensorModel.codeSensiReduce => 'Sec', + FlushMountedPresenceSensorModel.codeNoneDelay => 'Sec', + FlushMountedPresenceSensorModel.codePresenceDelay => 'Sec', + FlushMountedPresenceSensorModel.codeIlluminance => 'Lux', + FlushMountedPresenceSensorModel.codeFarDetection => 'm', + FlushMountedPresenceSensorModel.codeNearDetection => 'm', + _ => '', + }; + + String get getDisplayText { + final num? value = functionData.value; + double displayValue = value?.toDouble() ?? 0.0; + + if (functionData.functionCode == + FlushMountedPresenceSensorModel.codeNearDetection || + functionData.functionCode == + FlushMountedPresenceSensorModel.codeFarDetection) { + displayValue = displayValue / 100; + } + + switch (functionData.functionCode) { + case FlushMountedPresenceSensorModel.codeFarDetection: + case FlushMountedPresenceSensorModel.codeNearDetection: + return displayValue.toStringAsFixed(1); + case FlushMountedPresenceSensorModel.codeOccurDistReduce: + case FlushMountedPresenceSensorModel.codeSensiReduce: + case FlushMountedPresenceSensorModel.codePresenceDelay: + return displayValue.toStringAsFixed(0); + default: + return displayValue.toStringAsFixed(0); + } + } +} diff --git a/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/time_wheel.dart b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/time_wheel.dart new file mode 100644 index 00000000..56f74054 --- /dev/null +++ b/lib/pages/routines/widgets/routine_dialogs/flush_presence_sensor/time_wheel.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class TimeWheelPicker extends StatefulWidget { + final int initialHours; + final int initialMinutes; + final int initialSeconds; + final Function(int, int, int) onTimeChanged; + + const TimeWheelPicker({ + super.key, + required this.initialHours, + required this.initialMinutes, + required this.initialSeconds, + required this.onTimeChanged, + }); + + @override + State createState() => _TimeWheelPickerState(); +} + +class _TimeWheelPickerState extends State { + late FixedExtentScrollController _hoursController; + late FixedExtentScrollController _minutesController; + late FixedExtentScrollController _secondsController; + + @override + void initState() { + super.initState(); + _hoursController = + FixedExtentScrollController(initialItem: widget.initialHours); + _minutesController = + FixedExtentScrollController(initialItem: widget.initialMinutes); + _secondsController = + FixedExtentScrollController(initialItem: widget.initialSeconds); + } + + @override + void didUpdateWidget(TimeWheelPicker oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.initialHours != widget.initialHours) { + _hoursController.jumpToItem(widget.initialHours); + } + if (oldWidget.initialMinutes != widget.initialMinutes) { + _minutesController.jumpToItem(widget.initialMinutes); + } + if (oldWidget.initialSeconds != widget.initialSeconds) { + _secondsController.jumpToItem(widget.initialSeconds); + } + } + + + + @override + void dispose() { + _hoursController.dispose(); + _minutesController.dispose(); + _secondsController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildPickerColumn( + label: 'h', + controller: _hoursController, + itemCount: 3, + onChanged: (value) { + _handleTimeChange( + value, + _minutesController.selectedItem, + _secondsController.selectedItem, + ); + }), + const SizedBox(width: 5), + _buildPickerColumn( + label: 'm', + controller: _minutesController, + itemCount: 60, + onChanged: (value) { + _handleTimeChange( + _hoursController.selectedItem, + value, + _secondsController.selectedItem, + ); + }), + const SizedBox(width: 5), + _buildPickerColumn( + label: 's', + controller: _secondsController, + itemCount: 60, + onChanged: (value) => _handleTimeChange( + _hoursController.selectedItem, + _minutesController.selectedItem, + value, + ), + ), + ], + ); + } + + void _handleTimeChange(int hours, int minutes, int seconds) { + int total = hours * 3600 + minutes * 60 + seconds; + if (total > 10000) { + hours = 2; + minutes = 46; + seconds = 40; + total = 10000; + WidgetsBinding.instance.addPostFrameCallback((_) { + _hoursController.jumpToItem(hours); + _minutesController.jumpToItem(minutes); + _secondsController.jumpToItem(seconds); + }); + } + + widget.onTimeChanged(hours, minutes, seconds); + } + + Widget _buildPickerColumn({ + required String label, + required FixedExtentScrollController controller, + required int itemCount, + required Function(int) onChanged, + }) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 40, + width: 40, + padding: const EdgeInsets.symmetric(horizontal: 5), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(8), + ), + child: ListWheelScrollView.useDelegate( + controller: controller, + itemExtent: 40.0, + physics: const FixedExtentScrollPhysics(), + onSelectedItemChanged: onChanged, + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) => Center( + child: Text( + index.toString().padLeft(2), + style: const TextStyle( + fontSize: 18, + color: ColorsManager.blue1, + ), + ), + ), + childCount: itemCount, + ), + ), + ), + const SizedBox(width: 5), + Text( + label, + style: const TextStyle( + color: ColorsManager.blackColor, + fontSize: 18, + ), + ), + ], + ); + } +} diff --git a/lib/pages/routines/widgets/then_container.dart b/lib/pages/routines/widgets/then_container.dart index 3266d3b4..1e7e1382 100644 --- a/lib/pages/routines/widgets/then_container.dart +++ b/lib/pages/routines/widgets/then_container.dart @@ -27,7 +27,8 @@ class ThenContainer extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('THEN', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), state.isLoading && state.isUpdate == true ? const Center( @@ -40,11 +41,12 @@ class ThenContainer extends StatelessWidget { state.thenItems.length, (index) => GestureDetector( onTap: () async { - if (state.thenItems[index]['deviceId'] == + if (state.thenItems[index] + ['deviceId'] == 'delay') { final result = await DelayHelper - .showDelayPickerDialog( - context, state.thenItems[index]); + .showDelayPickerDialog(context, + state.thenItems[index]); if (result != null) { context @@ -64,14 +66,17 @@ class ThenContainer extends StatelessWidget { context: context, builder: (BuildContext context) => AutomationDialog( - automationName: state.thenItems[index] - ['name'] ?? - 'Automation', - automationId: state.thenItems[index] - ['deviceId'] ?? - '', - uniqueCustomId: state.thenItems[index] - ['uniqueCustomId'], + automationName: + state.thenItems[index] + ['name'] ?? + 'Automation', + automationId: + state.thenItems[index] + ['deviceId'] ?? + '', + uniqueCustomId: + state.thenItems[index] + ['uniqueCustomId'], ), ); @@ -80,11 +85,13 @@ class ThenContainer extends StatelessWidget { .read() .add(AddToThenContainer({ ...state.thenItems[index], - 'imagePath': Assets.automation, - 'title': state.thenItems[index] - ['name'] ?? + 'imagePath': + Assets.automation, + 'title': state.thenItems[index] - ['title'], + ['name'] ?? + state.thenItems[index] + ['title'], })); } return; @@ -109,8 +116,9 @@ class ThenContainer extends StatelessWidget { 'WPS', 'CPS', "GW", - ].contains( - state.thenItems[index]['productType'])) { + "NCPS" + ].contains(state.thenItems[index] + ['productType'])) { context.read().add( AddToThenContainer( state.thenItems[index])); @@ -120,7 +128,9 @@ class ThenContainer extends StatelessWidget { imagePath: state.thenItems[index] ['imagePath'] ?? '', - title: state.thenItems[index]['title'] ?? '', + title: state.thenItems[index] + ['title'] ?? + '', deviceData: state.thenItems[index], padding: const EdgeInsets.symmetric( horizontal: 4, vertical: 8), @@ -157,8 +167,8 @@ class ThenContainer extends StatelessWidget { } if (mutableData['type'] == 'automation') { - int index = state.thenItems - .indexWhere((item) => item['deviceId'] == mutableData['deviceId']); + int index = state.thenItems.indexWhere( + (item) => item['deviceId'] == mutableData['deviceId']); if (index != -1) { return; } @@ -183,8 +193,8 @@ class ThenContainer extends StatelessWidget { } if (mutableData['type'] == 'tap_to_run' && state.isAutomation) { - int index = state.thenItems - .indexWhere((item) => item['deviceId'] == mutableData['deviceId']); + int index = state.thenItems.indexWhere( + (item) => item['deviceId'] == mutableData['deviceId']); if (index != -1) { return; } @@ -222,7 +232,7 @@ class ThenContainer extends StatelessWidget { dialogType: "THEN"); if (result != null) { context.read().add(AddToThenContainer(mutableData)); - } else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS'] + } else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS', "NCPS"] .contains(mutableData['productType'])) { context.read().add(AddToThenContainer(mutableData)); } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index dd933789..43f7bd92 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -13,7 +13,8 @@ class Assets { static const String rightLine = "assets/images/right_line.png"; static const String google = "assets/images/google.svg"; static const String facebook = "assets/images/facebook.svg"; - static const String invisiblePassword = "assets/images/Password_invisible.svg"; + static const String invisiblePassword = + "assets/images/Password_invisible.svg"; static const String visiblePassword = "assets/images/password_visible.svg"; static const String accessIcon = "assets/images/access_icon.svg"; static const String spaseManagementIcon = @@ -30,7 +31,8 @@ class Assets { static const String emptyTable = "assets/images/empty_table.svg"; // General assets - static const String motionlessDetection = "assets/icons/motionless_detection.svg"; + static const String motionlessDetection = + "assets/icons/motionless_detection.svg"; static const String acHeating = "assets/icons/ac_heating.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; @@ -67,19 +69,22 @@ class Assets { "assets/icons/automation_functions/temp_password_unlock.svg"; static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg"; - static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; + static const String doorbell = + "assets/icons/automation_functions/doorbell.svg"; static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg"; static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; - static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; + static const String lockAlarm = + "assets/icons/automation_functions/lock_alarm.svg"; static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; - static const String presence = "assets/icons/automation_functions/presence.svg"; + static const String presence = + "assets/icons/automation_functions/presence.svg"; static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; static const String hijackAlarm = @@ -96,12 +101,15 @@ class Assets { // Presence Sensor Assets static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; - static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; + static const String sensorPresenceIcon = + "assets/icons/sensor_presence_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; - static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; - static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; + static const String presenceRecordIcon = + "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = + "assets/icons/help_description_ic.svg"; static const String lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -151,10 +159,12 @@ class Assets { static const String unit = 'assets/icons/unit_icon.svg'; static const String villa = 'assets/icons/villa_icon.svg'; static const String iconEdit = 'assets/icons/icon_edit_icon.svg'; - static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg'; + static const String textFieldSearch = + 'assets/icons/textfield_search_icon.svg'; static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg'; static const String addIcon = 'assets/icons/add_icon.svg'; - static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg'; + static const String smartThermostatIcon = + 'assets/icons/smart_thermostat_icon.svg'; static const String smartLightIcon = 'assets/icons/smart_light_icon.svg'; static const String presenceSensor = 'assets/icons/presence_sensor.svg'; static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg'; @@ -202,7 +212,8 @@ class Assets { //assets/icons/water_leak_normal.svg static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg'; //assets/icons/water_leak_detected.svg - static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg'; + static const String waterLeakDetected = + 'assets/icons/water_leak_detected.svg'; //assets/icons/automation_records.svg static const String automationRecords = 'assets/icons/automation_records.svg'; @@ -273,13 +284,16 @@ class Assets { "assets/icons/functions_icons/sensitivity.svg"; static const String assetsSensitivityOperationIcon = "assets/icons/functions_icons/sesitivity_operation_icon.svg"; - static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg"; + static const String assetsAcPower = + "assets/icons/functions_icons/ac_power.svg"; static const String assetsAcPowerOFF = "assets/icons/functions_icons/ac_power_off.svg"; static const String assetsChildLock = "assets/icons/functions_icons/child_lock.svg"; - static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg"; - static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg"; + static const String assetsFreezing = + "assets/icons/functions_icons/freezing.svg"; + static const String assetsFanSpeed = + "assets/icons/functions_icons/fan_speed.svg"; static const String assetsAcCooling = "assets/icons/functions_icons/ac_cooling.svg"; static const String assetsAcHeating = @@ -288,7 +302,8 @@ class Assets { "assets/icons/functions_icons/celsius_degrees.svg"; static const String assetsTempreture = "assets/icons/functions_icons/tempreture.svg"; - static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg"; + static const String assetsAcFanLow = + "assets/icons/functions_icons/ac_fan_low.svg"; static const String assetsAcFanMiddle = "assets/icons/functions_icons/ac_fan_middle.svg"; static const String assetsAcFanHigh = @@ -307,7 +322,8 @@ class Assets { "assets/icons/functions_icons/far_detection.svg"; static const String assetsFarDetectionFunction = "assets/icons/functions_icons/far_detection_function.svg"; - static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg"; + static const String assetsIndicator = + "assets/icons/functions_icons/indicator.svg"; static const String assetsMotionDetection = "assets/icons/functions_icons/motion_detection.svg"; static const String assetsMotionlessDetection = @@ -320,7 +336,8 @@ class Assets { "assets/icons/functions_icons/master_state.svg"; static const String assetsSwitchAlarmSound = "assets/icons/functions_icons/switch_alarm_sound.svg"; - static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg"; + static const String assetsResetOff = + "assets/icons/functions_icons/reset_off.svg"; // Assets for automation_functions static const String assetsCardUnlock = @@ -364,12 +381,14 @@ class Assets { static const String activeUser = 'assets/icons/active_user.svg'; static const String deActiveUser = 'assets/icons/deactive_user.svg'; static const String invitedIcon = 'assets/icons/invited_icon.svg'; - static const String rectangleCheckBox = 'assets/icons/rectangle_check_box.png'; + static const String rectangleCheckBox = + 'assets/icons/rectangle_check_box.png'; static const String CheckBoxChecked = 'assets/icons/box_checked.png'; static const String emptyBox = 'assets/icons/empty_box.png'; static const String completeProcessIcon = 'assets/icons/compleate_process_icon.svg'; - static const String currentProcessIcon = 'assets/icons/current_process_icon.svg'; + static const String currentProcessIcon = + 'assets/icons/current_process_icon.svg'; static const String uncomplete_ProcessIcon = 'assets/icons/uncompleate_process_icon.svg'; static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg'; @@ -390,9 +409,11 @@ class Assets { static const String successIcon = 'assets/icons/success_icon.svg'; static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg'; static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png'; - static const String scenesPlayIconCheck = 'assets/icons/scenesPlayIconCheck.png'; + static const String scenesPlayIconCheck = + 'assets/icons/scenesPlayIconCheck.png'; static const String presenceStateIcon = 'assets/icons/presence_state.svg'; - static const String currentDistanceIcon = 'assets/icons/current_distance_icon.svg'; + static const String currentDistanceIcon = + 'assets/icons/current_distance_icon.svg'; static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg'; static const String motionDetectionSensitivityIcon = @@ -415,28 +436,48 @@ class Assets { static const String cpsMode4 = 'assets/icons/cps_mode4.svg'; static const String closeToMotion = 'assets/icons/close_to_motion.svg'; static const String farAwayMotion = 'assets/icons/far_away_motion.svg'; - static const String communicationFault = 'assets/icons/communication_fault.svg'; + static const String communicationFault = + 'assets/icons/communication_fault.svg'; static const String radarFault = 'assets/icons/radar_fault.svg'; - static const String selfTestingSuccess = 'assets/icons/self_testing_success.svg'; - static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg'; - static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg'; + static const String selfTestingSuccess = + 'assets/icons/self_testing_success.svg'; + static const String selfTestingFailure = + 'assets/icons/self_testing_failure.svg'; + static const String selfTestingTimeout = + 'assets/icons/self_testing_timeout.svg'; static const String movingSpeed = 'assets/icons/moving_speed.svg'; static const String boundary = 'assets/icons/boundary.svg'; static const String motionMeter = 'assets/icons/motion_meter.svg'; - static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg'; - static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg'; + static const String spatialStaticValue = + 'assets/icons/spatial_static_value.svg'; + static const String spatialMotionValue = + 'assets/icons/spatial_motion_value.svg'; static const String presenceJudgementThrshold = 'assets/icons/presence_judgement_threshold.svg'; static const String spaceType = 'assets/icons/space_type.svg'; static const String sportsPara = 'assets/icons/sports_para.svg'; - static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg'; - static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg'; - static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg'; - static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.svg'; - static const String sensitivityFeature5 = 'assets/icons/sensitivity_feature_5.svg'; - static const String sensitivityFeature6 = 'assets/icons/sensitivity_feature_6.svg'; - static const String sensitivityFeature7 = 'assets/icons/sensitivity_feature_7.svg'; - static const String sensitivityFeature8 = 'assets/icons/sensitivity_feature_8.svg'; - static const String sensitivityFeature9 = 'assets/icons/sensitivity_feature_9.svg'; + static const String sensitivityFeature1 = + 'assets/icons/sensitivity_feature_1.svg'; + static const String sensitivityFeature2 = + 'assets/icons/sensitivity_feature_2.svg'; + static const String sensitivityFeature3 = + 'assets/icons/sensitivity_feature_3.svg'; + static const String sensitivityFeature4 = + 'assets/icons/sensitivity_feature_4.svg'; + static const String sensitivityFeature5 = + 'assets/icons/sensitivity_feature_5.svg'; + static const String sensitivityFeature6 = + 'assets/icons/sensitivity_feature_6.svg'; + static const String sensitivityFeature7 = + 'assets/icons/sensitivity_feature_7.svg'; + static const String sensitivityFeature8 = + 'assets/icons/sensitivity_feature_8.svg'; + static const String sensitivityFeature9 = + 'assets/icons/sensitivity_feature_9.svg'; static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg'; + static const String targetConfirmTimeIcon = + 'assets/icons/target_confirm_time_icon.svg'; + static const String DisappeDelayIcon = 'assets/icons/disappe_delay_icon.svg'; + static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg'; + static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg'; } diff --git a/lib/utils/enum/device_types.dart b/lib/utils/enum/device_types.dart index 7050f13a..7ad8e02c 100644 --- a/lib/utils/enum/device_types.dart +++ b/lib/utils/enum/device_types.dart @@ -17,6 +17,7 @@ enum DeviceType { ThreeTouch, GarageDoor, WaterLeak, + NCPS, DoorSensor, Other, } @@ -56,5 +57,6 @@ Map devicesTypesMap = { "2GT": DeviceType.TwoGang, "3GT": DeviceType.ThreeGang, 'GD': DeviceType.GarageDoor, - 'WL': DeviceType.WaterLeak + 'WL': DeviceType.WaterLeak, + 'NCPS': DeviceType.NCPS, }; diff --git a/pubspec.lock b/pubspec.lock index a0cbfaad..fb0d6a22 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -26,7 +26,7 @@ packages: source: hosted version: "2.11.0" bloc: - dependency: transitive + dependency: "direct main" description: name: bloc sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" From ea88f54d20c030f664f638d406e7abfff12e1162 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 29 Apr 2025 10:22:00 +0300 Subject: [PATCH 238/238] Refactor garage door control view to use 'doorcontact_state' code for fetching records --- .../garage_door/view/garage_door_control_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart index 54dd50e0..ae2fc9e4 100644 --- a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart +++ b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart @@ -184,7 +184,7 @@ class GarageDoorControlView extends StatelessWidget icon: Assets.records, onTap: () { context.read().add(FetchGarageDoorRecordsEvent( - code: 'switch_1', deviceId: status.uuid)); + code: 'doorcontact_state', deviceId: status.uuid)); }, status: false, textColor: ColorsManager.blackColor,